@uwdata/mosaic-core 0.14.1 → 0.16.1
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/dist/types/Coordinator.d.ts +37 -42
- package/dist/types/MosaicClient.d.ts +76 -44
- package/dist/types/QueryManager.d.ts +42 -17
- package/dist/types/Selection.d.ts +1 -1
- package/dist/types/SelectionClause.d.ts +1 -1
- package/dist/types/connectors/Connector.d.ts +25 -0
- package/dist/types/connectors/rest.d.ts +13 -17
- package/dist/types/connectors/socket.d.ts +98 -16
- package/dist/types/connectors/wasm.d.ts +133 -14
- package/dist/types/index-types.d.ts +1 -0
- package/dist/types/make-client.d.ts +51 -21
- package/dist/types/preagg/PreAggregator.d.ts +18 -16
- package/dist/types/preagg/preagg-columns.d.ts +2 -2
- package/dist/types/preagg/sufficient-statistics.d.ts +2 -2
- package/dist/types/types.d.ts +21 -0
- package/dist/types/util/cache.d.ts +14 -10
- package/dist/types/util/decode-ipc.d.ts +9 -4
- package/dist/types/util/void-logger.d.ts +3 -0
- package/package.json +4 -4
- package/src/Coordinator.js +36 -44
- package/src/MosaicClient.js +110 -46
- package/src/QueryConsolidator.js +2 -1
- package/src/QueryManager.js +31 -0
- package/src/Selection.js +4 -2
- package/src/SelectionClause.js +5 -2
- package/src/connectors/Connector.ts +30 -0
- package/src/connectors/rest.js +14 -12
- package/src/connectors/socket.js +112 -77
- package/src/connectors/wasm.js +118 -55
- package/src/index-types.ts +1 -0
- package/src/make-client.js +68 -31
- package/src/preagg/PreAggregator.js +20 -17
- package/src/preagg/preagg-columns.js +5 -2
- package/src/preagg/sufficient-statistics.js +2 -1
- package/src/types.ts +23 -0
- package/src/util/cache.js +20 -5
- package/src/util/decode-ipc.js +9 -5
- package/src/util/field-info.js +2 -1
- package/src/util/js-type.js +3 -1
- package/src/util/void-logger.js +4 -1
package/src/MosaicClient.js
CHANGED
|
@@ -1,29 +1,49 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
/** @import { Query } from '@uwdata/mosaic-sql' */
|
|
2
|
+
/** @import { Coordinator } from './Coordinator.js' */
|
|
3
|
+
/** @import { Selection } from './Selection.js' */
|
|
3
4
|
import { throttle } from './util/throttle.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
|
-
*
|
|
7
|
+
* A Mosaic client is a data consumer that indicates its data needs to a
|
|
8
|
+
* Mosaic coordinator via the query method. The coordinator is responsible
|
|
9
|
+
* for issuing queries and returning results to the client.
|
|
10
|
+
*
|
|
11
|
+
* The client life-cycle consists of connection to a coordinator,
|
|
12
|
+
* initialization (potentially involving queries for data schema and summary
|
|
13
|
+
* statistic information), and then interactive queries that may be driven by
|
|
14
|
+
* an associated selection. When no longer needed, a client should be
|
|
15
|
+
* disconnected from the coordinator.
|
|
16
|
+
*
|
|
17
|
+
* When enabled, a client will initialize and respond to query update requests.
|
|
18
|
+
* If disabled, the client will delay initialization and not respond to queries
|
|
19
|
+
* until enabled again. Disabling a client can improve system performance when
|
|
20
|
+
* associated interface elements are offscreen or disabled.
|
|
7
21
|
*/
|
|
8
22
|
export class MosaicClient {
|
|
9
23
|
/**
|
|
10
|
-
*
|
|
11
|
-
* @param {
|
|
12
|
-
* this client's data. If provided, a coordinator
|
|
13
|
-
* the client when the selection updates.
|
|
24
|
+
* Create a new client instance.
|
|
25
|
+
* @param {Selection} [filterSelection] An optional selection to
|
|
26
|
+
* interactively filter this client's data. If provided, a coordinator
|
|
27
|
+
* will re-query and update the client when the selection updates.
|
|
14
28
|
*/
|
|
15
29
|
constructor(filterSelection) {
|
|
16
|
-
/** @type {Selection} */
|
|
30
|
+
/** @type {Selection | undefined} */
|
|
17
31
|
this._filterBy = filterSelection;
|
|
18
32
|
this._requestUpdate = throttle(() => this.requestQuery(), true);
|
|
19
|
-
/** @type {Coordinator} */
|
|
33
|
+
/** @type {Coordinator | null} */
|
|
20
34
|
this._coordinator = null;
|
|
21
35
|
/** @type {Promise<any>} */
|
|
22
36
|
this._pending = Promise.resolve();
|
|
37
|
+
/** @type {boolean} */
|
|
38
|
+
this._enabled = true;
|
|
39
|
+
/** @type {boolean} */
|
|
40
|
+
this._initialized = false;
|
|
41
|
+
/** @type {Query | boolean | null} */
|
|
42
|
+
this._request = null;
|
|
23
43
|
}
|
|
24
44
|
|
|
25
45
|
/**
|
|
26
|
-
*
|
|
46
|
+
* @returns {Coordinator | null} this client's connected coordinator.
|
|
27
47
|
*/
|
|
28
48
|
get coordinator() {
|
|
29
49
|
return this._coordinator;
|
|
@@ -36,6 +56,33 @@ export class MosaicClient {
|
|
|
36
56
|
this._coordinator = coordinator;
|
|
37
57
|
}
|
|
38
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Return this client's enabled state.
|
|
61
|
+
*/
|
|
62
|
+
get enabled() {
|
|
63
|
+
return this._enabled;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Set this client's enabled state;
|
|
68
|
+
*/
|
|
69
|
+
set enabled(state) {
|
|
70
|
+
state = !!state; // ensure boolean
|
|
71
|
+
if (this._enabled !== state) {
|
|
72
|
+
this._enabled = state;
|
|
73
|
+
if (state) {
|
|
74
|
+
if (!this._initialized) {
|
|
75
|
+
// initialization includes a query request
|
|
76
|
+
this.initialize();
|
|
77
|
+
} else if (this._request) {
|
|
78
|
+
// request query now if requested while disabled
|
|
79
|
+
this.requestQuery(this._request === true ? undefined : this._request);
|
|
80
|
+
}
|
|
81
|
+
this._request = null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
39
86
|
/**
|
|
40
87
|
* Return a Promise that resolves once the client has updated.
|
|
41
88
|
*/
|
|
@@ -44,7 +91,7 @@ export class MosaicClient {
|
|
|
44
91
|
}
|
|
45
92
|
|
|
46
93
|
/**
|
|
47
|
-
*
|
|
94
|
+
* @returns {Selection | undefined} this client's filter selection.
|
|
48
95
|
*/
|
|
49
96
|
get filterBy() {
|
|
50
97
|
return this._filterBy;
|
|
@@ -52,8 +99,8 @@ export class MosaicClient {
|
|
|
52
99
|
|
|
53
100
|
/**
|
|
54
101
|
* Return a boolean indicating if the client query can be sped up with
|
|
55
|
-
* materialized views of pre-aggregated data. Should return true if changes
|
|
56
|
-
* the filterBy selection
|
|
102
|
+
* materialized views of pre-aggregated data. Should return true if changes
|
|
103
|
+
* to the filterBy selection do not change the groupby domain of the client
|
|
57
104
|
* query.
|
|
58
105
|
*/
|
|
59
106
|
get filterStable() {
|
|
@@ -61,25 +108,9 @@ export class MosaicClient {
|
|
|
61
108
|
}
|
|
62
109
|
|
|
63
110
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*/
|
|
68
|
-
fields() {
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Called by the coordinator to set the field info for this client.
|
|
74
|
-
* @param {import('./types.js').FieldInfo[]} info The field info result.
|
|
75
|
-
* @returns {this}
|
|
76
|
-
*/
|
|
77
|
-
fieldInfo(info) { // eslint-disable-line no-unused-vars
|
|
78
|
-
return this;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Prepare the client before the query() method is called.
|
|
111
|
+
* Prepare the client before the `query()` method is called. Subclasses
|
|
112
|
+
* should override this method as needed, potentially issuing one or more
|
|
113
|
+
* queries to gather data or metadata needed prior to `query` calls.
|
|
83
114
|
*/
|
|
84
115
|
async prepare() {
|
|
85
116
|
}
|
|
@@ -122,34 +153,67 @@ export class MosaicClient {
|
|
|
122
153
|
|
|
123
154
|
/**
|
|
124
155
|
* Request the coordinator to execute a query for this client.
|
|
125
|
-
* If an explicit query is not provided, the client query method will
|
|
126
|
-
* be called, filtered by the current filterBy selection. This method
|
|
127
|
-
*
|
|
156
|
+
* If an explicit query is not provided, the client `query` method will
|
|
157
|
+
* be called, filtered by the current `filterBy` selection. This method has
|
|
158
|
+
* no effect if the client is not connected to a coordinator. If the client
|
|
159
|
+
* is connected by currently disabled, the request will be serviced if the
|
|
160
|
+
* client is later enabled.
|
|
161
|
+
* @param {Query} [query] The query to request. If unspecified, the query
|
|
162
|
+
* will be determind by the client's `query` method and the current
|
|
163
|
+
* `filterBy` selection state.
|
|
128
164
|
* @returns {Promise}
|
|
129
165
|
*/
|
|
130
166
|
requestQuery(query) {
|
|
131
|
-
|
|
132
|
-
|
|
167
|
+
if (this._enabled) {
|
|
168
|
+
const q = query || this.query(this.filterBy?.predicate(this));
|
|
169
|
+
return this._coordinator?.requestQuery(this, q);
|
|
170
|
+
} else {
|
|
171
|
+
this._request = query ?? true;
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
133
174
|
}
|
|
134
175
|
|
|
135
176
|
/**
|
|
136
177
|
* Request that the coordinator perform a throttled update of this client
|
|
137
|
-
* using the default query. Unlike requestQuery, for which every call
|
|
138
|
-
*
|
|
139
|
-
*
|
|
178
|
+
* using the default query. Unlike requestQuery, for which every call results
|
|
179
|
+
* in an executed query, multiple calls to requestUpdate may be consolidated
|
|
180
|
+
* into a single update. This method has no effect if the client is not
|
|
181
|
+
* connected to a coordinator. If the client is connected but currently
|
|
182
|
+
* disabled, the request will be serviced if the client is later enabled.
|
|
140
183
|
*/
|
|
141
184
|
requestUpdate() {
|
|
142
|
-
this.
|
|
185
|
+
if (this._enabled) {
|
|
186
|
+
this._requestUpdate();
|
|
187
|
+
} else {
|
|
188
|
+
this.requestQuery();
|
|
189
|
+
}
|
|
143
190
|
}
|
|
144
191
|
|
|
145
192
|
/**
|
|
146
|
-
* Reset this client,
|
|
147
|
-
*
|
|
148
|
-
* registered with a coordinator.
|
|
149
|
-
* @returns {Promise}
|
|
193
|
+
* Reset this client, calling the prepare method and query requests. This
|
|
194
|
+
* method has no effect if the client is not registered with a coordinator.
|
|
150
195
|
*/
|
|
151
196
|
initialize() {
|
|
152
|
-
|
|
197
|
+
if (!this._enabled) {
|
|
198
|
+
// clear flag so we initialize when enabled again
|
|
199
|
+
this._initialized = false;
|
|
200
|
+
} else if (this._coordinator) {
|
|
201
|
+
// if connected, let's initialize
|
|
202
|
+
this._initialized = true;
|
|
203
|
+
this._pending = this.prepare().then(() => this.requestQuery());
|
|
204
|
+
}
|
|
205
|
+
}
|
|
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);
|
|
153
217
|
}
|
|
154
218
|
|
|
155
219
|
/**
|
package/src/QueryConsolidator.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { DescribeQuery,
|
|
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) {
|
package/src/QueryManager.js
CHANGED
|
@@ -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
|
-
/**
|
|
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.
|
package/src/SelectionClause.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
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
|
+
}
|
package/src/connectors/rest.js
CHANGED
|
@@ -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
|
-
|
|
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
|
};
|
package/src/connectors/socket.js
CHANGED
|
@@ -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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
},
|
|
36
|
+
const c = this;
|
|
37
|
+
this._events = {
|
|
38
|
+
open() {
|
|
39
|
+
c._connected = true;
|
|
40
|
+
c.next();
|
|
41
|
+
},
|
|
23
42
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
63
|
+
message({ data }) {
|
|
64
|
+
if (c._request) {
|
|
65
|
+
const { query, resolve, reject } = c._request;
|
|
42
66
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
69
|
-
if (
|
|
70
|
-
|
|
71
|
-
if (
|
|
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
|
-
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
}
|