@perspective-dev/client 4.0.0 → 4.1.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.
Files changed (45) hide show
  1. package/dist/cdn/perspective-server.worker.js +1 -1
  2. package/dist/cdn/perspective-server.worker.js.map +3 -3
  3. package/dist/cdn/perspective.js +2 -2
  4. package/dist/cdn/perspective.js.map +4 -4
  5. package/dist/esm/perspective.browser.d.ts +5 -1
  6. package/dist/esm/perspective.inline.js +2 -2
  7. package/dist/esm/perspective.inline.js.map +4 -4
  8. package/dist/esm/perspective.js +2 -2
  9. package/dist/esm/perspective.js.map +4 -4
  10. package/dist/esm/perspective.node.d.ts +6 -1
  11. package/dist/esm/perspective.node.js +722 -353
  12. package/dist/esm/perspective.node.js.map +4 -4
  13. package/dist/esm/ts-rs/ColumnType.d.ts +4 -0
  14. package/dist/esm/ts-rs/ViewConfig.d.ts +18 -0
  15. package/dist/esm/ts-rs/ViewWindow.d.ts +9 -9
  16. package/dist/esm/virtual_server.d.ts +47 -0
  17. package/dist/esm/virtual_servers/duckdb.d.ts +39 -0
  18. package/dist/esm/virtual_servers/duckdb.js +12 -0
  19. package/dist/esm/virtual_servers/duckdb.js.map +7 -0
  20. package/dist/esm/wasm/browser.d.ts +1 -1
  21. package/dist/wasm/perspective-js.d.ts +52 -13
  22. package/dist/wasm/perspective-js.js +680 -399
  23. package/dist/wasm/perspective-js.wasm +0 -0
  24. package/dist/wasm/perspective-js.wasm.d.ts +20 -8
  25. package/package.json +4 -1
  26. package/src/rust/client.rs +14 -5
  27. package/src/rust/lib.rs +11 -1
  28. package/src/rust/table.rs +3 -2
  29. package/src/rust/table_data.rs +19 -14
  30. package/src/rust/utils/browser.rs +0 -4
  31. package/src/rust/utils/console_logger.rs +3 -2
  32. package/src/rust/utils/errors.rs +10 -28
  33. package/src/rust/utils/futures.rs +3 -3
  34. package/src/rust/utils/json.rs +4 -4
  35. package/src/rust/virtual_server.rs +746 -0
  36. package/src/ts/perspective-server.worker.ts +33 -23
  37. package/src/ts/perspective.browser.ts +17 -2
  38. package/src/ts/perspective.node.ts +46 -11
  39. package/src/ts/ts-rs/ColumnType.ts +6 -0
  40. package/src/ts/ts-rs/ViewConfig.ts +8 -0
  41. package/src/ts/ts-rs/ViewWindow.ts +3 -3
  42. package/src/ts/virtual_server.ts +126 -0
  43. package/src/ts/virtual_servers/duckdb.ts +511 -0
  44. package/src/ts/wasm/browser.ts +17 -9
  45. package/tsconfig.json +1 -0
@@ -20,35 +20,45 @@ import { compile_perspective } from "./wasm/emscripten_api.ts";
20
20
  let GLOBAL_SERVER: PerspectiveServer;
21
21
  let POLL_THREAD: PerspectivePollThread;
22
22
 
23
- function bindPort(e: MessageEvent) {
24
- const port = e.ports[0];
25
- let session: PerspectiveSession;
26
- port.addEventListener("message", async (msg) => {
27
- if (msg.data.cmd === "init") {
28
- const id = msg.data.id;
29
- if (!GLOBAL_SERVER) {
30
- const module = await compile_perspective(msg.data.args[0]);
31
- GLOBAL_SERVER = new PerspectiveServer(module, {
32
- on_poll_request: () => POLL_THREAD.on_poll_request(),
33
- });
34
-
35
- POLL_THREAD = new PerspectivePollThread(GLOBAL_SERVER);
36
- }
37
-
38
- session = GLOBAL_SERVER.make_session(async (resp) => {
39
- const f = resp.slice().buffer;
40
- port.postMessage(f, { transfer: [f] });
23
+ let SESSION: PerspectiveSession | undefined;
24
+
25
+ async function handleMessage(this: MessagePort, msg: MessageEvent) {
26
+ if (msg.data.cmd === "init") {
27
+ const id = msg.data.id;
28
+ if (!GLOBAL_SERVER) {
29
+ const module = await compile_perspective(msg.data.args[0]);
30
+
31
+ GLOBAL_SERVER = new PerspectiveServer(module, {
32
+ on_poll_request: () => POLL_THREAD.on_poll_request(),
41
33
  });
42
34
 
43
- port.postMessage({ id });
35
+ POLL_THREAD = new PerspectivePollThread(GLOBAL_SERVER);
36
+ }
37
+
38
+ SESSION = GLOBAL_SERVER.make_session(async (resp) => {
39
+ const f = resp.slice().buffer;
40
+ this.postMessage(f, { transfer: [f] });
41
+ });
42
+
43
+ this.postMessage({ id });
44
+ } else {
45
+ if (SESSION) {
46
+ await SESSION?.handle_request(new Uint8Array(msg.data));
44
47
  } else {
45
- await session.handle_request(new Uint8Array(msg.data));
48
+ throw new Error("No session");
46
49
  }
47
- });
50
+ }
51
+ }
48
52
 
53
+ function bindPortSharedWorker(msg: MessageEvent) {
54
+ const port = msg.ports[0];
55
+ port.addEventListener("message", handleMessage.bind(port));
49
56
  port.start();
50
57
  }
51
58
 
52
59
  // @ts-expect-error wrong scope
53
- self.addEventListener("connect", bindPort);
54
- self.addEventListener("message", bindPort);
60
+ self.addEventListener("connect", bindPortSharedWorker);
61
+ self.addEventListener(
62
+ "message",
63
+ handleMessage.bind(self as unknown as MessagePort),
64
+ );
@@ -12,6 +12,9 @@
12
12
 
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
+ export type * from "./virtual_server.ts";
16
+
17
+ import * as psp_virtual from "./virtual_server.ts";
15
18
 
16
19
  import * as wasm_module from "../../dist/wasm/perspective-js.js";
17
20
  import * as api from "./wasm/browser.ts";
@@ -19,6 +22,12 @@ import { load_wasm_stage_0 } from "./wasm/decompress.ts";
19
22
 
20
23
  let GLOBAL_SERVER_WASM: Promise<ArrayBuffer | WebAssembly.Module>;
21
24
 
25
+ export async function createMessageHandler(
26
+ handler: psp_virtual.VirtualServerHandler,
27
+ ) {
28
+ return psp_virtual.createMessageHandler(await get_client(), handler);
29
+ }
30
+
22
31
  export function init_server(
23
32
  wasm:
24
33
  | Uint8Array
@@ -120,7 +129,7 @@ export async function websocket(url: string | URL) {
120
129
  }
121
130
 
122
131
  export async function worker(
123
- worker?: Promise<SharedWorker | ServiceWorker | Worker>,
132
+ worker?: Promise<SharedWorker | ServiceWorker | Worker | MessagePort>,
124
133
  ) {
125
134
  if (typeof worker === "undefined") {
126
135
  worker = get_worker();
@@ -129,4 +138,10 @@ export async function worker(
129
138
  return await api.worker(get_client(), get_server(), worker);
130
139
  }
131
140
 
132
- export default { websocket, worker, init_client, init_server };
141
+ export default {
142
+ websocket,
143
+ worker,
144
+ init_client,
145
+ init_server,
146
+ createMessageHandler,
147
+ };
@@ -11,6 +11,7 @@
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
13
  export type * from "../../dist/wasm/perspective-js.d.ts";
14
+ export type * from "./virtual_server.ts";
14
15
 
15
16
  import WebSocket, { WebSocketServer as HttpWebSocketServer } from "ws";
16
17
  import stoppable from "stoppable";
@@ -27,6 +28,9 @@ import { load_wasm_stage_0 } from "./wasm/decompress.js";
27
28
  import * as engine from "./wasm/engine.ts";
28
29
  import { compile_perspective } from "./wasm/emscripten_api.ts";
29
30
  import * as psp_websocket from "./websocket.ts";
31
+ import * as api from "./wasm/browser.ts";
32
+
33
+ import * as virtual_server from "./virtual_server.ts";
30
34
 
31
35
  const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
32
36
 
@@ -177,17 +181,6 @@ function buffer_to_arraybuffer(
177
181
  }
178
182
  }
179
183
 
180
- function invert_promise<T>(): [(t: T) => void, Promise<T>, (t: any) => void] {
181
- let sender: ((t: T) => void) | undefined = undefined,
182
- reject = undefined;
183
- let receiver: Promise<T> = new Promise((x, u) => {
184
- sender = x;
185
- reject = u;
186
- });
187
-
188
- return [sender!, receiver, reject!];
189
- }
190
-
191
184
  export class WebSocketServer {
192
185
  _server: http.Server | any; // stoppable has no type ...
193
186
  _wss: HttpWebSocketServer;
@@ -303,9 +296,51 @@ export async function websocket(
303
296
  );
304
297
  }
305
298
 
299
+ export async function worker(worker: Promise<MessagePort>) {
300
+ const port = await worker;
301
+ const client = new perspective_client.Client(
302
+ async (proto: Uint8Array) => {
303
+ const f = proto.slice().buffer;
304
+ port.postMessage(f, { transfer: [f] });
305
+ },
306
+ async () => {
307
+ console.debug("Closing WebWorker");
308
+ port.close();
309
+ },
310
+ );
311
+
312
+ const { promise, resolve, reject } = Promise.withResolvers();
313
+ port.onmessage = function listener(resp) {
314
+ port.onmessage = null;
315
+ resolve(null);
316
+ };
317
+
318
+ port.onmessageerror = function (...args) {
319
+ port.onmessage = null;
320
+ console.error(...args);
321
+ reject(args);
322
+ };
323
+
324
+ port.postMessage({ cmd: "init", args: [] });
325
+ await promise;
326
+ port.addEventListener("message", (json: MessageEvent<Uint8Array>) => {
327
+ client.handle_response(json.data);
328
+ });
329
+
330
+ console.log(client);
331
+ return client;
332
+ }
333
+
334
+ export function createMessageHandler(
335
+ handler: virtual_server.VirtualServerHandler,
336
+ ) {
337
+ return virtual_server.createMessageHandler(perspective_client, handler);
338
+ }
339
+
306
340
  export default {
307
341
  table,
308
342
  websocket,
343
+ worker,
309
344
  get_hosted_table_names,
310
345
  on_hosted_tables_update,
311
346
  remove_hosted_tables_update,
@@ -0,0 +1,6 @@
1
+ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
2
+
3
+ /**
4
+ * View types
5
+ */
6
+ export type ColumnType = "string" | "date" | "datetime" | "integer" | "float" | "boolean";
@@ -0,0 +1,8 @@
1
+ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
2
+ import type { Aggregate } from "./Aggregate.js";
3
+ import type { Expressions } from "./Expressions.js";
4
+ import type { Filter } from "./Filter.js";
5
+ import type { FilterReducer } from "./FilterReducer.js";
6
+ import type { Sort } from "./Sort.js";
7
+
8
+ export type ViewConfig = { group_by: Array<string>, split_by: Array<string>, sort: Array<Sort>, filter: Array<Filter>, filter_op?: FilterReducer, expressions: Expressions, columns: Array<string | null>, aggregates: { [key in string]?: Aggregate }, group_by_depth?: number | null, };
@@ -6,12 +6,12 @@
6
6
  * Some fields of [`ViewWindow`] are only applicable to specific methods of
7
7
  * [`View`].
8
8
  */
9
- export type ViewWindow = { start_row: number | null, start_col: number | null, end_row: number | null, end_col: number | null, id: boolean | null, index: boolean | null, leaves_only: boolean | null,
9
+ export type ViewWindow = { start_row?: number, start_col?: number, end_row?: number, end_col?: number, id?: boolean, index?: boolean, leaves_only?: boolean,
10
10
  /**
11
11
  * Only impacts [`View::to_csv`]
12
12
  */
13
- formatted: boolean | null,
13
+ formatted?: boolean,
14
14
  /**
15
15
  * Only impacts [`View::to_arrow`]
16
16
  */
17
- compression: string | null, };
17
+ compression?: string, };
@@ -0,0 +1,126 @@
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
+ import { ColumnType } from "./ts-rs/ColumnType.ts";
14
+ import { ViewConfig } from "./ts-rs/ViewConfig.ts";
15
+ import { ViewWindow } from "./ts-rs/ViewWindow.ts";
16
+
17
+ import type * as perspective from "../../dist/wasm/perspective-js.js";
18
+
19
+ /**
20
+ * VirtualServer API for implementing custom data sources in JavaScript/WASM.
21
+ *
22
+ * The VirtualServer pattern allows you to create custom data sources that
23
+ * integrate with Perspective's protocol. This is useful for:
24
+ * - Connecting to external databases (DuckDB, PostgreSQL, etc.)
25
+ * - Streaming data from APIs or message queues
26
+ * - Implementing custom aggregation or transformation logic
27
+ * - Creating data adapters without copying data into Perspective tables
28
+ *
29
+ * @module virtual_server
30
+ */
31
+
32
+ export interface ServerFeatures {
33
+ expressions?: boolean;
34
+ }
35
+
36
+ /**
37
+ * Handler interface that you implement to provide custom data sources.
38
+ *
39
+ * All methods will be called by the VirtualServer when handling protocol
40
+ * messages from Perspective clients. Methods can return values directly or
41
+ * return Promises for asynchronous operations (e.g., database queries).
42
+ */
43
+ export interface VirtualServerHandler {
44
+ getHostedTables(): string[] | Promise<string[]>;
45
+ tableSchema(
46
+ tableId: string,
47
+ ): Record<string, ColumnType> | Promise<Record<string, ColumnType>>;
48
+ tableSize(tableId: string): number | Promise<number>;
49
+ tableMakeView(
50
+ tableId: string,
51
+ viewId: string,
52
+ config: ViewConfig,
53
+ ): void | Promise<void>;
54
+ viewDelete(viewId: string): void | Promise<void>;
55
+ viewGetData(
56
+ viewId: string,
57
+ config: ViewConfig,
58
+ viewport: ViewWindow,
59
+ dataSlice: perspective.JsVirtualDataSlice,
60
+ ): void | Promise<void>;
61
+ viewSchema?(
62
+ viewId: string,
63
+ config?: ViewConfig,
64
+ ): Record<string, ColumnType> | Promise<Record<string, ColumnType>>;
65
+ viewSize?(viewId: string): number | Promise<number>;
66
+ tableValidateExpression?(
67
+ tableId: string,
68
+ expression: string,
69
+ ): ColumnType | Promise<ColumnType>;
70
+ getFeatures?(): ServerFeatures | Promise<ServerFeatures>;
71
+ makeTable?(
72
+ tableId: string,
73
+ data: string | Uint8Array,
74
+ ): void | Promise<void>;
75
+ }
76
+
77
+ export function createMessageHandler(
78
+ mod: typeof perspective,
79
+ handler: VirtualServerHandler,
80
+ ) {
81
+ let virtualServer: perspective.JsVirtualServer;
82
+ async function postMessage(port: MessagePort, msg: MessageEvent) {
83
+ if (msg.data.cmd === "init") {
84
+ try {
85
+ virtualServer = new mod.JsVirtualServer(handler);
86
+ if (msg.data.id !== undefined) {
87
+ port.postMessage({ id: msg.data.id });
88
+ } else {
89
+ port.postMessage(null);
90
+ }
91
+ } catch (error) {
92
+ console.error("Error initializing worker:", error);
93
+ throw error;
94
+ }
95
+ } else {
96
+ try {
97
+ const requestBytes = new Uint8Array(msg.data);
98
+ const responseBytes =
99
+ await virtualServer.handleRequest(requestBytes);
100
+ const buffer = responseBytes.slice().buffer;
101
+ port.postMessage(buffer, { transfer: [buffer] });
102
+ } catch (error) {
103
+ console.error("Error handling request in worker:", error);
104
+ throw error;
105
+ }
106
+ }
107
+ }
108
+
109
+ const channel = new MessageChannel();
110
+ channel.port1.onmessage = (message) => {
111
+ postMessage(channel.port1, message);
112
+ };
113
+
114
+ return channel.port2;
115
+ }
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";