@minnowdb/core 0.7.8 → 0.8.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/dist/engine/client.d.ts +5 -2
- package/dist/engine/client.js +14 -4
- package/dist/engine/database.js +632 -39
- package/dist/engine/errors.d.ts +2 -2
- package/dist/engine/errors.js +1 -1
- package/dist/engine/keyed-live.js +55 -18
- package/dist/engine/live-aggregate.d.ts +12 -0
- package/dist/engine/live-aggregate.js +226 -0
- package/dist/engine/live-patch.d.ts +21 -0
- package/dist/engine/live-patch.js +31 -0
- package/dist/engine/live.d.ts +107 -8
- package/dist/engine/live.js +353 -187
- package/dist/engine/optimizer.js +11 -6
- package/dist/engine/query-cache.js +2 -13
- package/dist/engine/query-generations.d.ts +8 -0
- package/dist/engine/query-generations.js +61 -0
- package/dist/engine/query-identity.d.ts +3 -0
- package/dist/engine/query-identity.js +41 -0
- package/dist/engine/query.d.ts +6 -11
- package/dist/engine/query.js +294 -428
- package/dist/engine/sql-domains.d.ts +4 -0
- package/dist/engine/sql-domains.js +121 -0
- package/dist/engine/sql-semantics.js +7 -4
- package/dist/engine/typed-live.d.ts +12 -1
- package/dist/engine/typed-live.js +110 -25
- package/dist/engine/windows.d.ts +12 -0
- package/dist/engine/windows.js +386 -0
- package/dist/engine/worker-server.js +12 -5
- package/dist/plan/model.d.ts +1 -1
- package/dist/worker-protocol/index.d.ts +1 -1
- package/dist/worker-protocol/index.js +1 -1
- package/package.json +1 -1
- package/postgres-feature-profile.json +6 -1
- package/sql-feature-matrix.json +20 -27
package/dist/engine/live.js
CHANGED
|
@@ -1,55 +1,14 @@
|
|
|
1
|
+
import { createLiveQueryPatch } from "./live-patch.js";
|
|
2
|
+
import { queryResultRetainedBytes } from "./query-cache.js";
|
|
3
|
+
import { encodeQueryIdentity } from "./query-identity.js";
|
|
1
4
|
import { LiveQueryLimitError } from "./errors.js";
|
|
2
5
|
const DEFAULT_LIVE_QUERY_MAX_GROUPS = 256;
|
|
3
6
|
const DEFAULT_LIVE_QUERY_MAX_SUBSCRIPTIONS = 1024;
|
|
4
7
|
const MAX_LIVE_QUERY_GROUPS = 4096;
|
|
5
8
|
const MAX_LIVE_QUERY_SUBSCRIPTIONS = 16384;
|
|
6
9
|
const MAX_LIVE_QUERY_SETS_PER_DATABASE = 256;
|
|
10
|
+
const LIVE_QUERY_EXECUTION_CONCURRENCY = 8;
|
|
7
11
|
import { LiveQueryLimitError as LiveQueryLimitError2 } from "./errors.js";
|
|
8
|
-
const digestScratch = new DataView(new ArrayBuffer(8));
|
|
9
|
-
function digestResult(result) {
|
|
10
|
-
let hash = 2166136261;
|
|
11
|
-
const mixByte = (byte) => {
|
|
12
|
-
hash = Math.imul(hash ^ byte, 16777619) >>> 0;
|
|
13
|
-
};
|
|
14
|
-
const mixNumber = (value) => {
|
|
15
|
-
digestScratch.setFloat64(0, value);
|
|
16
|
-
for (let index = 0; index < 8; index += 1)
|
|
17
|
-
mixByte(digestScratch.getUint8(index));
|
|
18
|
-
};
|
|
19
|
-
const mixString = (value) => {
|
|
20
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
21
|
-
const code = value.charCodeAt(index);
|
|
22
|
-
mixByte(code & 255);
|
|
23
|
-
mixByte(code >>> 8);
|
|
24
|
-
}
|
|
25
|
-
mixByte(255);
|
|
26
|
-
};
|
|
27
|
-
for (const column of result.columns)
|
|
28
|
-
mixString(column);
|
|
29
|
-
for (const domain of result.columnDomains)
|
|
30
|
-
mixString(JSON.stringify(domain));
|
|
31
|
-
for (const row of result.rows) {
|
|
32
|
-
for (const column of result.columns) {
|
|
33
|
-
const value = row[column] ?? null;
|
|
34
|
-
if (value === null)
|
|
35
|
-
mixByte(1);
|
|
36
|
-
else if (typeof value === "number") {
|
|
37
|
-
mixByte(2);
|
|
38
|
-
mixNumber(value);
|
|
39
|
-
} else if (typeof value === "string") {
|
|
40
|
-
mixByte(3);
|
|
41
|
-
mixString(value);
|
|
42
|
-
} else if (typeof value === "boolean")
|
|
43
|
-
mixByte(value ? 4 : 5);
|
|
44
|
-
else {
|
|
45
|
-
mixByte(6);
|
|
46
|
-
mixNumber(dateMilliseconds(value));
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
mixByte(254);
|
|
50
|
-
}
|
|
51
|
-
return hash;
|
|
52
|
-
}
|
|
53
12
|
function sameQueryValue(left, right) {
|
|
54
13
|
if (left instanceof Date || right instanceof Date) {
|
|
55
14
|
return left instanceof Date && right instanceof Date && Object.is(dateMilliseconds(left), dateMilliseconds(right));
|
|
@@ -67,12 +26,15 @@ function sameResult(left, right) {
|
|
|
67
26
|
return false;
|
|
68
27
|
}
|
|
69
28
|
}
|
|
29
|
+
const columns = left.columns;
|
|
70
30
|
for (let rowIndex = 0; rowIndex < left.rows.length; rowIndex += 1) {
|
|
71
31
|
const leftRow = left.rows[rowIndex];
|
|
72
32
|
const rightRow = right.rows[rowIndex];
|
|
73
33
|
if (leftRow === void 0 || rightRow === void 0)
|
|
74
34
|
return false;
|
|
75
|
-
|
|
35
|
+
if (leftRow === rightRow)
|
|
36
|
+
continue;
|
|
37
|
+
for (const column of columns) {
|
|
76
38
|
if (!sameQueryValue(leftRow[column] ?? null, rightRow[column] ?? null))
|
|
77
39
|
return false;
|
|
78
40
|
}
|
|
@@ -104,41 +66,6 @@ function cloneResult(result) {
|
|
|
104
66
|
rows: result.rows.map((row) => cloneRow(row, columns))
|
|
105
67
|
};
|
|
106
68
|
}
|
|
107
|
-
function encodeQueryIdentity(value, ancestors = /* @__PURE__ */ new Set()) {
|
|
108
|
-
if (value === null)
|
|
109
|
-
return "z";
|
|
110
|
-
if (typeof value === "undefined")
|
|
111
|
-
return "u";
|
|
112
|
-
if (typeof value === "boolean")
|
|
113
|
-
return value ? "b1" : "b0";
|
|
114
|
-
if (typeof value === "number") {
|
|
115
|
-
if (Number.isNaN(value))
|
|
116
|
-
return "nNaN;";
|
|
117
|
-
if (Object.is(value, -0))
|
|
118
|
-
return "n-0;";
|
|
119
|
-
return `n${String(value)};`;
|
|
120
|
-
}
|
|
121
|
-
if (typeof value === "string")
|
|
122
|
-
return `s${String(value.length)}:${value}`;
|
|
123
|
-
if (value instanceof Date)
|
|
124
|
-
return `d${String(dateMilliseconds(value))};`;
|
|
125
|
-
if (typeof value !== "object") {
|
|
126
|
-
throw new TypeError(`Unsupported live-query identity value: ${typeof value}`);
|
|
127
|
-
}
|
|
128
|
-
if (ancestors.has(value))
|
|
129
|
-
throw new TypeError("Live-query identity contains a cycle");
|
|
130
|
-
ancestors.add(value);
|
|
131
|
-
let encoded;
|
|
132
|
-
if (Array.isArray(value)) {
|
|
133
|
-
encoded = `a${String(value.length)}[${value.map((item) => encodeQueryIdentity(item, ancestors)).join("")}]`;
|
|
134
|
-
} else {
|
|
135
|
-
const record = value;
|
|
136
|
-
const keys = Object.keys(record).sort();
|
|
137
|
-
encoded = `o${String(keys.length)}{${keys.map((key) => `${encodeQueryIdentity(key, ancestors)}${encodeQueryIdentity(record[key], ancestors)}`).join("")}}`;
|
|
138
|
-
}
|
|
139
|
-
ancestors.delete(value);
|
|
140
|
-
return encoded;
|
|
141
|
-
}
|
|
142
69
|
function queryKey(query) {
|
|
143
70
|
return encodeQueryIdentity(typeof query === "string" ? ["sql", query] : query.kind === "sql-query" ? ["sql-query", query.sql, query.params] : ["typed-query", query.plan]);
|
|
144
71
|
}
|
|
@@ -148,6 +75,9 @@ function sameProbe(left, right) {
|
|
|
148
75
|
function versionOrdinal(version) {
|
|
149
76
|
return version ?? -1;
|
|
150
77
|
}
|
|
78
|
+
function newerProbe(left, right) {
|
|
79
|
+
return versionOrdinal(right.manifestVersion) > versionOrdinal(left.manifestVersion) ? right : left;
|
|
80
|
+
}
|
|
151
81
|
function boundedLiveLimit(value, maximum, label) {
|
|
152
82
|
if (!Number.isSafeInteger(value) || value < 1 || value > maximum) {
|
|
153
83
|
throw new RangeError(`Live query ${label} limit must be between 1 and ${String(maximum)}`);
|
|
@@ -157,6 +87,34 @@ function boundedLiveLimit(value, maximum, label) {
|
|
|
157
87
|
function catalogChangedBetween(previous, current) {
|
|
158
88
|
return versionOrdinal(current.manifestVersion) < versionOrdinal(previous.manifestVersion) || current.schemaEpoch !== previous.schemaEpoch;
|
|
159
89
|
}
|
|
90
|
+
function isResultSubscriber(subscriber) {
|
|
91
|
+
return subscriber.kind === "result" && !subscriber.closed;
|
|
92
|
+
}
|
|
93
|
+
function isObserver(subscriber) {
|
|
94
|
+
return subscriber.kind === "observer" && !subscriber.closed;
|
|
95
|
+
}
|
|
96
|
+
function groupExecutes(group) {
|
|
97
|
+
for (const subscriber of group.subscribers) {
|
|
98
|
+
if (subscriber.closed)
|
|
99
|
+
continue;
|
|
100
|
+
if (subscriber.kind === "result" || subscriber.options.suppressUnchanged === true)
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
function groupMemoizes(group) {
|
|
106
|
+
for (const subscriber of group.subscribers) {
|
|
107
|
+
if (!subscriber.closed && subscriber.kind === "observer")
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
function callLiveCallback(callback) {
|
|
113
|
+
try {
|
|
114
|
+
callback?.();
|
|
115
|
+
} catch {
|
|
116
|
+
}
|
|
117
|
+
}
|
|
160
118
|
class LiveQuerySet {
|
|
161
119
|
#host;
|
|
162
120
|
#channel;
|
|
@@ -166,8 +124,12 @@ class LiveQuerySet {
|
|
|
166
124
|
#groups = /* @__PURE__ */ new Map();
|
|
167
125
|
#opening = /* @__PURE__ */ new Map();
|
|
168
126
|
#groupsByTable = /* @__PURE__ */ new Map();
|
|
127
|
+
#lagging = /* @__PURE__ */ new Set();
|
|
169
128
|
#maxGroups;
|
|
170
129
|
#maxSubscriptions;
|
|
130
|
+
#maxRetainedBytes;
|
|
131
|
+
#sharedResults;
|
|
132
|
+
#incremental;
|
|
171
133
|
#stats = {
|
|
172
134
|
hints: 0,
|
|
173
135
|
versionChecks: 0,
|
|
@@ -177,10 +139,17 @@ class LiveQuerySet {
|
|
|
177
139
|
zoneSkips: 0,
|
|
178
140
|
notificationsSuppressed: 0,
|
|
179
141
|
invalidations: 0,
|
|
142
|
+
maintained: 0,
|
|
143
|
+
groupsVisited: 0,
|
|
144
|
+
retainedRows: 0,
|
|
145
|
+
retainedBytes: 0,
|
|
180
146
|
sharedExecutions: 0,
|
|
181
147
|
lastSweepMs: 0
|
|
182
148
|
};
|
|
183
149
|
#lastProbe;
|
|
150
|
+
#pendingProbe;
|
|
151
|
+
#executing = 0;
|
|
152
|
+
#executionWaiters = [];
|
|
184
153
|
#sweepChain = Promise.resolve();
|
|
185
154
|
#sweepQueued = false;
|
|
186
155
|
#pollTimer;
|
|
@@ -194,6 +163,11 @@ class LiveQuerySet {
|
|
|
194
163
|
}
|
|
195
164
|
this.#maxGroups = boundedLiveLimit(options.maxGroups ?? DEFAULT_LIVE_QUERY_MAX_GROUPS, MAX_LIVE_QUERY_GROUPS, "group");
|
|
196
165
|
this.#maxSubscriptions = boundedLiveLimit(options.maxSubscriptions ?? DEFAULT_LIVE_QUERY_MAX_SUBSCRIPTIONS, MAX_LIVE_QUERY_SUBSCRIPTIONS, "subscription");
|
|
166
|
+
this.#maxRetainedBytes = options.maxRetainedBytes ?? 64 * 1024 * 1024;
|
|
167
|
+
if (!Number.isSafeInteger(this.#maxRetainedBytes) || this.#maxRetainedBytes < 0)
|
|
168
|
+
throw new RangeError("Live query retained byte limit must be a non-negative safe integer");
|
|
169
|
+
this.#sharedResults = options.sharedResults === true;
|
|
170
|
+
this.#incremental = options.incremental !== false;
|
|
197
171
|
this.#host = host;
|
|
198
172
|
if (options.channel !== void 0) {
|
|
199
173
|
this.#channel = options.channel;
|
|
@@ -214,7 +188,25 @@ class LiveQuerySet {
|
|
|
214
188
|
}
|
|
215
189
|
}
|
|
216
190
|
get stats() {
|
|
217
|
-
|
|
191
|
+
let retainedRows = 0;
|
|
192
|
+
for (const group of this.#groups.values())
|
|
193
|
+
retainedRows += group.result?.rows.length ?? 0;
|
|
194
|
+
let retainedBytes = 0;
|
|
195
|
+
for (const group of this.#groups.values())
|
|
196
|
+
retainedBytes += group.retainedBytes;
|
|
197
|
+
return { ...this.#stats, retainedRows, retainedBytes };
|
|
198
|
+
}
|
|
199
|
+
#freshProbe() {
|
|
200
|
+
let pending = this.#pendingProbe;
|
|
201
|
+
if (pending === void 0) {
|
|
202
|
+
pending = Promise.resolve().then(() => {
|
|
203
|
+
if (this.#pendingProbe === pending)
|
|
204
|
+
this.#pendingProbe = void 0;
|
|
205
|
+
return this.#host.currentProbe();
|
|
206
|
+
});
|
|
207
|
+
this.#pendingProbe = pending;
|
|
208
|
+
}
|
|
209
|
+
return pending;
|
|
218
210
|
}
|
|
219
211
|
#throwIfClosed() {
|
|
220
212
|
if (this.#closed)
|
|
@@ -232,22 +224,25 @@ class LiveQuerySet {
|
|
|
232
224
|
let group;
|
|
233
225
|
let subscriber;
|
|
234
226
|
try {
|
|
235
|
-
|
|
227
|
+
const opened = await this.#getOrOpenGroup(query);
|
|
228
|
+
group = opened.group;
|
|
236
229
|
this.#throwIfClosed();
|
|
237
230
|
subscriber = {
|
|
238
231
|
kind: "result",
|
|
239
232
|
options,
|
|
240
233
|
delivered: false,
|
|
241
|
-
closed: false
|
|
234
|
+
closed: false,
|
|
235
|
+
seenDelivery: -1
|
|
242
236
|
};
|
|
243
237
|
group.subscribers.add(subscriber);
|
|
244
|
-
await this.
|
|
238
|
+
await this.#settleGroup(group, opened.fresh, true);
|
|
245
239
|
this.#throwIfClosed();
|
|
246
240
|
if (!subscriber.delivered) {
|
|
247
241
|
const result = group.result ?? (await this.#executeGroup(group)).result;
|
|
248
242
|
this.#throwIfClosed();
|
|
249
|
-
if (!subscriber.closed)
|
|
250
|
-
this.#deliverResult(subscriber, result);
|
|
243
|
+
if (!subscriber.closed) {
|
|
244
|
+
this.#deliverResult(group, subscriber, result, { ...group.seenProbe, initial: true });
|
|
245
|
+
}
|
|
251
246
|
}
|
|
252
247
|
} catch (error) {
|
|
253
248
|
if (group !== void 0 && subscriber !== void 0) {
|
|
@@ -261,13 +256,22 @@ class LiveQuerySet {
|
|
|
261
256
|
}
|
|
262
257
|
return this.#subscriptionHandle(group, subscriber);
|
|
263
258
|
}
|
|
259
|
+
subscribePatches(query, options) {
|
|
260
|
+
return this.subscribe(query, {
|
|
261
|
+
sharedResults: true,
|
|
262
|
+
onChange: (result, delivery) => options.onPatch(createLiveQueryPatch(result, delivery), delivery),
|
|
263
|
+
...options.onError === void 0 ? {} : { onError: options.onError.bind(options) },
|
|
264
|
+
...options.onComplete === void 0 ? {} : { onComplete: options.onComplete.bind(options) }
|
|
265
|
+
});
|
|
266
|
+
}
|
|
264
267
|
async observe(query, options) {
|
|
265
268
|
this.#throwIfClosed();
|
|
266
269
|
this.#reserveSubscription();
|
|
267
270
|
let group;
|
|
268
271
|
let subscriber;
|
|
269
272
|
try {
|
|
270
|
-
|
|
273
|
+
const opened = await this.#getOrOpenGroup(query);
|
|
274
|
+
group = opened.group;
|
|
271
275
|
this.#throwIfClosed();
|
|
272
276
|
subscriber = {
|
|
273
277
|
kind: "observer",
|
|
@@ -276,7 +280,7 @@ class LiveQuerySet {
|
|
|
276
280
|
closed: false
|
|
277
281
|
};
|
|
278
282
|
group.subscribers.add(subscriber);
|
|
279
|
-
await this.
|
|
283
|
+
await this.#settleGroup(group, opened.fresh, options.suppressUnchanged === true);
|
|
280
284
|
this.#throwIfClosed();
|
|
281
285
|
if (!subscriber.delivered) {
|
|
282
286
|
this.#deliverInvalidation(subscriber, { ...group.seenProbe, initial: true });
|
|
@@ -293,6 +297,22 @@ class LiveQuerySet {
|
|
|
293
297
|
}
|
|
294
298
|
return this.#subscriptionHandle(group, subscriber);
|
|
295
299
|
}
|
|
300
|
+
async #settleGroup(group, fresh, execute) {
|
|
301
|
+
if (execute && group.result === void 0) {
|
|
302
|
+
if (group.execution !== void 0) {
|
|
303
|
+
this.#stats.sharedExecutions += 1;
|
|
304
|
+
await group.execution;
|
|
305
|
+
} else {
|
|
306
|
+
await this.#executeGroup(group, {
|
|
307
|
+
probe: group.seenProbe,
|
|
308
|
+
memoize: groupMemoizes(group)
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
this.#throwIfClosed();
|
|
312
|
+
}
|
|
313
|
+
if (!fresh || this.#lagging.has(group))
|
|
314
|
+
await this.refresh();
|
|
315
|
+
}
|
|
296
316
|
#subscriptionHandle(group, subscriber) {
|
|
297
317
|
return {
|
|
298
318
|
dependencyTableIds: [...group.dependencies],
|
|
@@ -308,7 +328,7 @@ class LiveQuerySet {
|
|
|
308
328
|
this.#subscriptionCount -= 1;
|
|
309
329
|
group.subscribers.delete(subscriber);
|
|
310
330
|
if (complete)
|
|
311
|
-
subscriber.options.onComplete?.();
|
|
331
|
+
callLiveCallback(() => subscriber.options.onComplete?.());
|
|
312
332
|
this.#removeEmptyGroup(group);
|
|
313
333
|
}
|
|
314
334
|
#removeEmptyGroup(group) {
|
|
@@ -317,6 +337,7 @@ class LiveQuerySet {
|
|
|
317
337
|
if (this.#groups.get(group.key) !== group)
|
|
318
338
|
return;
|
|
319
339
|
this.#groups.delete(group.key);
|
|
340
|
+
this.#lagging.delete(group);
|
|
320
341
|
this.#unindexGroup(group, group.dependencies);
|
|
321
342
|
}
|
|
322
343
|
async #getOrOpenGroup(query) {
|
|
@@ -324,12 +345,12 @@ class LiveQuerySet {
|
|
|
324
345
|
const existing = this.#groups.get(key);
|
|
325
346
|
if (existing !== void 0) {
|
|
326
347
|
this.#stats.sharedExecutions += 1;
|
|
327
|
-
return existing;
|
|
348
|
+
return { group: existing, fresh: false };
|
|
328
349
|
}
|
|
329
350
|
const opening = this.#opening.get(key);
|
|
330
351
|
if (opening !== void 0) {
|
|
331
352
|
this.#stats.sharedExecutions += 1;
|
|
332
|
-
return opening;
|
|
353
|
+
return { group: await opening, fresh: false };
|
|
333
354
|
}
|
|
334
355
|
if (this.#groups.size + this.#opening.size >= this.#maxGroups) {
|
|
335
356
|
throw new LiveQueryLimitError("group", this.#maxGroups);
|
|
@@ -337,7 +358,7 @@ class LiveQuerySet {
|
|
|
337
358
|
const created = this.#openGroup(key, query);
|
|
338
359
|
this.#opening.set(key, created);
|
|
339
360
|
try {
|
|
340
|
-
return await created;
|
|
361
|
+
return { group: await created, fresh: true };
|
|
341
362
|
} finally {
|
|
342
363
|
this.#opening.delete(key);
|
|
343
364
|
}
|
|
@@ -351,13 +372,18 @@ class LiveQuerySet {
|
|
|
351
372
|
subscribers: /* @__PURE__ */ new Set(),
|
|
352
373
|
seenProbe: after,
|
|
353
374
|
result: void 0,
|
|
354
|
-
|
|
355
|
-
execution: void 0
|
|
375
|
+
retainedBytes: 0,
|
|
376
|
+
execution: void 0,
|
|
377
|
+
deliveries: 0,
|
|
378
|
+
maintenance: void 0,
|
|
379
|
+
unmaintainable: false
|
|
356
380
|
};
|
|
357
381
|
this.#groups.set(key, group);
|
|
358
382
|
this.#indexGroup(group, dependencies);
|
|
359
|
-
if (this.#lastProbe === void 0
|
|
383
|
+
if (this.#lastProbe === void 0)
|
|
360
384
|
this.#lastProbe = after;
|
|
385
|
+
else if (versionOrdinal(after.manifestVersion) < versionOrdinal(this.#lastProbe.manifestVersion) || after.schemaEpoch !== this.#lastProbe.schemaEpoch) {
|
|
386
|
+
this.#lagging.add(group);
|
|
361
387
|
}
|
|
362
388
|
return group;
|
|
363
389
|
}
|
|
@@ -388,12 +414,12 @@ class LiveQuerySet {
|
|
|
388
414
|
this.#indexGroup(group, next);
|
|
389
415
|
}
|
|
390
416
|
async #stableDependencies(query) {
|
|
391
|
-
let before = await this.#
|
|
417
|
+
let before = await this.#freshProbe();
|
|
392
418
|
this.#throwIfClosed();
|
|
393
419
|
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
394
|
-
const dependencies = await this.#host.dependencyTableIds(query);
|
|
420
|
+
const dependencies = await this.#host.dependencyTableIds(query, before);
|
|
395
421
|
this.#throwIfClosed();
|
|
396
|
-
const after = await this.#
|
|
422
|
+
const after = await this.#freshProbe();
|
|
397
423
|
this.#throwIfClosed();
|
|
398
424
|
if (!catalogChangedBetween(before, after))
|
|
399
425
|
return { dependencies, probe: after };
|
|
@@ -401,20 +427,35 @@ class LiveQuerySet {
|
|
|
401
427
|
}
|
|
402
428
|
throw new Error("Catalog kept changing while live-query dependencies were resolved");
|
|
403
429
|
}
|
|
404
|
-
async #executeGroup(group) {
|
|
430
|
+
async #executeGroup(group, context) {
|
|
405
431
|
if (group.execution !== void 0) {
|
|
406
432
|
this.#stats.sharedExecutions += 1;
|
|
407
433
|
return group.execution;
|
|
408
434
|
}
|
|
409
435
|
const execution = (async () => {
|
|
410
|
-
|
|
411
|
-
|
|
436
|
+
await this.#acquireExecutionSlot();
|
|
437
|
+
let executed;
|
|
438
|
+
let maintenance;
|
|
439
|
+
let retainedBytes;
|
|
440
|
+
try {
|
|
441
|
+
if (this.#incremental && this.#host.executeMaintainable !== void 0 && !group.unmaintainable) {
|
|
442
|
+
const maintained = await this.#host.executeMaintainable(group.query, context);
|
|
443
|
+
if (maintained === void 0)
|
|
444
|
+
group.unmaintainable = true;
|
|
445
|
+
else {
|
|
446
|
+
executed = maintained.result;
|
|
447
|
+
maintenance = maintained.state;
|
|
448
|
+
retainedBytes = maintained.retainedBytes;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
executed ??= await this.#host.execute(group.query, context);
|
|
452
|
+
} finally {
|
|
453
|
+
this.#releaseExecutionSlot();
|
|
454
|
+
}
|
|
412
455
|
const previous = group.result;
|
|
413
|
-
const changed = previous === void 0 ||
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
group.digest = digest;
|
|
417
|
-
return { result: retained, changed };
|
|
456
|
+
const changed = previous === void 0 || !sameResult(previous, executed);
|
|
457
|
+
this.#retain(group, executed, maintenance, retainedBytes);
|
|
458
|
+
return { result: executed, changed };
|
|
418
459
|
})();
|
|
419
460
|
group.execution = execution;
|
|
420
461
|
try {
|
|
@@ -424,11 +465,77 @@ class LiveQuerySet {
|
|
|
424
465
|
group.execution = void 0;
|
|
425
466
|
}
|
|
426
467
|
}
|
|
427
|
-
#
|
|
468
|
+
#retain(group, result, state, hint) {
|
|
469
|
+
const bytes = Math.max(queryResultRetainedBytes(result) + result.rows.length * 48, hint ?? (state === group.maintenance ? group.retainedBytes : 0));
|
|
470
|
+
let total = bytes;
|
|
471
|
+
for (const other of this.#groups.values())
|
|
472
|
+
if (other !== group)
|
|
473
|
+
total += other.retainedBytes;
|
|
474
|
+
if (!Number.isSafeInteger(total) || total > this.#maxRetainedBytes)
|
|
475
|
+
throw new LiveQueryLimitError("byte", this.#maxRetainedBytes);
|
|
476
|
+
group.result = result;
|
|
477
|
+
group.maintenance = state;
|
|
478
|
+
group.retainedBytes = bytes;
|
|
479
|
+
}
|
|
480
|
+
async #acquireExecutionSlot() {
|
|
481
|
+
if (this.#executing < LIVE_QUERY_EXECUTION_CONCURRENCY) {
|
|
482
|
+
this.#executing += 1;
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
await new Promise((resolve) => {
|
|
486
|
+
this.#executionWaiters.push(resolve);
|
|
487
|
+
});
|
|
488
|
+
this.#executing += 1;
|
|
489
|
+
}
|
|
490
|
+
#releaseExecutionSlot() {
|
|
491
|
+
this.#executing -= 1;
|
|
492
|
+
this.#executionWaiters.shift()?.();
|
|
493
|
+
}
|
|
494
|
+
async #maintainGroup(group, tableIds, after, until, probe) {
|
|
495
|
+
const host = this.#host;
|
|
496
|
+
if (host.maintain === void 0 || group.result === void 0 || group.maintenance === void 0 || group.execution !== void 0) {
|
|
497
|
+
return void 0;
|
|
498
|
+
}
|
|
499
|
+
const retained = group.result;
|
|
500
|
+
const state = group.maintenance;
|
|
501
|
+
const verdict = { declined: false };
|
|
502
|
+
const execution = (async () => {
|
|
503
|
+
await this.#acquireExecutionSlot();
|
|
504
|
+
let maintained;
|
|
505
|
+
try {
|
|
506
|
+
maintained = await host.maintain?.(group.query, retained, state, tableIds, after, until, probe);
|
|
507
|
+
} catch {
|
|
508
|
+
maintained = void 0;
|
|
509
|
+
} finally {
|
|
510
|
+
this.#releaseExecutionSlot();
|
|
511
|
+
}
|
|
512
|
+
if (maintained === void 0) {
|
|
513
|
+
verdict.declined = true;
|
|
514
|
+
return { result: retained, changed: false };
|
|
515
|
+
}
|
|
516
|
+
this.#retain(group, maintained.result, maintained.state, maintained.retainedBytes);
|
|
517
|
+
return {
|
|
518
|
+
result: maintained.result,
|
|
519
|
+
changed: maintained.changed,
|
|
520
|
+
...maintained.retained === void 0 ? {} : { retained: maintained.retained }
|
|
521
|
+
};
|
|
522
|
+
})();
|
|
523
|
+
group.execution = execution;
|
|
524
|
+
try {
|
|
525
|
+
const outcome = await execution;
|
|
526
|
+
return verdict.declined ? void 0 : outcome;
|
|
527
|
+
} finally {
|
|
528
|
+
if (group.execution === execution)
|
|
529
|
+
group.execution = void 0;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
#deliverResult(group, subscriber, result, delivery, retained) {
|
|
428
533
|
if (subscriber.closed)
|
|
429
534
|
return;
|
|
430
535
|
subscriber.delivered = true;
|
|
431
|
-
subscriber.
|
|
536
|
+
const consecutive = subscriber.seenDelivery === group.deliveries - 1;
|
|
537
|
+
subscriber.options.onChange(this.#sharedResults || subscriber.options.sharedResults === true ? result : cloneResult(result), retained !== void 0 && consecutive && !delivery.initial ? { ...delivery, retained } : delivery);
|
|
538
|
+
subscriber.seenDelivery = group.deliveries;
|
|
432
539
|
}
|
|
433
540
|
#deliverInvalidation(subscriber, invalidation) {
|
|
434
541
|
if (subscriber.closed)
|
|
@@ -456,6 +563,7 @@ class LiveQuerySet {
|
|
|
456
563
|
const groups = [...this.#groups.values()];
|
|
457
564
|
this.#groups.clear();
|
|
458
565
|
this.#groupsByTable.clear();
|
|
566
|
+
this.#lagging.clear();
|
|
459
567
|
for (const group of groups) {
|
|
460
568
|
const subscribers = [...group.subscribers];
|
|
461
569
|
group.subscribers.clear();
|
|
@@ -464,10 +572,10 @@ class LiveQuerySet {
|
|
|
464
572
|
continue;
|
|
465
573
|
subscriber.closed = true;
|
|
466
574
|
this.#subscriptionCount -= 1;
|
|
467
|
-
subscriber.options.onComplete?.();
|
|
575
|
+
callLiveCallback(() => subscriber.options.onComplete?.());
|
|
468
576
|
}
|
|
469
577
|
}
|
|
470
|
-
this.#onClosed
|
|
578
|
+
callLiveCallback(this.#onClosed);
|
|
471
579
|
}
|
|
472
580
|
#hint() {
|
|
473
581
|
if (this.#closed)
|
|
@@ -489,19 +597,39 @@ class LiveQuerySet {
|
|
|
489
597
|
#stillOpen() {
|
|
490
598
|
return !this.#closed;
|
|
491
599
|
}
|
|
600
|
+
async #sweepCandidates(last, current, changedSince) {
|
|
601
|
+
if (catalogChangedBetween(last, current))
|
|
602
|
+
return [...this.#groups.values()];
|
|
603
|
+
if (sameProbe(last, current))
|
|
604
|
+
return [...this.#lagging];
|
|
605
|
+
const changed = await changedSince(last.manifestVersion);
|
|
606
|
+
if (!this.#stillOpen())
|
|
607
|
+
return void 0;
|
|
608
|
+
if (changed === "all")
|
|
609
|
+
return [...this.#groups.values()];
|
|
610
|
+
const candidates = new Set(this.#lagging);
|
|
611
|
+
for (const tableId of changed) {
|
|
612
|
+
const groups = this.#groupsByTable.get(tableId);
|
|
613
|
+
if (groups === void 0)
|
|
614
|
+
continue;
|
|
615
|
+
for (const group of groups)
|
|
616
|
+
candidates.add(group);
|
|
617
|
+
}
|
|
618
|
+
return [...candidates];
|
|
619
|
+
}
|
|
492
620
|
async #sweep() {
|
|
493
621
|
if (this.#closed || this.#groups.size === 0)
|
|
494
622
|
return;
|
|
495
623
|
this.#stats.versionChecks += 1;
|
|
496
|
-
const current = await this.#
|
|
624
|
+
const current = await this.#freshProbe();
|
|
497
625
|
if (!this.#stillOpen())
|
|
498
626
|
return;
|
|
499
627
|
const last = this.#lastProbe ?? current;
|
|
500
|
-
|
|
501
|
-
if (sameProbe(last, current) && !anyLagging)
|
|
628
|
+
if (sameProbe(last, current) && this.#lagging.size === 0)
|
|
502
629
|
return;
|
|
503
630
|
const started = performance.now();
|
|
504
631
|
this.#stats.sweeps += 1;
|
|
632
|
+
const rerunsBefore = this.#stats.reruns;
|
|
505
633
|
const windowCache = /* @__PURE__ */ new Map();
|
|
506
634
|
const changedSince = (after) => {
|
|
507
635
|
const key = versionOrdinal(after);
|
|
@@ -512,101 +640,139 @@ class LiveQuerySet {
|
|
|
512
640
|
}
|
|
513
641
|
return pending;
|
|
514
642
|
};
|
|
515
|
-
|
|
643
|
+
const candidates = await this.#sweepCandidates(last, current, changedSince);
|
|
644
|
+
if (candidates === void 0)
|
|
645
|
+
return;
|
|
646
|
+
for (const group of candidates) {
|
|
516
647
|
if (!this.#stillOpen())
|
|
517
648
|
return;
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
649
|
+
await this.#sweepGroup(group, last, current, changedSince);
|
|
650
|
+
}
|
|
651
|
+
this.#lastProbe = current;
|
|
652
|
+
this.#stats.rerunsAvoided += this.#groups.size - (this.#stats.reruns - rerunsBefore);
|
|
653
|
+
this.#stats.lastSweepMs = performance.now() - started;
|
|
654
|
+
}
|
|
655
|
+
async #sweepGroup(group, last, current, changedSince) {
|
|
656
|
+
if (!this.#stillOpen())
|
|
657
|
+
return;
|
|
658
|
+
if (group.subscribers.size === 0 || this.#groups.get(group.key) !== group)
|
|
659
|
+
return;
|
|
660
|
+
this.#stats.groupsVisited += 1;
|
|
661
|
+
if (sameProbe(group.seenProbe, current)) {
|
|
662
|
+
this.#lagging.delete(group);
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
const prior = this.#lagging.has(group) ? group.seenProbe : newerProbe(group.seenProbe, last);
|
|
666
|
+
const catalogChanged = catalogChangedBetween(prior, current);
|
|
667
|
+
if (catalogChanged) {
|
|
668
|
+
group.maintenance = void 0;
|
|
669
|
+
group.unmaintainable = false;
|
|
670
|
+
try {
|
|
671
|
+
await this.#refreshDependencies(group);
|
|
672
|
+
} catch (error) {
|
|
673
|
+
this.#lagging.add(group);
|
|
674
|
+
this.#notifyGroupError(group, error);
|
|
675
|
+
return;
|
|
529
676
|
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
677
|
+
}
|
|
678
|
+
let relevant = [];
|
|
679
|
+
let affected = catalogChanged;
|
|
680
|
+
if (!affected && prior.manifestVersion !== current.manifestVersion) {
|
|
681
|
+
const changed = await changedSince(prior.manifestVersion);
|
|
682
|
+
if (!this.#stillOpen())
|
|
683
|
+
return;
|
|
684
|
+
if (changed === "all")
|
|
685
|
+
affected = true;
|
|
686
|
+
else {
|
|
687
|
+
relevant = [...group.dependencies].filter((tableId) => changed.has(tableId));
|
|
688
|
+
affected = relevant.length > 0;
|
|
542
689
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
690
|
+
}
|
|
691
|
+
if (!affected) {
|
|
692
|
+
this.#settleProbe(group, current);
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
if (!catalogChanged && relevant.length > 0 && current.manifestVersion !== null && this.#host.changeCanAffect !== void 0) {
|
|
696
|
+
let canAffect;
|
|
697
|
+
try {
|
|
698
|
+
canAffect = await this.#host.changeCanAffect(group.query, relevant, prior.manifestVersion, current.manifestVersion);
|
|
699
|
+
} catch {
|
|
700
|
+
canAffect = true;
|
|
547
701
|
}
|
|
548
|
-
if (!
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
702
|
+
if (!this.#stillOpen())
|
|
703
|
+
return;
|
|
704
|
+
if (!canAffect) {
|
|
705
|
+
this.#stats.zoneSkips += 1;
|
|
706
|
+
this.#settleProbe(group, current);
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
try {
|
|
711
|
+
let execution;
|
|
712
|
+
if (groupExecutes(group)) {
|
|
713
|
+
if (group.result === void 0 && group.execution !== void 0) {
|
|
714
|
+
this.#lagging.add(group);
|
|
556
715
|
return;
|
|
557
|
-
if (!canAffect) {
|
|
558
|
-
this.#stats.rerunsAvoided += 1;
|
|
559
|
-
this.#stats.zoneSkips += 1;
|
|
560
|
-
group.seenProbe = current;
|
|
561
|
-
continue;
|
|
562
716
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
if (
|
|
569
|
-
|
|
570
|
-
|
|
717
|
+
if (!catalogChanged && relevant.length > 0 && current.manifestVersion !== null) {
|
|
718
|
+
execution = await this.#maintainGroup(group, relevant, prior.manifestVersion, current.manifestVersion, current);
|
|
719
|
+
if (!this.#stillOpen() || this.#groups.get(group.key) !== group)
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
if (execution !== void 0)
|
|
723
|
+
this.#stats.maintained += 1;
|
|
724
|
+
else {
|
|
571
725
|
this.#stats.reruns += 1;
|
|
572
|
-
execution = await this.#executeGroup(group
|
|
573
|
-
|
|
574
|
-
|
|
726
|
+
execution = await this.#executeGroup(group, {
|
|
727
|
+
probe: current,
|
|
728
|
+
memoize: groupMemoizes(group)
|
|
729
|
+
});
|
|
730
|
+
if (!this.#stillOpen() || this.#groups.get(group.key) !== group)
|
|
731
|
+
return;
|
|
575
732
|
}
|
|
576
|
-
|
|
733
|
+
}
|
|
734
|
+
const invalidation = { ...current, initial: false };
|
|
735
|
+
if (execution?.changed === true)
|
|
736
|
+
group.deliveries += 1;
|
|
737
|
+
for (const subscriber of [...group.subscribers]) {
|
|
738
|
+
if (isObserver(subscriber)) {
|
|
739
|
+
if (execution !== void 0 && !execution.changed && subscriber.options.suppressUnchanged === true) {
|
|
740
|
+
this.#stats.notificationsSuppressed += 1;
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
577
743
|
try {
|
|
578
|
-
this.#deliverInvalidation(
|
|
744
|
+
this.#deliverInvalidation(subscriber, invalidation);
|
|
579
745
|
this.#stats.invalidations += 1;
|
|
580
746
|
} catch (error) {
|
|
581
|
-
|
|
747
|
+
callLiveCallback(() => subscriber.options.onError?.(error));
|
|
582
748
|
}
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
}
|
|
593
|
-
} else {
|
|
594
|
-
this.#stats.notificationsSuppressed += resultSubscribers.length;
|
|
749
|
+
} else if (isResultSubscriber(subscriber) && execution !== void 0) {
|
|
750
|
+
if (!execution.changed) {
|
|
751
|
+
this.#stats.notificationsSuppressed += 1;
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
try {
|
|
755
|
+
this.#deliverResult(group, subscriber, execution.result, invalidation, execution.retained);
|
|
756
|
+
} catch (error) {
|
|
757
|
+
callLiveCallback(() => subscriber.options.onError?.(error));
|
|
595
758
|
}
|
|
596
759
|
}
|
|
597
|
-
group.seenProbe = current;
|
|
598
|
-
} catch (error) {
|
|
599
|
-
this.#notifyGroupError(group, error);
|
|
600
760
|
}
|
|
761
|
+
this.#settleProbe(group, current);
|
|
762
|
+
} catch (error) {
|
|
763
|
+
this.#lagging.add(group);
|
|
764
|
+
this.#notifyGroupError(group, error);
|
|
601
765
|
}
|
|
602
|
-
|
|
603
|
-
|
|
766
|
+
}
|
|
767
|
+
#settleProbe(group, probe) {
|
|
768
|
+
group.seenProbe = probe;
|
|
769
|
+
this.#lagging.delete(group);
|
|
604
770
|
}
|
|
605
771
|
#notifyGroupError(group, error) {
|
|
606
772
|
for (const subscriber of group.subscribers) {
|
|
607
773
|
if (subscriber.closed)
|
|
608
774
|
continue;
|
|
609
|
-
subscriber.options.onError?.(error);
|
|
775
|
+
callLiveCallback(() => subscriber.options.onError?.(error));
|
|
610
776
|
}
|
|
611
777
|
}
|
|
612
778
|
async #changedTablesSince(after, until) {
|