@uwdata/mosaic-core 0.16.2 → 0.18.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 +1 -1
- package/README.md +0 -1
- package/dist/src/Coordinator.d.ts +147 -0
- package/dist/src/Coordinator.d.ts.map +1 -0
- package/dist/src/Coordinator.js +269 -0
- package/dist/src/Coordinator.js.map +1 -0
- package/dist/{types → src}/MosaicClient.d.ts +37 -42
- package/dist/src/MosaicClient.d.ts.map +1 -0
- package/dist/src/MosaicClient.js +213 -0
- package/dist/src/MosaicClient.js.map +1 -0
- package/dist/src/Param.d.ts +56 -0
- package/dist/src/Param.d.ts.map +1 -0
- package/dist/src/Param.js +89 -0
- package/dist/src/Param.js.map +1 -0
- package/dist/src/QueryConsolidator.d.ts +11 -0
- package/dist/src/QueryConsolidator.d.ts.map +1 -0
- package/dist/src/QueryConsolidator.js +249 -0
- package/dist/src/QueryConsolidator.js.map +1 -0
- package/dist/src/QueryManager.d.ts +77 -0
- package/dist/src/QueryManager.d.ts.map +1 -0
- package/dist/src/QueryManager.js +174 -0
- package/dist/src/QueryManager.js.map +1 -0
- package/dist/src/Selection.d.ts +222 -0
- package/dist/src/Selection.d.ts.map +1 -0
- package/dist/src/Selection.js +319 -0
- package/dist/src/Selection.js.map +1 -0
- package/dist/src/SelectionClause.d.ts +192 -0
- package/dist/src/SelectionClause.d.ts.map +1 -0
- package/dist/src/SelectionClause.js +126 -0
- package/dist/src/SelectionClause.js.map +1 -0
- package/dist/{types → src}/connectors/Connector.d.ts +6 -5
- package/dist/src/connectors/Connector.d.ts.map +1 -0
- package/dist/src/connectors/Connector.js +2 -0
- package/dist/src/connectors/Connector.js.map +1 -0
- package/dist/src/connectors/rest.d.ts +24 -0
- package/dist/src/connectors/rest.d.ts.map +1 -0
- package/dist/src/connectors/rest.js +37 -0
- package/dist/src/connectors/rest.js.map +1 -0
- package/dist/src/connectors/socket.d.ts +40 -0
- package/dist/src/connectors/socket.d.ts.map +1 -0
- package/dist/src/connectors/socket.js +115 -0
- package/dist/src/connectors/socket.js.map +1 -0
- package/dist/src/connectors/wasm.d.ts +53 -0
- package/dist/src/connectors/wasm.d.ts.map +1 -0
- package/dist/src/connectors/wasm.js +113 -0
- package/dist/src/connectors/wasm.js.map +1 -0
- package/dist/src/index.d.ts +28 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +25 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/make-client.d.ts +35 -0
- package/dist/src/make-client.d.ts.map +1 -0
- package/dist/src/make-client.js +52 -0
- package/dist/src/make-client.js.map +1 -0
- package/dist/{types → src}/preagg/PreAggregator.d.ts +64 -94
- package/dist/src/preagg/PreAggregator.d.ts.map +1 -0
- package/dist/src/preagg/PreAggregator.js +382 -0
- package/dist/src/preagg/PreAggregator.js.map +1 -0
- package/dist/{types → src}/preagg/preagg-columns.d.ts +10 -8
- package/dist/src/preagg/preagg-columns.d.ts.map +1 -0
- package/dist/src/preagg/preagg-columns.js +95 -0
- package/dist/src/preagg/preagg-columns.js.map +1 -0
- package/dist/src/preagg/sufficient-statistics.d.ts +14 -0
- package/dist/src/preagg/sufficient-statistics.d.ts.map +1 -0
- package/dist/src/preagg/sufficient-statistics.js +446 -0
- package/dist/src/preagg/sufficient-statistics.js.map +1 -0
- package/dist/{types → src}/types.d.ts +23 -9
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/{types → src}/util/AsyncDispatch.d.ts +53 -32
- package/dist/src/util/AsyncDispatch.d.ts.map +1 -0
- package/dist/src/util/AsyncDispatch.js +188 -0
- package/dist/src/util/AsyncDispatch.js.map +1 -0
- package/dist/src/util/cache.d.ts +19 -0
- package/dist/src/util/cache.d.ts.map +1 -0
- package/dist/src/util/cache.js +66 -0
- package/dist/src/util/cache.js.map +1 -0
- package/dist/src/util/decode-ipc.d.ts +12 -0
- package/dist/src/util/decode-ipc.d.ts.map +1 -0
- package/{src → dist/src}/util/decode-ipc.js +5 -6
- package/dist/src/util/decode-ipc.js.map +1 -0
- package/dist/src/util/distinct.d.ts +3 -0
- package/dist/src/util/distinct.d.ts.map +1 -0
- package/dist/src/util/distinct.js +16 -0
- package/dist/src/util/distinct.js.map +1 -0
- package/dist/src/util/field-info.d.ts +26 -0
- package/dist/src/util/field-info.d.ts.map +1 -0
- package/dist/src/util/field-info.js +91 -0
- package/dist/src/util/field-info.js.map +1 -0
- package/dist/src/util/hash.d.ts +2 -0
- package/dist/src/util/hash.d.ts.map +1 -0
- package/dist/src/util/hash.js +26 -0
- package/dist/src/util/hash.js.map +1 -0
- package/dist/src/util/is-activatable.d.ts +8 -0
- package/dist/src/util/is-activatable.d.ts.map +1 -0
- package/dist/src/util/is-activatable.js +10 -0
- package/dist/src/util/is-activatable.js.map +1 -0
- package/dist/src/util/is-arrow-table.d.ts +9 -0
- package/dist/src/util/is-arrow-table.d.ts.map +1 -0
- package/dist/src/util/is-arrow-table.js +11 -0
- package/dist/src/util/is-arrow-table.js.map +1 -0
- package/dist/src/util/js-type.d.ts +9 -0
- package/dist/src/util/js-type.d.ts.map +1 -0
- package/dist/src/util/js-type.js +59 -0
- package/dist/src/util/js-type.js.map +1 -0
- package/dist/{types → src}/util/priority-queue.d.ts +12 -14
- package/dist/src/util/priority-queue.d.ts.map +1 -0
- package/dist/src/util/priority-queue.js +81 -0
- package/dist/src/util/priority-queue.js.map +1 -0
- package/dist/src/util/query-result.d.ts +47 -0
- package/dist/src/util/query-result.d.ts.map +1 -0
- package/dist/src/util/query-result.js +83 -0
- package/dist/src/util/query-result.js.map +1 -0
- package/dist/src/util/synchronizer.d.ts +36 -0
- package/dist/src/util/synchronizer.d.ts.map +1 -0
- package/dist/src/util/synchronizer.js +52 -0
- package/dist/src/util/synchronizer.js.map +1 -0
- package/dist/src/util/throttle.d.ts +12 -0
- package/dist/src/util/throttle.d.ts.map +1 -0
- package/dist/src/util/throttle.js +51 -0
- package/dist/src/util/throttle.js.map +1 -0
- package/dist/src/util/to-data-columns.d.ts +22 -0
- package/dist/src/util/to-data-columns.d.ts.map +1 -0
- package/dist/src/util/to-data-columns.js +51 -0
- package/dist/src/util/to-data-columns.js.map +1 -0
- package/dist/src/util/void-logger.d.ts +13 -0
- package/dist/src/util/void-logger.d.ts.map +1 -0
- package/dist/src/util/void-logger.js +13 -0
- package/dist/src/util/void-logger.js.map +1 -0
- package/package.json +15 -13
- package/src/Coordinator.ts +367 -0
- package/src/{MosaicClient.js → MosaicClient.ts} +49 -43
- package/src/{Param.js → Param.ts} +29 -28
- package/src/{QueryConsolidator.js → QueryConsolidator.ts} +81 -58
- package/src/{QueryManager.js → QueryManager.ts} +61 -54
- package/src/Selection.ts +388 -0
- package/src/SelectionClause.ts +275 -0
- package/src/connectors/Connector.ts +6 -6
- package/src/connectors/rest.ts +56 -0
- package/src/connectors/{socket.js → socket.ts} +53 -42
- package/src/connectors/{wasm.js → wasm.ts} +46 -62
- package/src/{index.js → index.ts} +13 -1
- package/src/make-client.ts +93 -0
- package/src/preagg/{PreAggregator.js → PreAggregator.ts} +164 -145
- package/src/preagg/{preagg-columns.js → preagg-columns.ts} +27 -24
- package/src/preagg/{sufficient-statistics.js → sufficient-statistics.ts} +161 -110
- package/src/types.ts +24 -9
- package/src/util/{AsyncDispatch.js → AsyncDispatch.ts} +62 -43
- package/src/util/{cache.js → cache.ts} +25 -15
- package/src/util/decode-ipc.ts +15 -0
- package/src/util/{distinct.js → distinct.ts} +3 -3
- package/src/util/{field-info.js → field-info.ts} +31 -32
- package/src/util/{hash.js → hash.ts} +4 -4
- package/src/util/is-activatable.ts +11 -0
- package/src/util/is-arrow-table.ts +12 -0
- package/src/util/{js-type.js → js-type.ts} +7 -5
- package/src/util/{priority-queue.js → priority-queue.ts} +32 -20
- package/src/util/{query-result.js → query-result.ts} +24 -17
- package/src/util/synchronizer.ts +56 -0
- package/src/util/throttle.ts +59 -0
- package/src/util/to-data-columns.ts +65 -0
- package/src/util/void-logger.ts +23 -0
- package/dist/types/Coordinator.d.ts +0 -164
- package/dist/types/Param.d.ts +0 -47
- package/dist/types/QueryConsolidator.d.ts +0 -9
- package/dist/types/QueryManager.d.ts +0 -91
- package/dist/types/Selection.d.ts +0 -235
- package/dist/types/SelectionClause.d.ts +0 -105
- package/dist/types/connectors/rest.d.ts +0 -13
- package/dist/types/connectors/socket.d.ts +0 -100
- package/dist/types/connectors/wasm.d.ts +0 -135
- package/dist/types/index-types.d.ts +0 -4
- package/dist/types/index.d.ts +0 -19
- package/dist/types/make-client.d.ts +0 -78
- package/dist/types/preagg/sufficient-statistics.d.ts +0 -13
- package/dist/types/util/cache.d.ts +0 -17
- package/dist/types/util/decode-ipc.d.ts +0 -12
- package/dist/types/util/distinct.d.ts +0 -2
- package/dist/types/util/field-info.d.ts +0 -23
- package/dist/types/util/hash.d.ts +0 -1
- package/dist/types/util/is-activatable.d.ts +0 -6
- package/dist/types/util/is-arrow-table.d.ts +0 -8
- package/dist/types/util/js-type.d.ts +0 -7
- package/dist/types/util/query-result.d.ts +0 -44
- package/dist/types/util/selection-types.d.ts +0 -114
- package/dist/types/util/synchronizer.d.ts +0 -29
- package/dist/types/util/throttle.d.ts +0 -13
- package/dist/types/util/to-data-columns.d.ts +0 -29
- package/dist/types/util/void-logger.d.ts +0 -10
- package/jsconfig.json +0 -11
- package/src/Coordinator.js +0 -337
- package/src/Selection.js +0 -380
- package/src/SelectionClause.js +0 -159
- package/src/connectors/rest.js +0 -38
- package/src/index-types.ts +0 -4
- package/src/make-client.js +0 -101
- package/src/util/is-activatable.js +0 -8
- package/src/util/is-arrow-table.js +0 -10
- package/src/util/selection-types.ts +0 -137
- package/src/util/synchronizer.js +0 -47
- package/src/util/throttle.js +0 -54
- package/src/util/to-data-columns.js +0 -60
- package/src/util/void-logger.js +0 -13
- package/tsconfig.json +0 -11
- package/vitest.config.ts +0 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import type { Connector } from './connectors/Connector.js';
|
|
2
|
+
import type { Cache, Logger, QueryEntry, QueryRequest } from './types.js';
|
|
3
3
|
import { consolidator } from './QueryConsolidator.js';
|
|
4
4
|
import { lruCache, voidCache } from './util/cache.js';
|
|
5
5
|
import { PriorityQueue } from './util/priority-queue.js';
|
|
@@ -9,38 +9,38 @@ import { voidLogger } from './util/void-logger.js';
|
|
|
9
9
|
export const Priority = Object.freeze({ High: 0, Normal: 1, Low: 2 });
|
|
10
10
|
|
|
11
11
|
export class QueryManager {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
private queue: PriorityQueue<QueryEntry>;
|
|
13
|
+
private db: Connector | null;
|
|
14
|
+
private clientCache: Cache | null;
|
|
15
|
+
private _logger: Logger;
|
|
16
|
+
private _logQueries: boolean;
|
|
17
|
+
private _consolidate: ReturnType<typeof consolidator> | null;
|
|
18
|
+
/** Requests pending with the query manager. */
|
|
19
|
+
public pendingResults: QueryResult[];
|
|
20
|
+
private maxConcurrentRequests: number;
|
|
21
|
+
private pendingExec: boolean;
|
|
22
|
+
|
|
23
|
+
constructor(maxConcurrentRequests: number = 32) {
|
|
16
24
|
this.queue = new PriorityQueue(3);
|
|
17
|
-
/** @type {Connector} */
|
|
18
25
|
this.db = null;
|
|
19
|
-
/** @type {Cache} */
|
|
20
26
|
this.clientCache = null;
|
|
21
|
-
/** @type {Logger} */
|
|
22
27
|
this._logger = voidLogger();
|
|
23
|
-
/** @type {boolean} */
|
|
24
28
|
this._logQueries = false;
|
|
25
|
-
/** @type {ReturnType<typeof consolidator> | null} */
|
|
26
29
|
this._consolidate = null;
|
|
27
|
-
/**
|
|
28
|
-
* Requests pending with the query manager.
|
|
29
|
-
* @type {QueryResult[]}
|
|
30
|
-
*/
|
|
31
30
|
this.pendingResults = [];
|
|
32
|
-
/** @type {number} */
|
|
33
31
|
this.maxConcurrentRequests = maxConcurrentRequests;
|
|
34
|
-
/** @type {boolean} */
|
|
35
32
|
this.pendingExec = false;
|
|
36
33
|
}
|
|
37
34
|
|
|
38
|
-
next() {
|
|
35
|
+
next(): void {
|
|
39
36
|
if (this.queue.isEmpty() || this.pendingResults.length > this.maxConcurrentRequests || this.pendingExec) {
|
|
40
37
|
return;
|
|
41
38
|
}
|
|
42
39
|
|
|
43
|
-
const
|
|
40
|
+
const entry = this.queue.next();
|
|
41
|
+
if (!entry) return;
|
|
42
|
+
|
|
43
|
+
const { request, result } = entry;
|
|
44
44
|
|
|
45
45
|
this.pendingResults.push(result);
|
|
46
46
|
if (request.type === 'exec') this.pendingExec = true;
|
|
@@ -48,7 +48,7 @@ export class QueryManager {
|
|
|
48
48
|
this.submit(request, result).finally(() => {
|
|
49
49
|
// return from the queue all requests that are ready
|
|
50
50
|
while (this.pendingResults.length && this.pendingResults[0].state !== QueryState.pending) {
|
|
51
|
-
const result = this.pendingResults.shift()
|
|
51
|
+
const result = this.pendingResults.shift()!;
|
|
52
52
|
if (result.state === QueryState.ready) {
|
|
53
53
|
result.fulfill();
|
|
54
54
|
} else if (result.state === QueryState.done) {
|
|
@@ -62,29 +62,27 @@ export class QueryManager {
|
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
64
|
* Add an entry to the query queue with a priority.
|
|
65
|
-
* @param
|
|
66
|
-
* @param
|
|
67
|
-
* @param {QueryResult} [entry.result] The query result.
|
|
68
|
-
* @param {number} priority The query priority, defaults to `Priority.Normal`.
|
|
65
|
+
* @param entry The entry to add.
|
|
66
|
+
* @param priority The query priority, defaults to `Priority.Normal`.
|
|
69
67
|
*/
|
|
70
|
-
enqueue(entry, priority = Priority.Normal) {
|
|
68
|
+
enqueue(entry: QueryEntry, priority: number = Priority.Normal): void {
|
|
71
69
|
this.queue.insert(entry, priority);
|
|
72
70
|
this.next();
|
|
73
71
|
}
|
|
74
72
|
|
|
75
73
|
/**
|
|
76
74
|
* Submit the query to the connector.
|
|
77
|
-
* @param
|
|
78
|
-
* @param
|
|
75
|
+
* @param request The request.
|
|
76
|
+
* @param result The query result.
|
|
79
77
|
*/
|
|
80
|
-
async submit(request, result) {
|
|
78
|
+
async submit(request: QueryRequest, result: QueryResult): Promise<void> {
|
|
81
79
|
try {
|
|
82
80
|
const { query, type, cache = false, options } = request;
|
|
83
81
|
const sql = query ? `${query}` : null;
|
|
84
82
|
|
|
85
83
|
// check query cache
|
|
86
84
|
if (cache) {
|
|
87
|
-
const cached = this.clientCache
|
|
85
|
+
const cached = this.clientCache!.get(sql!);
|
|
88
86
|
if (cached) {
|
|
89
87
|
const data = await cached;
|
|
90
88
|
this._logger.debug('Cache');
|
|
@@ -99,12 +97,13 @@ export class QueryManager {
|
|
|
99
97
|
this._logger.debug('Query', { type, sql, ...options });
|
|
100
98
|
}
|
|
101
99
|
|
|
102
|
-
|
|
103
|
-
|
|
100
|
+
// @ts-expect-error type may be exec | json | arrow
|
|
101
|
+
const promise = this.db!.query({ type, sql: sql!, ...options });
|
|
102
|
+
if (cache) this.clientCache!.set(sql!, promise);
|
|
104
103
|
|
|
105
104
|
const data = await promise;
|
|
106
105
|
|
|
107
|
-
if (cache) this.clientCache
|
|
106
|
+
if (cache) this.clientCache!.set(sql!, data);
|
|
108
107
|
|
|
109
108
|
this._logger.debug(`Request: ${(performance.now() - t0).toFixed(1)}`);
|
|
110
109
|
result.ready(type === 'exec' ? null : data);
|
|
@@ -115,10 +114,12 @@ export class QueryManager {
|
|
|
115
114
|
|
|
116
115
|
/**
|
|
117
116
|
* Get or set the current query cache.
|
|
118
|
-
* @param
|
|
119
|
-
* @returns
|
|
117
|
+
* @param value Cache value to set
|
|
118
|
+
* @returns Current cache
|
|
120
119
|
*/
|
|
121
|
-
cache(
|
|
120
|
+
cache(): Cache | null;
|
|
121
|
+
cache(value: Cache | boolean): Cache;
|
|
122
|
+
cache(value?: Cache | boolean): Cache | null {
|
|
122
123
|
return value !== undefined
|
|
123
124
|
? (this.clientCache = value === true ? lruCache() : (value || voidCache()))
|
|
124
125
|
: this.clientCache;
|
|
@@ -126,38 +127,44 @@ export class QueryManager {
|
|
|
126
127
|
|
|
127
128
|
/**
|
|
128
129
|
* Get or set the current logger.
|
|
129
|
-
* @param
|
|
130
|
-
* @returns
|
|
130
|
+
* @param value Logger to set
|
|
131
|
+
* @returns Current logger
|
|
131
132
|
*/
|
|
132
|
-
logger(
|
|
133
|
+
logger(): Logger;
|
|
134
|
+
logger(value: Logger): Logger;
|
|
135
|
+
logger(value?: Logger): Logger {
|
|
133
136
|
return value ? (this._logger = value) : this._logger;
|
|
134
137
|
}
|
|
135
138
|
|
|
136
139
|
/**
|
|
137
140
|
* Get or set if queries should be logged.
|
|
138
|
-
* @param
|
|
139
|
-
* @returns
|
|
141
|
+
* @param value Whether to log queries
|
|
142
|
+
* @returns Current logging state
|
|
140
143
|
*/
|
|
141
|
-
logQueries(
|
|
144
|
+
logQueries(): boolean;
|
|
145
|
+
logQueries(value: boolean): boolean;
|
|
146
|
+
logQueries(value?: boolean): boolean {
|
|
142
147
|
return value !== undefined ? this._logQueries = !!value : this._logQueries;
|
|
143
148
|
}
|
|
144
149
|
|
|
145
150
|
/**
|
|
146
151
|
* Get or set the database connector.
|
|
147
|
-
* @param
|
|
148
|
-
* @returns
|
|
152
|
+
* @param connector Connector to set
|
|
153
|
+
* @returns Current connector
|
|
149
154
|
*/
|
|
150
|
-
connector(
|
|
155
|
+
connector(): Connector | null;
|
|
156
|
+
connector(connector: Connector): Connector;
|
|
157
|
+
connector(connector?: Connector): Connector | null {
|
|
151
158
|
return connector ? (this.db = connector) : this.db;
|
|
152
159
|
}
|
|
153
160
|
|
|
154
161
|
/**
|
|
155
162
|
* Indicate if query consolidation should be performed.
|
|
156
|
-
* @param
|
|
163
|
+
* @param flag Whether to enable consolidation
|
|
157
164
|
*/
|
|
158
|
-
consolidate(flag) {
|
|
165
|
+
consolidate(flag: boolean): void {
|
|
159
166
|
if (flag && !this._consolidate) {
|
|
160
|
-
this._consolidate = consolidator(this.enqueue.bind(this), this.clientCache);
|
|
167
|
+
this._consolidate = consolidator(this.enqueue.bind(this), this.clientCache!);
|
|
161
168
|
} else if (!flag && this._consolidate) {
|
|
162
169
|
this._consolidate = null;
|
|
163
170
|
}
|
|
@@ -165,11 +172,11 @@ export class QueryManager {
|
|
|
165
172
|
|
|
166
173
|
/**
|
|
167
174
|
* Request a query result.
|
|
168
|
-
* @param
|
|
169
|
-
* @param
|
|
170
|
-
* @returns
|
|
175
|
+
* @param request The request.
|
|
176
|
+
* @param priority The query priority, defaults to `Priority.Normal`.
|
|
177
|
+
* @returns A query result promise.
|
|
171
178
|
*/
|
|
172
|
-
request(request, priority = Priority.Normal) {
|
|
179
|
+
request(request: QueryRequest, priority: number = Priority.Normal): QueryResult {
|
|
173
180
|
const result = new QueryResult();
|
|
174
181
|
const entry = { request, result };
|
|
175
182
|
if (this._consolidate) {
|
|
@@ -180,7 +187,7 @@ export class QueryManager {
|
|
|
180
187
|
return result;
|
|
181
188
|
}
|
|
182
189
|
|
|
183
|
-
cancel(requests) {
|
|
190
|
+
cancel(requests: QueryResult[]): void {
|
|
184
191
|
const set = new Set(requests);
|
|
185
192
|
if (set.size) {
|
|
186
193
|
this.queue.remove(({ result }) => {
|
|
@@ -199,7 +206,7 @@ export class QueryManager {
|
|
|
199
206
|
}
|
|
200
207
|
}
|
|
201
208
|
|
|
202
|
-
clear() {
|
|
209
|
+
clear(): void {
|
|
203
210
|
this.queue.remove(({ result }) => {
|
|
204
211
|
result.reject('Cleared');
|
|
205
212
|
return true;
|
|
@@ -210,4 +217,4 @@ export class QueryManager {
|
|
|
210
217
|
}
|
|
211
218
|
this.pendingResults = [];
|
|
212
219
|
}
|
|
213
|
-
}
|
|
220
|
+
}
|
package/src/Selection.ts
ADDED
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import { type MosaicClient } from './MosaicClient.js';
|
|
2
|
+
import { type ExprNode, FilterExpr, literal, MaybeArray, or } from '@uwdata/mosaic-sql';
|
|
3
|
+
import { Param } from './Param.js';
|
|
4
|
+
import { ClauseSource, SelectionClause } from './SelectionClause.js';
|
|
5
|
+
|
|
6
|
+
export interface SelectionOptions {
|
|
7
|
+
/** Boolean flag indicating cross-filtered resolution. If true, selection clauses will not be applied to the clients they are associated with. */
|
|
8
|
+
cross?: boolean;
|
|
9
|
+
/** Boolean flag indicating if a lack of clauses should correspond to an empty selection with no records. This setting determines the default selection state. */
|
|
10
|
+
empty?: boolean;
|
|
11
|
+
/** Upstream selections whose clauses should be included as part of the new selection. Any clauses published to upstream selections will be relayed to the new selection. */
|
|
12
|
+
include?: Selection | Selection[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface SelectionResolverOptions extends Pick<SelectionOptions, "empty" | "cross"> {
|
|
16
|
+
/** Boolean flag to indicate a union strategy. If false, an intersection strategy is used. */
|
|
17
|
+
union?: boolean;
|
|
18
|
+
/** Boolean flag to indicate single clauses only. */
|
|
19
|
+
single?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Test if a value is a Selection instance.
|
|
24
|
+
* @param x The value to test.
|
|
25
|
+
* @returns True if the input is a Selection, false otherwise.
|
|
26
|
+
*/
|
|
27
|
+
export function isSelection(x: unknown): x is Selection {
|
|
28
|
+
return x instanceof Selection;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function create(options: SelectionResolverOptions, include?: Selection | Selection[]): Selection {
|
|
32
|
+
return new Selection(
|
|
33
|
+
new SelectionResolver(options),
|
|
34
|
+
include ? [include].flat() : undefined
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type SelectionClauseArray = SelectionClause[] & { active?: SelectionClause };
|
|
39
|
+
type ResolvedPredicate = MaybeArray<string | boolean | ExprNode> | undefined;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Represents a dynamic set of query filter predicates.
|
|
43
|
+
*/
|
|
44
|
+
export class Selection extends Param<SelectionClauseArray> {
|
|
45
|
+
_resolved: SelectionClauseArray;
|
|
46
|
+
_resolver: SelectionResolver;
|
|
47
|
+
_relay: Set<Selection>;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Create a new Selection instance with an
|
|
51
|
+
* intersect (conjunction) resolution strategy.
|
|
52
|
+
* @param options The selection options.
|
|
53
|
+
* @returns The new Selection instance.
|
|
54
|
+
*/
|
|
55
|
+
static intersect({ cross = false, empty = false, include = [] }: SelectionOptions = {}): Selection {
|
|
56
|
+
return create({ cross, empty }, include);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Create a new Selection instance with a
|
|
61
|
+
* union (disjunction) resolution strategy.
|
|
62
|
+
* @param options The selection options.
|
|
63
|
+
* @returns The new Selection instance.
|
|
64
|
+
*/
|
|
65
|
+
static union({ cross = false, empty = false, include = [] }: SelectionOptions = {}): Selection {
|
|
66
|
+
return create({ cross, empty, union: true }, include);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Create a new Selection instance with a singular resolution strategy
|
|
71
|
+
* that keeps only the most recent selection clause.
|
|
72
|
+
* @param options The selection options.
|
|
73
|
+
* @returns The new Selection instance.
|
|
74
|
+
*/
|
|
75
|
+
static single({ cross = false, empty = false, include = [] }: SelectionOptions = {}): Selection {
|
|
76
|
+
return create({ cross, empty, single: true }, include);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Create a new Selection instance with a
|
|
81
|
+
* cross-filtered intersect resolution strategy.
|
|
82
|
+
* @param options The selection options.
|
|
83
|
+
* @returns The new Selection instance.
|
|
84
|
+
*/
|
|
85
|
+
static crossfilter({ empty = false, include = [] }: Omit<SelectionOptions, 'cross'> = {}): Selection {
|
|
86
|
+
return create({ cross: true, empty }, include);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Create a new Selection instance.
|
|
91
|
+
* @param resolver The selection resolution
|
|
92
|
+
* strategy to apply.
|
|
93
|
+
* @param include Upstream selections whose clauses
|
|
94
|
+
* should be included as part of this selection. Any clauses published
|
|
95
|
+
* to these upstream selections will be relayed to this selection.
|
|
96
|
+
*/
|
|
97
|
+
constructor(resolver = new SelectionResolver(), include: Selection[] = []) {
|
|
98
|
+
super([]);
|
|
99
|
+
this._resolved = this._value!;
|
|
100
|
+
this._resolver = resolver;
|
|
101
|
+
this._relay = new Set();
|
|
102
|
+
if (Array.isArray(include)) {
|
|
103
|
+
for (const sel of include) {
|
|
104
|
+
sel._relay.add(this);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Create a cloned copy of this Selection instance.
|
|
111
|
+
* @returns A clone of this selection.
|
|
112
|
+
*/
|
|
113
|
+
clone(): Selection {
|
|
114
|
+
const s = new Selection(this._resolver);
|
|
115
|
+
s._value = s._resolved = this._value!;
|
|
116
|
+
return s;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Create a clone of this Selection with clauses corresponding
|
|
121
|
+
* to the provided source removed.
|
|
122
|
+
* @param source The clause source to remove.
|
|
123
|
+
* @returns A cloned and updated Selection.
|
|
124
|
+
*/
|
|
125
|
+
remove(source: ClauseSource): Selection {
|
|
126
|
+
const s = this.clone();
|
|
127
|
+
s._value = s._resolved = s._resolver.resolve(
|
|
128
|
+
this._resolved,
|
|
129
|
+
{ source } as SelectionClause
|
|
130
|
+
);
|
|
131
|
+
s._value.active = { source } as SelectionClause;
|
|
132
|
+
return s;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* The selection clause resolver.
|
|
137
|
+
*/
|
|
138
|
+
get resolver(): SelectionResolver {
|
|
139
|
+
return this._resolver;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Indicate if this selection has a single resolution strategy.
|
|
144
|
+
*/
|
|
145
|
+
get single(): boolean {
|
|
146
|
+
return this._resolver.single;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* The current array of selection clauses.
|
|
151
|
+
*/
|
|
152
|
+
get clauses(): SelectionClauseArray {
|
|
153
|
+
return super.value!;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* The current active (most recently updated) selection clause.
|
|
158
|
+
*/
|
|
159
|
+
get active(): SelectionClause {
|
|
160
|
+
return this.clauses.active!;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The value corresponding to the current active selection clause.
|
|
165
|
+
* This method ensures compatibility where a normal Param is expected.
|
|
166
|
+
*/
|
|
167
|
+
// @ts-expect-error return type differs from Param parent class
|
|
168
|
+
get value(): unknown {
|
|
169
|
+
return this.active?.value;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* The value corresponding to a given source. Returns undefined if
|
|
174
|
+
* this selection does not include a clause from this source.
|
|
175
|
+
* @param source The clause source to look up the value for.
|
|
176
|
+
*/
|
|
177
|
+
valueFor(source: unknown): unknown {
|
|
178
|
+
return this.clauses.find(c => c.source === source)?.value;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Emit an activate event with the given selection clause.
|
|
183
|
+
* @param clause The clause representing the potential activation.
|
|
184
|
+
*/
|
|
185
|
+
activate(clause: SelectionClause): void {
|
|
186
|
+
// @ts-expect-error selection operates differently than scalar param
|
|
187
|
+
this.emit('activate', clause);
|
|
188
|
+
this._relay.forEach(sel => sel.activate(clause));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Update the selection with a new selection clause.
|
|
193
|
+
* @param clause The selection clause to add.
|
|
194
|
+
* @returns This Selection instance.
|
|
195
|
+
*/
|
|
196
|
+
// @ts-expect-error selection and param use differing value types
|
|
197
|
+
update(clause: SelectionClause): this {
|
|
198
|
+
// we maintain an up-to-date list of all resolved clauses
|
|
199
|
+
// this ensures consistent clause state across unemitted event values
|
|
200
|
+
this._resolved = this._resolver.resolve(this._resolved, clause, true);
|
|
201
|
+
this._resolved.active = clause;
|
|
202
|
+
this._relay.forEach(sel => sel.update(clause));
|
|
203
|
+
return super.update(this._resolved);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Reset the selection state by removing all provided clauses. If no clause
|
|
208
|
+
* array is provided as an argument, all current clauses are removed. The
|
|
209
|
+
* reset method (if defined) is invoked on all corresponding clause sources.
|
|
210
|
+
* The reset is relayed to downstream selections that include this selection.
|
|
211
|
+
* @param clauses The clauses to remove. If unspecified, all current clauses are removed.
|
|
212
|
+
* @returns This selection instance.
|
|
213
|
+
*/
|
|
214
|
+
reset(clauses?: SelectionClause[]): this {
|
|
215
|
+
clauses ??= this._resolved;
|
|
216
|
+
clauses.forEach(c => c.source?.reset?.());
|
|
217
|
+
this._resolved = this._resolved.filter(c => clauses!.includes(c));
|
|
218
|
+
this._relay.forEach(sel => sel.reset(clauses));
|
|
219
|
+
return super.update(this._resolved = []);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Upon value-typed updates, sets the current clause list to the
|
|
224
|
+
* input value and returns the active clause value.
|
|
225
|
+
* @param type The event type.
|
|
226
|
+
* @param value The input event value.
|
|
227
|
+
* @returns For value-typed events, returns the active clause
|
|
228
|
+
* values. Otherwise returns the input event value as-is.
|
|
229
|
+
*/
|
|
230
|
+
// @ts-expect-error selection and param use differing value types
|
|
231
|
+
willEmit(type: string, value: unknown): unknown {
|
|
232
|
+
if (type === 'value') {
|
|
233
|
+
this._value = value as SelectionClauseArray;
|
|
234
|
+
return this.value;
|
|
235
|
+
}
|
|
236
|
+
return value;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Upon value-typed updates, returns a dispatch queue filter function.
|
|
241
|
+
* The return value depends on the selection resolution strategy.
|
|
242
|
+
* @param type The event type.
|
|
243
|
+
* @param value The new event value that will be enqueued.
|
|
244
|
+
* @returns A dispatch queue filter function. For non-value events,
|
|
245
|
+
* returns a function that always returns null (no filtering).
|
|
246
|
+
*/
|
|
247
|
+
// @ts-expect-error selection and param use differing value types
|
|
248
|
+
emitQueueFilter(
|
|
249
|
+
type: string,
|
|
250
|
+
value: SelectionClauseArray
|
|
251
|
+
): ((value: SelectionClauseArray) => boolean) | null {
|
|
252
|
+
return type === 'value'
|
|
253
|
+
? this._resolver.queueFilter(value)
|
|
254
|
+
: null;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Indicates if a selection clause should not be applied to a given client.
|
|
259
|
+
* The return value depends on the selection resolution strategy.
|
|
260
|
+
* @param client The client to test.
|
|
261
|
+
* @param clause The selection clause.
|
|
262
|
+
* @returns True if the client should be skipped, false otherwise.
|
|
263
|
+
*/
|
|
264
|
+
skip(client: MosaicClient, clause: SelectionClause): boolean {
|
|
265
|
+
return this._resolver.skip(client, clause);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Return a selection query predicate for the given client.
|
|
270
|
+
* @param client The client whose data may be filtered.
|
|
271
|
+
* @param noSkip Disable skipping of active
|
|
272
|
+
* cross-filtered sources. If set true, the source of the active
|
|
273
|
+
* clause in a cross-filtered selection will not be skipped.
|
|
274
|
+
* @returns The query predicate for filtering client data,
|
|
275
|
+
* based on the current state of this selection.
|
|
276
|
+
*/
|
|
277
|
+
predicate(client: MosaicClient, noSkip: boolean = false): ResolvedPredicate {
|
|
278
|
+
const { clauses } = this;
|
|
279
|
+
const active = noSkip ? null : clauses.active;
|
|
280
|
+
return this._resolver.predicate(clauses, active!, client);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Implements selection clause resolution strategies.
|
|
286
|
+
*/
|
|
287
|
+
export class SelectionResolver {
|
|
288
|
+
union: boolean;
|
|
289
|
+
cross: boolean;
|
|
290
|
+
single: boolean;
|
|
291
|
+
empty: boolean;
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Create a new selection resolved instance.
|
|
295
|
+
* @param options The resolution strategy options.
|
|
296
|
+
* @param options.union Boolean flag to indicate a union strategy.
|
|
297
|
+
* If false, an intersection strategy is used.
|
|
298
|
+
* @param options.cross Boolean flag to indicate cross-filtering.
|
|
299
|
+
* @param options.single Boolean flag to indicate single clauses only.
|
|
300
|
+
* @param options.empty Boolean flag indicating if a lack
|
|
301
|
+
* of clauses should correspond to an empty selection with no records. This
|
|
302
|
+
* setting determines the default selection state.
|
|
303
|
+
*/
|
|
304
|
+
constructor({ union, cross, single, empty }: SelectionResolverOptions = {}) {
|
|
305
|
+
this.union = !!union;
|
|
306
|
+
this.cross = !!cross;
|
|
307
|
+
this.single = !!single;
|
|
308
|
+
this.empty = !!empty;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Resolve a list of selection clauses according to the resolution strategy.
|
|
313
|
+
* @param clauseList An array of selection clauses.
|
|
314
|
+
* @param clause A new selection clause to add.
|
|
315
|
+
* @returns An updated array of selection clauses.
|
|
316
|
+
*/
|
|
317
|
+
resolve(
|
|
318
|
+
clauseList: SelectionClause[],
|
|
319
|
+
clause: SelectionClause,
|
|
320
|
+
reset: boolean = false
|
|
321
|
+
): SelectionClause[] {
|
|
322
|
+
const { source, predicate } = clause;
|
|
323
|
+
const filtered = clauseList.filter(c => source !== c.source);
|
|
324
|
+
const clauses = this.single ? [] : filtered;
|
|
325
|
+
if (this.single && reset) filtered.forEach(c => c.source?.reset?.());
|
|
326
|
+
if (predicate) clauses.push(clause);
|
|
327
|
+
return clauses;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Indicates if a selection clause should not be applied to a given client.
|
|
332
|
+
* The return value depends on the resolution strategy.
|
|
333
|
+
* @param client The selection clause.
|
|
334
|
+
* @param clause The client to test.
|
|
335
|
+
* @returns True if the client should be skipped, false otherwise.
|
|
336
|
+
*/
|
|
337
|
+
skip(client: MosaicClient, clause: SelectionClause): boolean {
|
|
338
|
+
return Boolean(this.cross && clause?.clients?.has(client));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Return a selection query predicate for the given client.
|
|
343
|
+
* @param clauseList An array of selection clauses.
|
|
344
|
+
* @param active The current active selection clause.
|
|
345
|
+
* @param client The client whose data may be filtered.
|
|
346
|
+
* @returns The query predicate for filtering client data,
|
|
347
|
+
* based on the current state of this selection.
|
|
348
|
+
*/
|
|
349
|
+
predicate(
|
|
350
|
+
clauseList: SelectionClause[],
|
|
351
|
+
active: SelectionClause,
|
|
352
|
+
client: MosaicClient
|
|
353
|
+
): ResolvedPredicate {
|
|
354
|
+
const { empty, union } = this;
|
|
355
|
+
|
|
356
|
+
if (empty && !clauseList.length) {
|
|
357
|
+
return [literal(false)];
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// do nothing if cross-filtering and client is currently active
|
|
361
|
+
if (this.skip(client, active)) return undefined;
|
|
362
|
+
|
|
363
|
+
// remove client-specific predicates if cross-filtering
|
|
364
|
+
const predicates: FilterExpr = clauseList
|
|
365
|
+
.filter(clause => !this.skip(client, clause))
|
|
366
|
+
.map(clause => clause.predicate!);
|
|
367
|
+
|
|
368
|
+
// return appropriate conjunction or disjunction
|
|
369
|
+
// an array of predicates is implicitly conjunctive
|
|
370
|
+
return union && predicates.length > 1 ? or(predicates) : predicates;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Returns a filter function for queued selection updates.
|
|
375
|
+
* @param value The new event value that will be enqueued.
|
|
376
|
+
* @returns A dispatch queue filter
|
|
377
|
+
* function, or null if all unemitted event values should be filtered.
|
|
378
|
+
*/
|
|
379
|
+
queueFilter(
|
|
380
|
+
value: SelectionClauseArray
|
|
381
|
+
): ((value: SelectionClauseArray) => boolean) | null {
|
|
382
|
+
if (this.cross) {
|
|
383
|
+
const source = value.active?.source;
|
|
384
|
+
return value => value.active?.source !== source;
|
|
385
|
+
}
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
}
|