@uwdata/mosaic-core 0.15.0 → 0.16.2

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 (39) hide show
  1. package/dist/types/Coordinator.d.ts +22 -25
  2. package/dist/types/MosaicClient.d.ts +20 -11
  3. package/dist/types/QueryManager.d.ts +42 -17
  4. package/dist/types/Selection.d.ts +1 -1
  5. package/dist/types/SelectionClause.d.ts +1 -1
  6. package/dist/types/connectors/Connector.d.ts +25 -0
  7. package/dist/types/connectors/rest.d.ts +13 -17
  8. package/dist/types/connectors/socket.d.ts +98 -16
  9. package/dist/types/connectors/wasm.d.ts +133 -14
  10. package/dist/types/index-types.d.ts +1 -0
  11. package/dist/types/preagg/PreAggregator.d.ts +1 -1
  12. package/dist/types/preagg/preagg-columns.d.ts +2 -2
  13. package/dist/types/preagg/sufficient-statistics.d.ts +2 -2
  14. package/dist/types/types.d.ts +21 -0
  15. package/dist/types/util/cache.d.ts +14 -10
  16. package/dist/types/util/decode-ipc.d.ts +9 -4
  17. package/dist/types/util/void-logger.d.ts +3 -0
  18. package/package.json +4 -4
  19. package/src/Coordinator.js +15 -12
  20. package/src/MosaicClient.js +17 -5
  21. package/src/QueryConsolidator.js +2 -1
  22. package/src/QueryManager.js +31 -0
  23. package/src/Selection.js +4 -2
  24. package/src/SelectionClause.js +5 -2
  25. package/src/connectors/Connector.ts +30 -0
  26. package/src/connectors/rest.js +14 -12
  27. package/src/connectors/socket.js +112 -77
  28. package/src/connectors/wasm.js +118 -55
  29. package/src/index-types.ts +1 -0
  30. package/src/make-client.js +0 -4
  31. package/src/preagg/PreAggregator.js +8 -6
  32. package/src/preagg/preagg-columns.js +5 -2
  33. package/src/preagg/sufficient-statistics.js +2 -1
  34. package/src/types.ts +23 -0
  35. package/src/util/cache.js +20 -5
  36. package/src/util/decode-ipc.js +9 -5
  37. package/src/util/field-info.js +2 -1
  38. package/src/util/js-type.js +3 -1
  39. package/src/util/void-logger.js +4 -1
@@ -4,4 +4,7 @@ export function voidLogger(): {
4
4
  log(..._: any[]): void;
5
5
  warn(..._: any[]): void;
6
6
  error(..._: any[]): void;
7
+ group(label: any): void;
8
+ groupCollapsed(label: any): void;
9
+ groupEnd(): void;
7
10
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uwdata/mosaic-core",
3
- "version": "0.15.0",
3
+ "version": "0.16.2",
4
4
  "description": "Scalable and extensible linked data views.",
5
5
  "keywords": [
6
6
  "mosaic",
@@ -32,10 +32,10 @@
32
32
  "dependencies": {
33
33
  "@duckdb/duckdb-wasm": "^1.29.0",
34
34
  "@uwdata/flechette": "^2.0.0",
35
- "@uwdata/mosaic-sql": "^0.15.0"
35
+ "@uwdata/mosaic-sql": "^0.16.2"
36
36
  },
37
37
  "devDependencies": {
38
- "@uwdata/mosaic-duckdb": "^0.15.0"
38
+ "@uwdata/mosaic-duckdb": "^0.16.2"
39
39
  },
40
- "gitHead": "671ad1ba86749a8435bd4aa7e722e2a8553f2cb0"
40
+ "gitHead": "26d2719f4bcab471d2831145e1f03f39f3509869"
41
41
  }
@@ -1,9 +1,10 @@
1
+ /** @import { Connector } from './connectors/Connector.js' */
1
2
  /** @import { PreAggregateOptions } from './preagg/PreAggregator.js' */
2
3
  /** @import { QueryResult } from './util/query-result.js' */
3
4
  /** @import { SelectionClause } from './util/selection-types.js' */
4
5
  /** @import { MosaicClient } from './MosaicClient.js' */
5
6
  /** @import { Selection } from './Selection.js' */
6
- /** @import { QueryType } from './types.js' */
7
+ /** @import { Logger, QueryType } from './types.js' */
7
8
  import { socketConnector } from './connectors/socket.js';
8
9
  import { PreAggregator } from './preagg/PreAggregator.js';
9
10
  import { voidLogger } from './util/void-logger.js';
@@ -33,15 +34,17 @@ export function coordinator(instance) {
33
34
  * A Mosaic Coordinator manages all database communication for clients and
34
35
  * handles selection updates. The Coordinator also performs optimizations
35
36
  * including query caching, consolidation, and pre-aggregation.
36
- * @param {*} [db] Database connector. Defaults to a web socket connection.
37
- * @param {object} [options] Coordinator options.
38
- * @param {*} [options.logger=console] The logger to use, defaults to `console`.
39
- * @param {*} [options.manager] The query manager to use.
40
- * @param {boolean} [options.cache=true] Boolean flag to enable/disable query caching.
41
- * @param {boolean} [options.consolidate=true] Boolean flag to enable/disable query consolidation.
42
- * @param {PreAggregateOptions} [options.preagg] Options for the Pre-aggregator.
43
37
  */
44
38
  export class Coordinator {
39
+ /**
40
+ * @param {Connector} [db] Database connector. Defaults to a web socket connection.
41
+ * @param {object} [options] Coordinator options.
42
+ * @param {Logger} [options.logger=console] The logger to use, defaults to `console`.
43
+ * @param {QueryManager} [options.manager] The query manager to use.
44
+ * @param {boolean} [options.cache=true] Boolean flag to enable/disable query caching.
45
+ * @param {boolean} [options.consolidate=true] Boolean flag to enable/disable query consolidation.
46
+ * @param {PreAggregateOptions} [options.preagg] Options for the Pre-aggregator.
47
+ */
45
48
  constructor(db = socketConnector(), {
46
49
  logger = console,
47
50
  manager = new QueryManager(),
@@ -78,8 +81,8 @@ export class Coordinator {
78
81
 
79
82
  /**
80
83
  * Get or set the database connector.
81
- * @param {*} [db] The database connector to use.
82
- * @returns The current database connector.
84
+ * @param {Connector} [db] The database connector to use.
85
+ * @returns {Connector} The current database connector.
83
86
  */
84
87
  databaseConnector(db) {
85
88
  return this.manager.connector(db);
@@ -87,8 +90,8 @@ export class Coordinator {
87
90
 
88
91
  /**
89
92
  * Get or set the logger.
90
- * @param {*} logger The logger to use.
91
- * @returns The current logger
93
+ * @param {Logger} [logger] The logger to use.
94
+ * @returns {Logger} The current logger
92
95
  */
93
96
  logger(logger) {
94
97
  if (arguments.length) {
@@ -27,10 +27,10 @@ export class MosaicClient {
27
27
  * will re-query and update the client when the selection updates.
28
28
  */
29
29
  constructor(filterSelection) {
30
- /** @type {Selection} */
30
+ /** @type {Selection | undefined} */
31
31
  this._filterBy = filterSelection;
32
32
  this._requestUpdate = throttle(() => this.requestQuery(), true);
33
- /** @type {Coordinator} */
33
+ /** @type {Coordinator | null} */
34
34
  this._coordinator = null;
35
35
  /** @type {Promise<any>} */
36
36
  this._pending = Promise.resolve();
@@ -38,12 +38,12 @@ export class MosaicClient {
38
38
  this._enabled = true;
39
39
  /** @type {boolean} */
40
40
  this._initialized = false;
41
- /** @type {Query | boolean} */
41
+ /** @type {Query | boolean | null} */
42
42
  this._request = null;
43
43
  }
44
44
 
45
45
  /**
46
- * Return this client's connected coordinator.
46
+ * @returns {Coordinator | null} this client's connected coordinator.
47
47
  */
48
48
  get coordinator() {
49
49
  return this._coordinator;
@@ -91,7 +91,7 @@ export class MosaicClient {
91
91
  }
92
92
 
93
93
  /**
94
- * Return this client's filter selection.
94
+ * @returns {Selection | undefined} this client's filter selection.
95
95
  */
96
96
  get filterBy() {
97
97
  return this._filterBy;
@@ -204,6 +204,18 @@ export class MosaicClient {
204
204
  }
205
205
  }
206
206
 
207
+ /**
208
+ * Remove this client: disconnect from the coordinator and free up any
209
+ * resource use. This method has no effect if the client is not connected
210
+ * to a coordinator.
211
+ *
212
+ * If overriding this method in a client subclass, be sure to also
213
+ * disconnect from the coordinator.
214
+ */
215
+ destroy() {
216
+ this.coordinator?.disconnect(this);
217
+ }
218
+
207
219
  /**
208
220
  * Requests a client update, for example to (re-)render an interface
209
221
  * component.
@@ -1,4 +1,5 @@
1
- import { DescribeQuery, isAggregateExpression, isColumnRef, isDescribeQuery, isSelectQuery, Query } from '@uwdata/mosaic-sql';
1
+ /** @import { DescribeQuery, Query } from '@uwdata/mosaic-sql' */
2
+ import { isAggregateExpression, isColumnRef, isDescribeQuery, isSelectQuery } from '@uwdata/mosaic-sql';
2
3
  import { QueryResult } from './util/query-result.js';
3
4
 
4
5
  function wait(callback) {
@@ -1,3 +1,5 @@
1
+ /** @import { Connector } from './connectors/Connector.js' */
2
+ /** @import { Cache, Logger } from './types.js' */
1
3
  import { consolidator } from './QueryConsolidator.js';
2
4
  import { lruCache, voidCache } from './util/cache.js';
3
5
  import { PriorityQueue } from './util/priority-queue.js';
@@ -12,10 +14,15 @@ export class QueryManager {
12
14
  ) {
13
15
  /** @type {PriorityQueue} */
14
16
  this.queue = new PriorityQueue(3);
17
+ /** @type {Connector} */
15
18
  this.db = null;
19
+ /** @type {Cache} */
16
20
  this.clientCache = null;
21
+ /** @type {Logger} */
17
22
  this._logger = voidLogger();
23
+ /** @type {boolean} */
18
24
  this._logQueries = false;
25
+ /** @type {ReturnType<typeof consolidator> | null} */
19
26
  this._consolidate = null;
20
27
  /**
21
28
  * Requests pending with the query manager.
@@ -106,24 +113,48 @@ export class QueryManager {
106
113
  }
107
114
  }
108
115
 
116
+ /**
117
+ * Get or set the current query cache.
118
+ * @param {Cache | boolean} [value]
119
+ * @returns {Cache}
120
+ */
109
121
  cache(value) {
110
122
  return value !== undefined
111
123
  ? (this.clientCache = value === true ? lruCache() : (value || voidCache()))
112
124
  : this.clientCache;
113
125
  }
114
126
 
127
+ /**
128
+ * Get or set the current logger.
129
+ * @param {Logger} [value]
130
+ * @returns {Logger}
131
+ */
115
132
  logger(value) {
116
133
  return value ? (this._logger = value) : this._logger;
117
134
  }
118
135
 
136
+ /**
137
+ * Get or set if queries should be logged.
138
+ * @param {boolean} [value]
139
+ * @returns {boolean}
140
+ */
119
141
  logQueries(value) {
120
142
  return value !== undefined ? this._logQueries = !!value : this._logQueries;
121
143
  }
122
144
 
145
+ /**
146
+ * Get or set the database connector.
147
+ * @param {Connector} [connector]
148
+ * @returns {Connector}
149
+ */
123
150
  connector(connector) {
124
151
  return connector ? (this.db = connector) : this.db;
125
152
  }
126
153
 
154
+ /**
155
+ * Indicate if query consolidation should be performed.
156
+ * @param {boolean} flag
157
+ */
127
158
  consolidate(flag) {
128
159
  if (flag && !this._consolidate) {
129
160
  this._consolidate = consolidator(this.enqueue.bind(this), this.clientCache);
package/src/Selection.js CHANGED
@@ -1,7 +1,9 @@
1
- /** @import {SelectionClause} from './util/selection-types.js' */
1
+ /**
2
+ * @import { MosaicClient } from './MosaicClient.js'
3
+ * @import { SelectionClause } from './util/selection-types.js'
4
+ */
2
5
  import { literal, or } from '@uwdata/mosaic-sql';
3
6
  import { Param } from './Param.js';
4
- import { MosaicClient } from './MosaicClient.js';
5
7
 
6
8
  /**
7
9
  * Test if a value is a Selection instance.
@@ -1,5 +1,8 @@
1
- import { ExprNode, and, contains, isBetween, isIn, isNotDistinct, literal, or, prefix, regexp_matches, suffix } from '@uwdata/mosaic-sql';
2
- import { MosaicClient } from './MosaicClient.js';
1
+ /**
2
+ * @import { ExprNode } from '@uwdata/mosaic-sql'
3
+ * @import { MosaicClient } from './MosaicClient.js'
4
+ */
5
+ import { and, contains, isBetween, isIn, isNotDistinct, literal, or, prefix, regexp_matches, suffix } from '@uwdata/mosaic-sql';
3
6
 
4
7
  /**
5
8
  * @typedef {import('./util/selection-types.js').SelectionClause} SelectionClause
@@ -0,0 +1,30 @@
1
+ import type { Table } from '@uwdata/flechette';
2
+
3
+ export interface QueryRequest {
4
+ /** The query type. */
5
+ type?: string;
6
+ /** A SQL query string. */
7
+ sql: string;
8
+ }
9
+
10
+ export interface ArrowQueryRequest extends QueryRequest {
11
+ /** The query type. */
12
+ type?: 'arrow';
13
+ }
14
+
15
+ export interface ExecQueryRequest extends QueryRequest {
16
+ /** The query type. */
17
+ type: 'exec';
18
+ }
19
+
20
+ export interface JSONQueryRequest extends QueryRequest {
21
+ /** The query type. */
22
+ type: 'json';
23
+ }
24
+
25
+ export interface Connector {
26
+ /** Issue a query and return the result. */
27
+ query(query: ArrowQueryRequest): Promise<Table>;
28
+ query(query: ExecQueryRequest): Promise<void>;
29
+ query(query: JSONQueryRequest): Promise<Record<string, any>[]>;
30
+ }
@@ -1,16 +1,19 @@
1
+ /** @import { ExtractionOptions } from '@uwdata/flechette' */
2
+ /** @import { Connector } from './Connector.js' */
1
3
  import { decodeIPC } from '../util/decode-ipc.js';
2
4
 
3
- export function restConnector(uri = 'http://localhost:3000/') {
5
+ /**
6
+ * Connect to a DuckDB server over an HTTP REST interface.
7
+ * @param {object} [options] Connector options.
8
+ * @param {string} [options.uri] The URI for the DuckDB REST server.
9
+ * @param {ExtractionOptions} [options.ipc] Arrow IPC extraction options.
10
+ * @returns {Connector} A connector instance.
11
+ */
12
+ export function restConnector({
13
+ uri = 'http://localhost:3000/',
14
+ ipc = undefined,
15
+ } = {}) {
4
16
  return {
5
- /**
6
- * Query the DuckDB server.
7
- * @param {object} query
8
- * @param {'exec' | 'arrow' | 'json' | 'create-bundle' | 'load-bundle'} [query.type] The query type.
9
- * @param {string} [query.sql] A SQL query string.
10
- * @param {string[]} [query.queries] The queries used to create a bundle.
11
- * @param {string} [query.name] The name of a bundle to create or load.
12
- * @returns the query result
13
- */
14
17
  async query(query) {
15
18
  const req = fetch(uri, {
16
19
  method: 'POST',
@@ -21,7 +24,6 @@ export function restConnector(uri = 'http://localhost:3000/') {
21
24
  body: JSON.stringify(query)
22
25
  });
23
26
 
24
-
25
27
  const res = await req;
26
28
 
27
29
  if (!res.ok) {
@@ -29,7 +31,7 @@ export function restConnector(uri = 'http://localhost:3000/') {
29
31
  }
30
32
 
31
33
  return query.type === 'exec' ? req
32
- : query.type === 'arrow' ? decodeIPC(await res.arrayBuffer())
34
+ : query.type === 'arrow' ? decodeIPC(await res.arrayBuffer(), ipc)
33
35
  : res.json();
34
36
  }
35
37
  };
@@ -1,100 +1,135 @@
1
+ /** @import { ExtractionOptions, Table } from '@uwdata/flechette' */
2
+ /** @import { ArrowQueryRequest, Connector, ExecQueryRequest, JSONQueryRequest } from './Connector.js' */
1
3
  import { decodeIPC } from '../util/decode-ipc.js';
2
4
 
3
- export function socketConnector(uri = 'ws://localhost:3000/') {
4
- const queue = [];
5
- let connected = false;
6
- let request = null;
7
- let ws;
5
+ /**
6
+ * Connect to a DuckDB server over a WebSocket interface.
7
+ * @param {object} [options] Connector options.
8
+ * @param {string} [options.uri] The URI for the DuckDB REST server.
9
+ * @param {ExtractionOptions} [options.ipc] Arrow IPC extraction options.
10
+ * @returns {SocketConnector} A connector instance.
11
+ */
12
+ export function socketConnector(options) {
13
+ return new SocketConnector(options);
14
+ }
8
15
 
9
- const events = {
10
- open() {
11
- connected = true;
12
- next();
13
- },
16
+ /**
17
+ * DuckDB socket connector.
18
+ * @implements {Connector}
19
+ */
20
+ export class SocketConnector {
21
+ /**
22
+ * @param {object} [options] Connector options.
23
+ * @param {string} [options.uri] The URI for the DuckDB REST server.
24
+ * @param {ExtractionOptions} [options.ipc] Arrow IPC extraction options.
25
+ */
26
+ constructor({
27
+ uri = 'ws://localhost:3000/',
28
+ ipc = undefined,
29
+ } = {}) {
30
+ this._uri = uri;
31
+ this._queue = [];
32
+ this._connected = false;
33
+ this._request = null;
34
+ this._ws = null;
14
35
 
15
- close() {
16
- connected = false;
17
- request = null;
18
- ws = null;
19
- while (queue.length) {
20
- queue.shift().reject('Socket closed');
21
- }
22
- },
36
+ const c = this;
37
+ this._events = {
38
+ open() {
39
+ c._connected = true;
40
+ c.next();
41
+ },
23
42
 
24
- error(event) {
25
- if (request) {
26
- const { reject } = request;
27
- request = null;
28
- next();
29
- reject(event);
30
- } else {
31
- console.error('WebSocket error: ', event);
32
- }
33
- },
43
+ close() {
44
+ c._connected = false;
45
+ c._request = null;
46
+ c._ws = null;
47
+ while (c._queue.length) {
48
+ c._queue.shift().reject('Socket closed');
49
+ }
50
+ },
34
51
 
35
- message({ data }) {
36
- if (request) {
37
- const { query, resolve, reject } = request;
52
+ error(event) {
53
+ if (c._request) {
54
+ const { reject } = c._request;
55
+ c._request = null;
56
+ c.next();
57
+ reject(event);
58
+ } else {
59
+ console.error('WebSocket error: ', event);
60
+ }
61
+ },
38
62
 
39
- // clear state, start next request
40
- request = null;
41
- next();
63
+ message({ data }) {
64
+ if (c._request) {
65
+ const { query, resolve, reject } = c._request;
42
66
 
43
- // process result
44
- if (typeof data === 'string') {
45
- const json = JSON.parse(data);
46
- json.error ? reject(json.error) : resolve(json);
47
- } else if (query.type === 'exec') {
48
- resolve();
49
- } else if (query.type === 'arrow') {
50
- resolve(decodeIPC(data));
67
+ // clear state, start next request
68
+ c._request = null;
69
+ c.next();
70
+
71
+ // process result
72
+ if (typeof data === 'string') {
73
+ const json = JSON.parse(data);
74
+ json.error ? reject(json.error) : resolve(json);
75
+ } else if (query.type === 'exec') {
76
+ resolve();
77
+ } else if (query.type === 'arrow') {
78
+ resolve(decodeIPC(data, ipc));
79
+ } else {
80
+ throw new Error(`Unexpected socket data: ${data}`);
81
+ }
51
82
  } else {
52
- throw new Error(`Unexpected socket data: ${data}`);
83
+ console.log('WebSocket message: ', data);
53
84
  }
54
- } else {
55
- console.log('WebSocket message: ', data);
56
85
  }
57
86
  }
58
87
  }
59
88
 
60
- function init() {
61
- ws = new WebSocket(uri);
62
- ws.binaryType = 'arraybuffer';
63
- for (const type in events) {
64
- ws.addEventListener(type, events[type]);
89
+ get connected() {
90
+ return this._connected;
91
+ }
92
+
93
+ init() {
94
+ this._ws = new WebSocket(this._uri);
95
+ this._ws.binaryType = 'arraybuffer';
96
+ for (const type in this._events) {
97
+ this._ws.addEventListener(type, this._events[type]);
65
98
  }
66
99
  }
67
100
 
68
- function enqueue(query, resolve, reject) {
69
- if (ws == null) init();
70
- queue.push({ query, resolve, reject });
71
- if (connected && !request) next();
101
+ enqueue(query, resolve, reject) {
102
+ if (this._ws == null) this.init();
103
+ this._queue.push({ query, resolve, reject });
104
+ if (this._connected && !this._request) this.next();
72
105
  }
73
106
 
74
- function next() {
75
- if (queue.length) {
76
- request = queue.shift();
77
- ws.send(JSON.stringify(request.query));
107
+ next() {
108
+ if (this._queue.length) {
109
+ this._request = this._queue.shift();
110
+ this._ws.send(JSON.stringify(this._request.query));
78
111
  }
79
112
  }
80
113
 
81
- return {
82
- get connected() {
83
- return connected;
84
- },
85
- /**
86
- * Query the DuckDB server.
87
- * @param {object} query
88
- * @param {'exec' | 'arrow' | 'json' | 'create-bundle' | 'load-bundle'} [query.type] The query type.
89
- * @param {string} [query.sql] A SQL query string.
90
- * @param {string[]} [query.queries] The queries used to create a bundle.
91
- * @param {string} [query.name] The name of a bundle to create or load.
92
- * @returns the query result
93
- */
94
- query(query) {
95
- return new Promise(
96
- (resolve, reject) => enqueue(query, resolve, reject)
97
- );
98
- }
99
- };
114
+ /**
115
+ * @overload
116
+ * @param {ArrowQueryRequest} query
117
+ * @returns {Promise<Table>}
118
+ *
119
+ * @overload
120
+ * @param {ExecQueryRequest} query
121
+ * @returns {Promise<void>}
122
+ *
123
+ * @overload
124
+ * @param {JSONQueryRequest} query
125
+ * @returns {Promise<Record<string, any>[]>}
126
+ *
127
+ * @param {ArrowQueryRequest | ExecQueryRequest | JSONQueryRequest} query
128
+ * @returns {Promise<Table | void | Record<string, any>[]>}}
129
+ */
130
+ query(query) {
131
+ return new Promise(
132
+ (resolve, reject) => this.enqueue(query, resolve, reject)
133
+ );
134
+ }
100
135
  }