@perspective-dev/client 4.0.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.
- package/LICENSE.md +193 -0
- package/README.md +3 -0
- package/dist/cdn/perspective-server.worker.js +2 -0
- package/dist/cdn/perspective-server.worker.js.map +7 -0
- package/dist/cdn/perspective.js +3 -0
- package/dist/cdn/perspective.js.map +7 -0
- package/dist/esm/perspective-server.worker.d.ts +1 -0
- package/dist/esm/perspective.browser.d.ts +14 -0
- package/dist/esm/perspective.inline.js +3 -0
- package/dist/esm/perspective.inline.js.map +7 -0
- package/dist/esm/perspective.js +3 -0
- package/dist/esm/perspective.js.map +7 -0
- package/dist/esm/perspective.node.d.ts +60 -0
- package/dist/esm/perspective.node.js +2431 -0
- package/dist/esm/perspective.node.js.map +7 -0
- package/dist/esm/ts-rs/Aggregate.d.ts +1 -0
- package/dist/esm/ts-rs/ColumnWindow.d.ts +4 -0
- package/dist/esm/ts-rs/DeleteOptions.d.ts +6 -0
- package/dist/esm/ts-rs/Expressions.d.ts +3 -0
- package/dist/esm/ts-rs/Filter.d.ts +2 -0
- package/dist/esm/ts-rs/FilterReducer.d.ts +1 -0
- package/dist/esm/ts-rs/FilterTerm.d.ts +2 -0
- package/dist/esm/ts-rs/OnUpdateMode.d.ts +9 -0
- package/dist/esm/ts-rs/OnUpdateOptions.d.ts +7 -0
- package/dist/esm/ts-rs/Scalar.d.ts +5 -0
- package/dist/esm/ts-rs/Sort.d.ts +2 -0
- package/dist/esm/ts-rs/SortDir.d.ts +1 -0
- package/dist/esm/ts-rs/SystemInfo.d.ts +40 -0
- package/dist/esm/ts-rs/TableInitOptions.d.ts +22 -0
- package/dist/esm/ts-rs/TableReadFormat.d.ts +7 -0
- package/dist/esm/ts-rs/UpdateOptions.d.ts +8 -0
- package/dist/esm/ts-rs/ViewConfigUpdate.d.ts +90 -0
- package/dist/esm/ts-rs/ViewOnUpdateResp.d.ts +4 -0
- package/dist/esm/ts-rs/ViewWindow.d.ts +23 -0
- package/dist/esm/wasm/browser.d.ts +21 -0
- package/dist/esm/wasm/decompress.d.ts +1 -0
- package/dist/esm/wasm/emscripten_api.d.ts +5 -0
- package/dist/esm/wasm/engine.d.ts +40 -0
- package/dist/esm/wasm/perspective-server.poly.d.ts +1 -0
- package/dist/esm/websocket.d.ts +4 -0
- package/dist/wasm/perspective-js.d.ts +712 -0
- package/dist/wasm/perspective-js.js +1934 -0
- package/dist/wasm/perspective-js.wasm +0 -0
- package/dist/wasm/perspective-js.wasm.d.ts +75 -0
- package/package.json +68 -0
- package/src/rust/client.rs +483 -0
- package/src/rust/lib.rs +70 -0
- package/src/rust/table.rs +364 -0
- package/src/rust/table_data.rs +159 -0
- package/src/rust/utils/browser.rs +39 -0
- package/src/rust/utils/console_logger.rs +236 -0
- package/src/rust/utils/errors.rs +288 -0
- package/src/rust/utils/futures.rs +174 -0
- package/src/rust/utils/json.rs +252 -0
- package/src/rust/utils/local_poll_loop.rs +63 -0
- package/src/rust/utils/mod.rs +32 -0
- package/src/rust/utils/serde.rs +46 -0
- package/src/rust/utils/trace_allocator.rs +98 -0
- package/src/rust/view.rs +355 -0
- package/src/ts/perspective-server.worker.ts +54 -0
- package/src/ts/perspective.browser.ts +132 -0
- package/src/ts/perspective.cdn.ts +22 -0
- package/src/ts/perspective.inline.ts +27 -0
- package/src/ts/perspective.node.ts +315 -0
- package/src/ts/ts-rs/Aggregate.ts +3 -0
- package/src/ts/ts-rs/ColumnWindow.ts +3 -0
- package/src/ts/ts-rs/DeleteOptions.ts +6 -0
- package/src/ts/ts-rs/Expressions.ts +3 -0
- package/src/ts/ts-rs/Filter.ts +4 -0
- package/src/ts/ts-rs/FilterReducer.ts +3 -0
- package/src/ts/ts-rs/FilterTerm.ts +4 -0
- package/src/ts/ts-rs/OnUpdateData.ts +8 -0
- package/src/ts/ts-rs/OnUpdateMode.ts +11 -0
- package/src/ts/ts-rs/OnUpdateOptions.ts +7 -0
- package/src/ts/ts-rs/Scalar.ts +7 -0
- package/src/ts/ts-rs/Sort.ts +4 -0
- package/src/ts/ts-rs/SortDir.ts +3 -0
- package/src/ts/ts-rs/SystemInfo.ts +41 -0
- package/src/ts/ts-rs/TableInitOptions.ts +21 -0
- package/src/ts/ts-rs/TableReadFormat.ts +9 -0
- package/src/ts/ts-rs/UpdateOptions.ts +7 -0
- package/src/ts/ts-rs/ViewConfigUpdate.ts +87 -0
- package/src/ts/ts-rs/ViewOnUpdateResp.ts +3 -0
- package/src/ts/ts-rs/ViewWindow.ts +17 -0
- package/src/ts/wasm/browser.ts +123 -0
- package/src/ts/wasm/decompress.ts +64 -0
- package/src/ts/wasm/emscripten_api.ts +63 -0
- package/src/ts/wasm/engine.ts +271 -0
- package/src/ts/wasm/perspective-server.poly.ts +244 -0
- package/src/ts/websocket.ts +95 -0
- package/tsconfig.json +20 -0
|
Binary file
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/* tslint:disable */
|
|
2
|
+
/* eslint-disable */
|
|
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
|
+
export const __wbg_client_free: (a: number, b: number) => void;
|
|
8
|
+
export const client___getClassname: (a: number, b: number) => void;
|
|
9
|
+
export const client_new: (a: number, b: number, c: number) => void;
|
|
10
|
+
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
|
+
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
|
+
export const client_on_hosted_tables_update: (a: number, b: number) => number;
|
|
19
|
+
export const client_remove_hosted_tables_update: (a: number, b: number) => number;
|
|
20
|
+
export const client_system_info: (a: number) => number;
|
|
21
|
+
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
|
+
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
|
+
export const table_columns: (a: number) => number;
|
|
32
|
+
export const table_make_port: (a: number) => number;
|
|
33
|
+
export const table_on_delete: (a: number, b: number) => number;
|
|
34
|
+
export const table_remove_delete: (a: number, b: number) => number;
|
|
35
|
+
export const table_remove: (a: number, b: number, c: number) => number;
|
|
36
|
+
export const table_replace: (a: number, b: number, c: number) => number;
|
|
37
|
+
export const table_update: (a: number, b: number, c: number) => number;
|
|
38
|
+
export const table_view: (a: number, b: number) => number;
|
|
39
|
+
export const table_validate_expressions: (a: number, b: number) => number;
|
|
40
|
+
export const __wbg_view_free: (a: number, b: number) => void;
|
|
41
|
+
export const view___get_model: (a: number) => number;
|
|
42
|
+
export const view_column_paths: (a: number, b: number) => number;
|
|
43
|
+
export const view_delete: (a: number) => number;
|
|
44
|
+
export const view_dimensions: (a: number) => number;
|
|
45
|
+
export const view_expression_schema: (a: number) => number;
|
|
46
|
+
export const view_get_config: (a: number) => number;
|
|
47
|
+
export const view_get_min_max: (a: number, b: number, c: number) => number;
|
|
48
|
+
export const view_num_rows: (a: number) => number;
|
|
49
|
+
export const view_schema: (a: number) => number;
|
|
50
|
+
export const view_to_arrow: (a: number, b: number) => number;
|
|
51
|
+
export const view_to_columns_string: (a: number, b: number) => number;
|
|
52
|
+
export const view_to_columns: (a: number, b: number) => number;
|
|
53
|
+
export const view_to_json_string: (a: number, b: number) => number;
|
|
54
|
+
export const view_to_json: (a: number, b: number) => number;
|
|
55
|
+
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 init: () => void;
|
|
66
|
+
export const proxysession_new: (a: number, b: number) => number;
|
|
67
|
+
export const __wbindgen_export_0: (a: number, b: number) => number;
|
|
68
|
+
export const __wbindgen_export_1: (a: number, b: number, c: number, d: number) => number;
|
|
69
|
+
export const __wbindgen_export_2: (a: number) => void;
|
|
70
|
+
export const __wbindgen_export_3: (a: number, b: number, c: number) => void;
|
|
71
|
+
export const __wbindgen_export_4: WebAssembly.Table;
|
|
72
|
+
export const __wbindgen_add_to_stack_pointer: (a: number) => number;
|
|
73
|
+
export const __wbindgen_export_5: (a: number, b: number) => number;
|
|
74
|
+
export const __wbindgen_export_6: (a: number, b: number, c: number) => void;
|
|
75
|
+
export const __wbindgen_export_7: (a: number, b: number, c: number, d: number) => void;
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@perspective-dev/client",
|
|
3
|
+
"version": "4.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/perspective-dev/perspective"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"license": "Apache-2.0",
|
|
11
|
+
"unpkg": "dist/cdn/perspective.js",
|
|
12
|
+
"jsdelivr": "dist/cdn/perspective.js",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"node": {
|
|
16
|
+
"types": "./dist/esm/perspective.node.d.ts",
|
|
17
|
+
"default": "./dist/esm/perspective.node.js"
|
|
18
|
+
},
|
|
19
|
+
"types": "./dist/esm/perspective.browser.d.ts",
|
|
20
|
+
"default": "./dist/esm/perspective.js"
|
|
21
|
+
},
|
|
22
|
+
"./node": {
|
|
23
|
+
"types": "./dist/esm/perspective.node.d.ts",
|
|
24
|
+
"default": "./dist/esm/perspective.node.js"
|
|
25
|
+
},
|
|
26
|
+
"./dist/*": "./dist/*",
|
|
27
|
+
"./src/*": "./src/*",
|
|
28
|
+
"./test/*": "./test/*",
|
|
29
|
+
"./package.json": "./package.json",
|
|
30
|
+
"./tsconfig.json": "./tsconfig.json"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist/**/*",
|
|
34
|
+
"src/**/*",
|
|
35
|
+
"tsconfig.json"
|
|
36
|
+
],
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@perspective-dev/server": "",
|
|
42
|
+
"pro_self_extracting_wasm": "0.0.9",
|
|
43
|
+
"stoppable": "=1.1.0",
|
|
44
|
+
"ws": "^8.17.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@perspective-dev/esbuild-plugin": "",
|
|
48
|
+
"@perspective-dev/metadata": "",
|
|
49
|
+
"@perspective-dev/test": "",
|
|
50
|
+
"@playwright/experimental-ct-react": "=1.52.0",
|
|
51
|
+
"@playwright/test": "=1.52.0",
|
|
52
|
+
"@types/node": ">=22",
|
|
53
|
+
"@types/ws": ">=8",
|
|
54
|
+
"@types/stoppable": ">=1",
|
|
55
|
+
"apache-arrow": "18.1.0",
|
|
56
|
+
"superstore-arrow": "3.2.0",
|
|
57
|
+
"lodash": "^4.17.20",
|
|
58
|
+
"moment": "^2.30.1",
|
|
59
|
+
"typedoc": "^0.28.7",
|
|
60
|
+
"typescript": ">=5 <6",
|
|
61
|
+
"zx": ">=8 <9"
|
|
62
|
+
},
|
|
63
|
+
"scripts": {
|
|
64
|
+
"build": "node ./build.mjs",
|
|
65
|
+
"clean": "node ./clean.mjs",
|
|
66
|
+
"docs": "node ./docs.mjs"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,483 @@
|
|
|
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
|
+
use std::error::Error;
|
|
14
|
+
use std::future::Future;
|
|
15
|
+
use std::sync::Arc;
|
|
16
|
+
|
|
17
|
+
use derivative::Derivative;
|
|
18
|
+
use futures::channel::oneshot;
|
|
19
|
+
use js_sys::{Function, Uint8Array};
|
|
20
|
+
#[cfg(doc)]
|
|
21
|
+
use perspective_client::SystemInfo;
|
|
22
|
+
use perspective_client::{
|
|
23
|
+
ClientError, ReconnectCallback, Session, TableData, TableInitOptions, asyncfn,
|
|
24
|
+
};
|
|
25
|
+
use wasm_bindgen::prelude::*;
|
|
26
|
+
use wasm_bindgen_derive::TryFromJsValue;
|
|
27
|
+
use wasm_bindgen_futures::{JsFuture, future_to_promise};
|
|
28
|
+
|
|
29
|
+
pub use crate::table::*;
|
|
30
|
+
use crate::utils::{ApiError, ApiResult, JsValueSerdeExt, LocalPollLoop};
|
|
31
|
+
use crate::{TableDataExt, apierror};
|
|
32
|
+
|
|
33
|
+
#[wasm_bindgen]
|
|
34
|
+
extern "C" {
|
|
35
|
+
#[derive(Clone)]
|
|
36
|
+
#[wasm_bindgen(typescript_type = "TableInitOptions")]
|
|
37
|
+
pub type JsTableInitOptions;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
#[wasm_bindgen]
|
|
41
|
+
#[derive(Clone)]
|
|
42
|
+
pub struct ProxySession(perspective_client::ProxySession);
|
|
43
|
+
|
|
44
|
+
#[wasm_bindgen]
|
|
45
|
+
impl ProxySession {
|
|
46
|
+
#[wasm_bindgen(constructor)]
|
|
47
|
+
pub fn new(client: &Client, on_response: &Function) -> Self {
|
|
48
|
+
let poll_loop = LocalPollLoop::new({
|
|
49
|
+
let on_response = on_response.clone();
|
|
50
|
+
move |msg: Vec<u8>| {
|
|
51
|
+
let msg = Uint8Array::from(&msg[..]);
|
|
52
|
+
on_response.call1(&JsValue::UNDEFINED, &JsValue::from(msg))?;
|
|
53
|
+
Ok(JsValue::null())
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
// NB: This swallows any errors raised by the inner callback
|
|
57
|
+
let on_response = Box::new(move |msg: &[u8]| {
|
|
58
|
+
wasm_bindgen_futures::spawn_local(poll_loop.poll(msg.to_vec()));
|
|
59
|
+
Ok(())
|
|
60
|
+
});
|
|
61
|
+
Self(perspective_client::ProxySession::new(
|
|
62
|
+
client.client.clone(),
|
|
63
|
+
on_response,
|
|
64
|
+
))
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
#[wasm_bindgen]
|
|
68
|
+
pub async fn handle_request(&self, value: JsValue) -> ApiResult<()> {
|
|
69
|
+
let uint8array = Uint8Array::new(&value);
|
|
70
|
+
let slice = uint8array.to_vec();
|
|
71
|
+
self.0.handle_request(&slice).await?;
|
|
72
|
+
Ok(())
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
pub async fn close(self) {
|
|
76
|
+
self.0.close().await;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/// An instance of a [`Client`] is a connection to a single
|
|
81
|
+
/// `perspective_server::Server`, whether locally in-memory or remote over some
|
|
82
|
+
/// transport like a WebSocket.
|
|
83
|
+
///
|
|
84
|
+
/// The browser and node.js libraries both support the `websocket(url)`
|
|
85
|
+
/// constructor, which connects to a remote `perspective_server::Server`
|
|
86
|
+
/// instance over a WebSocket transport.
|
|
87
|
+
///
|
|
88
|
+
/// In the browser, the `worker()` constructor creates a new Web Worker
|
|
89
|
+
/// `perspective_server::Server` and returns a [`Client`] connected to it.
|
|
90
|
+
///
|
|
91
|
+
/// In node.js, a pre-instantied [`Client`] connected synhronously to a global
|
|
92
|
+
/// singleton `perspective_server::Server` is the default module export.
|
|
93
|
+
///
|
|
94
|
+
/// # JavaScript Examples
|
|
95
|
+
///
|
|
96
|
+
/// Create a Web Worker `perspective_server::Server` in the browser and return a
|
|
97
|
+
/// [`Client`] instance connected for it:
|
|
98
|
+
///
|
|
99
|
+
/// ```javascript
|
|
100
|
+
/// import perspective from "@perspective-dev/client";
|
|
101
|
+
/// const client = await perspective.worker();
|
|
102
|
+
/// ```
|
|
103
|
+
///
|
|
104
|
+
/// Create a WebSocket connection to a remote `perspective_server::Server`:
|
|
105
|
+
///
|
|
106
|
+
/// ```javascript
|
|
107
|
+
/// import perspective from "@perspective-dev/client";
|
|
108
|
+
/// const client = await perspective.websocket("ws://locahost:8080/ws");
|
|
109
|
+
/// ```
|
|
110
|
+
///
|
|
111
|
+
/// Access the synchronous client in node.js:
|
|
112
|
+
///
|
|
113
|
+
/// ```javascript
|
|
114
|
+
/// import { default as client } from "@perspective-dev/client";
|
|
115
|
+
/// ```
|
|
116
|
+
#[wasm_bindgen]
|
|
117
|
+
#[derive(TryFromJsValue, Clone)]
|
|
118
|
+
pub struct Client {
|
|
119
|
+
pub(crate) close: Option<Function>,
|
|
120
|
+
pub(crate) client: perspective_client::Client,
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
impl PartialEq for Client {
|
|
124
|
+
fn eq(&self, other: &Self) -> bool {
|
|
125
|
+
self.client.get_name() == other.client.get_name()
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/// A wrapper around [`js_sys::Function`] to ease async integration for the
|
|
130
|
+
/// `reconnect` argument of [`Client::on_error`] callback.
|
|
131
|
+
#[derive(Derivative)]
|
|
132
|
+
#[derivative(Clone(bound = ""))]
|
|
133
|
+
struct JsReconnect<I>(Arc<dyn Fn(I) -> js_sys::Promise>);
|
|
134
|
+
|
|
135
|
+
unsafe impl<I> Send for JsReconnect<I> {}
|
|
136
|
+
unsafe impl<I> Sync for JsReconnect<I> {}
|
|
137
|
+
|
|
138
|
+
impl<I> JsReconnect<I> {
|
|
139
|
+
fn run(&self, args: I) -> js_sys::Promise {
|
|
140
|
+
self.0(args)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
fn run_all(
|
|
144
|
+
&self,
|
|
145
|
+
args: I,
|
|
146
|
+
) -> impl Future<Output = Result<(), Box<dyn Error + Send + Sync + 'static>>>
|
|
147
|
+
+ Send
|
|
148
|
+
+ Sync
|
|
149
|
+
+ 'static
|
|
150
|
+
+ use<I> {
|
|
151
|
+
let (sender, receiver) = oneshot::channel::<Result<(), Box<dyn Error + Send + Sync>>>();
|
|
152
|
+
let p = self.0(args);
|
|
153
|
+
let _ = future_to_promise(async move {
|
|
154
|
+
let result = JsFuture::from(p)
|
|
155
|
+
.await
|
|
156
|
+
.map(|_| ())
|
|
157
|
+
.map_err(|x| format!("{x:?}").into());
|
|
158
|
+
|
|
159
|
+
sender.send(result).unwrap();
|
|
160
|
+
Ok(JsValue::UNDEFINED)
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
async move { receiver.await.unwrap() }
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
impl<F, I> From<F> for JsReconnect<I>
|
|
168
|
+
where
|
|
169
|
+
F: Fn(I) -> js_sys::Promise + 'static,
|
|
170
|
+
{
|
|
171
|
+
fn from(value: F) -> Self {
|
|
172
|
+
JsReconnect(Arc::new(value))
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
impl Client {
|
|
177
|
+
pub fn get_client(&self) -> &'_ perspective_client::Client {
|
|
178
|
+
&self.client
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
#[wasm_bindgen]
|
|
183
|
+
impl Client {
|
|
184
|
+
#[wasm_bindgen(constructor)]
|
|
185
|
+
pub fn new(send_request: Function, close: Option<Function>) -> ApiResult<Self> {
|
|
186
|
+
let send_request = JsReconnect::from(move |mut v: Vec<u8>| {
|
|
187
|
+
let buff2 = unsafe { js_sys::Uint8Array::view_mut_raw(v.as_mut_ptr(), v.len()) };
|
|
188
|
+
send_request
|
|
189
|
+
.call1(&JsValue::UNDEFINED, &buff2)
|
|
190
|
+
.unwrap()
|
|
191
|
+
.unchecked_into::<js_sys::Promise>()
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
let client = perspective_client::Client::new_with_callback(None, move |msg| {
|
|
195
|
+
send_request.run_all(msg)
|
|
196
|
+
})?;
|
|
197
|
+
|
|
198
|
+
Ok(Client { close, client })
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
#[wasm_bindgen]
|
|
202
|
+
pub fn new_proxy_session(&self, on_response: &Function) -> ProxySession {
|
|
203
|
+
ProxySession::new(self, on_response)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
#[wasm_bindgen]
|
|
207
|
+
pub async fn handle_response(&self, value: &JsValue) -> ApiResult<()> {
|
|
208
|
+
let uint8array = Uint8Array::new(value);
|
|
209
|
+
let slice = uint8array.to_vec();
|
|
210
|
+
self.client.handle_response(&slice).await?;
|
|
211
|
+
Ok(())
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
#[wasm_bindgen]
|
|
215
|
+
pub async fn handle_error(&self, error: String, reconnect: Option<Function>) -> ApiResult<()> {
|
|
216
|
+
self.client
|
|
217
|
+
.handle_error(
|
|
218
|
+
ClientError::Unknown(error),
|
|
219
|
+
reconnect.map(|reconnect| {
|
|
220
|
+
let reconnect =
|
|
221
|
+
JsReconnect::from(move |()| match reconnect.call0(&JsValue::UNDEFINED) {
|
|
222
|
+
Ok(x) => x.unchecked_into::<js_sys::Promise>(),
|
|
223
|
+
Err(e) => {
|
|
224
|
+
// This error may occur when _invoking_ the function
|
|
225
|
+
tracing::warn!("{:?}", e);
|
|
226
|
+
js_sys::Promise::reject(&format!("C {e:?}").into())
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
asyncfn!(reconnect, async move || {
|
|
231
|
+
if let Err(e) = JsFuture::from(reconnect.run(())).await {
|
|
232
|
+
if let Some(e) = e.dyn_ref::<js_sys::Object>() {
|
|
233
|
+
Err(ClientError::Unknown(e.to_string().as_string().unwrap()))
|
|
234
|
+
} else {
|
|
235
|
+
Err(ClientError::Unknown(e.as_string().unwrap()))
|
|
236
|
+
}
|
|
237
|
+
} else {
|
|
238
|
+
Ok(())
|
|
239
|
+
}
|
|
240
|
+
})
|
|
241
|
+
}),
|
|
242
|
+
)
|
|
243
|
+
.await?;
|
|
244
|
+
|
|
245
|
+
Ok(())
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
#[wasm_bindgen]
|
|
249
|
+
pub async fn on_error(&self, callback: Function) -> ApiResult<u32> {
|
|
250
|
+
let callback = JsReconnect::from(
|
|
251
|
+
move |(message, reconnect): (ClientError, Option<ReconnectCallback>)| {
|
|
252
|
+
let cl: Closure<dyn Fn() -> js_sys::Promise> = Closure::new(move || {
|
|
253
|
+
let reconnect = reconnect.clone();
|
|
254
|
+
future_to_promise(async move {
|
|
255
|
+
if let Some(f) = reconnect {
|
|
256
|
+
(*f)().await.map_err(|e| JsValue::from(format!("A {e}")))?;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
Ok(JsValue::UNDEFINED)
|
|
260
|
+
})
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
if let Err(e) = callback.call2(
|
|
264
|
+
&JsValue::UNDEFINED,
|
|
265
|
+
&JsValue::from(apierror!(ClientError(message))),
|
|
266
|
+
&cl.into_js_value(),
|
|
267
|
+
) {
|
|
268
|
+
tracing::warn!("{:?}", e);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
js_sys::Promise::resolve(&JsValue::UNDEFINED)
|
|
272
|
+
},
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
let poll_loop = LocalPollLoop::new_async(move |x| JsFuture::from(callback.run(x)));
|
|
276
|
+
let id = self
|
|
277
|
+
.client
|
|
278
|
+
.on_error(asyncfn!(poll_loop, async move |message, reconnect| {
|
|
279
|
+
poll_loop.poll((message, reconnect)).await;
|
|
280
|
+
Ok(())
|
|
281
|
+
}))
|
|
282
|
+
.await?;
|
|
283
|
+
|
|
284
|
+
Ok(id)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/// Creates a new [`Table`] from either a _schema_ or _data_.
|
|
288
|
+
///
|
|
289
|
+
/// The [`Client::table`] factory function can be initialized with either a
|
|
290
|
+
/// _schema_ (see [`Table::schema`]), or data in one of these formats:
|
|
291
|
+
///
|
|
292
|
+
/// - Apache Arrow
|
|
293
|
+
/// - CSV
|
|
294
|
+
/// - JSON row-oriented
|
|
295
|
+
/// - JSON column-oriented
|
|
296
|
+
/// - NDJSON
|
|
297
|
+
///
|
|
298
|
+
/// When instantiated with _data_, the schema is inferred from this data.
|
|
299
|
+
/// While this is convenient, inferrence is sometimes imperfect e.g.
|
|
300
|
+
/// when the input is empty, null or ambiguous. For these cases,
|
|
301
|
+
/// [`Client::table`] can first be instantiated with a explicit schema.
|
|
302
|
+
///
|
|
303
|
+
/// When instantiated with a _schema_, the resulting [`Table`] is empty but
|
|
304
|
+
/// with known column names and column types. When subsqeuently
|
|
305
|
+
/// populated with [`Table::update`], these columns will be _coerced_ to
|
|
306
|
+
/// the schema's type. This behavior can be useful when
|
|
307
|
+
/// [`Client::table`]'s column type inferences doesn't work.
|
|
308
|
+
///
|
|
309
|
+
/// The resulting [`Table`] is _virtual_, and invoking its methods
|
|
310
|
+
/// dispatches events to the `perspective_server::Server` this
|
|
311
|
+
/// [`Client`] connects to, where the data is stored and all calculation
|
|
312
|
+
/// occurs.
|
|
313
|
+
///
|
|
314
|
+
/// # Arguments
|
|
315
|
+
///
|
|
316
|
+
/// - `arg` - Either _schema_ or initialization _data_.
|
|
317
|
+
/// - `options` - Optional configuration which provides one of:
|
|
318
|
+
/// - `limit` - The max number of rows the resulting [`Table`] can
|
|
319
|
+
/// store.
|
|
320
|
+
/// - `index` - The column name to use as an _index_ column. If this
|
|
321
|
+
/// `Table` is being instantiated by _data_, this column name must be
|
|
322
|
+
/// present in the data.
|
|
323
|
+
/// - `name` - The name of the table. This will be generated if it is
|
|
324
|
+
/// not provided.
|
|
325
|
+
/// - `format` - The explicit format of the input data, can be one of
|
|
326
|
+
/// `"json"`, `"columns"`, `"csv"` or `"arrow"`. This overrides
|
|
327
|
+
/// language-specific type dispatch behavior, which allows stringified
|
|
328
|
+
/// and byte array alternative inputs.
|
|
329
|
+
///
|
|
330
|
+
/// # JavaScript Examples
|
|
331
|
+
///
|
|
332
|
+
/// Load a CSV from a `string`:
|
|
333
|
+
///
|
|
334
|
+
/// ```javascript
|
|
335
|
+
/// const table = await client.table("x,y\n1,2\n3,4");
|
|
336
|
+
/// ```
|
|
337
|
+
///
|
|
338
|
+
/// Load an Arrow from an `ArrayBuffer`:
|
|
339
|
+
///
|
|
340
|
+
/// ```javascript
|
|
341
|
+
/// import * as fs from "node:fs/promises";
|
|
342
|
+
/// const table2 = await client.table(await fs.readFile("superstore.arrow"));
|
|
343
|
+
/// ```
|
|
344
|
+
///
|
|
345
|
+
/// Load a CSV from a `UInt8Array` (the default for this type is Arrow)
|
|
346
|
+
/// using a format override:
|
|
347
|
+
///
|
|
348
|
+
/// ```javascript
|
|
349
|
+
/// const enc = new TextEncoder();
|
|
350
|
+
/// const table = await client.table(enc.encode("x,y\n1,2\n3,4"), {
|
|
351
|
+
/// format: "csv",
|
|
352
|
+
/// });
|
|
353
|
+
/// ```
|
|
354
|
+
///
|
|
355
|
+
/// Create a table with an `index`:
|
|
356
|
+
///
|
|
357
|
+
/// ```javascript
|
|
358
|
+
/// const table = await client.table(data, { index: "Row ID" });
|
|
359
|
+
/// ```
|
|
360
|
+
#[wasm_bindgen]
|
|
361
|
+
pub async fn table(
|
|
362
|
+
&self,
|
|
363
|
+
value: &JsTableInitData,
|
|
364
|
+
options: Option<JsTableInitOptions>,
|
|
365
|
+
) -> ApiResult<Table> {
|
|
366
|
+
let options = options
|
|
367
|
+
.into_serde_ext::<Option<TableInitOptions>>()?
|
|
368
|
+
.unwrap_or_default();
|
|
369
|
+
|
|
370
|
+
let args = TableData::from_js_value(value, options.format)?;
|
|
371
|
+
Ok(Table(self.client.table(args, options).await?))
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/// Terminates this [`Client`], cleaning up any [`crate::View`] handles the
|
|
375
|
+
/// [`Client`] has open as well as its callbacks.
|
|
376
|
+
#[wasm_bindgen]
|
|
377
|
+
pub fn terminate(&self) -> ApiResult<JsValue> {
|
|
378
|
+
if let Some(f) = self.close.clone() {
|
|
379
|
+
Ok(f.call0(&JsValue::UNDEFINED)?)
|
|
380
|
+
} else {
|
|
381
|
+
Err(ApiError::new("Client type cannot be terminated"))
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/// Opens a [`Table`] that is hosted on the `perspective_server::Server`
|
|
386
|
+
/// that is connected to this [`Client`].
|
|
387
|
+
///
|
|
388
|
+
/// The `name` property of [`TableInitOptions`] is used to identify each
|
|
389
|
+
/// [`Table`]. [`Table`] `name`s can be looked up for each [`Client`]
|
|
390
|
+
/// via [`Client::get_hosted_table_names`].
|
|
391
|
+
///
|
|
392
|
+
/// # JavaScript Examples
|
|
393
|
+
///
|
|
394
|
+
/// Get a virtual [`Table`] named "table_one" from this [`Client`]
|
|
395
|
+
///
|
|
396
|
+
/// ```javascript
|
|
397
|
+
/// const tables = await client.open_table("table_one");
|
|
398
|
+
/// ```
|
|
399
|
+
#[wasm_bindgen]
|
|
400
|
+
pub async fn open_table(&self, entity_id: String) -> ApiResult<Table> {
|
|
401
|
+
Ok(Table(self.client.open_table(entity_id).await?))
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/// Retrieves the names of all tables that this client has access to.
|
|
405
|
+
///
|
|
406
|
+
/// `name` is a string identifier unique to the [`Table`] (per [`Client`]),
|
|
407
|
+
/// which can be used in conjunction with [`Client::open_table`] to get
|
|
408
|
+
/// a [`Table`] instance without the use of [`Client::table`]
|
|
409
|
+
/// constructor directly (e.g., one created by another [`Client`]).
|
|
410
|
+
///
|
|
411
|
+
/// # JavaScript Examples
|
|
412
|
+
///
|
|
413
|
+
/// ```javascript
|
|
414
|
+
/// const tables = await client.get_hosted_table_names();
|
|
415
|
+
/// ```
|
|
416
|
+
#[wasm_bindgen]
|
|
417
|
+
pub async fn get_hosted_table_names(&self) -> ApiResult<JsValue> {
|
|
418
|
+
Ok(JsValue::from_serde_ext(
|
|
419
|
+
&self.client.get_hosted_table_names().await?,
|
|
420
|
+
)?)
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/// Register a callback which is invoked whenever [`Client::table`] (on this
|
|
424
|
+
/// [`Client`]) or [`Table::delete`] (on a [`Table`] belinging to this
|
|
425
|
+
/// [`Client`]) are called.
|
|
426
|
+
#[wasm_bindgen]
|
|
427
|
+
pub async fn on_hosted_tables_update(&self, on_update_js: Function) -> ApiResult<u32> {
|
|
428
|
+
let poll_loop = LocalPollLoop::new(move |_| on_update_js.call0(&JsValue::UNDEFINED));
|
|
429
|
+
let on_update = Box::new(move || poll_loop.poll(()));
|
|
430
|
+
let id = self.client.on_hosted_tables_update(on_update).await?;
|
|
431
|
+
Ok(id)
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/// Remove a callback previously registered via
|
|
435
|
+
/// `Client::on_hosted_tables_update`.
|
|
436
|
+
#[wasm_bindgen]
|
|
437
|
+
pub async fn remove_hosted_tables_update(&self, update_id: u32) -> ApiResult<()> {
|
|
438
|
+
self.client.remove_hosted_tables_update(update_id).await?;
|
|
439
|
+
Ok(())
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/// Provides the [`SystemInfo`] struct, implementation-specific metadata
|
|
443
|
+
/// about the [`perspective_server::Server`] runtime such as Memory and
|
|
444
|
+
/// CPU usage.
|
|
445
|
+
///
|
|
446
|
+
/// For WebAssembly servers, this method includes the WebAssembly heap size.
|
|
447
|
+
///
|
|
448
|
+
/// # JavaScript Examples
|
|
449
|
+
///
|
|
450
|
+
/// ```javascript
|
|
451
|
+
/// const info = await client.system_info();
|
|
452
|
+
/// ```
|
|
453
|
+
#[wasm_bindgen]
|
|
454
|
+
pub async fn system_info(&self) -> ApiResult<JsSystemInfo> {
|
|
455
|
+
let mut info = self.client.system_info().await?;
|
|
456
|
+
|
|
457
|
+
#[cfg(feature = "trace-allocator")]
|
|
458
|
+
if let perspective_client::SystemInfo {
|
|
459
|
+
client_used: None,
|
|
460
|
+
client_heap: None,
|
|
461
|
+
..
|
|
462
|
+
} = &info
|
|
463
|
+
{
|
|
464
|
+
let (client_used, client_heap) = crate::utils::get_used();
|
|
465
|
+
info.client_used = Some(client_used as u64);
|
|
466
|
+
info.client_heap = Some(client_heap as u64);
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
let timestamp = web_sys::window()
|
|
470
|
+
.and_then(|x| x.performance())
|
|
471
|
+
.map(|x| x.now() as u64);
|
|
472
|
+
|
|
473
|
+
info.timestamp = timestamp;
|
|
474
|
+
let record = JsValue::from_serde_ext(&info.cast::<f64>())?;
|
|
475
|
+
Ok(record.unchecked_into())
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
#[wasm_bindgen]
|
|
480
|
+
extern "C" {
|
|
481
|
+
#[wasm_bindgen(typescript_type = "SystemInfo")]
|
|
482
|
+
pub type JsSystemInfo;
|
|
483
|
+
}
|