@perspective-dev/client 4.1.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20,22 +20,17 @@ use std::sync::{Arc, Mutex};
20
20
  use indexmap::IndexMap;
21
21
  use js_sys::{Array, Date, Object, Reflect};
22
22
  use perspective_client::proto::{ColumnType, HostedTable};
23
- use perspective_client::virtual_server::{
24
- Features, ResultExt, VirtualDataSlice, VirtualServer, VirtualServerHandler,
25
- };
23
+ use perspective_client::virtual_server;
24
+ use perspective_client::virtual_server::{Features, ResultExt, VirtualServerHandler};
26
25
  use serde::Serialize;
27
26
  use wasm_bindgen::prelude::*;
28
27
  use wasm_bindgen_futures::JsFuture;
29
28
 
29
+ use crate::JsViewConfig;
30
30
  use crate::utils::{ApiError, ApiFuture, *};
31
31
 
32
- // Conditional type alias matching the trait definition
33
- #[cfg(target_arch = "wasm32")]
34
32
  type HandlerFuture<T> = Pin<Box<dyn Future<Output = T>>>;
35
33
 
36
- #[cfg(not(target_arch = "wasm32"))]
37
- type HandlerFuture<T> = Pin<Box<dyn Future<Output = T> + Send>>;
38
-
39
34
  #[derive(Debug)]
40
35
  pub struct JsError(JsValue);
41
36
 
@@ -65,16 +60,8 @@ impl From<serde_wasm_bindgen::Error> for JsError {
65
60
  }
66
61
  }
67
62
 
68
- // SAFETY: In WASM, we're always single-threaded, so JsError can safely be Send
69
- // + Sync
70
- unsafe impl Send for JsError {}
71
- unsafe impl Sync for JsError {}
72
-
73
63
  pub struct JsServerHandler(Object);
74
64
 
75
- unsafe impl Send for JsServerHandler {}
76
- unsafe impl Sync for JsServerHandler {}
77
-
78
65
  impl JsServerHandler {
79
66
  fn call_method_js(&self, method: &str, args: &Array) -> Result<JsValue, JsError> {
80
67
  let func = Reflect::get(&self.0, &JsValue::from_str(method))?;
@@ -90,7 +77,7 @@ impl JsServerHandler {
90
77
  // Check if result is a Promise
91
78
  if result.is_instance_of::<js_sys::Promise>() {
92
79
  let promise = js_sys::Promise::from(result);
93
- JsFuture::from(promise).await.map_err(|e| JsError(e))
80
+ JsFuture::from(promise).await.map_err(JsError)
94
81
  } else {
95
82
  Ok(result)
96
83
  }
@@ -293,11 +280,7 @@ impl VirtualServerHandler for JsServerHandler {
293
280
 
294
281
  let handler = self.0.clone();
295
282
  let view_id = view_id.to_string();
296
- let config_value = if has_view_schema {
297
- serde_wasm_bindgen::to_value(config).ok()
298
- } else {
299
- None
300
- };
283
+ let config_value = JsValue::from_serde_ext(config).ok();
301
284
 
302
285
  Box::pin(async move {
303
286
  let this = JsServerHandler(handler);
@@ -446,7 +429,6 @@ impl VirtualServerHandler for JsServerHandler {
446
429
 
447
430
  let handler = self.0.clone();
448
431
  let table_id = table_id.to_string();
449
-
450
432
  use perspective_client::proto::make_table_data::Data;
451
433
  let data_value = match &data.data {
452
434
  Some(Data::FromCsv(csv)) => JsValue::from_str(csv),
@@ -474,22 +456,25 @@ impl VirtualServerHandler for JsServerHandler {
474
456
  &self,
475
457
  view_id: &str,
476
458
  config: &perspective_client::config::ViewConfig,
459
+ schema: &IndexMap<String, ColumnType>,
477
460
  viewport: &perspective_client::proto::ViewPort,
478
- ) -> HandlerFuture<Result<VirtualDataSlice, Self::Error>> {
461
+ ) -> HandlerFuture<Result<virtual_server::VirtualDataSlice, Self::Error>> {
479
462
  let handler = self.0.clone();
480
463
  let view_id = view_id.to_string();
481
464
  let window: JsViewPort = viewport.clone().into();
482
465
  let config_value = serde_wasm_bindgen::to_value(config).unwrap();
483
466
  let window_value = serde_wasm_bindgen::to_value(&window).unwrap();
467
+ let schema_value = JsValue::from_serde_ext(&schema).unwrap();
484
468
 
485
469
  Box::pin(async move {
486
470
  let this = JsServerHandler(handler);
487
- let data = JsVirtualDataSlice::default();
471
+ let data = VirtualDataSlice::new(config_value.clone().unchecked_into());
488
472
 
489
473
  {
490
474
  let args = Array::new();
491
475
  args.push(&JsValue::from_str(&view_id));
492
476
  args.push(&config_value);
477
+ args.push(&schema_value);
493
478
  args.push(&window_value);
494
479
  args.push(&JsValue::from(data.clone()));
495
480
  this.call_method_js_async("viewGetData", &args).await?;
@@ -497,8 +482,8 @@ impl VirtualServerHandler for JsServerHandler {
497
482
 
498
483
  // Lock the mutex and take ownership of the inner data
499
484
  // We can't unwrap the Arc because the JsValue might still hold a reference
500
- let JsVirtualDataSlice(_obj, arc) = data;
501
- let slice = std::mem::take(&mut *arc.lock().unwrap());
485
+ let VirtualDataSlice(_obj, arc) = data;
486
+ let slice = std::mem::take(&mut *arc.lock().unwrap()).unwrap();
502
487
  Ok(slice)
503
488
  })
504
489
  }
@@ -530,24 +515,20 @@ impl From<perspective_client::proto::ViewPort> for JsViewPort {
530
515
  }
531
516
  }
532
517
 
533
- #[wasm_bindgen]
518
+ #[wasm_bindgen(js_name = "VirtualDataSlice")]
534
519
  #[derive(Clone)]
535
- pub struct JsVirtualDataSlice(Object, Arc<Mutex<VirtualDataSlice>>);
536
-
537
- impl Default for JsVirtualDataSlice {
538
- fn default() -> Self {
539
- JsVirtualDataSlice(
540
- Object::new(),
541
- Arc::new(Mutex::new(VirtualDataSlice::default())),
542
- )
543
- }
544
- }
520
+ pub struct VirtualDataSlice(Object, Arc<Mutex<Option<virtual_server::VirtualDataSlice>>>);
545
521
 
546
522
  #[wasm_bindgen]
547
- impl JsVirtualDataSlice {
523
+ impl VirtualDataSlice {
548
524
  #[wasm_bindgen(constructor)]
549
- pub fn new() -> Self {
550
- Self::default()
525
+ pub fn new(config: JsViewConfig) -> Self {
526
+ VirtualDataSlice(
527
+ Object::new(),
528
+ Arc::new(Mutex::new(Some(virtual_server::VirtualDataSlice::new(
529
+ config.into_serde_ext().unwrap(),
530
+ )))),
531
+ )
551
532
  }
552
533
 
553
534
  #[wasm_bindgen(js_name = "setCol")]
@@ -582,12 +563,16 @@ impl JsVirtualDataSlice {
582
563
  self.1
583
564
  .lock()
584
565
  .unwrap()
566
+ .as_mut()
567
+ .unwrap()
585
568
  .set_col(name, group_by_index, index as usize, None as Option<String>)
586
569
  .unwrap();
587
570
  } else if let Some(s) = val.as_string() {
588
571
  self.1
589
572
  .lock()
590
573
  .unwrap()
574
+ .as_mut()
575
+ .unwrap()
591
576
  .set_col(name, group_by_index, index as usize, Some(s))
592
577
  .unwrap();
593
578
  } else {
@@ -608,12 +593,16 @@ impl JsVirtualDataSlice {
608
593
  self.1
609
594
  .lock()
610
595
  .unwrap()
596
+ .as_mut()
597
+ .unwrap()
611
598
  .set_col(name, group_by_index, index as usize, None as Option<i32>)
612
599
  .unwrap();
613
600
  } else if let Some(n) = val.as_f64() {
614
601
  self.1
615
602
  .lock()
616
603
  .unwrap()
604
+ .as_mut()
605
+ .unwrap()
617
606
  .set_col(name, group_by_index, index as usize, Some(n as i32))
618
607
  .unwrap();
619
608
  } else {
@@ -634,12 +623,16 @@ impl JsVirtualDataSlice {
634
623
  self.1
635
624
  .lock()
636
625
  .unwrap()
626
+ .as_mut()
627
+ .unwrap()
637
628
  .set_col(name, group_by_index, index as usize, None as Option<f64>)
638
629
  .unwrap();
639
630
  } else if let Some(n) = val.as_f64() {
640
631
  self.1
641
632
  .lock()
642
633
  .unwrap()
634
+ .as_mut()
635
+ .unwrap()
643
636
  .set_col(name, group_by_index, index as usize, Some(n))
644
637
  .unwrap();
645
638
  } else {
@@ -660,12 +653,16 @@ impl JsVirtualDataSlice {
660
653
  self.1
661
654
  .lock()
662
655
  .unwrap()
656
+ .as_mut()
657
+ .unwrap()
663
658
  .set_col(name, group_by_index, index as usize, None as Option<bool>)
664
659
  .unwrap();
665
660
  } else if let Some(b) = val.as_bool() {
666
661
  self.1
667
662
  .lock()
668
663
  .unwrap()
664
+ .as_mut()
665
+ .unwrap()
669
666
  .set_col(name, group_by_index, index as usize, Some(b))
670
667
  .unwrap();
671
668
  } else {
@@ -686,6 +683,8 @@ impl JsVirtualDataSlice {
686
683
  self.1
687
684
  .lock()
688
685
  .unwrap()
686
+ .as_mut()
687
+ .unwrap()
689
688
  .set_col(name, group_by_index, index as usize, None as Option<i64>)
690
689
  .unwrap();
691
690
  } else if let Some(date) = val.dyn_ref::<Date>() {
@@ -693,30 +692,35 @@ impl JsVirtualDataSlice {
693
692
  self.1
694
693
  .lock()
695
694
  .unwrap()
695
+ .as_mut()
696
+ .unwrap()
696
697
  .set_col(name, group_by_index, index as usize, Some(timestamp))
697
698
  .unwrap();
698
699
  } else if let Some(n) = val.as_f64() {
699
700
  self.1
700
701
  .lock()
701
702
  .unwrap()
703
+ .as_mut()
704
+ .unwrap()
702
705
  .set_col(name, group_by_index, index as usize, Some(n as i64))
703
706
  .unwrap();
704
707
  } else {
705
708
  tracing::error!("Unhandled datetime value");
706
709
  }
710
+
707
711
  Ok(())
708
712
  }
709
713
  }
710
714
 
711
715
  #[wasm_bindgen]
712
- pub struct JsVirtualServer(Rc<UnsafeCell<VirtualServer<JsServerHandler>>>);
716
+ pub struct VirtualServer(Rc<UnsafeCell<virtual_server::VirtualServer<JsServerHandler>>>);
713
717
 
714
718
  #[wasm_bindgen]
715
- impl JsVirtualServer {
719
+ impl VirtualServer {
716
720
  #[wasm_bindgen(constructor)]
717
- pub fn new(handler: Object) -> Result<JsVirtualServer, JsValue> {
718
- Ok(JsVirtualServer(Rc::new(UnsafeCell::new(
719
- VirtualServer::new(JsServerHandler(handler)),
721
+ pub fn new(handler: Object) -> Result<VirtualServer, JsValue> {
722
+ Ok(VirtualServer(Rc::new(UnsafeCell::new(
723
+ virtual_server::VirtualServer::new(JsServerHandler(handler)),
720
724
  ))))
721
725
  }
722
726
 
@@ -13,13 +13,23 @@
13
13
  export type * from "../../dist/wasm/perspective-js.d.ts";
14
14
  import type * as psp from "../../dist/wasm/perspective-js.d.ts";
15
15
  export type * from "./virtual_server.ts";
16
-
17
16
  import * as psp_virtual from "./virtual_server.ts";
18
-
19
17
  import * as wasm_module from "../../dist/wasm/perspective-js.js";
20
18
  import * as api from "./wasm/browser.ts";
21
19
  import { load_wasm_stage_0 } from "./wasm/decompress.ts";
22
20
 
21
+ export {
22
+ GenericSQLVirtualServerModel,
23
+ VirtualDataSlice,
24
+ VirtualServer,
25
+ } from "../../dist/wasm/perspective-js.js";
26
+
27
+ import {
28
+ GenericSQLVirtualServerModel,
29
+ VirtualDataSlice,
30
+ VirtualServer,
31
+ } from "../../dist/wasm/perspective-js.js";
32
+
23
33
  let GLOBAL_SERVER_WASM: Promise<ArrayBuffer | WebAssembly.Module>;
24
34
 
25
35
  export async function createMessageHandler(
@@ -144,4 +154,7 @@ export default {
144
154
  init_client,
145
155
  init_server,
146
156
  createMessageHandler,
157
+ GenericSQLVirtualServerModel,
158
+ VirtualDataSlice,
159
+ VirtualServer,
147
160
  };
@@ -29,9 +29,20 @@ import * as engine from "./wasm/engine.ts";
29
29
  import { compile_perspective } from "./wasm/emscripten_api.ts";
30
30
  import * as psp_websocket from "./websocket.ts";
31
31
  import * as api from "./wasm/browser.ts";
32
-
33
32
  import * as virtual_server from "./virtual_server.ts";
34
33
 
34
+ export {
35
+ GenericSQLVirtualServerModel,
36
+ VirtualDataSlice,
37
+ VirtualServer,
38
+ } from "../../dist/wasm/perspective-js.js";
39
+
40
+ import {
41
+ GenericSQLVirtualServerModel,
42
+ VirtualDataSlice,
43
+ VirtualServer,
44
+ } from "../../dist/wasm/perspective-js.js";
45
+
35
46
  const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
36
47
 
37
48
  const { resolve } = createRequire(import.meta.url);
@@ -337,6 +348,12 @@ export function createMessageHandler(
337
348
  return virtual_server.createMessageHandler(perspective_client, handler);
338
349
  }
339
350
 
351
+ /**
352
+ * The initialized WASM module. Use this when you need to pass the module
353
+ * to components that require it, such as `DuckDBHandler`.
354
+ */
355
+ export { perspective_client as wasmModule };
356
+
340
357
  export default {
341
358
  table,
342
359
  websocket,
@@ -347,4 +364,7 @@ export default {
347
364
  on_error,
348
365
  system_info,
349
366
  WebSocketServer,
367
+ GenericSQLVirtualServerModel,
368
+ VirtualDataSlice,
369
+ VirtualServer,
350
370
  };
@@ -55,8 +55,9 @@ export interface VirtualServerHandler {
55
55
  viewGetData(
56
56
  viewId: string,
57
57
  config: ViewConfig,
58
+ schema: Record<string, ColumnType>,
58
59
  viewport: ViewWindow,
59
- dataSlice: perspective.JsVirtualDataSlice,
60
+ dataSlice: perspective.VirtualDataSlice,
60
61
  ): void | Promise<void>;
61
62
  viewSchema?(
62
63
  viewId: string,
@@ -78,11 +79,11 @@ export function createMessageHandler(
78
79
  mod: typeof perspective,
79
80
  handler: VirtualServerHandler,
80
81
  ) {
81
- let virtualServer: perspective.JsVirtualServer;
82
+ let virtualServer: perspective.VirtualServer;
82
83
  async function postMessage(port: MessagePort, msg: MessageEvent) {
83
84
  if (msg.data.cmd === "init") {
84
85
  try {
85
- virtualServer = new mod.JsVirtualServer(handler);
86
+ virtualServer = new mod.VirtualServer(handler);
86
87
  if (msg.data.id !== undefined) {
87
88
  port.postMessage({ id: msg.data.id });
88
89
  } else {
@@ -113,14 +114,3 @@ export function createMessageHandler(
113
114
 
114
115
  return channel.port2;
115
116
  }
116
-
117
- /**
118
- * Re-export the WASM VirtualServer and VirtualDataSlice classes with better names.
119
- *
120
- * VirtualServer: Handles Perspective protocol messages using your custom handler
121
- * VirtualDataSlice: Used to fill data in viewGetData callbacks
122
- */
123
- export {
124
- JsVirtualServer as VirtualServer,
125
- JsVirtualDataSlice as VirtualDataSlice,
126
- } from "../../dist/wasm/perspective-js.js";