@uwdata/mosaic-core 0.0.1 → 0.2.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 +28 -0
- package/README.md +2 -2
- package/dist/mosaic-core.js +2956 -1368
- package/dist/mosaic-core.min.js +7 -7
- package/package.json +6 -5
- package/src/Catalog.js +35 -37
- package/src/Coordinator.js +70 -66
- package/src/DataTileIndexer.js +54 -53
- package/src/FilterGroup.js +6 -9
- package/src/MosaicClient.js +64 -0
- package/src/Param.js +92 -0
- package/src/QueryManager.js +109 -0
- package/src/Selection.js +231 -35
- package/src/{clients → connectors}/rest.js +1 -1
- package/src/{clients → connectors}/socket.js +1 -1
- package/src/{clients → connectors}/wasm.js +1 -1
- package/src/index.js +9 -5
- package/src/util/AsyncDispatch.js +180 -0
- package/src/util/cache.js +58 -0
- package/src/util/distinct.js +14 -0
- package/src/util/priority-queue.js +85 -0
- package/src/util/summarize.js +23 -0
- package/src/util/synchronizer.js +47 -0
- package/src/util/throttle.js +22 -4
- package/src/util/void-logger.js +9 -0
- package/src/QueryCache.js +0 -65
- package/src/Signal.js +0 -40
- package/src/util/skip-client.js +0 -3
- package/src/util/sql-from.js +0 -22
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event dispatcher supporting asynchronous updates.
|
|
3
|
+
* If an event handler callback returns a Promise, this dispatcher will
|
|
4
|
+
* wait for all such Promises to settle before dispatching future events
|
|
5
|
+
* of the same type.
|
|
6
|
+
*/
|
|
7
|
+
export class AsyncDispatch {
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Create a new asynchronous dispatcher instance.
|
|
11
|
+
*/
|
|
12
|
+
constructor() {
|
|
13
|
+
this._callbacks = new Map;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Add an event listener callback for the provided event type.
|
|
18
|
+
* @param {string} type The event type.
|
|
19
|
+
* @param {(value: *) => Promise?} callback The event handler
|
|
20
|
+
* callback function to add. If the callback has already been
|
|
21
|
+
* added for the event type, this method has no effect.
|
|
22
|
+
*/
|
|
23
|
+
addEventListener(type, callback) {
|
|
24
|
+
if (!this._callbacks.has(type)) {
|
|
25
|
+
this._callbacks.set(type, {
|
|
26
|
+
callbacks: new Set,
|
|
27
|
+
pending: null,
|
|
28
|
+
queue: new DispatchQueue()
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
const entry = this._callbacks.get(type);
|
|
32
|
+
entry.callbacks.add(callback);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Remove an event listener callback for the provided event type.
|
|
37
|
+
* @param {string} type The event type.
|
|
38
|
+
* @param {(value: *) => Promise?} callback The event handler
|
|
39
|
+
* callback function to remove.
|
|
40
|
+
*/
|
|
41
|
+
removeEventListener(type, callback) {
|
|
42
|
+
const entry = this._callbacks.get(type);
|
|
43
|
+
if (entry) {
|
|
44
|
+
entry.callbacks.delete(callback);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Lifecycle method that returns the event value to emit.
|
|
50
|
+
* This default implementation simply returns the input value as-is.
|
|
51
|
+
* Subclasses may override this method to implement custom transformations
|
|
52
|
+
* prior to emitting an event value to all listeners.
|
|
53
|
+
* @param {string} type The event type.
|
|
54
|
+
* @param {*} value The event value.
|
|
55
|
+
* @returns The (possibly transformed) event value to emit.
|
|
56
|
+
*/
|
|
57
|
+
willEmit(type, value) {
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Lifecycle method that returns a filter function for updating the
|
|
63
|
+
* queue of unemitted event values prior to enqueueing a new value.
|
|
64
|
+
* This default implementation simply returns null, indicating that
|
|
65
|
+
* any other unemitted event values should be dropped (that is, all
|
|
66
|
+
* queued events are filtered)
|
|
67
|
+
* @param {*} value The new event value that will be enqueued.
|
|
68
|
+
* @returns {(value: *) => boolean|null} A dispatch queue filter
|
|
69
|
+
* function, or null if all unemitted event values should be filtered.
|
|
70
|
+
*/
|
|
71
|
+
emitQueueFilter() {
|
|
72
|
+
// removes all pending items
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Cancel all unemitted event values for the given event type.
|
|
78
|
+
* @param {string} type The event type.
|
|
79
|
+
*/
|
|
80
|
+
cancel(type) {
|
|
81
|
+
const entry = this._callbacks.get(type);
|
|
82
|
+
entry?.queue.clear();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Emit an event value to listeners for the given event type.
|
|
87
|
+
* If a previous emit has not yet resolved, the event value
|
|
88
|
+
* will be queued to be emitted later.
|
|
89
|
+
* The actual event value given to listeners will be the result
|
|
90
|
+
* of passing the input value through the emitValue() method.
|
|
91
|
+
* @param {string} type The event type.
|
|
92
|
+
* @param {*} value The event value.
|
|
93
|
+
*/
|
|
94
|
+
emit(type, value) {
|
|
95
|
+
const entry = this._callbacks.get(type) || {};
|
|
96
|
+
if (entry.pending) {
|
|
97
|
+
entry.queue.enqueue(value, this.emitQueueFilter(type, value));
|
|
98
|
+
} else {
|
|
99
|
+
const event = this.willEmit(type, value);
|
|
100
|
+
const { callbacks, queue } = entry;
|
|
101
|
+
if (callbacks?.size) {
|
|
102
|
+
const promise = Promise
|
|
103
|
+
.allSettled(Array.from(callbacks, callback => callback(event)))
|
|
104
|
+
.then(() => {
|
|
105
|
+
entry.pending = null;
|
|
106
|
+
if (!queue.isEmpty()) {
|
|
107
|
+
this.emit(type, queue.dequeue());
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
entry.pending = promise;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Queue for managing unemitted event values.
|
|
118
|
+
*/
|
|
119
|
+
export class DispatchQueue {
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Create a new dispatch queue instance.
|
|
123
|
+
*/
|
|
124
|
+
constructor() {
|
|
125
|
+
this.clear();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Clear the queue state of all event values.
|
|
130
|
+
*/
|
|
131
|
+
clear() {
|
|
132
|
+
this.next = null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Indicate if the queue is empty.
|
|
137
|
+
* @returns {boolean} True if queue is empty, false otherwise.
|
|
138
|
+
*/
|
|
139
|
+
isEmpty() {
|
|
140
|
+
return !this.next;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Add a new value to the queue, and optionally filter the
|
|
145
|
+
* current queue content in response.
|
|
146
|
+
* @param {*} value The value to add.
|
|
147
|
+
* @param {(value: *) => boolean} [filter] An optional filter
|
|
148
|
+
* function to apply to existing queue content. If unspecified
|
|
149
|
+
* or falsy, all previously queued values are removed. Otherwise,
|
|
150
|
+
* the provided function is applied to all queue entries. The
|
|
151
|
+
* entry is retained if the filter function returns a truthy value,
|
|
152
|
+
* otherwise the entry is removed.
|
|
153
|
+
*/
|
|
154
|
+
enqueue(value, filter) {
|
|
155
|
+
const tail = { value };
|
|
156
|
+
if (filter && this.next) {
|
|
157
|
+
let curr = this;
|
|
158
|
+
while (curr.next) {
|
|
159
|
+
if (filter(curr.next.value)) {
|
|
160
|
+
curr = curr.next;
|
|
161
|
+
} else {
|
|
162
|
+
curr.next = curr.next.next;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
curr.next = tail;
|
|
166
|
+
} else {
|
|
167
|
+
this.next = tail;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Remove and return the next queued event value.
|
|
173
|
+
* @returns {*} The next event value in the queue.
|
|
174
|
+
*/
|
|
175
|
+
dequeue() {
|
|
176
|
+
const { next } = this;
|
|
177
|
+
this.next = next?.next;
|
|
178
|
+
return next?.value;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const requestIdle = typeof requestIdleCallback !== 'undefined'
|
|
2
|
+
? requestIdleCallback
|
|
3
|
+
: setTimeout;
|
|
4
|
+
|
|
5
|
+
export const voidCache = () => ({
|
|
6
|
+
get: () => undefined,
|
|
7
|
+
set: (key, value) => value,
|
|
8
|
+
clear: () => {}
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export function lruCache({
|
|
12
|
+
max = 1000, // max entries
|
|
13
|
+
ttl = 3 * 60 * 60 * 1000 // time-to-live, default 3 hours
|
|
14
|
+
} = {}) {
|
|
15
|
+
let cache = new Map;
|
|
16
|
+
|
|
17
|
+
function evict() {
|
|
18
|
+
const expire = performance.now() - ttl;
|
|
19
|
+
let lruKey = null;
|
|
20
|
+
let lruLast = Infinity;
|
|
21
|
+
|
|
22
|
+
for (const [key, value] of cache) {
|
|
23
|
+
const { last } = value;
|
|
24
|
+
|
|
25
|
+
// least recently used entry seen so far
|
|
26
|
+
if (last < lruLast) {
|
|
27
|
+
lruKey = key;
|
|
28
|
+
lruLast = last;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// remove if time since last access exceeds ttl
|
|
32
|
+
if (expire > last) {
|
|
33
|
+
cache.delete(key);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// remove lru entry
|
|
38
|
+
if (lruKey) {
|
|
39
|
+
cache.delete(lruKey);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
get(key) {
|
|
45
|
+
const entry = cache.get(key);
|
|
46
|
+
if (entry) {
|
|
47
|
+
entry.last = performance.now();
|
|
48
|
+
return entry.value;
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
set(key, value) {
|
|
52
|
+
cache.set(key, { last: performance.now(), value });
|
|
53
|
+
if (cache.size > max) requestIdle(evict);
|
|
54
|
+
return value;
|
|
55
|
+
},
|
|
56
|
+
clear() { cache = new Map; }
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function distinct(a, b) {
|
|
2
|
+
return a === b ? false
|
|
3
|
+
: a instanceof Date && b instanceof Date ? +a !== +b
|
|
4
|
+
: Array.isArray(a) && Array.isArray(b) ? distinctArray(a, b)
|
|
5
|
+
: true;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function distinctArray(a, b) {
|
|
9
|
+
if (a.length !== b.length) return true;
|
|
10
|
+
for (let i = 0; i < a.length; ++i) {
|
|
11
|
+
if (a[i] !== b[i]) return true;
|
|
12
|
+
}
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a new priority queue instance.
|
|
3
|
+
* @param {number} ranks An integer number of rank-order priority levels.
|
|
4
|
+
* @returns A priority queue instance.
|
|
5
|
+
*/
|
|
6
|
+
export function priorityQueue(ranks) {
|
|
7
|
+
// one list for each integer priority level
|
|
8
|
+
const queue = Array.from(
|
|
9
|
+
{ length: ranks },
|
|
10
|
+
() => ({ head: null, tail: null })
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
/**
|
|
15
|
+
* Indicate if the queue is empty.
|
|
16
|
+
* @returns [boolean] true if empty, false otherwise.
|
|
17
|
+
*/
|
|
18
|
+
isEmpty() {
|
|
19
|
+
return queue.every(list => !list.head);
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Insert an item into the queue with a given priority rank.
|
|
24
|
+
* @param {*} item The item to add.
|
|
25
|
+
* @param {number} rank The integer priority rank.
|
|
26
|
+
* Priority ranks are integers starting at zero.
|
|
27
|
+
* Lower ranks indicate higher priority.
|
|
28
|
+
*/
|
|
29
|
+
insert(item, rank) {
|
|
30
|
+
const list = queue[rank];
|
|
31
|
+
if (!list) {
|
|
32
|
+
throw new Error(`Invalid queue priority rank: ${rank}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const node = { item, next: null };
|
|
36
|
+
if (list.head === null) {
|
|
37
|
+
list.head = list.tail = node;
|
|
38
|
+
} else {
|
|
39
|
+
list.tail = (list.tail.next = node);
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Remove a set of items from the queue, regardless of priority rank.
|
|
45
|
+
* If a provided item is not in the queue it will be ignored.
|
|
46
|
+
* @param {(item: *) => boolean} test A predicate function to test
|
|
47
|
+
* if an item should be removed (true to drop, false to keep).
|
|
48
|
+
*/
|
|
49
|
+
remove(test) {
|
|
50
|
+
for (const list of queue) {
|
|
51
|
+
let { head, tail } = list;
|
|
52
|
+
for (let prev = null, curr = head; curr; prev = curr, curr = curr.next) {
|
|
53
|
+
if (test(curr.item)) {
|
|
54
|
+
if (curr === head) {
|
|
55
|
+
head = curr.next;
|
|
56
|
+
} else {
|
|
57
|
+
prev.next = curr.next;
|
|
58
|
+
}
|
|
59
|
+
if (curr === tail) tail = prev || head;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
list.head = head;
|
|
63
|
+
list.tail = tail;
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Remove and return the next highest priority item.
|
|
69
|
+
* @returns {*} The next item in the queue,
|
|
70
|
+
* or undefined if this queue is empty.
|
|
71
|
+
*/
|
|
72
|
+
next() {
|
|
73
|
+
for (const list of queue) {
|
|
74
|
+
const { head } = list;
|
|
75
|
+
if (head !== null) {
|
|
76
|
+
list.head = head.next;
|
|
77
|
+
if (list.tail === head) {
|
|
78
|
+
list.tail = null;
|
|
79
|
+
}
|
|
80
|
+
return head.item;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Query, count, isNull, max, min } from '@uwdata/mosaic-sql';
|
|
2
|
+
|
|
3
|
+
export const Count = 'count';
|
|
4
|
+
export const Nulls = 'nulls';
|
|
5
|
+
export const Max = 'max';
|
|
6
|
+
export const Min = 'min';
|
|
7
|
+
export const Distinct = 'distinct';
|
|
8
|
+
|
|
9
|
+
export const Stats = { Count, Nulls, Max, Min, Distinct };
|
|
10
|
+
|
|
11
|
+
export const statMap = {
|
|
12
|
+
[Count]: count,
|
|
13
|
+
[Distinct]: column => count(column).distinct(),
|
|
14
|
+
[Max]: max,
|
|
15
|
+
[Min]: min,
|
|
16
|
+
[Nulls]: column => count().where(isNull(column))
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function summarize({ table, column }, stats) {
|
|
20
|
+
return Query
|
|
21
|
+
.from(table)
|
|
22
|
+
.select(stats.map(s => [s, statMap[s](column)]));
|
|
23
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a new synchronizer instance to aid synchronization
|
|
3
|
+
* of updates on multiple pending operations.
|
|
4
|
+
*/
|
|
5
|
+
export function synchronizer() {
|
|
6
|
+
const set = new Set;
|
|
7
|
+
let done;
|
|
8
|
+
let promise = new Promise(resolve => done = resolve);
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
/**
|
|
12
|
+
* Mark an item as pending.
|
|
13
|
+
* @param {*} item An item to synchronize on.
|
|
14
|
+
*/
|
|
15
|
+
pending(item) {
|
|
16
|
+
set.add(item);
|
|
17
|
+
},
|
|
18
|
+
/**
|
|
19
|
+
* Mark a pending item as ready, indicating it is
|
|
20
|
+
* ready for a synchronized update.
|
|
21
|
+
* @param {*} item An item to synchronize on.
|
|
22
|
+
* @returns {boolean} True if the synchronizer is ready to
|
|
23
|
+
* resolve, false otherwise.
|
|
24
|
+
*/
|
|
25
|
+
ready(item) {
|
|
26
|
+
set.delete(item);
|
|
27
|
+
return set.size === 0;
|
|
28
|
+
},
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the current synchronization cycle, causing the synchronize
|
|
31
|
+
* promise to resolve and thereby trigger downstream updates.
|
|
32
|
+
*/
|
|
33
|
+
resolve() {
|
|
34
|
+
promise = new Promise(resolve => {
|
|
35
|
+
done();
|
|
36
|
+
done = resolve;
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
/**
|
|
40
|
+
* The promise for the current synchronization cycle.
|
|
41
|
+
* @return {Promise} The synchronization promise.
|
|
42
|
+
*/
|
|
43
|
+
get promise() {
|
|
44
|
+
return promise;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/util/throttle.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
const NIL = {};
|
|
2
|
+
|
|
3
|
+
export function throttle(callback, debounce = false) {
|
|
2
4
|
let curr;
|
|
3
5
|
let next;
|
|
6
|
+
let pending = NIL;
|
|
4
7
|
|
|
5
8
|
function invoke(event) {
|
|
6
9
|
curr = callback(event).then(() => {
|
|
7
10
|
if (next) {
|
|
8
|
-
const value = next;
|
|
11
|
+
const { value } = next;
|
|
9
12
|
next = null;
|
|
10
13
|
invoke(value);
|
|
11
14
|
} else {
|
|
@@ -15,8 +18,23 @@ export function throttle(callback) {
|
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
function enqueue(event) {
|
|
18
|
-
next = event;
|
|
21
|
+
next = { event };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function process(event) {
|
|
25
|
+
curr ? enqueue(event) : invoke(event);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function delay(event) {
|
|
29
|
+
if (pending !== event) {
|
|
30
|
+
requestAnimationFrame(() => {
|
|
31
|
+
const e = pending;
|
|
32
|
+
pending = NIL;
|
|
33
|
+
process(e);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
pending = event;
|
|
19
37
|
}
|
|
20
38
|
|
|
21
|
-
return
|
|
39
|
+
return debounce ? delay : process;
|
|
22
40
|
}
|
package/src/QueryCache.js
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
export class QueryCache {
|
|
2
|
-
constructor({
|
|
3
|
-
max = 1000, // max entries
|
|
4
|
-
ttl = 3 * 60 * 60 * 1000 // time-to-live, default 3 hours
|
|
5
|
-
} = {}) {
|
|
6
|
-
this.max = max;
|
|
7
|
-
this.ttl = ttl;
|
|
8
|
-
this.clear();
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
clear() {
|
|
12
|
-
this.cache = new Map();
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
get(key) {
|
|
16
|
-
const entry = this.cache.get(key);
|
|
17
|
-
if (entry) {
|
|
18
|
-
entry.last = performance.now();
|
|
19
|
-
return entry.promise;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
set(key, promise) {
|
|
24
|
-
const { cache, max } = this;
|
|
25
|
-
const now = performance.now();
|
|
26
|
-
|
|
27
|
-
const receive = promise.then(result => {
|
|
28
|
-
console.log(`Query: ${Math.round(performance.now() - now)}`);
|
|
29
|
-
return result;
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
cache.set(key, { last: now, promise });
|
|
33
|
-
if (cache.size > max) {
|
|
34
|
-
setTimeout(() => this.evict());
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return receive;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
evict() {
|
|
41
|
-
const expire = performance.now() - this.ttl;
|
|
42
|
-
let lruKey = null;
|
|
43
|
-
let lruLast = Infinity;
|
|
44
|
-
|
|
45
|
-
for (const [key, value] of this.cache) {
|
|
46
|
-
const { last } = value;
|
|
47
|
-
|
|
48
|
-
// least recently used entry seen so far
|
|
49
|
-
if (last < lruLast) {
|
|
50
|
-
lruKey = key;
|
|
51
|
-
lruLast = last;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// remove if time since last access exceeds ttl
|
|
55
|
-
if (expire > last) {
|
|
56
|
-
this.cache.delete(key);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// remove lru entry
|
|
61
|
-
if (lruKey) {
|
|
62
|
-
this.cache.delete(lruKey);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
package/src/Signal.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
export function isSignal(x) {
|
|
2
|
-
return x instanceof Signal;
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
export class Signal {
|
|
6
|
-
constructor(value) {
|
|
7
|
-
this._value = value;
|
|
8
|
-
this._listeners = new Map;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
get value() {
|
|
12
|
-
return this._value;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
update(value, { force } = {}) {
|
|
16
|
-
const changed = this._value !== value;
|
|
17
|
-
if (changed) this._value = value;
|
|
18
|
-
if (changed || force) this.emit('value', this.value);
|
|
19
|
-
return this;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
addEventListener(type, callback) {
|
|
23
|
-
let list = this._listeners.get(type) || [];
|
|
24
|
-
if (list.indexOf(callback) < 0) {
|
|
25
|
-
list = list.concat(callback);
|
|
26
|
-
}
|
|
27
|
-
this._listeners.set(type, list);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
removeEventListener(type, callback) {
|
|
31
|
-
const list = this._listeners.get(type);
|
|
32
|
-
if (list?.length) {
|
|
33
|
-
this._listeners.set(type, list.filter(x => x !== callback));
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
emit(type, event) {
|
|
38
|
-
this._listeners.get(type)?.forEach(l => l(event));
|
|
39
|
-
}
|
|
40
|
-
}
|
package/src/util/skip-client.js
DELETED
package/src/util/sql-from.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { literalToSQL } from '@uwdata/mosaic-sql';
|
|
2
|
-
|
|
3
|
-
export function sqlFrom(data, {
|
|
4
|
-
columns = Object.keys(data?.[0] || {})
|
|
5
|
-
} = {}) {
|
|
6
|
-
let keys = [];
|
|
7
|
-
if (Array.isArray(columns)) {
|
|
8
|
-
keys = columns;
|
|
9
|
-
columns = keys.reduce((m, k) => (m[k] = k, m), {});
|
|
10
|
-
} else if (columns) {
|
|
11
|
-
keys = Object.keys(columns);
|
|
12
|
-
}
|
|
13
|
-
if (!keys.length) {
|
|
14
|
-
throw new Error('Can not create table from empty column set.');
|
|
15
|
-
}
|
|
16
|
-
const subq = [];
|
|
17
|
-
for (const datum of data) {
|
|
18
|
-
const sel = keys.map(k => `${literalToSQL(datum[k])} AS "${columns[k]}"`);
|
|
19
|
-
subq.push(`(SELECT ${sel.join(', ')})`);
|
|
20
|
-
}
|
|
21
|
-
return subq.join(' UNION ALL ');
|
|
22
|
-
}
|