@uwdata/mosaic-core 0.11.0 → 0.12.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/README.md +1 -1
- package/dist/mosaic-core.js +11612 -10855
- package/dist/mosaic-core.min.js +7 -7
- package/dist/types/Coordinator.d.ts +169 -0
- package/dist/types/MosaicClient.d.ts +94 -0
- package/dist/types/Param.d.ts +47 -0
- package/dist/types/QueryConsolidator.d.ts +9 -0
- package/dist/types/QueryManager.d.ts +64 -0
- package/dist/types/Selection.d.ts +224 -0
- package/dist/types/SelectionClause.d.ts +105 -0
- package/dist/types/connectors/rest.d.ts +17 -0
- package/dist/types/connectors/socket.d.ts +18 -0
- package/dist/types/connectors/wasm.d.ts +16 -0
- package/dist/types/index.d.ts +25 -0
- package/dist/types/preagg/PreAggregator.d.ts +178 -0
- package/dist/types/preagg/preagg-columns.d.ts +14 -0
- package/dist/types/preagg/sufficient-statistics.d.ts +13 -0
- package/dist/types/util/AsyncDispatch.d.ts +100 -0
- package/dist/types/util/cache.d.ts +13 -0
- package/dist/types/util/decode-ipc.d.ts +7 -0
- package/dist/types/util/distinct.d.ts +2 -0
- package/dist/types/util/field-info.d.ts +13 -0
- package/dist/types/util/hash.d.ts +1 -0
- package/dist/types/util/is-arrow-table.d.ts +8 -0
- package/dist/types/util/js-type.d.ts +1 -0
- package/dist/types/util/priority-queue.d.ts +37 -0
- package/dist/types/util/query-result.d.ts +44 -0
- package/dist/types/util/selection-types.d.ts +114 -0
- package/dist/types/util/synchronizer.d.ts +29 -0
- package/dist/types/util/throttle.d.ts +11 -0
- package/dist/types/util/to-data-columns.d.ts +29 -0
- package/dist/types/util/void-logger.d.ts +7 -0
- package/jsconfig.json +11 -0
- package/package.json +10 -8
- package/src/Coordinator.js +14 -14
- package/src/MosaicClient.js +5 -4
- package/src/QueryConsolidator.js +22 -33
- package/src/QueryManager.js +76 -45
- package/src/Selection.js +8 -5
- package/src/SelectionClause.js +19 -22
- package/src/connectors/rest.js +3 -1
- package/src/connectors/socket.js +3 -1
- package/src/connectors/wasm.js +1 -1
- package/src/index.js +13 -0
- package/src/preagg/PreAggregator.js +407 -0
- package/src/preagg/preagg-columns.js +103 -0
- package/src/preagg/sufficient-statistics.js +439 -0
- package/src/util/field-info.js +16 -5
- package/src/util/hash.js +1 -1
- package/src/util/query-result.js +44 -2
- package/src/util/selection-types.ts +3 -3
- package/src/util/throttle.js +11 -9
- package/src/util/void-logger.js +6 -5
- package/tsconfig.json +11 -0
- package/src/DataCubeIndexer.js +0 -378
- package/src/util/index-columns.js +0 -537
package/src/QueryManager.js
CHANGED
|
@@ -1,56 +1,85 @@
|
|
|
1
1
|
import { consolidator } from './QueryConsolidator.js';
|
|
2
2
|
import { lruCache, voidCache } from './util/cache.js';
|
|
3
3
|
import { PriorityQueue } from './util/priority-queue.js';
|
|
4
|
-
import { QueryResult } from './util/query-result.js';
|
|
4
|
+
import { QueryResult, QueryState } from './util/query-result.js';
|
|
5
|
+
import { voidLogger } from './util/void-logger.js';
|
|
5
6
|
|
|
6
|
-
export const Priority = { High: 0, Normal: 1, Low: 2 };
|
|
7
|
+
export const Priority = Object.freeze({ High: 0, Normal: 1, Low: 2 });
|
|
7
8
|
|
|
8
9
|
export class QueryManager {
|
|
9
|
-
constructor(
|
|
10
|
+
constructor(
|
|
11
|
+
maxConcurrentRequests = 32
|
|
12
|
+
) {
|
|
10
13
|
this.queue = new PriorityQueue(3);
|
|
11
14
|
this.db = null;
|
|
12
15
|
this.clientCache = null;
|
|
13
|
-
this._logger =
|
|
16
|
+
this._logger = voidLogger();
|
|
14
17
|
this._logQueries = false;
|
|
15
|
-
this.recorders = [];
|
|
16
|
-
this.pending = null;
|
|
17
18
|
this._consolidate = null;
|
|
19
|
+
/**
|
|
20
|
+
* Requests pending with the query manager.
|
|
21
|
+
*
|
|
22
|
+
* @type {QueryResult[]}
|
|
23
|
+
*/
|
|
24
|
+
this.pendingResults = [];
|
|
25
|
+
this.maxConcurrentRequests = maxConcurrentRequests;
|
|
26
|
+
this.pendingExec = false;
|
|
18
27
|
}
|
|
19
28
|
|
|
20
29
|
next() {
|
|
21
|
-
if (this.
|
|
30
|
+
if (this.queue.isEmpty() || this.pendingResults.length > this.maxConcurrentRequests || this.pendingExec) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
22
34
|
const { request, result } = this.queue.next();
|
|
23
|
-
|
|
24
|
-
this.
|
|
35
|
+
|
|
36
|
+
this.pendingResults.push(result);
|
|
37
|
+
if (request.type === 'exec') this.pendingExec = true;
|
|
38
|
+
|
|
39
|
+
this.submit(request, result).finally(() => {
|
|
40
|
+
// return from the queue all requests that are ready
|
|
41
|
+
while (this.pendingResults.length && this.pendingResults[0].state !== QueryState.pending) {
|
|
42
|
+
const result = this.pendingResults.shift();
|
|
43
|
+
if (result.state === QueryState.ready) {
|
|
44
|
+
result.fulfill();
|
|
45
|
+
} else if (result.state === QueryState.done) {
|
|
46
|
+
this._logger.warn('Found resolved query in pending results.');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (request.type === 'exec') this.pendingExec = false;
|
|
50
|
+
this.next();
|
|
51
|
+
});
|
|
25
52
|
}
|
|
26
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Add an entry to the query queue with a priority.
|
|
56
|
+
* @param {object} entry The entry to add.
|
|
57
|
+
* @param {*} [entry.request] The query request.
|
|
58
|
+
* @param {QueryResult} [entry.result] The query result.
|
|
59
|
+
* @param {number} priority The query priority, defaults to `Priority.Normal`.
|
|
60
|
+
*/
|
|
27
61
|
enqueue(entry, priority = Priority.Normal) {
|
|
28
62
|
this.queue.insert(entry, priority);
|
|
29
63
|
this.next();
|
|
30
64
|
}
|
|
31
65
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Submit the query to the connector.
|
|
68
|
+
* @param {*} request The request.
|
|
69
|
+
* @param {QueryResult} result The query result.
|
|
70
|
+
*/
|
|
38
71
|
async submit(request, result) {
|
|
39
72
|
try {
|
|
40
|
-
const { query, type, cache = false,
|
|
73
|
+
const { query, type, cache = false, options } = request;
|
|
41
74
|
const sql = query ? `${query}` : null;
|
|
42
75
|
|
|
43
|
-
// update recorders
|
|
44
|
-
if (record) {
|
|
45
|
-
this.recordQuery(sql);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
76
|
// check query cache
|
|
49
77
|
if (cache) {
|
|
50
78
|
const cached = this.clientCache.get(sql);
|
|
51
79
|
if (cached) {
|
|
80
|
+
const data = await cached;
|
|
52
81
|
this._logger.debug('Cache');
|
|
53
|
-
result.
|
|
82
|
+
result.ready(data);
|
|
54
83
|
return;
|
|
55
84
|
}
|
|
56
85
|
}
|
|
@@ -60,10 +89,16 @@ export class QueryManager {
|
|
|
60
89
|
if (this._logQueries) {
|
|
61
90
|
this._logger.debug('Query', { type, sql, ...options });
|
|
62
91
|
}
|
|
63
|
-
|
|
92
|
+
|
|
93
|
+
const promise = this.db.query({ type, sql, ...options });
|
|
94
|
+
if (cache) this.clientCache.set(sql, promise);
|
|
95
|
+
|
|
96
|
+
const data = await promise;
|
|
97
|
+
|
|
64
98
|
if (cache) this.clientCache.set(sql, data);
|
|
99
|
+
|
|
65
100
|
this._logger.debug(`Request: ${(performance.now() - t0).toFixed(1)}`);
|
|
66
|
-
result.
|
|
101
|
+
result.ready(type === 'exec' ? null : data);
|
|
67
102
|
} catch (err) {
|
|
68
103
|
result.reject(err);
|
|
69
104
|
}
|
|
@@ -89,12 +124,18 @@ export class QueryManager {
|
|
|
89
124
|
|
|
90
125
|
consolidate(flag) {
|
|
91
126
|
if (flag && !this._consolidate) {
|
|
92
|
-
this._consolidate = consolidator(this.enqueue.bind(this), this.clientCache
|
|
127
|
+
this._consolidate = consolidator(this.enqueue.bind(this), this.clientCache);
|
|
93
128
|
} else if (!flag && this._consolidate) {
|
|
94
129
|
this._consolidate = null;
|
|
95
130
|
}
|
|
96
131
|
}
|
|
97
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Request a query result.
|
|
135
|
+
* @param {*} request The request.
|
|
136
|
+
* @param {number} priority The query priority, defaults to `Priority.Normal`.
|
|
137
|
+
* @returns {QueryResult} A query result promise.
|
|
138
|
+
*/
|
|
98
139
|
request(request, priority = Priority.Normal) {
|
|
99
140
|
const result = new QueryResult();
|
|
100
141
|
const entry = { request, result };
|
|
@@ -116,6 +157,12 @@ export class QueryManager {
|
|
|
116
157
|
}
|
|
117
158
|
return false;
|
|
118
159
|
});
|
|
160
|
+
|
|
161
|
+
for (const result of this.pendingResults) {
|
|
162
|
+
if (set.has(result)) {
|
|
163
|
+
result.reject('Canceled');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
119
166
|
}
|
|
120
167
|
}
|
|
121
168
|
|
|
@@ -124,26 +171,10 @@ export class QueryManager {
|
|
|
124
171
|
result.reject('Cleared');
|
|
125
172
|
return true;
|
|
126
173
|
});
|
|
127
|
-
}
|
|
128
174
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
state.push(query);
|
|
134
|
-
},
|
|
135
|
-
reset() {
|
|
136
|
-
state = [];
|
|
137
|
-
},
|
|
138
|
-
snapshot() {
|
|
139
|
-
return state.slice();
|
|
140
|
-
},
|
|
141
|
-
stop() {
|
|
142
|
-
this.recorders = this.recorders.filter(x => x !== recorder);
|
|
143
|
-
return state;
|
|
144
|
-
}
|
|
145
|
-
};
|
|
146
|
-
this.recorders.push(recorder);
|
|
147
|
-
return recorder;
|
|
175
|
+
for (const result of this.pendingResults) {
|
|
176
|
+
result.reject('Cleared');
|
|
177
|
+
}
|
|
178
|
+
this.pendingResults = [];
|
|
148
179
|
}
|
|
149
180
|
}
|
package/src/Selection.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { or } from '@uwdata/mosaic-sql';
|
|
1
|
+
import { literal, or } from '@uwdata/mosaic-sql';
|
|
2
2
|
import { Param } from './Param.js';
|
|
3
|
+
import { MosaicClient } from './MosaicClient.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Test if a value is a Selection instance.
|
|
@@ -315,9 +316,11 @@ export class SelectionResolver {
|
|
|
315
316
|
|
|
316
317
|
/**
|
|
317
318
|
* Return a selection query predicate for the given client.
|
|
318
|
-
* @param {
|
|
319
|
-
*
|
|
320
|
-
* @param {
|
|
319
|
+
* @param {import('./util/selection-types.js').SelectionClause[]} clauseList
|
|
320
|
+
* An array of selection clauses.
|
|
321
|
+
* @param {import('./util/selection-types.js').SelectionClause} active
|
|
322
|
+
* The current active selection clause.
|
|
323
|
+
* @param {MosaicClient} client The client whose data may be filtered.
|
|
321
324
|
* @returns {*} The query predicate for filtering client data,
|
|
322
325
|
* based on the current state of this selection.
|
|
323
326
|
*/
|
|
@@ -325,7 +328,7 @@ export class SelectionResolver {
|
|
|
325
328
|
const { empty, union } = this;
|
|
326
329
|
|
|
327
330
|
if (empty && !clauseList.length) {
|
|
328
|
-
return [
|
|
331
|
+
return [literal(false)];
|
|
329
332
|
}
|
|
330
333
|
|
|
331
334
|
// do nothing if cross-filtering and client is currently active
|
package/src/SelectionClause.js
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
SQLExpression, and, contains, isBetween, isNotDistinct, literal,
|
|
3
|
-
or, prefix, regexp_matches, suffix
|
|
4
|
-
} from '@uwdata/mosaic-sql';
|
|
1
|
+
import { ExprNode, and, contains, isBetween, isIn, isNotDistinct, literal, or, prefix, regexp_matches, suffix } from '@uwdata/mosaic-sql';
|
|
5
2
|
import { MosaicClient } from './MosaicClient.js';
|
|
6
3
|
|
|
7
4
|
/**
|
|
@@ -10,12 +7,11 @@ import { MosaicClient } from './MosaicClient.js';
|
|
|
10
7
|
* @typedef {import('./util/selection-types.js').Extent} Extent
|
|
11
8
|
* @typedef {import('./util/selection-types.js').MatchMethod} MatchMethod
|
|
12
9
|
* @typedef {import('./util/selection-types.js').BinMethod} BinMethod
|
|
13
|
-
* @typedef {SQLExpression | string} Field
|
|
14
10
|
*/
|
|
15
11
|
|
|
16
12
|
/**
|
|
17
13
|
* Generate a selection clause for a single selected point value.
|
|
18
|
-
* @param {
|
|
14
|
+
* @param {import('@uwdata/mosaic-sql').ExprValue} field The table column or expression to select.
|
|
19
15
|
* @param {*} value The selected value.
|
|
20
16
|
* @param {object} options Additional clause properties.
|
|
21
17
|
* @param {*} options.source The source component generating this clause.
|
|
@@ -28,7 +24,7 @@ export function clausePoint(field, value, {
|
|
|
28
24
|
source,
|
|
29
25
|
clients = source ? new Set([source]) : undefined
|
|
30
26
|
}) {
|
|
31
|
-
/** @type {
|
|
27
|
+
/** @type {ExprNode | null} */
|
|
32
28
|
const predicate = value !== undefined
|
|
33
29
|
? isNotDistinct(field, literal(value))
|
|
34
30
|
: null;
|
|
@@ -43,9 +39,9 @@ export function clausePoint(field, value, {
|
|
|
43
39
|
|
|
44
40
|
/**
|
|
45
41
|
* Generate a selection clause for multiple selected point values.
|
|
46
|
-
* @param {
|
|
47
|
-
* @param {any[][]} value The selected values, as an array of
|
|
48
|
-
*
|
|
42
|
+
* @param {import('@uwdata/mosaic-sql').ExprValue[]} fields The table columns or expressions to select.
|
|
43
|
+
* @param {any[][] | undefined} value The selected values, as an array of
|
|
44
|
+
* arrays. Each subarray contains values for each *fields* entry.
|
|
49
45
|
* @param {object} options Additional clause properties.
|
|
50
46
|
* @param {*} options.source The source component generating this clause.
|
|
51
47
|
* @param {Set<MosaicClient>} [options.clients] The Mosaic clients associated
|
|
@@ -57,14 +53,15 @@ export function clausePoints(fields, value, {
|
|
|
57
53
|
source,
|
|
58
54
|
clients = source ? new Set([source]) : undefined
|
|
59
55
|
}) {
|
|
60
|
-
/** @type {
|
|
56
|
+
/** @type {ExprNode | null} */
|
|
61
57
|
let predicate = null;
|
|
62
58
|
if (value) {
|
|
63
|
-
const clauses = value.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
59
|
+
const clauses = value.length && fields.length === 1
|
|
60
|
+
? [isIn(fields[0], value.map(v => literal(v[0])))]
|
|
61
|
+
: value.map(v => and(v.map((_, i) => isNotDistinct(fields[i], literal(_)))));
|
|
62
|
+
predicate = value.length === 0 ? literal(false)
|
|
63
|
+
: clauses.length > 1 ? or(clauses)
|
|
64
|
+
: clauses[0];
|
|
68
65
|
}
|
|
69
66
|
return {
|
|
70
67
|
meta: { type: 'point' },
|
|
@@ -77,7 +74,7 @@ export function clausePoints(fields, value, {
|
|
|
77
74
|
|
|
78
75
|
/**
|
|
79
76
|
* Generate a selection clause for a selected 1D interval.
|
|
80
|
-
* @param {
|
|
77
|
+
* @param {import('@uwdata/mosaic-sql').ExprValue} field The table column or expression to select.
|
|
81
78
|
* @param {Extent} value The selected interval as a [lo, hi] array.
|
|
82
79
|
* @param {object} options Additional clause properties.
|
|
83
80
|
* @param {*} options.source The source component generating this clause.
|
|
@@ -96,7 +93,7 @@ export function clauseInterval(field, value, {
|
|
|
96
93
|
scale,
|
|
97
94
|
pixelSize = 1
|
|
98
95
|
}) {
|
|
99
|
-
/** @type {
|
|
96
|
+
/** @type {ExprNode | null} */
|
|
100
97
|
const predicate = value != null ? isBetween(field, value) : null;
|
|
101
98
|
/** @type {import('./util/selection-types.js').IntervalMetadata} */
|
|
102
99
|
const meta = { type: 'interval', scales: scale && [scale], bin, pixelSize };
|
|
@@ -105,7 +102,7 @@ export function clauseInterval(field, value, {
|
|
|
105
102
|
|
|
106
103
|
/**
|
|
107
104
|
* Generate a selection clause for multiple selected intervals.
|
|
108
|
-
* @param {
|
|
105
|
+
* @param {import('@uwdata/mosaic-sql').ExprValue[]} fields The table columns or expressions to select.
|
|
109
106
|
* @param {Extent[]} value The selected intervals, as an array of extents.
|
|
110
107
|
* @param {object} options Additional clause properties.
|
|
111
108
|
* @param {*} options.source The source component generating this clause.
|
|
@@ -125,7 +122,7 @@ export function clauseIntervals(fields, value, {
|
|
|
125
122
|
scales = [],
|
|
126
123
|
pixelSize = 1
|
|
127
124
|
}) {
|
|
128
|
-
/** @type {
|
|
125
|
+
/** @type {ExprNode | null} */
|
|
129
126
|
const predicate = value != null
|
|
130
127
|
? and(fields.map((f, i) => isBetween(f, value[i])))
|
|
131
128
|
: null;
|
|
@@ -138,7 +135,7 @@ const MATCH_METHODS = { contains, prefix, suffix, regexp: regexp_matches };
|
|
|
138
135
|
|
|
139
136
|
/**
|
|
140
137
|
* Generate a selection clause for text search matching.
|
|
141
|
-
* @param {
|
|
138
|
+
* @param {import('@uwdata/mosaic-sql').ExprValue} field The table column or expression to select.
|
|
142
139
|
* @param {string} value The selected text search query string.
|
|
143
140
|
* @param {object} options Additional clause properties.
|
|
144
141
|
* @param {*} options.source The source component generating this clause.
|
|
@@ -153,7 +150,7 @@ export function clauseMatch(field, value, {
|
|
|
153
150
|
source, clients = undefined, method = 'contains'
|
|
154
151
|
}) {
|
|
155
152
|
let fn = MATCH_METHODS[method];
|
|
156
|
-
/** @type {
|
|
153
|
+
/** @type {ExprNode | null} */
|
|
157
154
|
const predicate = value ? fn(field, literal(value)) : null;
|
|
158
155
|
/** @type {import('./util/selection-types.js').MatchMetadata} */
|
|
159
156
|
const meta = { type: 'match', method };
|
package/src/connectors/rest.js
CHANGED
|
@@ -6,7 +6,9 @@ export function restConnector(uri = 'http://localhost:3000/') {
|
|
|
6
6
|
* Query the DuckDB server.
|
|
7
7
|
* @param {object} query
|
|
8
8
|
* @param {'exec' | 'arrow' | 'json' | 'create-bundle' | 'load-bundle'} [query.type] The query type.
|
|
9
|
-
* @param {string} query.sql A SQL query string.
|
|
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.
|
|
10
12
|
* @returns the query result
|
|
11
13
|
*/
|
|
12
14
|
async query(query) {
|
package/src/connectors/socket.js
CHANGED
|
@@ -86,7 +86,9 @@ export function socketConnector(uri = 'ws://localhost:3000/') {
|
|
|
86
86
|
* Query the DuckDB server.
|
|
87
87
|
* @param {object} query
|
|
88
88
|
* @param {'exec' | 'arrow' | 'json' | 'create-bundle' | 'load-bundle'} [query.type] The query type.
|
|
89
|
-
* @param {string} query.sql A SQL query string.
|
|
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.
|
|
90
92
|
* @returns the query result
|
|
91
93
|
*/
|
|
92
94
|
query(query) {
|
package/src/connectors/wasm.js
CHANGED
|
@@ -61,7 +61,7 @@ export function wasmConnector(options = {}) {
|
|
|
61
61
|
/**
|
|
62
62
|
* Query the DuckDB-WASM instance.
|
|
63
63
|
* @param {object} query
|
|
64
|
-
* @param {'exec' | 'arrow' | 'json'
|
|
64
|
+
* @param {'exec' | 'arrow' | 'json'} [query.type] The query type.
|
|
65
65
|
* @param {string} query.sql A SQL query string.
|
|
66
66
|
* @returns the query result
|
|
67
67
|
*/
|
package/src/index.js
CHANGED
|
@@ -22,3 +22,16 @@ export { isArrowTable } from './util/is-arrow-table.js';
|
|
|
22
22
|
export { synchronizer } from './util/synchronizer.js';
|
|
23
23
|
export { throttle } from './util/throttle.js';
|
|
24
24
|
export { toDataColumns } from './util/to-data-columns.js';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {import('./util/selection-types.js').ClauseMetadata} ClauseMetadata
|
|
28
|
+
* @typedef {import('./util/selection-types.js').PointMetadata} PointMetadata
|
|
29
|
+
* @typedef {import('./util/selection-types.js').MatchMethod} MatchMethod
|
|
30
|
+
* @typedef {import('./util/selection-types.js').MatchMetadata} MatchMetadata
|
|
31
|
+
* @typedef {import('./util/selection-types.js').ScaleType} ScaleType
|
|
32
|
+
* @typedef {import('./util/selection-types.js').Extent} Extent
|
|
33
|
+
* @typedef {import('./util/selection-types.js').Scale} Scale
|
|
34
|
+
* @typedef {import('./util/selection-types.js').BinMethod} BinMethod
|
|
35
|
+
* @typedef {import('./util/selection-types.js').IntervalMetadata} IntervalMetadata
|
|
36
|
+
* @typedef {import('./util/selection-types.js').SelectionClause} SelectionClause
|
|
37
|
+
*/
|