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
77#[wasm_bindgen(typescript_custom_section)]
83const TS_VIRTUAL_SERVER_HANDLER: &'static str = r#"
84/**
85 * A table hosted by a `VirtualServerHandler`, as returned by
86 * `getHostedTables()`. A plain `string` is shorthand for `{ name }`.
87 */
88export interface VirtualHostedTable {
89 name: string;
90 index?: string;
91 limit?: number;
92}
93
94/**
95 * Handler interface that you implement to provide custom data sources.
96 *
97 * All methods will be called by the `VirtualServer` when handling protocol
98 * messages from Perspective clients. Methods can return values directly or
99 * return Promises for asynchronous operations (e.g., database queries).
100 * Optional methods fall back to defaults documented per-method.
101 */
102export interface VirtualServerHandler {
103 getHostedTables():
104 | (string | VirtualHostedTable)[]
105 | Promise<(string | VirtualHostedTable)[]>;
106 tableSchema(
107 tableId: string,
108 ): Record<string, ColumnType> | Promise<Record<string, ColumnType>>;
109 tableSize(tableId: string): number | Promise<number>;
110 tableMakeView(
111 tableId: string,
112 viewId: string,
113 config: ViewConfigUpdate,
114 ): void | Promise<void>;
115 viewDelete(viewId: string): void | Promise<void>;
116 viewGetData(
117 viewId: string,
118 config: ViewConfig,
119 schema: Record<string, ColumnType>,
120 viewport: ViewWindow,
121 dataSlice: VirtualDataSlice,
122 ): void | Promise<void>;
123
124 /** Defaults to `tableSchema(viewId)`. */
125 viewSchema?(
126 viewId: string,
127 config: ViewConfig,
128 ): Record<string, ColumnType> | Promise<Record<string, ColumnType>>;
129
130 /** Defaults to `tableSize(viewId)`. */
131 viewSize?(viewId: string): number | Promise<number>;
132
133 /** Defaults to the length of `tableSchema(tableId)`. */
134 tableColumnsSize?(tableId: string): number | Promise<number>;
135
136 /** Defaults to the length of `viewSchema(viewId, config)`. */
137 viewColumnSize?(
138 viewId: string,
139 config: ViewConfig,
140 ): number | Promise<number>;
141
142 /** Required when `getFeatures()` reports `expressions: true`. */
143 tableValidateExpression?(
144 tableId: string,
145 expression: string,
146 ): ColumnType | Promise<ColumnType>;
147
148 viewGetMinMax?(
149 viewId: string,
150 columnName: string,
151 config: ViewConfig,
152 ): { min: Scalar; max: Scalar } | Promise<{ min: Scalar; max: Scalar }>;
153
154 /** Defaults to no optional features. */
155 getFeatures?(): Features | Promise<Features>;
156
157 /** Defaults to port `0`. */
158 tableMakePort?(): number | Promise<number>;
159
160 makeTable?(
161 tableId: string,
162 data: string | Uint8Array,
163 ): void | Promise<void>;
164}
165"#;
166
167#[wasm_bindgen]
168extern "C" {
169 #[wasm_bindgen(typescript_type = "VirtualServerHandler")]
170 pub type JsVirtualServerHandler;
171}
172
173pub struct JsServerHandler(Object);
174
175impl JsServerHandler {
176 fn call_method_js(&self, method: &str, args: &Array) -> Result<JsValue, JsError> {
177 let func = Reflect::get(&self.0, &JsValue::from_str(method))?;
178 let func = func
179 .dyn_ref::<js_sys::Function>()
180 .ok_or_else(|| JsError(JsValue::from_str(&format!("{} is not a function", method))))?;
181 Ok(func.apply(&self.0, args)?)
182 }
183
184 async fn call_method_js_async(&self, method: &str, args: &Array) -> Result<JsValue, JsError> {
185 let result = self.call_method_js(method, args)?;
186
187 if result.is_instance_of::<js_sys::Promise>() {
189 let promise = js_sys::Promise::from(result);
190 JsFuture::from(promise).await.map_err(JsError)
191 } else {
192 Ok(result)
193 }
194 }
195}
196
197impl VirtualServerHandler for JsServerHandler {
198 type Error = JsError;
199
200 fn get_features(&self) -> HandlerFuture<Result<Features<'_>, Self::Error>> {
201 let has_method = Reflect::get(&self.0, &JsValue::from_str("getFeatures"))
202 .map(|val| !val.is_undefined())
203 .unwrap_or(false);
204
205 if !has_method {
206 return Box::pin(async { Ok(Features::default()) });
207 }
208
209 let handler = self.0.clone();
210 Box::pin(async move {
211 let this = JsServerHandler(handler);
212 let args = Array::new();
213 let result = this.call_method_js_async("getFeatures", &args).await?;
214 Ok(serde_wasm_bindgen::from_value(result)?)
215 })
216 }
217
218 fn get_hosted_tables(&self) -> HandlerFuture<Result<Vec<HostedTable>, Self::Error>> {
219 let handler = self.0.clone();
220 Box::pin(async move {
221 let this = JsServerHandler(handler);
222 let args = Array::new();
223 let result = this.call_method_js_async("getHostedTables", &args).await?;
224 let array = result.dyn_ref::<Array>().ok_or_else(|| {
225 JsError(JsValue::from_str("getHostedTables must return an array"))
226 })?;
227
228 let mut tables = Vec::new();
229 for i in 0..array.length() {
230 let item = array.get(i);
231 if let Some(s) = item.as_string() {
232 tables.push(HostedTable {
233 entity_id: s,
234 index: None,
235 limit: None,
236 });
237 } else if item.is_object() {
238 let name = Reflect::get(&item, &JsValue::from_str("name"))?
239 .as_string()
240 .ok_or_else(|| JsError(JsValue::from_str("name must be a string")))?;
241 let index = Reflect::get(&item, &JsValue::from_str("index"))
242 .ok()
243 .and_then(|v| v.as_string());
244 let limit = Reflect::get(&item, &JsValue::from_str("limit"))
245 .ok()
246 .and_then(|v| v.as_f64().map(|x| x as u32));
247 tables.push(HostedTable {
248 entity_id: name,
249 index,
250 limit,
251 });
252 }
253 }
254 Ok(tables)
255 })
256 }
257
258 fn table_schema(
259 &self,
260 table_id: &str,
261 ) -> HandlerFuture<Result<IndexMap<String, ColumnType>, Self::Error>> {
262 let handler = self.0.clone();
263 let table_id = table_id.to_string();
264 Box::pin(async move {
265 let this = JsServerHandler(handler);
266 let args = Array::new();
267 args.push(&JsValue::from_str(&table_id));
268 let result = this.call_method_js_async("tableSchema", &args).await?;
269 let obj = result
270 .dyn_ref::<Object>()
271 .ok_or_else(|| JsError(JsValue::from_str("tableSchema must return an object")))?;
272
273 let mut schema = IndexMap::new();
274 let entries = Object::entries(obj);
275 for i in 0..entries.length() {
276 let entry = entries.get(i);
277 let entry_array = entry.dyn_ref::<Array>().unwrap();
278 let key = entry_array.get(0).as_string().unwrap();
279 let value = entry_array.get(1).as_string().unwrap();
280 schema.insert(key, ColumnType::from_str(&value).unwrap());
281 }
282 Ok(schema)
283 })
284 }
285
286 fn table_size(&self, table_id: &str) -> HandlerFuture<Result<u32, Self::Error>> {
287 let handler = self.0.clone();
288 let table_id = table_id.to_string();
289 Box::pin(async move {
290 let this = JsServerHandler(handler);
291 let args = Array::new();
292 args.push(&JsValue::from_str(&table_id));
293 let result = this.call_method_js_async("tableSize", &args).await?;
294 result
295 .as_f64()
296 .map(|x| x as u32)
297 .ok_or_else(|| JsError(JsValue::from_str("tableSize must return a number")))
298 })
299 }
300
301 fn table_column_size(&self, view_id: &str) -> HandlerFuture<Result<u32, Self::Error>> {
302 let has_method = Reflect::get(&self.0, &JsValue::from_str("tableColumnsSize"))
303 .map(|val| !val.is_undefined())
304 .unwrap_or(false);
305
306 let handler = self.0.clone();
307 let view_id = view_id.to_string();
308 Box::pin(async move {
309 let this = JsServerHandler(handler);
310 let args = Array::new();
311 args.push(&JsValue::from_str(&view_id));
312 if has_method {
313 let result = this.call_method_js_async("tableColumnsSize", &args).await?;
314 result.as_f64().map(|x| x as u32).ok_or_else(|| {
315 JsError(JsValue::from_str(
316 "tableColumnsSize must
317 return a number",
318 ))
319 })
320 } else {
321 Ok(this.table_schema(view_id.as_str()).await?.len() as u32)
322 }
323 })
324 }
325
326 fn table_validate_expression(
327 &self,
328 table_id: &str,
329 expression: &str,
330 ) -> HandlerFuture<Result<ColumnType, Self::Error>> {
331 let has_method = Reflect::get(&self.0, &JsValue::from_str("tableValidateExpression"))
333 .map(|val| !val.is_undefined())
334 .unwrap_or(false);
335
336 let handler = self.0.clone();
337 let table_id = table_id.to_string();
338 let expression = expression.to_string();
339 Box::pin(async move {
340 if !has_method {
341 return Err(JsError(JsValue::from_str(
342 "feature `table_validate_expression` not implemented",
343 )));
344 }
345
346 let this = JsServerHandler(handler);
347 let args = Array::new();
348 args.push(&JsValue::from_str(&table_id));
349 args.push(&JsValue::from_str(&expression));
350 let result = this
351 .call_method_js_async("tableValidateExpression", &args)
352 .await?;
353
354 let type_str = result
355 .as_string()
356 .ok_or_else(|| JsError(JsValue::from_str("Must return a string")))?;
357
358 Ok(ColumnType::from_str(&type_str).unwrap())
359 })
360 }
361
362 fn table_make_view(
363 &mut self,
364 table_id: &str,
365 view_id: &str,
366 config: &mut perspective_client::config::ViewConfigUpdate,
367 ) -> HandlerFuture<Result<String, Self::Error>> {
368 let handler = self.0.clone();
369 let table_id = table_id.to_string();
370 let view_id = view_id.to_string();
371 let config = config.clone();
372 Box::pin(async move {
373 let this = JsServerHandler(handler);
374 let args = Array::new();
375 args.push(&JsValue::from_str(&table_id));
376 args.push(&JsValue::from_str(&view_id));
377 args.push(&JsValue::from_serde_ext(&config)?);
378 let _ = this.call_method_js_async("tableMakeView", &args).await?;
379 Ok(view_id.to_string())
380 })
381 }
382
383 fn view_schema(
384 &self,
385 view_id: &str,
386 config: &perspective_client::config::ViewConfig,
387 ) -> HandlerFuture<Result<IndexMap<String, ColumnType>, Self::Error>> {
388 let has_view_schema = Reflect::get(&self.0, &JsValue::from_str("viewSchema"))
389 .is_ok_and(|v| !v.is_undefined());
390
391 let handler = self.0.clone();
392 let view_id = view_id.to_string();
393 let config_value = JsValue::from_serde_ext(config).ok();
394
395 Box::pin(async move {
396 let this = JsServerHandler(handler);
397 let args = Array::new();
398 args.push(&JsValue::from_str(&view_id));
399 if let Some(cv) = config_value {
400 args.push(&cv);
401 }
402
403 let result = this
404 .call_method_js_async(
405 if has_view_schema {
406 "viewSchema"
407 } else {
408 "tableSchema"
409 },
410 &args,
411 )
412 .await?;
413
414 let obj = result
415 .dyn_ref::<Object>()
416 .ok_or_else(|| JsError(JsValue::from_str("viewSchema must return an object")))?;
417
418 let mut schema = IndexMap::new();
419 let entries = Object::entries(obj);
420 for i in 0..entries.length() {
421 let entry = entries.get(i);
422 let entry_array = entry.dyn_ref::<Array>().unwrap();
423 let key = entry_array.get(0).as_string().unwrap();
424 let value = entry_array.get(1).as_string().unwrap();
425 schema.insert(key, ColumnType::from_str(&value).unwrap());
426 }
427
428 Ok(schema)
429 })
430 }
431
432 fn view_size(&self, view_id: &str) -> HandlerFuture<Result<u32, Self::Error>> {
433 let handler = self.0.clone();
434 let view_id = view_id.to_string();
435 let has_view_size =
436 Reflect::get(&self.0, &JsValue::from_str("viewSize")).is_ok_and(|v| !v.is_undefined());
437
438 Box::pin(async move {
439 let this = JsServerHandler(handler);
440 let args = Array::new();
441 args.push(&JsValue::from_str(&view_id));
442 let result = this
443 .call_method_js_async(
444 if has_view_size {
445 "viewSize"
446 } else {
447 "tableSize"
448 },
449 &args,
450 )
451 .await?;
452
453 result
454 .as_f64()
455 .map(|x| x as u32)
456 .ok_or_else(|| JsError(JsValue::from_str("viewSize must return a number")))
457 })
458 }
459
460 fn view_column_size(
461 &self,
462 view_id: &str,
463 config: &perspective_client::config::ViewConfig,
464 ) -> HandlerFuture<Result<u32, Self::Error>> {
465 let has_method = Reflect::get(&self.0, &JsValue::from_str("viewColumnSize"))
466 .map(|val| !val.is_undefined())
467 .unwrap_or(false);
468
469 let handler = self.0.clone();
470 let view_id = view_id.to_string();
471 let config_value = JsValue::from_serde_ext(config).unwrap();
472 let config = config.clone();
473 Box::pin(async move {
474 let this = JsServerHandler(handler);
475 let args = Array::new();
476 args.push(&JsValue::from_str(&view_id));
477 args.push(&config_value);
478 if has_method {
479 let result = this.call_method_js_async("viewColumnSize", &args).await?;
480 result.as_f64().map(|x| x as u32).ok_or_else(|| {
481 JsError(JsValue::from_str("viewColumnSize must return a number"))
482 })
483 } else {
484 Ok(this.view_schema(view_id.as_str(), &config).await?.len() as u32)
485 }
486 })
487 }
488
489 fn view_delete(&self, view_id: &str) -> HandlerFuture<Result<(), Self::Error>> {
490 let handler = self.0.clone();
491 let view_id = view_id.to_string();
492 Box::pin(async move {
493 let this = JsServerHandler(handler);
494 let args = Array::new();
495 args.push(&JsValue::from_str(&view_id));
496 this.call_method_js_async("viewDelete", &args).await?;
497 Ok(())
498 })
499 }
500
501 fn table_make_port(
502 &self,
503 _req: &perspective_client::proto::TableMakePortReq,
504 ) -> HandlerFuture<Result<u32, Self::Error>> {
505 let has_method = Reflect::get(&self.0, &JsValue::from_str("tableMakePort"))
506 .map(|val| !val.is_undefined())
507 .unwrap_or(false);
508
509 if !has_method {
510 return Box::pin(async { Ok(0) });
511 }
512
513 let handler = self.0.clone();
514 Box::pin(async move {
515 let this = JsServerHandler(handler);
516 let args = Array::new();
517 let result = this.call_method_js_async("tableMakePort", &args).await?;
518 result
519 .as_f64()
520 .map(|x| x as u32)
521 .ok_or_else(|| JsError(JsValue::from_str("tableMakePort must return a number")))
522 })
523 }
524
525 fn make_table(
526 &mut self,
527 table_id: &str,
528 data: &perspective_client::proto::MakeTableData,
529 ) -> HandlerFuture<Result<(), Self::Error>> {
530 let has_method = Reflect::get(&self.0, &JsValue::from_str("makeTable"))
531 .map(|val| !val.is_undefined())
532 .unwrap_or(false);
533
534 if !has_method {
535 return Box::pin(async {
536 Err(JsError(JsValue::from_str("makeTable not implemented")))
537 });
538 }
539
540 let handler = self.0.clone();
541 let table_id = table_id.to_string();
542 use perspective_client::proto::make_table_data::Data;
543 let data_value = match &data.data {
544 Some(Data::FromCsv(csv)) => JsValue::from_str(csv),
545 Some(Data::FromArrow(arrow)) => {
546 let uint8array = js_sys::Uint8Array::from(arrow.as_slice());
547 JsValue::from(uint8array)
548 },
549 Some(Data::FromRows(rows)) => JsValue::from_str(rows),
550 Some(Data::FromCols(cols)) => JsValue::from_str(cols),
551 Some(Data::FromNdjson(ndjson)) => JsValue::from_str(ndjson),
552 _ => JsValue::from_str(""),
553 };
554
555 Box::pin(async move {
556 let this = JsServerHandler(handler);
557 let args = Array::new();
558 args.push(&JsValue::from_str(&table_id));
559 args.push(&data_value);
560 this.call_method_js_async("makeTable", &args).await?;
561 Ok(())
562 })
563 }
564
565 fn view_get_min_max(
566 &self,
567 view_id: &str,
568 column_name: &str,
569 config: &perspective_client::config::ViewConfig,
570 ) -> HandlerFuture<
571 Result<
572 (
573 perspective_client::config::Scalar,
574 perspective_client::config::Scalar,
575 ),
576 Self::Error,
577 >,
578 > {
579 let has_method = Reflect::get(&self.0, &JsValue::from_str("viewGetMinMax"))
580 .map(|val| !val.is_undefined())
581 .unwrap_or(false);
582
583 if !has_method {
584 return Box::pin(async {
585 Err(JsError(JsValue::from_str("viewGetMinMax not implemented")))
586 });
587 }
588
589 let handler = self.0.clone();
590 let view_id = view_id.to_string();
591 let column_name = column_name.to_string();
592 let config_js = JsValue::from_serde_ext(config).unwrap();
593 Box::pin(async move {
594 let this = JsServerHandler(handler);
595 let args = Array::new();
596 args.push(&JsValue::from_str(&view_id));
597 args.push(&JsValue::from_str(&column_name));
598 args.push(&config_js);
599 let result = this.call_method_js_async("viewGetMinMax", &args).await?;
600 let obj = result.dyn_ref::<Object>().unwrap();
601 let min_val = Reflect::get(obj, &JsValue::from_str(wasm_bindgen::intern("min")))?;
602 let max_val = Reflect::get(obj, &JsValue::from_str(wasm_bindgen::intern("max")))?;
603 Ok((jsvalue_to_scalar(&min_val), jsvalue_to_scalar(&max_val)))
604 })
605 }
606
607 fn view_get_data(
608 &self,
609 view_id: &str,
610 config: &perspective_client::config::ViewConfig,
611 schema: &IndexMap<String, ColumnType>,
612 viewport: &perspective_client::proto::ViewPort,
613 ) -> HandlerFuture<Result<virtual_server::VirtualDataSlice, Self::Error>> {
614 let handler = self.0.clone();
615 let view_id = view_id.to_string();
616 let window: JsViewPort = viewport.clone().into();
617 let config_value = JsValue::from_serde_ext(config).unwrap();
618 let window_value = JsValue::from_serde_ext(&window).unwrap();
619 let schema_value = JsValue::from_serde_ext(&schema).unwrap();
620
621 Box::pin(async move {
622 let this = JsServerHandler(handler);
623 let data = VirtualDataSlice::new(config_value.clone().unchecked_into());
624
625 {
626 let args = Array::new();
627 args.push(&JsValue::from_str(&view_id));
628 args.push(&config_value);
629 args.push(&schema_value);
630 args.push(&window_value);
631 args.push(&JsValue::from(data.clone()));
632 this.call_method_js_async("viewGetData", &args).await?;
633 }
634
635 let VirtualDataSlice(_obj, arc) = data;
638 let slice = std::mem::take(&mut *arc.lock().unwrap()).unwrap();
639 Ok(slice)
640 })
641 }
642}
643
644#[derive(Serialize, PartialEq)]
645pub struct JsViewPort {
646 #[serde(default, skip_serializing_if = "Option::is_none")]
647 pub start_row: ::core::option::Option<u32>,
648
649 #[serde(default, skip_serializing_if = "Option::is_none")]
650 pub start_col: ::core::option::Option<u32>,
651
652 #[serde(default, skip_serializing_if = "Option::is_none")]
653 pub end_row: ::core::option::Option<u32>,
654
655 #[serde(default, skip_serializing_if = "Option::is_none")]
656 pub end_col: ::core::option::Option<u32>,
657}
658
659impl From<perspective_client::proto::ViewPort> for JsViewPort {
660 fn from(value: perspective_client::proto::ViewPort) -> Self {
661 JsViewPort {
662 start_row: value.start_row,
663 start_col: value.start_col,
664 end_row: value.end_row,
665 end_col: value.end_col,
666 }
667 }
668}
669
670#[wasm_bindgen(js_name = "VirtualDataSlice")]
671#[derive(Clone)]
672pub struct VirtualDataSlice(Object, Arc<Mutex<Option<virtual_server::VirtualDataSlice>>>);
673
674#[wasm_bindgen]
675impl VirtualDataSlice {
676 #[wasm_bindgen(constructor)]
677 pub fn new(config: JsViewConfig) -> Self {
678 VirtualDataSlice(
679 Object::new(),
680 Arc::new(Mutex::new(Some(virtual_server::VirtualDataSlice::new(
681 config.into_serde_ext().unwrap(),
682 )))),
683 )
684 }
685
686 #[wasm_bindgen(js_name = "fromArrowIpc")]
687 pub fn from_arrow_ipc(&self, ipc: Uint8Array) -> Result<(), JsValue> {
688 self.1
689 .lock()
690 .unwrap()
691 .as_mut()
692 .unwrap()
693 .from_arrow_ipc(&ipc.to_vec())
694 .map_err(|e| JsValue::from_str(&e.to_string()))
695 }
696
697 #[wasm_bindgen(js_name = "setCol")]
698 pub fn set_col(
699 &self,
700 dtype: &str,
701 name: &str,
702 index: u32,
703 val: JsValue,
704 group_by_index: Option<usize>,
705 ) -> Result<(), JsValue> {
706 match dtype {
707 "string" => self.set_string_col(name, index, val, group_by_index),
708 "integer" => self.set_integer_col(name, index, val, group_by_index),
709 "float" => self.set_float_col(name, index, val, group_by_index),
710 "date" => self.set_datetime_col(name, index, val, group_by_index),
711 "datetime" => self.set_datetime_col(name, index, val, group_by_index),
712 "boolean" => self.set_boolean_col(name, index, val, group_by_index),
713 _ => Err(JsValue::from_str("Unknown type")),
714 }
715 }
716
717 #[wasm_bindgen(js_name = "setStringCol")]
718 pub fn set_string_col(
719 &self,
720 name: &str,
721 index: u32,
722 val: JsValue,
723 group_by_index: Option<usize>,
724 ) -> Result<(), JsValue> {
725 if val.is_null() || val.is_undefined() {
726 self.1
727 .lock()
728 .unwrap()
729 .as_mut()
730 .unwrap()
731 .set_col(name, group_by_index, index as usize, None as Option<String>)
732 .unwrap();
733 } else if let Some(s) = val.as_string() {
734 self.1
735 .lock()
736 .unwrap()
737 .as_mut()
738 .unwrap()
739 .set_col(name, group_by_index, index as usize, Some(s))
740 .unwrap();
741 } else {
742 tracing::error!("Unhandled string value");
743 }
744 Ok(())
745 }
746
747 #[wasm_bindgen(js_name = "setIntegerCol")]
748 pub fn set_integer_col(
749 &self,
750 name: &str,
751 index: u32,
752 val: JsValue,
753 group_by_index: Option<usize>,
754 ) -> Result<(), JsValue> {
755 if val.is_null() || val.is_undefined() {
756 self.1
757 .lock()
758 .unwrap()
759 .as_mut()
760 .unwrap()
761 .set_col(name, group_by_index, index as usize, None as Option<i32>)
762 .unwrap();
763 } else if let Some(n) = val.as_f64() {
764 self.1
765 .lock()
766 .unwrap()
767 .as_mut()
768 .unwrap()
769 .set_col(name, group_by_index, index as usize, Some(n as i32))
770 .unwrap();
771 } else {
772 tracing::error!("Unhandled integer value");
773 }
774 Ok(())
775 }
776
777 #[wasm_bindgen(js_name = "setFloatCol")]
778 pub fn set_float_col(
779 &self,
780 name: &str,
781 index: u32,
782 val: JsValue,
783 group_by_index: Option<usize>,
784 ) -> Result<(), JsValue> {
785 if val.is_null() || val.is_undefined() {
786 self.1
787 .lock()
788 .unwrap()
789 .as_mut()
790 .unwrap()
791 .set_col(name, group_by_index, index as usize, None as Option<f64>)
792 .unwrap();
793 } else if let Some(n) = val.as_f64() {
794 self.1
795 .lock()
796 .unwrap()
797 .as_mut()
798 .unwrap()
799 .set_col(name, group_by_index, index as usize, Some(n))
800 .unwrap();
801 } else {
802 tracing::error!("Unhandled float value");
803 }
804 Ok(())
805 }
806
807 #[wasm_bindgen(js_name = "setBooleanCol")]
808 pub fn set_boolean_col(
809 &self,
810 name: &str,
811 index: u32,
812 val: JsValue,
813 group_by_index: Option<usize>,
814 ) -> Result<(), JsValue> {
815 if val.is_null() || val.is_undefined() {
816 self.1
817 .lock()
818 .unwrap()
819 .as_mut()
820 .unwrap()
821 .set_col(name, group_by_index, index as usize, None as Option<bool>)
822 .unwrap();
823 } else if let Some(b) = val.as_bool() {
824 self.1
825 .lock()
826 .unwrap()
827 .as_mut()
828 .unwrap()
829 .set_col(name, group_by_index, index as usize, Some(b))
830 .unwrap();
831 } else {
832 tracing::error!("Unhandled boolean value");
833 }
834 Ok(())
835 }
836
837 #[wasm_bindgen(js_name = "setDatetimeCol")]
838 pub fn set_datetime_col(
839 &self,
840 name: &str,
841 index: u32,
842 val: JsValue,
843 group_by_index: Option<usize>,
844 ) -> Result<(), JsValue> {
845 if val.is_null() || val.is_undefined() {
846 self.1
847 .lock()
848 .unwrap()
849 .as_mut()
850 .unwrap()
851 .set_col(name, group_by_index, index as usize, None as Option<i64>)
852 .unwrap();
853 } else if let Some(date) = val.dyn_ref::<Date>() {
854 let timestamp = date.get_time() as i64;
855 self.1
856 .lock()
857 .unwrap()
858 .as_mut()
859 .unwrap()
860 .set_col(name, group_by_index, index as usize, Some(timestamp))
861 .unwrap();
862 } else if let Some(n) = val.as_f64() {
863 self.1
864 .lock()
865 .unwrap()
866 .as_mut()
867 .unwrap()
868 .set_col(name, group_by_index, index as usize, Some(n as i64))
869 .unwrap();
870 } else {
871 tracing::error!("Unhandled datetime value");
872 }
873
874 Ok(())
875 }
876}
877
878#[wasm_bindgen]
879pub struct VirtualServer(Rc<UnsafeCell<virtual_server::VirtualServer<JsServerHandler>>>);
880
881#[wasm_bindgen]
882impl VirtualServer {
883 #[wasm_bindgen(constructor)]
884 pub fn new(handler: JsVirtualServerHandler) -> Result<VirtualServer, JsValue> {
885 Ok(VirtualServer(Rc::new(UnsafeCell::new(
886 virtual_server::VirtualServer::new(JsServerHandler(handler.unchecked_into())),
887 ))))
888 }
889
890 #[wasm_bindgen(js_name = "handleRequest")]
891 pub fn handle_request(&self, bytes: &[u8]) -> ApiFuture<Vec<u8>> {
892 let bytes = bytes.to_vec();
893 let server = self.0.clone();
894
895 ApiFuture::new(async move {
896 let result = unsafe {
901 (&mut *server.as_ref().get())
902 .handle_request(bytes::Bytes::from(bytes))
903 .await
904 };
905
906 match result.get_internal_error() {
907 Ok(x) => Ok(x.to_vec()),
908 Err(Ok(x)) => Err(ApiError::from(JsValue::from(x))),
909 Err(Err(x)) => Err(ApiError::from(JsValue::from_str(&x))),
910 }
911 })
912 }
913}