@perspective-dev/client 4.1.1 → 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.
Binary file
@@ -1,85 +1,97 @@
1
1
  /* tslint:disable */
2
2
  /* eslint-disable */
3
3
  export const memory: WebAssembly.Memory;
4
- export const __wbg_proxysession_free: (a: number, b: number) => void;
5
- export const proxysession_handle_request: (a: number, b: number) => number;
6
- export const proxysession_close: (a: number) => number;
7
4
  export const __wbg_client_free: (a: number, b: number) => void;
5
+ export const __wbg_genericsqlvirtualservermodel_free: (a: number, b: number) => void;
6
+ export const __wbg_proxysession_free: (a: number, b: number) => void;
7
+ export const __wbg_table_free: (a: number, b: number) => void;
8
+ export const __wbg_view_free: (a: number, b: number) => void;
9
+ export const __wbg_virtualdataslice_free: (a: number, b: number) => void;
10
+ export const __wbg_virtualserver_free: (a: number, b: number) => void;
8
11
  export const client___getClassname: (a: number, b: number) => void;
12
+ export const client_get_hosted_table_names: (a: number) => number;
13
+ export const client_handle_error: (a: number, b: number, c: number, d: number) => number;
14
+ export const client_handle_response: (a: number, b: number) => number;
9
15
  export const client_new: (a: number, b: number, c: number) => void;
10
16
  export const client_new_proxy_session: (a: number, b: number) => number;
11
- export const client_handle_response: (a: number, b: number) => number;
12
- export const client_handle_error: (a: number, b: number, c: number, d: number) => number;
13
17
  export const client_on_error: (a: number, b: number) => number;
14
- export const client_table: (a: number, b: number, c: number) => number;
15
- export const client_terminate: (a: number, b: number) => void;
16
- export const client_open_table: (a: number, b: number, c: number) => number;
17
- export const client_get_hosted_table_names: (a: number) => number;
18
18
  export const client_on_hosted_tables_update: (a: number, b: number) => number;
19
+ export const client_open_table: (a: number, b: number, c: number) => number;
19
20
  export const client_remove_hosted_tables_update: (a: number, b: number) => number;
20
21
  export const client_system_info: (a: number) => number;
22
+ export const client_table: (a: number, b: number, c: number) => number;
23
+ export const client_terminate: (a: number, b: number) => void;
24
+ export const genericsqlvirtualservermodel_getHostedTables: (a: number, b: number) => void;
25
+ export const genericsqlvirtualservermodel_new: (a: number, b: number) => void;
26
+ export const genericsqlvirtualservermodel_tableMakeView: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
27
+ export const genericsqlvirtualservermodel_tableSchema: (a: number, b: number, c: number, d: number) => void;
28
+ export const genericsqlvirtualservermodel_tableSize: (a: number, b: number, c: number, d: number) => void;
29
+ export const genericsqlvirtualservermodel_tableValidateExpression: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
30
+ export const genericsqlvirtualservermodel_viewColumnSize: (a: number, b: number, c: number, d: number) => void;
31
+ export const genericsqlvirtualservermodel_viewDelete: (a: number, b: number, c: number, d: number) => void;
32
+ export const genericsqlvirtualservermodel_viewGetData: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
33
+ export const genericsqlvirtualservermodel_viewSchema: (a: number, b: number, c: number, d: number) => void;
34
+ export const genericsqlvirtualservermodel_viewSize: (a: number, b: number, c: number, d: number) => void;
35
+ export const init: () => void;
36
+ export const proxysession_close: (a: number) => number;
37
+ export const proxysession_handle_request: (a: number, b: number) => number;
38
+ export const proxysession_new: (a: number, b: number) => number;
21
39
  export const table___getClassname: (a: number, b: number) => void;
22
- export const __wbg_table_free: (a: number, b: number) => void;
23
- export const table_get_index: (a: number) => number;
24
- export const table_get_client: (a: number) => number;
25
- export const table_get_name: (a: number) => number;
26
- export const table_get_limit: (a: number) => number;
27
40
  export const table_clear: (a: number) => number;
28
- export const table_delete: (a: number, b: number) => number;
29
- export const table_size: (a: number) => number;
30
- export const table_schema: (a: number) => number;
31
41
  export const table_columns: (a: number) => number;
42
+ export const table_delete: (a: number, b: number) => number;
43
+ export const table_get_client: (a: number) => number;
44
+ export const table_get_index: (a: number) => number;
45
+ export const table_get_limit: (a: number) => number;
46
+ export const table_get_name: (a: number) => number;
32
47
  export const table_make_port: (a: number) => number;
33
48
  export const table_on_delete: (a: number, b: number) => number;
34
- export const table_remove_delete: (a: number, b: number) => number;
35
49
  export const table_remove: (a: number, b: number, c: number) => number;
50
+ export const table_remove_delete: (a: number, b: number) => number;
36
51
  export const table_replace: (a: number, b: number, c: number) => number;
52
+ export const table_schema: (a: number) => number;
53
+ export const table_size: (a: number) => number;
37
54
  export const table_update: (a: number, b: number, c: number) => number;
38
- export const table_view: (a: number, b: number) => number;
39
55
  export const table_validate_expressions: (a: number, b: number) => number;
40
- export const __wbg_view_free: (a: number, b: number) => void;
56
+ export const table_view: (a: number, b: number) => number;
41
57
  export const view___get_model: (a: number) => number;
58
+ export const view_collapse: (a: number, b: number) => number;
42
59
  export const view_column_paths: (a: number, b: number) => number;
43
60
  export const view_delete: (a: number) => number;
44
61
  export const view_dimensions: (a: number) => number;
62
+ export const view_expand: (a: number, b: number) => number;
45
63
  export const view_expression_schema: (a: number) => number;
46
64
  export const view_get_config: (a: number) => number;
47
65
  export const view_get_min_max: (a: number, b: number, c: number) => number;
66
+ export const view_num_columns: (a: number) => number;
48
67
  export const view_num_rows: (a: number) => number;
68
+ export const view_on_delete: (a: number, b: number) => number;
69
+ export const view_on_update: (a: number, b: number, c: number) => number;
70
+ export const view_remove_delete: (a: number, b: number) => number;
71
+ export const view_remove_update: (a: number, b: number) => number;
49
72
  export const view_schema: (a: number) => number;
73
+ export const view_set_depth: (a: number, b: number) => number;
50
74
  export const view_to_arrow: (a: number, b: number) => number;
51
- export const view_to_columns_string: (a: number, b: number) => number;
52
75
  export const view_to_columns: (a: number, b: number) => number;
53
- export const view_to_json_string: (a: number, b: number) => number;
76
+ export const view_to_columns_string: (a: number, b: number) => number;
77
+ export const view_to_csv: (a: number, b: number) => number;
54
78
  export const view_to_json: (a: number, b: number) => number;
79
+ export const view_to_json_string: (a: number, b: number) => number;
55
80
  export const view_to_ndjson: (a: number, b: number) => number;
56
- export const view_to_csv: (a: number, b: number) => number;
57
- export const view_on_update: (a: number, b: number, c: number) => number;
58
- export const view_remove_update: (a: number, b: number) => number;
59
- export const view_on_delete: (a: number, b: number) => number;
60
- export const view_num_columns: (a: number) => number;
61
- export const view_remove_delete: (a: number, b: number) => number;
62
- export const view_collapse: (a: number, b: number) => number;
63
- export const view_expand: (a: number, b: number) => number;
64
- export const view_set_depth: (a: number, b: number) => number;
65
- export const __wbg_jsvirtualdataslice_free: (a: number, b: number) => void;
66
- export const jsvirtualdataslice_new: () => number;
67
- export const jsvirtualdataslice_setCol: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number) => void;
68
- export const jsvirtualdataslice_setStringCol: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
69
- export const jsvirtualdataslice_setIntegerCol: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
70
- export const jsvirtualdataslice_setFloatCol: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
71
- export const jsvirtualdataslice_setBooleanCol: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
72
- export const jsvirtualdataslice_setDatetimeCol: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
73
- export const __wbg_jsvirtualserver_free: (a: number, b: number) => void;
74
- export const jsvirtualserver_new: (a: number, b: number) => void;
75
- export const jsvirtualserver_handleRequest: (a: number, b: number, c: number) => number;
76
- export const init: () => void;
77
- export const proxysession_new: (a: number, b: number) => number;
78
- export const __wasm_bindgen_func_elem_553: (a: number, b: number) => number;
79
- export const __wasm_bindgen_func_elem_123: (a: number, b: number) => void;
80
- export const __wasm_bindgen_func_elem_3515: (a: number, b: number, c: number) => void;
81
- export const __wasm_bindgen_func_elem_3500: (a: number, b: number) => void;
82
- export const __wasm_bindgen_func_elem_5070: (a: number, b: number, c: number, d: number) => void;
81
+ export const virtualdataslice_new: (a: number) => number;
82
+ export const virtualdataslice_setBooleanCol: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
83
+ export const virtualdataslice_setCol: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number) => void;
84
+ export const virtualdataslice_setDatetimeCol: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
85
+ export const virtualdataslice_setFloatCol: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
86
+ export const virtualdataslice_setIntegerCol: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
87
+ export const virtualdataslice_setStringCol: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
88
+ export const virtualserver_handleRequest: (a: number, b: number, c: number) => number;
89
+ export const virtualserver_new: (a: number, b: number) => void;
90
+ export const __wasm_bindgen_func_elem_1421: (a: number, b: number) => void;
91
+ export const __wasm_bindgen_func_elem_3793: (a: number, b: number) => void;
92
+ export const __wasm_bindgen_func_elem_5384: (a: number, b: number, c: number, d: number) => void;
93
+ export const __wasm_bindgen_func_elem_3795: (a: number, b: number, c: number) => void;
94
+ export const __wasm_bindgen_func_elem_1668: (a: number, b: number) => number;
83
95
  export const __wbindgen_export: (a: number, b: number) => number;
84
96
  export const __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
85
97
  export const __wbindgen_export3: (a: number) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@perspective-dev/client",
3
- "version": "4.1.1",
3
+ "version": "4.2.0",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
@@ -48,6 +48,7 @@
48
48
  "@perspective-dev/esbuild-plugin": "",
49
49
  "@perspective-dev/metadata": "",
50
50
  "@perspective-dev/test": "",
51
+ "@clickhouse/client-web": "^1.12.0",
51
52
  "@duckdb/duckdb-wasm": "^1.30.0",
52
53
  "@playwright/experimental-ct-react": "=1.52.0",
53
54
  "@playwright/test": "=1.52.0",
@@ -0,0 +1,189 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ //! WASM bindings for the DuckDB SQL query builder.
14
+
15
+ use std::str::FromStr;
16
+
17
+ use indexmap::IndexMap;
18
+ use js_sys::Object;
19
+ use perspective_client::config::ViewConfig;
20
+ use perspective_client::proto::{ColumnType, ViewPort};
21
+ use perspective_client::virtual_server;
22
+ use wasm_bindgen::prelude::*;
23
+
24
+ use crate::utils::*;
25
+
26
+ /// JavaScript-facing DuckDB SQL query builder.
27
+ ///
28
+ /// This struct wraps the Rust `DuckDBSqlBuilder` and exposes it to JavaScript
29
+ /// via wasm_bindgen.
30
+ #[wasm_bindgen]
31
+ pub struct GenericSQLVirtualServerModel {
32
+ inner: virtual_server::GenericSQLVirtualServerModel,
33
+ }
34
+
35
+ #[wasm_bindgen]
36
+ extern "C" {
37
+ pub type JsGenericSQLVirtualServerModelArgs;
38
+ }
39
+
40
+ #[wasm_bindgen]
41
+ impl GenericSQLVirtualServerModel {
42
+ /// Creates a new `JsDuckDBSqlBuilder` instance.
43
+ #[wasm_bindgen(constructor)]
44
+ pub fn new(args: Option<JsGenericSQLVirtualServerModelArgs>) -> Result<Self, JsValue> {
45
+ Ok(Self {
46
+ inner: virtual_server::GenericSQLVirtualServerModel::new(
47
+ args.map(|x| x.into_serde_ext())
48
+ .transpose()?
49
+ .unwrap_or_default(),
50
+ ),
51
+ })
52
+ }
53
+
54
+ /// Returns the SQL query to list all hosted tables.
55
+ #[wasm_bindgen(js_name = "getHostedTables")]
56
+ pub fn get_hosted_tables(&self) -> Result<String, JsValue> {
57
+ self.inner
58
+ .get_hosted_tables()
59
+ .map_err(|e| JsValue::from_str(&e.to_string()))
60
+ }
61
+
62
+ /// Returns the SQL query to describe a table's schema.
63
+ #[wasm_bindgen(js_name = "tableSchema")]
64
+ pub fn table_schema(&self, table_id: &str) -> Result<String, JsValue> {
65
+ self.inner
66
+ .table_schema(table_id)
67
+ .map_err(|e| JsValue::from_str(&e.to_string()))
68
+ }
69
+
70
+ /// Returns the SQL query to get the row count of a table.
71
+ #[wasm_bindgen(js_name = "tableSize")]
72
+ pub fn table_size(&self, table_id: &str) -> Result<String, JsValue> {
73
+ self.inner
74
+ .table_size(table_id)
75
+ .map_err(|e| JsValue::from_str(&e.to_string()))
76
+ }
77
+
78
+ /// Returns the SQL query to get the column count of a view.
79
+ #[wasm_bindgen(js_name = "viewColumnSize")]
80
+ pub fn view_column_size(&self, view_id: &str) -> Result<String, JsValue> {
81
+ self.inner
82
+ .view_column_size(view_id)
83
+ .map_err(|e| JsValue::from_str(&e.to_string()))
84
+ }
85
+
86
+ /// Returns the SQL query to validate an expression against a table.
87
+ #[wasm_bindgen(js_name = "tableValidateExpression")]
88
+ pub fn table_validate_expression(
89
+ &self,
90
+ table_id: &str,
91
+ expression: &str,
92
+ ) -> Result<String, JsValue> {
93
+ self.inner
94
+ .table_validate_expression(table_id, expression)
95
+ .map_err(|e| JsValue::from_str(&e.to_string()))
96
+ }
97
+
98
+ /// Returns the SQL query to delete a view.
99
+ #[wasm_bindgen(js_name = "viewDelete")]
100
+ pub fn view_delete(&self, view_id: &str) -> Result<String, JsValue> {
101
+ self.inner
102
+ .view_delete(view_id)
103
+ .map_err(|e| JsValue::from_str(&e.to_string()))
104
+ }
105
+
106
+ /// Returns the SQL query to create a view from a table with the given
107
+ /// configuration.
108
+ #[wasm_bindgen(js_name = "tableMakeView")]
109
+ pub fn table_make_view(
110
+ &self,
111
+ table_id: &str,
112
+ view_id: &str,
113
+ config: JsValue,
114
+ ) -> Result<String, JsValue> {
115
+ let config: ViewConfig = serde_wasm_bindgen::from_value(config)
116
+ .map_err(|e| JsValue::from_str(&e.to_string()))?;
117
+
118
+ self.inner
119
+ .table_make_view(table_id, view_id, &config)
120
+ .map_err(|e| JsValue::from_str(&e.to_string()))
121
+ }
122
+
123
+ /// Returns the SQL query to fetch data from a view with the given viewport.
124
+ #[wasm_bindgen(js_name = "viewGetData")]
125
+ pub fn view_get_data(
126
+ &self,
127
+ view_id: &str,
128
+ config: JsValue,
129
+ viewport: JsValue,
130
+ schema: JsValue,
131
+ ) -> Result<String, JsValue> {
132
+ let config: ViewConfig = serde_wasm_bindgen::from_value(config)
133
+ .map_err(|e| JsValue::from_str(&e.to_string()))?;
134
+
135
+ let viewport: ViewPort = serde_wasm_bindgen::from_value(viewport)
136
+ .map_err(|e| JsValue::from_str(&e.to_string()))?;
137
+
138
+ let schema = self.parse_schema(schema)?;
139
+
140
+ self.inner
141
+ .view_get_data(view_id, &config, &viewport, &schema)
142
+ .map_err(|e| JsValue::from_str(&e.to_string()))
143
+ }
144
+
145
+ /// Returns the SQL query to describe a view's schema.
146
+ #[wasm_bindgen(js_name = "viewSchema")]
147
+ pub fn view_schema(&self, view_id: &str) -> Result<String, JsValue> {
148
+ self.inner
149
+ .view_schema(view_id)
150
+ .map_err(|e| JsValue::from_str(&e.to_string()))
151
+ }
152
+
153
+ /// Returns the SQL query to get the row count of a view.
154
+ #[wasm_bindgen(js_name = "viewSize")]
155
+ pub fn view_size(&self, view_id: &str) -> Result<String, JsValue> {
156
+ self.inner
157
+ .view_size(view_id)
158
+ .map_err(|e| JsValue::from_str(&e.to_string()))
159
+ }
160
+ }
161
+
162
+ impl GenericSQLVirtualServerModel {
163
+ fn parse_schema(&self, schema: JsValue) -> Result<IndexMap<String, ColumnType>, JsValue> {
164
+ let obj = schema.dyn_ref::<Object>().ok_or_else(|| {
165
+ JsValue::from_str("Schema must be an object mapping column names to types")
166
+ })?;
167
+
168
+ let mut result = IndexMap::new();
169
+ let entries = Object::entries(obj);
170
+ for i in 0..entries.length() {
171
+ let entry = entries.get(i);
172
+ let entry_array = entry
173
+ .dyn_ref::<js_sys::Array>()
174
+ .ok_or_else(|| JsValue::from_str("Invalid schema entry"))?;
175
+ let key = entry_array
176
+ .get(0)
177
+ .as_string()
178
+ .ok_or_else(|| JsValue::from_str("Column name must be a string"))?;
179
+ let value = entry_array
180
+ .get(1)
181
+ .as_string()
182
+ .ok_or_else(|| JsValue::from_str("Column type must be a string"))?;
183
+ let column_type = ColumnType::from_str(&value)
184
+ .map_err(|_| JsValue::from_str(&format!("Unknown column type: {}", value)))?;
185
+ result.insert(key, column_type);
186
+ }
187
+ Ok(result)
188
+ }
189
+ }
package/src/rust/lib.rs CHANGED
@@ -26,20 +26,20 @@
26
26
  extern crate alloc;
27
27
 
28
28
  mod client;
29
+ mod generic_sql_model;
29
30
  mod table;
30
31
  mod table_data;
31
32
  pub mod utils;
32
33
  mod view;
33
- #[cfg(target_arch = "wasm32")]
34
34
  mod virtual_server;
35
35
 
36
36
  #[cfg(feature = "export-init")]
37
37
  use wasm_bindgen::prelude::*;
38
38
 
39
39
  pub use crate::client::Client;
40
+ pub use crate::generic_sql_model::*;
40
41
  pub use crate::table::*;
41
42
  pub use crate::table_data::*;
42
- #[cfg(target_arch = "wasm32")]
43
43
  pub use crate::virtual_server::*;
44
44
 
45
45
  #[cfg(feature = "export-init")]
@@ -170,10 +170,10 @@ impl<S: Subscriber + for<'a> LookupSpan<'a>> Layer<S> for WasmLogger {
170
170
  }
171
171
 
172
172
  fn on_record(&self, id: &tracing::Id, values: &tracing::span::Record<'_>, ctx: Context<'_, S>) {
173
- if let Some(span_ref) = ctx.span(id) {
174
- if let Some(debug_record) = span_ref.extensions_mut().get_mut::<LogLineBuffer>() {
175
- values.record(debug_record);
176
- }
173
+ if let Some(span_ref) = ctx.span(id)
174
+ && let Some(debug_record) = span_ref.extensions_mut().get_mut::<LogLineBuffer>()
175
+ {
176
+ values.record(debug_record);
177
177
  }
178
178
  }
179
179
 
@@ -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