1use std::cell::UnsafeCell;
14use std::future::Future;
15use std::pin::Pin;
16use std::rc::Rc;
17use std::str::FromStr;
18use std::sync::{Arc, Mutex};
19
20use indexmap::IndexMap;
21use js_sys::{Array, Date, Object, Reflect, Uint8Array};
22use perspective_client::proto::{ColumnType, HostedTable};
23use perspective_client::virtual_server;
24use perspective_client::virtual_server::{Features, ResultExt, VirtualServerHandler};
25use serde::Serialize;
26use wasm_bindgen::prelude::*;
27use wasm_bindgen_futures::JsFuture;
28
29use crate::JsViewConfig;
30use crate::utils::{ApiError, ApiFuture, *};
31
32type HandlerFuture<T> = Pin<Box<dyn Future<Output = T>>>;
33
34#[derive(Debug)]
35pub struct JsError(JsValue);
36
37impl std::fmt::Display for JsError {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 write!(f, "{:?}", self.0)
40 }
41}
42
43impl std::error::Error for JsError {}
44
45impl From<JsValue> for JsError {
46 fn from(value: JsValue) -> Self {
47 JsError(value)
48 }
49}
50
51impl From<JsError> for JsValue {
52 fn from(error: JsError) -> Self {
53 error.0
54 }
55}
56
57impl From<serde_wasm_bindgen::Error> for JsError {
58 fn from(error: serde_wasm_bindgen::Error) -> Self {
59 JsError(error.into())
60 }
61}
62
63fn jsvalue_to_scalar(val: &JsValue) -> perspective_client::config::Scalar {
64 if val.is_null() || val.is_undefined() {
65 perspective_client::config::Scalar::Null
66 } else if let Some(b) = val.as_bool() {
67 perspective_client::config::Scalar::Bool(b)
68 } else if let Some(n) = val.as_f64() {
69 perspective_client::config::Scalar::Float(n)
70 } else if let Some(s) = val.as_string() {
71 perspective_client::config::Scalar::String(s)
72 } else {
73 perspective_client::config::Scalar::Null
74 }
75}
76
77pub struct JsServerHandler(Object);
78
79impl JsServerHandler {
80 fn call_method_js(&self, method: &str, args: &Array) -> Result<JsValue, JsError> {
81 let func = Reflect::get(&self.0, &JsValue::from_str(method))?;
82 let func = func
83 .dyn_ref::<js_sys::Function>()
84 .ok_or_else(|| JsError(JsValue::from_str(&format!("{} is not a function", method))))?;
85 Ok(func.apply(&self.0, args)?)
86 }
87
88 async fn call_method_js_async(&self, method: &str, args: &Array) -> Result<JsValue, JsError> {
89 let result = self.call_method_js(method, args)?;
90
91 if result.is_instance_of::<js_sys::Promise>() {
93 let promise = js_sys::Promise::from(result);
94 JsFuture::from(promise).await.map_err(JsError)
95 } else {
96 Ok(result)
97 }
98 }
99}
100
101impl VirtualServerHandler for JsServerHandler {
102 type Error = JsError;
103
104 fn get_features(&self) -> HandlerFuture<Result<Features<'_>, Self::Error>> {
105 let has_method = Reflect::get(&self.0, &JsValue::from_str("getFeatures"))
106 .map(|val| !val.is_undefined())
107 .unwrap_or(false);
108
109 if !has_method {
110 return Box::pin(async { Ok(Features::default()) });
111 }
112
113 let handler = self.0.clone();
114 Box::pin(async move {
115 let this = JsServerHandler(handler);
116 let args = Array::new();
117 let result = this.call_method_js_async("getFeatures", &args).await?;
118 Ok(serde_wasm_bindgen::from_value(result)?)
119 })
120 }
121
122 fn get_hosted_tables(&self) -> HandlerFuture<Result<Vec<HostedTable>, Self::Error>> {
123 let handler = self.0.clone();
124 Box::pin(async move {
125 let this = JsServerHandler(handler);
126 let args = Array::new();
127 let result = this.call_method_js_async("getHostedTables", &args).await?;
128 let array = result.dyn_ref::<Array>().ok_or_else(|| {
129 JsError(JsValue::from_str("getHostedTables must return an array"))
130 })?;
131
132 let mut tables = Vec::new();
133 for i in 0..array.length() {
134 let item = array.get(i);
135 if let Some(s) = item.as_string() {
136 tables.push(HostedTable {
137 entity_id: s,
138 index: None,
139 limit: None,
140 });
141 } else if item.is_object() {
142 let name = Reflect::get(&item, &JsValue::from_str("name"))?
143 .as_string()
144 .ok_or_else(|| JsError(JsValue::from_str("name must be a string")))?;
145 let index = Reflect::get(&item, &JsValue::from_str("index"))
146 .ok()
147 .and_then(|v| v.as_string());
148 let limit = Reflect::get(&item, &JsValue::from_str("limit"))
149 .ok()
150 .and_then(|v| v.as_f64().map(|x| x as u32));
151 tables.push(HostedTable {
152 entity_id: name,
153 index,
154 limit,
155 });
156 }
157 }
158 Ok(tables)
159 })
160 }
161
162 fn table_schema(
163 &self,
164 table_id: &str,
165 ) -> HandlerFuture<Result<IndexMap<String, ColumnType>, Self::Error>> {
166 let handler = self.0.clone();
167 let table_id = table_id.to_string();
168 Box::pin(async move {
169 let this = JsServerHandler(handler);
170 let args = Array::new();
171 args.push(&JsValue::from_str(&table_id));
172 let result = this.call_method_js_async("tableSchema", &args).await?;
173 let obj = result
174 .dyn_ref::<Object>()
175 .ok_or_else(|| JsError(JsValue::from_str("tableSchema must return an object")))?;
176
177 let mut schema = IndexMap::new();
178 let entries = Object::entries(obj);
179 for i in 0..entries.length() {
180 let entry = entries.get(i);
181 let entry_array = entry.dyn_ref::<Array>().unwrap();
182 let key = entry_array.get(0).as_string().unwrap();
183 let value = entry_array.get(1).as_string().unwrap();
184 schema.insert(key, ColumnType::from_str(&value).unwrap());
185 }
186 Ok(schema)
187 })
188 }
189
190 fn table_size(&self, table_id: &str) -> HandlerFuture<Result<u32, Self::Error>> {
191 let handler = self.0.clone();
192 let table_id = table_id.to_string();
193 Box::pin(async move {
194 let this = JsServerHandler(handler);
195 let args = Array::new();
196 args.push(&JsValue::from_str(&table_id));
197 let result = this.call_method_js_async("tableSize", &args).await?;
198 result
199 .as_f64()
200 .map(|x| x as u32)
201 .ok_or_else(|| JsError(JsValue::from_str("tableSize must return a number")))
202 })
203 }
204
205 fn table_column_size(&self, view_id: &str) -> HandlerFuture<Result<u32, Self::Error>> {
206 let has_method = Reflect::get(&self.0, &JsValue::from_str("tableColumnsSize"))
207 .map(|val| !val.is_undefined())
208 .unwrap_or(false);
209
210 let handler = self.0.clone();
211 let view_id = view_id.to_string();
212 Box::pin(async move {
213 let this = JsServerHandler(handler);
214 let args = Array::new();
215 args.push(&JsValue::from_str(&view_id));
216 if has_method {
217 let result = this.call_method_js_async("tableColumnsSize", &args).await?;
218 result.as_f64().map(|x| x as u32).ok_or_else(|| {
219 JsError(JsValue::from_str(
220 "tableColumnsSize must
221 return a number",
222 ))
223 })
224 } else {
225 Ok(this.table_schema(view_id.as_str()).await?.len() as u32)
226 }
227 })
228 }
229
230 fn table_validate_expression(
231 &self,
232 table_id: &str,
233 expression: &str,
234 ) -> HandlerFuture<Result<ColumnType, Self::Error>> {
235 let has_method = Reflect::get(&self.0, &JsValue::from_str("tableValidateExpression"))
237 .map(|val| !val.is_undefined())
238 .unwrap_or(false);
239
240 let handler = self.0.clone();
241 let table_id = table_id.to_string();
242 let expression = expression.to_string();
243 Box::pin(async move {
244 if !has_method {
245 return Err(JsError(JsValue::from_str(
246 "feature `table_validate_expression` not implemented",
247 )));
248 }
249
250 let this = JsServerHandler(handler);
251 let args = Array::new();
252 args.push(&JsValue::from_str(&table_id));
253 args.push(&JsValue::from_str(&expression));
254 let result = this
255 .call_method_js_async("tableValidateExpression", &args)
256 .await?;
257
258 let type_str = result
259 .as_string()
260 .ok_or_else(|| JsError(JsValue::from_str("Must return a string")))?;
261
262 Ok(ColumnType::from_str(&type_str).unwrap())
263 })
264 }
265
266 fn table_make_view(
267 &mut self,
268 table_id: &str,
269 view_id: &str,
270 config: &mut perspective_client::config::ViewConfigUpdate,
271 ) -> HandlerFuture<Result<String, Self::Error>> {
272 let handler = self.0.clone();
273 let table_id = table_id.to_string();
274 let view_id = view_id.to_string();
275 let config = config.clone();
276 Box::pin(async move {
277 let this = JsServerHandler(handler);
278 let args = Array::new();
279 args.push(&JsValue::from_str(&table_id));
280 args.push(&JsValue::from_str(&view_id));
281 args.push(&JsValue::from_serde_ext(&config)?);
282 let _ = this.call_method_js_async("tableMakeView", &args).await?;
283 Ok(view_id.to_string())
284 })
285 }
286
287 fn view_schema(
288 &self,
289 view_id: &str,
290 config: &perspective_client::config::ViewConfig,
291 ) -> HandlerFuture<Result<IndexMap<String, ColumnType>, Self::Error>> {
292 let has_view_schema = Reflect::get(&self.0, &JsValue::from_str("viewSchema"))
293 .is_ok_and(|v| !v.is_undefined());
294
295 let handler = self.0.clone();
296 let view_id = view_id.to_string();
297 let config_value = JsValue::from_serde_ext(config).ok();
298
299 Box::pin(async move {
300 let this = JsServerHandler(handler);
301 let args = Array::new();
302 args.push(&JsValue::from_str(&view_id));
303 if let Some(cv) = config_value {
304 args.push(&cv);
305 }
306
307 let result = this
308 .call_method_js_async(
309 if has_view_schema {
310 "viewSchema"
311 } else {
312 "tableSchema"
313 },
314 &args,
315 )
316 .await?;
317
318 let obj = result
319 .dyn_ref::<Object>()
320 .ok_or_else(|| JsError(JsValue::from_str("viewSchema must return an object")))?;
321
322 let mut schema = IndexMap::new();
323 let entries = Object::entries(obj);
324 for i in 0..entries.length() {
325 let entry = entries.get(i);
326 let entry_array = entry.dyn_ref::<Array>().unwrap();
327 let key = entry_array.get(0).as_string().unwrap();
328 let value = entry_array.get(1).as_string().unwrap();
329 schema.insert(key, ColumnType::from_str(&value).unwrap());
330 }
331
332 Ok(schema)
333 })
334 }
335
336 fn view_size(&self, view_id: &str) -> HandlerFuture<Result<u32, Self::Error>> {
337 let handler = self.0.clone();
338 let view_id = view_id.to_string();
339 let has_view_size =
340 Reflect::get(&self.0, &JsValue::from_str("viewSize")).is_ok_and(|v| !v.is_undefined());
341
342 Box::pin(async move {
343 let this = JsServerHandler(handler);
344 let args = Array::new();
345 args.push(&JsValue::from_str(&view_id));
346 let result = this
347 .call_method_js_async(
348 if has_view_size {
349 "viewSize"
350 } else {
351 "tableSize"
352 },
353 &args,
354 )
355 .await?;
356
357 result
358 .as_f64()
359 .map(|x| x as u32)
360 .ok_or_else(|| JsError(JsValue::from_str("viewSize must return a number")))
361 })
362 }
363
364 fn view_column_size(
365 &self,
366 view_id: &str,
367 config: &perspective_client::config::ViewConfig,
368 ) -> HandlerFuture<Result<u32, Self::Error>> {
369 let has_method = Reflect::get(&self.0, &JsValue::from_str("viewColumnSize"))
370 .map(|val| !val.is_undefined())
371 .unwrap_or(false);
372
373 let handler = self.0.clone();
374 let view_id = view_id.to_string();
375 let config_value = serde_wasm_bindgen::to_value(config).unwrap();
376 let config = config.clone();
377 Box::pin(async move {
378 let this = JsServerHandler(handler);
379 let args = Array::new();
380 args.push(&JsValue::from_str(&view_id));
381 args.push(&config_value);
382 if has_method {
383 let result = this.call_method_js_async("viewColumnSize", &args).await?;
384 result.as_f64().map(|x| x as u32).ok_or_else(|| {
385 JsError(JsValue::from_str("viewColumnSize must return a number"))
386 })
387 } else {
388 Ok(this.view_schema(view_id.as_str(), &config).await?.len() as u32)
389 }
390 })
391 }
392
393 fn view_delete(&self, view_id: &str) -> HandlerFuture<Result<(), Self::Error>> {
394 let handler = self.0.clone();
395 let view_id = view_id.to_string();
396 Box::pin(async move {
397 let this = JsServerHandler(handler);
398 let args = Array::new();
399 args.push(&JsValue::from_str(&view_id));
400 this.call_method_js_async("viewDelete", &args).await?;
401 Ok(())
402 })
403 }
404
405 fn table_make_port(
406 &self,
407 _req: &perspective_client::proto::TableMakePortReq,
408 ) -> HandlerFuture<Result<u32, Self::Error>> {
409 let has_method = Reflect::get(&self.0, &JsValue::from_str("tableMakePort"))
410 .map(|val| !val.is_undefined())
411 .unwrap_or(false);
412
413 if !has_method {
414 return Box::pin(async { Ok(0) });
415 }
416
417 let handler = self.0.clone();
418 Box::pin(async move {
419 let this = JsServerHandler(handler);
420 let args = Array::new();
421 let result = this.call_method_js_async("tableMakePort", &args).await?;
422 result
423 .as_f64()
424 .map(|x| x as u32)
425 .ok_or_else(|| JsError(JsValue::from_str("tableMakePort must return a number")))
426 })
427 }
428
429 fn make_table(
430 &mut self,
431 table_id: &str,
432 data: &perspective_client::proto::MakeTableData,
433 ) -> HandlerFuture<Result<(), Self::Error>> {
434 let has_method = Reflect::get(&self.0, &JsValue::from_str("makeTable"))
435 .map(|val| !val.is_undefined())
436 .unwrap_or(false);
437
438 if !has_method {
439 return Box::pin(async {
440 Err(JsError(JsValue::from_str("makeTable not implemented")))
441 });
442 }
443
444 let handler = self.0.clone();
445 let table_id = table_id.to_string();
446 use perspective_client::proto::make_table_data::Data;
447 let data_value = match &data.data {
448 Some(Data::FromCsv(csv)) => JsValue::from_str(csv),
449 Some(Data::FromArrow(arrow)) => {
450 let uint8array = js_sys::Uint8Array::from(arrow.as_slice());
451 JsValue::from(uint8array)
452 },
453 Some(Data::FromRows(rows)) => JsValue::from_str(rows),
454 Some(Data::FromCols(cols)) => JsValue::from_str(cols),
455 Some(Data::FromNdjson(ndjson)) => JsValue::from_str(ndjson),
456 _ => JsValue::from_str(""),
457 };
458
459 Box::pin(async move {
460 let this = JsServerHandler(handler);
461 let args = Array::new();
462 args.push(&JsValue::from_str(&table_id));
463 args.push(&data_value);
464 this.call_method_js_async("makeTable", &args).await?;
465 Ok(())
466 })
467 }
468
469 fn view_get_min_max(
470 &self,
471 view_id: &str,
472 column_name: &str,
473 config: &perspective_client::config::ViewConfig,
474 ) -> HandlerFuture<
475 Result<
476 (
477 perspective_client::config::Scalar,
478 perspective_client::config::Scalar,
479 ),
480 Self::Error,
481 >,
482 > {
483 let has_method = Reflect::get(&self.0, &JsValue::from_str("viewGetMinMax"))
484 .map(|val| !val.is_undefined())
485 .unwrap_or(false);
486
487 if !has_method {
488 return Box::pin(async {
489 Err(JsError(JsValue::from_str("viewGetMinMax not implemented")))
490 });
491 }
492
493 let handler = self.0.clone();
494 let view_id = view_id.to_string();
495 let column_name = column_name.to_string();
496 let config_js = serde_wasm_bindgen::to_value(config).unwrap();
497 Box::pin(async move {
498 let this = JsServerHandler(handler);
499 let args = Array::new();
500 args.push(&JsValue::from_str(&view_id));
501 args.push(&JsValue::from_str(&column_name));
502 args.push(&config_js);
503 let result = this.call_method_js_async("viewGetMinMax", &args).await?;
504 let obj = result.dyn_ref::<Object>().unwrap();
505 let min_val = Reflect::get(obj, &JsValue::from_str(wasm_bindgen::intern("min")))?;
506 let max_val = Reflect::get(obj, &JsValue::from_str(wasm_bindgen::intern("max")))?;
507 Ok((jsvalue_to_scalar(&min_val), jsvalue_to_scalar(&max_val)))
508 })
509 }
510
511 fn view_get_data(
512 &self,
513 view_id: &str,
514 config: &perspective_client::config::ViewConfig,
515 schema: &IndexMap<String, ColumnType>,
516 viewport: &perspective_client::proto::ViewPort,
517 ) -> HandlerFuture<Result<virtual_server::VirtualDataSlice, Self::Error>> {
518 let handler = self.0.clone();
519 let view_id = view_id.to_string();
520 let window: JsViewPort = viewport.clone().into();
521 let config_value = serde_wasm_bindgen::to_value(config).unwrap();
522 let window_value = serde_wasm_bindgen::to_value(&window).unwrap();
523 let schema_value = JsValue::from_serde_ext(&schema).unwrap();
524
525 Box::pin(async move {
526 let this = JsServerHandler(handler);
527 let data = VirtualDataSlice::new(config_value.clone().unchecked_into());
528
529 {
530 let args = Array::new();
531 args.push(&JsValue::from_str(&view_id));
532 args.push(&config_value);
533 args.push(&schema_value);
534 args.push(&window_value);
535 args.push(&JsValue::from(data.clone()));
536 this.call_method_js_async("viewGetData", &args).await?;
537 }
538
539 let VirtualDataSlice(_obj, arc) = data;
542 let slice = std::mem::take(&mut *arc.lock().unwrap()).unwrap();
543 Ok(slice)
544 })
545 }
546}
547
548#[derive(Serialize, PartialEq)]
549pub struct JsViewPort {
550 #[serde(default, skip_serializing_if = "Option::is_none")]
551 pub start_row: ::core::option::Option<u32>,
552
553 #[serde(default, skip_serializing_if = "Option::is_none")]
554 pub start_col: ::core::option::Option<u32>,
555
556 #[serde(default, skip_serializing_if = "Option::is_none")]
557 pub end_row: ::core::option::Option<u32>,
558
559 #[serde(default, skip_serializing_if = "Option::is_none")]
560 pub end_col: ::core::option::Option<u32>,
561}
562
563impl From<perspective_client::proto::ViewPort> for JsViewPort {
564 fn from(value: perspective_client::proto::ViewPort) -> Self {
565 JsViewPort {
566 start_row: value.start_row,
567 start_col: value.start_col,
568 end_row: value.end_row,
569 end_col: value.end_col,
570 }
571 }
572}
573
574#[wasm_bindgen(js_name = "VirtualDataSlice")]
575#[derive(Clone)]
576pub struct VirtualDataSlice(Object, Arc<Mutex<Option<virtual_server::VirtualDataSlice>>>);
577
578#[wasm_bindgen]
579impl VirtualDataSlice {
580 #[wasm_bindgen(constructor)]
581 pub fn new(config: JsViewConfig) -> Self {
582 VirtualDataSlice(
583 Object::new(),
584 Arc::new(Mutex::new(Some(virtual_server::VirtualDataSlice::new(
585 config.into_serde_ext().unwrap(),
586 )))),
587 )
588 }
589
590 #[wasm_bindgen(js_name = "fromArrowIpc")]
591 pub fn from_arrow_ipc(&self, ipc: Uint8Array) -> Result<(), JsValue> {
592 self.1
593 .lock()
594 .unwrap()
595 .as_mut()
596 .unwrap()
597 .from_arrow_ipc(&ipc.to_vec())
598 .map_err(|e| JsValue::from_str(&e.to_string()))
599 }
600
601 #[wasm_bindgen(js_name = "setCol")]
602 pub fn set_col(
603 &self,
604 dtype: &str,
605 name: &str,
606 index: u32,
607 val: JsValue,
608 group_by_index: Option<usize>,
609 ) -> Result<(), JsValue> {
610 match dtype {
611 "string" => self.set_string_col(name, index, val, group_by_index),
612 "integer" => self.set_integer_col(name, index, val, group_by_index),
613 "float" => self.set_float_col(name, index, val, group_by_index),
614 "date" => self.set_datetime_col(name, index, val, group_by_index),
615 "datetime" => self.set_datetime_col(name, index, val, group_by_index),
616 "boolean" => self.set_boolean_col(name, index, val, group_by_index),
617 _ => Err(JsValue::from_str("Unknown type")),
618 }
619 }
620
621 #[wasm_bindgen(js_name = "setStringCol")]
622 pub fn set_string_col(
623 &self,
624 name: &str,
625 index: u32,
626 val: JsValue,
627 group_by_index: Option<usize>,
628 ) -> Result<(), JsValue> {
629 if val.is_null() || val.is_undefined() {
630 self.1
631 .lock()
632 .unwrap()
633 .as_mut()
634 .unwrap()
635 .set_col(name, group_by_index, index as usize, None as Option<String>)
636 .unwrap();
637 } else if let Some(s) = val.as_string() {
638 self.1
639 .lock()
640 .unwrap()
641 .as_mut()
642 .unwrap()
643 .set_col(name, group_by_index, index as usize, Some(s))
644 .unwrap();
645 } else {
646 tracing::error!("Unhandled string value");
647 }
648 Ok(())
649 }
650
651 #[wasm_bindgen(js_name = "setIntegerCol")]
652 pub fn set_integer_col(
653 &self,
654 name: &str,
655 index: u32,
656 val: JsValue,
657 group_by_index: Option<usize>,
658 ) -> Result<(), JsValue> {
659 if val.is_null() || val.is_undefined() {
660 self.1
661 .lock()
662 .unwrap()
663 .as_mut()
664 .unwrap()
665 .set_col(name, group_by_index, index as usize, None as Option<i32>)
666 .unwrap();
667 } else if let Some(n) = val.as_f64() {
668 self.1
669 .lock()
670 .unwrap()
671 .as_mut()
672 .unwrap()
673 .set_col(name, group_by_index, index as usize, Some(n as i32))
674 .unwrap();
675 } else {
676 tracing::error!("Unhandled integer value");
677 }
678 Ok(())
679 }
680
681 #[wasm_bindgen(js_name = "setFloatCol")]
682 pub fn set_float_col(
683 &self,
684 name: &str,
685 index: u32,
686 val: JsValue,
687 group_by_index: Option<usize>,
688 ) -> Result<(), JsValue> {
689 if val.is_null() || val.is_undefined() {
690 self.1
691 .lock()
692 .unwrap()
693 .as_mut()
694 .unwrap()
695 .set_col(name, group_by_index, index as usize, None as Option<f64>)
696 .unwrap();
697 } else if let Some(n) = val.as_f64() {
698 self.1
699 .lock()
700 .unwrap()
701 .as_mut()
702 .unwrap()
703 .set_col(name, group_by_index, index as usize, Some(n))
704 .unwrap();
705 } else {
706 tracing::error!("Unhandled float value");
707 }
708 Ok(())
709 }
710
711 #[wasm_bindgen(js_name = "setBooleanCol")]
712 pub fn set_boolean_col(
713 &self,
714 name: &str,
715 index: u32,
716 val: JsValue,
717 group_by_index: Option<usize>,
718 ) -> Result<(), JsValue> {
719 if val.is_null() || val.is_undefined() {
720 self.1
721 .lock()
722 .unwrap()
723 .as_mut()
724 .unwrap()
725 .set_col(name, group_by_index, index as usize, None as Option<bool>)
726 .unwrap();
727 } else if let Some(b) = val.as_bool() {
728 self.1
729 .lock()
730 .unwrap()
731 .as_mut()
732 .unwrap()
733 .set_col(name, group_by_index, index as usize, Some(b))
734 .unwrap();
735 } else {
736 tracing::error!("Unhandled boolean value");
737 }
738 Ok(())
739 }
740
741 #[wasm_bindgen(js_name = "setDatetimeCol")]
742 pub fn set_datetime_col(
743 &self,
744 name: &str,
745 index: u32,
746 val: JsValue,
747 group_by_index: Option<usize>,
748 ) -> Result<(), JsValue> {
749 if val.is_null() || val.is_undefined() {
750 self.1
751 .lock()
752 .unwrap()
753 .as_mut()
754 .unwrap()
755 .set_col(name, group_by_index, index as usize, None as Option<i64>)
756 .unwrap();
757 } else if let Some(date) = val.dyn_ref::<Date>() {
758 let timestamp = date.get_time() as i64;
759 self.1
760 .lock()
761 .unwrap()
762 .as_mut()
763 .unwrap()
764 .set_col(name, group_by_index, index as usize, Some(timestamp))
765 .unwrap();
766 } else if let Some(n) = val.as_f64() {
767 self.1
768 .lock()
769 .unwrap()
770 .as_mut()
771 .unwrap()
772 .set_col(name, group_by_index, index as usize, Some(n as i64))
773 .unwrap();
774 } else {
775 tracing::error!("Unhandled datetime value");
776 }
777
778 Ok(())
779 }
780}
781
782#[wasm_bindgen]
783pub struct VirtualServer(Rc<UnsafeCell<virtual_server::VirtualServer<JsServerHandler>>>);
784
785#[wasm_bindgen]
786impl VirtualServer {
787 #[wasm_bindgen(constructor)]
788 pub fn new(handler: Object) -> Result<VirtualServer, JsValue> {
789 Ok(VirtualServer(Rc::new(UnsafeCell::new(
790 virtual_server::VirtualServer::new(JsServerHandler(handler)),
791 ))))
792 }
793
794 #[wasm_bindgen(js_name = "handleRequest")]
795 pub fn handle_request(&self, bytes: &[u8]) -> ApiFuture<Vec<u8>> {
796 let bytes = bytes.to_vec();
797 let server = self.0.clone();
798
799 ApiFuture::new(async move {
800 let result = unsafe {
805 (&mut *server.as_ref().get())
806 .handle_request(bytes::Bytes::from(bytes))
807 .await
808 };
809
810 match result.get_internal_error() {
811 Ok(x) => Ok(x.to_vec()),
812 Err(Ok(x)) => Err(ApiError::from(JsValue::from(x))),
813 Err(Err(x)) => Err(ApiError::from(JsValue::from_str(&x))),
814 }
815 })
816 }
817}