@minnowdb/core 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/block-format/block.d.ts +32 -13
- package/dist/block-format/block.js +179 -55
- package/dist/block-format/checksum.d.ts +2 -1
- package/dist/block-format/checksum.js +5 -2
- package/dist/block-format/codecs.d.ts +7 -2
- package/dist/block-format/codecs.js +53 -12
- package/dist/block-format/column.d.ts +3 -2
- package/dist/block-format/column.js +96 -30
- package/dist/block-format/index.d.ts +2 -2
- package/dist/block-format/index.js +2 -2
- package/dist/block-format/physical.d.ts +8 -2
- package/dist/block-format/physical.js +14 -6
- package/dist/block-format/types.d.ts +7 -3
- package/dist/block-format/types.js +0 -1
- package/dist/block-format/unicode.d.ts +11 -0
- package/dist/block-format/unicode.js +46 -0
- package/dist/date-value.d.ts +15 -0
- package/dist/date-value.js +64 -0
- package/dist/engine/artifact-cache.d.ts +0 -1
- package/dist/engine/artifact-cache.js +0 -1
- package/dist/engine/batch.d.ts +5 -2
- package/dist/engine/batch.js +0 -1
- package/dist/engine/buffered-writer.d.ts +2 -1
- package/dist/engine/buffered-writer.js +34 -9
- package/dist/engine/cache-limits.d.ts +25 -0
- package/dist/engine/cache-limits.js +25 -0
- package/dist/engine/catalog.d.ts +5 -6
- package/dist/engine/catalog.js +5 -5
- package/dist/engine/client.d.ts +27 -7
- package/dist/engine/client.js +197 -31
- package/dist/engine/database.d.ts +161 -38
- package/dist/engine/database.js +4326 -1523
- package/dist/engine/defaults.d.ts +0 -1
- package/dist/engine/defaults.js +2 -4
- package/dist/engine/errors.d.ts +19 -1
- package/dist/engine/errors.js +32 -2
- package/dist/engine/fts.d.ts +0 -6
- package/dist/engine/fts.js +39 -14
- package/dist/engine/group-index.d.ts +0 -1
- package/dist/engine/group-index.js +6 -11
- package/dist/engine/index.d.ts +16 -11
- package/dist/engine/index.js +12 -10
- package/dist/engine/join-index.d.ts +0 -1
- package/dist/engine/join-index.js +2 -2
- package/dist/engine/keyed-live.d.ts +57 -0
- package/dist/engine/keyed-live.js +226 -0
- package/dist/engine/live-api.d.ts +4 -0
- package/dist/engine/live-api.js +4 -0
- package/dist/engine/live.d.ts +43 -26
- package/dist/engine/live.js +499 -129
- package/dist/engine/memory.d.ts +2 -1
- package/dist/engine/memory.js +3 -2
- package/dist/engine/optimizer.d.ts +0 -1
- package/dist/engine/optimizer.js +4 -4
- package/dist/engine/query-api.d.ts +3 -0
- package/dist/engine/query-api.js +3 -0
- package/dist/engine/query-cache.d.ts +0 -1
- package/dist/engine/query-cache.js +4 -4
- package/dist/engine/query.d.ts +4 -7
- package/dist/engine/query.js +348 -155
- package/dist/engine/result-wire.d.ts +0 -1
- package/dist/engine/result-wire.js +2 -2
- package/dist/engine/schema-wire.d.ts +1 -2
- package/dist/engine/schema-wire.js +0 -1
- package/dist/engine/schema.d.ts +3 -8
- package/dist/engine/schema.js +41 -24
- package/dist/engine/sort-keys.d.ts +0 -1
- package/dist/engine/sort-keys.js +2 -2
- package/dist/engine/sql-domains.d.ts +6 -1
- package/dist/engine/sql-domains.js +209 -45
- package/dist/engine/sql-driver.d.ts +2 -2
- package/dist/engine/sql-driver.js +0 -1
- package/dist/engine/sql-json.d.ts +0 -1
- package/dist/engine/sql-json.js +53 -7
- package/dist/engine/sql-semantics.d.ts +8 -5
- package/dist/engine/sql-semantics.js +427 -65
- package/dist/engine/typed-live.d.ts +67 -0
- package/dist/engine/typed-live.js +349 -0
- package/dist/engine/vector.d.ts +7 -1
- package/dist/engine/vector.js +202 -58
- package/dist/engine/worker-host.d.ts +8 -2
- package/dist/engine/worker-host.js +616 -152
- package/dist/engine/worker.d.ts +0 -1
- package/dist/engine/worker.js +3 -3
- package/dist/engine/write-block-planner.d.ts +18 -0
- package/dist/engine/write-block-planner.js +112 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/plan/index.d.ts +0 -1
- package/dist/plan/index.js +0 -1
- package/dist/storage/index.d.ts +1 -1
- package/dist/storage/index.js +1 -1
- package/dist/storage/indexeddb.d.ts +74 -65
- package/dist/storage/indexeddb.js +12301 -2076
- package/dist/storage/memory.d.ts +56 -66
- package/dist/storage/memory.js +1004 -139
- package/dist/storage/opfs/files.d.ts +18 -7
- package/dist/storage/opfs/files.js +115 -28
- package/dist/storage/opfs/index.d.ts +1 -1
- package/dist/storage/opfs/index.js +1 -1
- package/dist/storage/opfs/leader.d.ts +403 -91
- package/dist/storage/opfs/leader.js +4318 -602
- package/dist/storage/opfs/rpc.d.ts +15 -3
- package/dist/storage/opfs/rpc.js +223 -2
- package/dist/storage/opfs/snapshot-ledger.d.ts +40 -0
- package/dist/storage/opfs/snapshot-ledger.js +281 -0
- package/dist/storage/opfs/store.d.ts +26 -121
- package/dist/storage/opfs/store.js +436 -388
- package/dist/storage/persistence.d.ts +37 -0
- package/dist/storage/persistence.js +78 -0
- package/dist/storage/snapshot-stream.d.ts +24 -0
- package/dist/storage/snapshot-stream.js +897 -0
- package/dist/storage/snapshot.d.ts +9 -99
- package/dist/storage/snapshot.js +33 -309
- package/dist/storage/toolkit/extents.d.ts +41 -7
- package/dist/storage/toolkit/extents.js +402 -39
- package/dist/storage/toolkit/index.d.ts +3 -4
- package/dist/storage/toolkit/index.js +27 -3
- package/dist/storage/toolkit/record-core.d.ts +174 -61
- package/dist/storage/toolkit/record-core.js +5401 -849
- package/dist/storage/toolkit/sync-file.d.ts +10 -1
- package/dist/storage/toolkit/sync-file.js +50 -2
- package/dist/storage/toolkit/wal.d.ts +23 -5
- package/dist/storage/toolkit/wal.js +89 -27
- package/dist/storage/toolkit/wire.d.ts +4 -6
- package/dist/storage/toolkit/wire.js +80 -25
- package/dist/storage/types.d.ts +1271 -218
- package/dist/storage/types.js +1471 -138
- package/dist/testing/block-store-conformance.d.ts +0 -1
- package/dist/testing/block-store-conformance.js +877 -138
- package/dist/testing/index.d.ts +42 -31
- package/dist/testing/index.js +100 -68
- package/dist/testing/opfs-shim.d.ts +13 -3
- package/dist/testing/opfs-shim.js +40 -6
- package/dist/testing/simulator.d.ts +0 -1
- package/dist/testing/simulator.js +43 -12
- package/dist/testing/sqllogictest.d.ts +0 -1
- package/dist/testing/sqllogictest.js +0 -1
- package/dist/transactions/index.d.ts +81 -20
- package/dist/transactions/index.js +1050 -268
- package/dist/worker-protocol/index.d.ts +3 -2
- package/dist/worker-protocol/index.js +2 -5
- package/package.json +49 -1
- package/dist/block-format/block.d.ts.map +0 -1
- package/dist/block-format/block.js.map +0 -1
- package/dist/block-format/checksum.d.ts.map +0 -1
- package/dist/block-format/checksum.js.map +0 -1
- package/dist/block-format/codecs.d.ts.map +0 -1
- package/dist/block-format/codecs.js.map +0 -1
- package/dist/block-format/column.d.ts.map +0 -1
- package/dist/block-format/column.js.map +0 -1
- package/dist/block-format/index.d.ts.map +0 -1
- package/dist/block-format/index.js.map +0 -1
- package/dist/block-format/physical.d.ts.map +0 -1
- package/dist/block-format/physical.js.map +0 -1
- package/dist/block-format/types.d.ts.map +0 -1
- package/dist/block-format/types.js.map +0 -1
- package/dist/engine/artifact-cache.d.ts.map +0 -1
- package/dist/engine/artifact-cache.js.map +0 -1
- package/dist/engine/batch.d.ts.map +0 -1
- package/dist/engine/batch.js.map +0 -1
- package/dist/engine/buffered-writer.d.ts.map +0 -1
- package/dist/engine/buffered-writer.js.map +0 -1
- package/dist/engine/catalog.d.ts.map +0 -1
- package/dist/engine/catalog.js.map +0 -1
- package/dist/engine/client.d.ts.map +0 -1
- package/dist/engine/client.js.map +0 -1
- package/dist/engine/coordinator.d.ts +0 -17
- package/dist/engine/coordinator.d.ts.map +0 -1
- package/dist/engine/coordinator.js +0 -60
- package/dist/engine/coordinator.js.map +0 -1
- package/dist/engine/database.d.ts.map +0 -1
- package/dist/engine/database.js.map +0 -1
- package/dist/engine/defaults.d.ts.map +0 -1
- package/dist/engine/defaults.js.map +0 -1
- package/dist/engine/errors.d.ts.map +0 -1
- package/dist/engine/errors.js.map +0 -1
- package/dist/engine/fts.d.ts.map +0 -1
- package/dist/engine/fts.js.map +0 -1
- package/dist/engine/group-index.d.ts.map +0 -1
- package/dist/engine/group-index.js.map +0 -1
- package/dist/engine/index.d.ts.map +0 -1
- package/dist/engine/index.js.map +0 -1
- package/dist/engine/join-index.d.ts.map +0 -1
- package/dist/engine/join-index.js.map +0 -1
- package/dist/engine/live.d.ts.map +0 -1
- package/dist/engine/live.js.map +0 -1
- package/dist/engine/memory.d.ts.map +0 -1
- package/dist/engine/memory.js.map +0 -1
- package/dist/engine/optimizer.d.ts.map +0 -1
- package/dist/engine/optimizer.js.map +0 -1
- package/dist/engine/query-cache.d.ts.map +0 -1
- package/dist/engine/query-cache.js.map +0 -1
- package/dist/engine/query.d.ts.map +0 -1
- package/dist/engine/query.js.map +0 -1
- package/dist/engine/result-wire.d.ts.map +0 -1
- package/dist/engine/result-wire.js.map +0 -1
- package/dist/engine/schema-wire.d.ts.map +0 -1
- package/dist/engine/schema-wire.js.map +0 -1
- package/dist/engine/schema.d.ts.map +0 -1
- package/dist/engine/schema.js.map +0 -1
- package/dist/engine/sort-keys.d.ts.map +0 -1
- package/dist/engine/sort-keys.js.map +0 -1
- package/dist/engine/sql-domains.d.ts.map +0 -1
- package/dist/engine/sql-domains.js.map +0 -1
- package/dist/engine/sql-driver.d.ts.map +0 -1
- package/dist/engine/sql-driver.js.map +0 -1
- package/dist/engine/sql-json.d.ts.map +0 -1
- package/dist/engine/sql-json.js.map +0 -1
- package/dist/engine/sql-semantics.d.ts.map +0 -1
- package/dist/engine/sql-semantics.js.map +0 -1
- package/dist/engine/vector.d.ts.map +0 -1
- package/dist/engine/vector.js.map +0 -1
- package/dist/engine/worker-host.d.ts.map +0 -1
- package/dist/engine/worker-host.js.map +0 -1
- package/dist/engine/worker.d.ts.map +0 -1
- package/dist/engine/worker.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/plan/index.d.ts.map +0 -1
- package/dist/plan/index.js.map +0 -1
- package/dist/storage/fixture-shape.d.ts +0 -42
- package/dist/storage/fixture-shape.d.ts.map +0 -1
- package/dist/storage/fixture-shape.js +0 -146
- package/dist/storage/fixture-shape.js.map +0 -1
- package/dist/storage/index.d.ts.map +0 -1
- package/dist/storage/index.js.map +0 -1
- package/dist/storage/indexeddb.d.ts.map +0 -1
- package/dist/storage/indexeddb.js.map +0 -1
- package/dist/storage/memory.d.ts.map +0 -1
- package/dist/storage/memory.js.map +0 -1
- package/dist/storage/opfs/files.d.ts.map +0 -1
- package/dist/storage/opfs/files.js.map +0 -1
- package/dist/storage/opfs/index.d.ts.map +0 -1
- package/dist/storage/opfs/index.js.map +0 -1
- package/dist/storage/opfs/leader.d.ts.map +0 -1
- package/dist/storage/opfs/leader.js.map +0 -1
- package/dist/storage/opfs/rpc.d.ts.map +0 -1
- package/dist/storage/opfs/rpc.js.map +0 -1
- package/dist/storage/opfs/store.d.ts.map +0 -1
- package/dist/storage/opfs/store.js.map +0 -1
- package/dist/storage/snapshot.d.ts.map +0 -1
- package/dist/storage/snapshot.js.map +0 -1
- package/dist/storage/toolkit/extents.d.ts.map +0 -1
- package/dist/storage/toolkit/extents.js.map +0 -1
- package/dist/storage/toolkit/index.d.ts.map +0 -1
- package/dist/storage/toolkit/index.js.map +0 -1
- package/dist/storage/toolkit/record-core.d.ts.map +0 -1
- package/dist/storage/toolkit/record-core.js.map +0 -1
- package/dist/storage/toolkit/sync-file.d.ts.map +0 -1
- package/dist/storage/toolkit/sync-file.js.map +0 -1
- package/dist/storage/toolkit/wal.d.ts.map +0 -1
- package/dist/storage/toolkit/wal.js.map +0 -1
- package/dist/storage/toolkit/wire.d.ts.map +0 -1
- package/dist/storage/toolkit/wire.js.map +0 -1
- package/dist/storage/types.d.ts.map +0 -1
- package/dist/storage/types.js.map +0 -1
- package/dist/testing/block-store-conformance.d.ts.map +0 -1
- package/dist/testing/block-store-conformance.js.map +0 -1
- package/dist/testing/index.d.ts.map +0 -1
- package/dist/testing/index.js.map +0 -1
- package/dist/testing/opfs-shim.d.ts.map +0 -1
- package/dist/testing/opfs-shim.js.map +0 -1
- package/dist/testing/seeds.d.ts +0 -11
- package/dist/testing/seeds.d.ts.map +0 -1
- package/dist/testing/seeds.js +0 -50
- package/dist/testing/seeds.js.map +0 -1
- package/dist/testing/simulator.d.ts.map +0 -1
- package/dist/testing/simulator.js.map +0 -1
- package/dist/testing/sqllogictest.d.ts.map +0 -1
- package/dist/testing/sqllogictest.js.map +0 -1
- package/dist/transactions/index.d.ts.map +0 -1
- package/dist/transactions/index.js.map +0 -1
- package/dist/worker-protocol/index.d.ts.map +0 -1
- package/dist/worker-protocol/index.js.map +0 -1
package/dist/engine/live.js
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
export const DEFAULT_LIVE_QUERY_MAX_GROUPS = 256;
|
|
2
|
+
export const DEFAULT_LIVE_QUERY_MAX_SUBSCRIPTIONS = 1_024;
|
|
3
|
+
export const MAX_LIVE_QUERY_GROUPS = 4_096;
|
|
4
|
+
export const MAX_LIVE_QUERY_SUBSCRIPTIONS = 16_384;
|
|
5
|
+
export const MAX_LIVE_QUERY_SETS_PER_DATABASE = 256;
|
|
6
|
+
export class LiveQueryLimitError extends Error {
|
|
7
|
+
resource;
|
|
8
|
+
limit;
|
|
9
|
+
name = "LiveQueryLimitError";
|
|
10
|
+
constructor(resource, limit) {
|
|
11
|
+
super(resource === "set"
|
|
12
|
+
? `A database cannot retain more than ${String(limit)} live-query sets`
|
|
13
|
+
: `A live-query set cannot retain more than ${String(limit)} ${resource} records`);
|
|
14
|
+
this.resource = resource;
|
|
15
|
+
this.limit = limit;
|
|
16
|
+
}
|
|
4
17
|
}
|
|
5
18
|
const digestScratch = new DataView(new ArrayBuffer(8));
|
|
6
|
-
/**
|
|
7
|
-
* A 32-bit FNV-1a digest of a result, mixed from typed values: a number's eight bytes, a
|
|
8
|
-
* string's char codes, a tag per type and per null. Building a string per cell and hashing
|
|
9
|
-
* that used to cost several times the query it digested.
|
|
10
|
-
*/
|
|
19
|
+
/** Fast inequality filter. Equal digests are always followed by exact comparison. */
|
|
11
20
|
function digestResult(result) {
|
|
12
21
|
let hash = 0x811c9dc5;
|
|
13
22
|
const mixByte = (byte) => {
|
|
@@ -18,9 +27,9 @@ function digestResult(result) {
|
|
|
18
27
|
for (let index = 0; index < 8; index += 1)
|
|
19
28
|
mixByte(digestScratch.getUint8(index));
|
|
20
29
|
};
|
|
21
|
-
const mixString = (
|
|
22
|
-
for (let index = 0; index <
|
|
23
|
-
const code =
|
|
30
|
+
const mixString = (value) => {
|
|
31
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
32
|
+
const code = value.charCodeAt(index);
|
|
24
33
|
mixByte(code & 0xff);
|
|
25
34
|
mixByte(code >>> 8);
|
|
26
35
|
}
|
|
@@ -45,20 +54,142 @@ function digestResult(result) {
|
|
|
45
54
|
mixByte(value ? 4 : 5);
|
|
46
55
|
else {
|
|
47
56
|
mixByte(6);
|
|
48
|
-
mixNumber(value
|
|
57
|
+
mixNumber(dateMilliseconds(value));
|
|
49
58
|
}
|
|
50
59
|
}
|
|
51
60
|
mixByte(0xfe);
|
|
52
61
|
}
|
|
53
62
|
return hash;
|
|
54
63
|
}
|
|
64
|
+
function sameQueryValue(left, right) {
|
|
65
|
+
if (left instanceof Date || right instanceof Date) {
|
|
66
|
+
return (left instanceof Date &&
|
|
67
|
+
right instanceof Date &&
|
|
68
|
+
Object.is(dateMilliseconds(left), dateMilliseconds(right)));
|
|
69
|
+
}
|
|
70
|
+
return Object.is(left, right);
|
|
71
|
+
}
|
|
72
|
+
function sameResult(left, right) {
|
|
73
|
+
if (left.columns.length !== right.columns.length || left.rows.length !== right.rows.length) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
for (let index = 0; index < left.columns.length; index += 1) {
|
|
77
|
+
if (left.columns[index] !== right.columns[index])
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
for (let rowIndex = 0; rowIndex < left.rows.length; rowIndex += 1) {
|
|
81
|
+
const leftRow = left.rows[rowIndex];
|
|
82
|
+
const rightRow = right.rows[rowIndex];
|
|
83
|
+
if (leftRow === undefined || rightRow === undefined)
|
|
84
|
+
return false;
|
|
85
|
+
for (const column of left.columns) {
|
|
86
|
+
if (!sameQueryValue(leftRow[column] ?? null, rightRow[column] ?? null))
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
function cloneRow(row, columns) {
|
|
93
|
+
const cloned = {};
|
|
94
|
+
for (const column of columns) {
|
|
95
|
+
const value = row[column] ?? null;
|
|
96
|
+
const copy = value instanceof Date ? copyDate(value) : value;
|
|
97
|
+
if (column === "__proto__") {
|
|
98
|
+
Object.defineProperty(cloned, column, {
|
|
99
|
+
value: copy,
|
|
100
|
+
enumerable: true,
|
|
101
|
+
configurable: true,
|
|
102
|
+
writable: true,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
else
|
|
106
|
+
cloned[column] = copy;
|
|
107
|
+
}
|
|
108
|
+
return cloned;
|
|
109
|
+
}
|
|
110
|
+
function cloneResult(result) {
|
|
111
|
+
const columns = [...result.columns];
|
|
112
|
+
return { columns, rows: result.rows.map((row) => cloneRow(row, columns)) };
|
|
113
|
+
}
|
|
114
|
+
/** Type-tagged, length-delimited structural identity. Unlike JSON, it preserves Date vs string,
|
|
115
|
+
* -0, non-finite numbers, and undefined fields, so deduplication cannot merge distinct plans. */
|
|
116
|
+
function encodeQueryIdentity(value, ancestors = new Set()) {
|
|
117
|
+
if (value === null)
|
|
118
|
+
return "z";
|
|
119
|
+
if (typeof value === "undefined")
|
|
120
|
+
return "u";
|
|
121
|
+
if (typeof value === "boolean")
|
|
122
|
+
return value ? "b1" : "b0";
|
|
123
|
+
if (typeof value === "number") {
|
|
124
|
+
if (Number.isNaN(value))
|
|
125
|
+
return "nNaN;";
|
|
126
|
+
if (Object.is(value, -0))
|
|
127
|
+
return "n-0;";
|
|
128
|
+
return `n${String(value)};`;
|
|
129
|
+
}
|
|
130
|
+
if (typeof value === "string")
|
|
131
|
+
return `s${String(value.length)}:${value}`;
|
|
132
|
+
if (value instanceof Date)
|
|
133
|
+
return `d${String(dateMilliseconds(value))};`;
|
|
134
|
+
if (typeof value !== "object") {
|
|
135
|
+
throw new TypeError(`Unsupported live-query identity value: ${typeof value}`);
|
|
136
|
+
}
|
|
137
|
+
if (ancestors.has(value))
|
|
138
|
+
throw new TypeError("Live-query identity contains a cycle");
|
|
139
|
+
ancestors.add(value);
|
|
140
|
+
let encoded;
|
|
141
|
+
if (Array.isArray(value)) {
|
|
142
|
+
encoded = `a${String(value.length)}[${value
|
|
143
|
+
.map((item) => encodeQueryIdentity(item, ancestors))
|
|
144
|
+
.join("")}]`;
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
const record = value;
|
|
148
|
+
const keys = Object.keys(record).sort();
|
|
149
|
+
encoded = `o${String(keys.length)}{${keys
|
|
150
|
+
.map((key) => `${encodeQueryIdentity(key, ancestors)}${encodeQueryIdentity(record[key], ancestors)}`)
|
|
151
|
+
.join("")}}`;
|
|
152
|
+
}
|
|
153
|
+
ancestors.delete(value);
|
|
154
|
+
return encoded;
|
|
155
|
+
}
|
|
156
|
+
function queryKey(query) {
|
|
157
|
+
return encodeQueryIdentity(typeof query === "string"
|
|
158
|
+
? ["sql", query]
|
|
159
|
+
: query.kind === "sql-query"
|
|
160
|
+
? ["sql-query", query.sql, query.params]
|
|
161
|
+
: ["typed-query", query.plan]);
|
|
162
|
+
}
|
|
163
|
+
function sameProbe(left, right) {
|
|
164
|
+
return left.manifestVersion === right.manifestVersion && left.schemaEpoch === right.schemaEpoch;
|
|
165
|
+
}
|
|
166
|
+
function versionOrdinal(version) {
|
|
167
|
+
return version ?? -1;
|
|
168
|
+
}
|
|
169
|
+
function boundedLiveLimit(value, maximum, label) {
|
|
170
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > maximum) {
|
|
171
|
+
throw new RangeError(`Live query ${label} limit must be between 1 and ${String(maximum)}`);
|
|
172
|
+
}
|
|
173
|
+
return value;
|
|
174
|
+
}
|
|
175
|
+
/** Structural changes have their own counter so ordinary commits and physical-index state churn
|
|
176
|
+
* do not force dependency resolution. A manifest reset is still widened: it can only result from
|
|
177
|
+
* replacing or rebuilding the durable database state. */
|
|
178
|
+
function catalogChangedBetween(previous, current) {
|
|
179
|
+
return (versionOrdinal(current.manifestVersion) < versionOrdinal(previous.manifestVersion) ||
|
|
180
|
+
current.schemaEpoch !== previous.schemaEpoch);
|
|
181
|
+
}
|
|
55
182
|
export class LiveQuerySet {
|
|
56
183
|
#host;
|
|
57
184
|
#channel;
|
|
58
185
|
#channelListener = () => {
|
|
59
186
|
this.#hint();
|
|
60
187
|
};
|
|
61
|
-
#
|
|
188
|
+
#groups = new Map();
|
|
189
|
+
#opening = new Map();
|
|
190
|
+
#groupsByTable = new Map();
|
|
191
|
+
#maxGroups;
|
|
192
|
+
#maxSubscriptions;
|
|
62
193
|
#stats = {
|
|
63
194
|
hints: 0,
|
|
64
195
|
versionChecks: 0,
|
|
@@ -67,16 +198,25 @@ export class LiveQuerySet {
|
|
|
67
198
|
rerunsAvoided: 0,
|
|
68
199
|
zoneSkips: 0,
|
|
69
200
|
notificationsSuppressed: 0,
|
|
201
|
+
invalidations: 0,
|
|
202
|
+
sharedExecutions: 0,
|
|
70
203
|
lastSweepMs: 0,
|
|
71
204
|
};
|
|
72
|
-
#
|
|
205
|
+
#lastProbe;
|
|
73
206
|
#sweepChain = Promise.resolve();
|
|
74
207
|
#sweepQueued = false;
|
|
75
208
|
#pollTimer;
|
|
76
209
|
#closed = false;
|
|
210
|
+
#subscriptionCount = 0;
|
|
77
211
|
#ownsChannel;
|
|
78
212
|
#onClosed;
|
|
79
213
|
constructor(host, options = {}) {
|
|
214
|
+
if (options.pollIntervalMs !== undefined &&
|
|
215
|
+
(!Number.isSafeInteger(options.pollIntervalMs) || options.pollIntervalMs <= 0)) {
|
|
216
|
+
throw new RangeError("Live query poll interval must be a positive whole number");
|
|
217
|
+
}
|
|
218
|
+
this.#maxGroups = boundedLiveLimit(options.maxGroups ?? DEFAULT_LIVE_QUERY_MAX_GROUPS, MAX_LIVE_QUERY_GROUPS, "group");
|
|
219
|
+
this.#maxSubscriptions = boundedLiveLimit(options.maxSubscriptions ?? DEFAULT_LIVE_QUERY_MAX_SUBSCRIPTIONS, MAX_LIVE_QUERY_SUBSCRIPTIONS, "subscription");
|
|
80
220
|
this.#host = host;
|
|
81
221
|
if (options.channel !== undefined) {
|
|
82
222
|
this.#channel = options.channel;
|
|
@@ -105,56 +245,239 @@ export class LiveQuerySet {
|
|
|
105
245
|
if (this.#closed)
|
|
106
246
|
throw new Error("Live query set is closed");
|
|
107
247
|
}
|
|
108
|
-
|
|
248
|
+
#reserveSubscription() {
|
|
249
|
+
if (this.#subscriptionCount >= this.#maxSubscriptions) {
|
|
250
|
+
throw new LiveQueryLimitError("subscription", this.#maxSubscriptions);
|
|
251
|
+
}
|
|
252
|
+
this.#subscriptionCount += 1;
|
|
253
|
+
}
|
|
254
|
+
/** Registers a query, delivers its current result, and shares work with equal statements. */
|
|
109
255
|
async subscribe(query, options) {
|
|
110
256
|
this.#throwIfClosed();
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
257
|
+
this.#reserveSubscription();
|
|
258
|
+
let group;
|
|
259
|
+
let subscriber;
|
|
260
|
+
try {
|
|
261
|
+
group = await this.#getOrOpenGroup(query);
|
|
262
|
+
this.#throwIfClosed();
|
|
263
|
+
subscriber = {
|
|
264
|
+
kind: "result",
|
|
265
|
+
options,
|
|
266
|
+
delivered: false,
|
|
267
|
+
closed: false,
|
|
268
|
+
};
|
|
269
|
+
group.subscribers.add(subscriber);
|
|
270
|
+
// Reconcile before exposing a shared cached result to a late subscriber.
|
|
271
|
+
await this.refresh();
|
|
272
|
+
this.#throwIfClosed();
|
|
273
|
+
if (!subscriber.delivered) {
|
|
274
|
+
const result = group.result ?? (await this.#executeGroup(group)).result;
|
|
275
|
+
this.#throwIfClosed();
|
|
276
|
+
if (!subscriber.closed)
|
|
277
|
+
this.#deliverResult(subscriber, result);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
if (group !== undefined && subscriber !== undefined) {
|
|
282
|
+
this.#removeSubscriber(group, subscriber, false);
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
this.#subscriptionCount -= 1;
|
|
286
|
+
if (group !== undefined)
|
|
287
|
+
this.#removeEmptyGroup(group);
|
|
288
|
+
}
|
|
289
|
+
throw error;
|
|
125
290
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
291
|
+
return this.#subscriptionHandle(group, subscriber);
|
|
292
|
+
}
|
|
293
|
+
/** Observes invalidation without executing the statement inside the set. */
|
|
294
|
+
async observe(query, options) {
|
|
129
295
|
this.#throwIfClosed();
|
|
130
|
-
|
|
131
|
-
|
|
296
|
+
this.#reserveSubscription();
|
|
297
|
+
let group;
|
|
298
|
+
let subscriber;
|
|
132
299
|
try {
|
|
133
|
-
|
|
300
|
+
group = await this.#getOrOpenGroup(query);
|
|
301
|
+
this.#throwIfClosed();
|
|
302
|
+
subscriber = {
|
|
303
|
+
kind: "observer",
|
|
304
|
+
options,
|
|
305
|
+
delivered: false,
|
|
306
|
+
closed: false,
|
|
307
|
+
};
|
|
308
|
+
group.subscribers.add(subscriber);
|
|
309
|
+
await this.refresh();
|
|
310
|
+
this.#throwIfClosed();
|
|
311
|
+
if (!subscriber.delivered) {
|
|
312
|
+
this.#deliverInvalidation(subscriber, { ...group.seenProbe, initial: true });
|
|
313
|
+
}
|
|
134
314
|
}
|
|
135
315
|
catch (error) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
316
|
+
if (group !== undefined && subscriber !== undefined) {
|
|
317
|
+
this.#removeSubscriber(group, subscriber, false);
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
this.#subscriptionCount -= 1;
|
|
321
|
+
if (group !== undefined)
|
|
322
|
+
this.#removeEmptyGroup(group);
|
|
323
|
+
}
|
|
139
324
|
throw error;
|
|
140
325
|
}
|
|
326
|
+
return this.#subscriptionHandle(group, subscriber);
|
|
327
|
+
}
|
|
328
|
+
#subscriptionHandle(group, subscriber) {
|
|
141
329
|
return {
|
|
142
|
-
dependencyTableIds: [...dependencies],
|
|
330
|
+
dependencyTableIds: [...group.dependencies],
|
|
143
331
|
close: () => {
|
|
144
|
-
|
|
145
|
-
return;
|
|
146
|
-
subscription.closed = true;
|
|
147
|
-
this.#subscriptions.delete(subscription);
|
|
148
|
-
subscription.options.onComplete?.();
|
|
332
|
+
this.#removeSubscriber(group, subscriber, true);
|
|
149
333
|
},
|
|
150
334
|
};
|
|
151
335
|
}
|
|
336
|
+
#removeSubscriber(group, subscriber, complete) {
|
|
337
|
+
if (subscriber.closed)
|
|
338
|
+
return;
|
|
339
|
+
subscriber.closed = true;
|
|
340
|
+
this.#subscriptionCount -= 1;
|
|
341
|
+
group.subscribers.delete(subscriber);
|
|
342
|
+
if (complete)
|
|
343
|
+
subscriber.options.onComplete?.();
|
|
344
|
+
this.#removeEmptyGroup(group);
|
|
345
|
+
}
|
|
346
|
+
#removeEmptyGroup(group) {
|
|
347
|
+
if (group.subscribers.size !== 0)
|
|
348
|
+
return;
|
|
349
|
+
if (this.#groups.get(group.key) !== group)
|
|
350
|
+
return;
|
|
351
|
+
this.#groups.delete(group.key);
|
|
352
|
+
this.#unindexGroup(group, group.dependencies);
|
|
353
|
+
}
|
|
354
|
+
async #getOrOpenGroup(query) {
|
|
355
|
+
const key = queryKey(query);
|
|
356
|
+
const existing = this.#groups.get(key);
|
|
357
|
+
if (existing !== undefined) {
|
|
358
|
+
this.#stats.sharedExecutions += 1;
|
|
359
|
+
return existing;
|
|
360
|
+
}
|
|
361
|
+
const opening = this.#opening.get(key);
|
|
362
|
+
if (opening !== undefined) {
|
|
363
|
+
this.#stats.sharedExecutions += 1;
|
|
364
|
+
return opening;
|
|
365
|
+
}
|
|
366
|
+
if (this.#groups.size + this.#opening.size >= this.#maxGroups) {
|
|
367
|
+
throw new LiveQueryLimitError("group", this.#maxGroups);
|
|
368
|
+
}
|
|
369
|
+
const created = this.#openGroup(key, query);
|
|
370
|
+
this.#opening.set(key, created);
|
|
371
|
+
try {
|
|
372
|
+
return await created;
|
|
373
|
+
}
|
|
374
|
+
finally {
|
|
375
|
+
this.#opening.delete(key);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
async #openGroup(key, query) {
|
|
379
|
+
const { dependencies, probe: after } = await this.#stableDependencies(query);
|
|
380
|
+
const group = {
|
|
381
|
+
key,
|
|
382
|
+
query,
|
|
383
|
+
dependencies,
|
|
384
|
+
subscribers: new Set(),
|
|
385
|
+
seenProbe: after,
|
|
386
|
+
result: undefined,
|
|
387
|
+
digest: undefined,
|
|
388
|
+
execution: undefined,
|
|
389
|
+
};
|
|
390
|
+
this.#groups.set(key, group);
|
|
391
|
+
this.#indexGroup(group, dependencies);
|
|
392
|
+
if (this.#lastProbe === undefined ||
|
|
393
|
+
versionOrdinal(after.manifestVersion) < versionOrdinal(this.#lastProbe.manifestVersion)) {
|
|
394
|
+
this.#lastProbe = after;
|
|
395
|
+
}
|
|
396
|
+
return group;
|
|
397
|
+
}
|
|
398
|
+
#indexGroup(group, dependencies) {
|
|
399
|
+
for (const tableId of dependencies) {
|
|
400
|
+
let groups = this.#groupsByTable.get(tableId);
|
|
401
|
+
if (groups === undefined) {
|
|
402
|
+
groups = new Set();
|
|
403
|
+
this.#groupsByTable.set(tableId, groups);
|
|
404
|
+
}
|
|
405
|
+
groups.add(group);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
#unindexGroup(group, dependencies) {
|
|
409
|
+
for (const tableId of dependencies) {
|
|
410
|
+
const groups = this.#groupsByTable.get(tableId);
|
|
411
|
+
groups?.delete(group);
|
|
412
|
+
if (groups?.size === 0)
|
|
413
|
+
this.#groupsByTable.delete(tableId);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
async #refreshDependencies(group) {
|
|
417
|
+
const { dependencies: next } = await this.#stableDependencies(group.query);
|
|
418
|
+
if (!this.#stillOpen() || !this.#groups.has(group.key))
|
|
419
|
+
return;
|
|
420
|
+
this.#unindexGroup(group, group.dependencies);
|
|
421
|
+
group.dependencies = next;
|
|
422
|
+
this.#indexGroup(group, next);
|
|
423
|
+
}
|
|
424
|
+
/** Resolve a view/table set inside a catalog-stable probe window or refuse stale indexing. */
|
|
425
|
+
async #stableDependencies(query) {
|
|
426
|
+
let before = await this.#host.currentProbe();
|
|
427
|
+
this.#throwIfClosed();
|
|
428
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
429
|
+
const dependencies = await this.#host.dependencyTableIds(query);
|
|
430
|
+
this.#throwIfClosed();
|
|
431
|
+
const after = await this.#host.currentProbe();
|
|
432
|
+
this.#throwIfClosed();
|
|
433
|
+
if (!catalogChangedBetween(before, after))
|
|
434
|
+
return { dependencies, probe: after };
|
|
435
|
+
before = after;
|
|
436
|
+
}
|
|
437
|
+
throw new Error("Catalog kept changing while live-query dependencies were resolved");
|
|
438
|
+
}
|
|
439
|
+
async #executeGroup(group) {
|
|
440
|
+
if (group.execution !== undefined) {
|
|
441
|
+
this.#stats.sharedExecutions += 1;
|
|
442
|
+
return group.execution;
|
|
443
|
+
}
|
|
444
|
+
const execution = (async () => {
|
|
445
|
+
const executed = await this.#host.execute(group.query);
|
|
446
|
+
const digest = digestResult(executed);
|
|
447
|
+
const previous = group.result;
|
|
448
|
+
const changed = previous === undefined || group.digest !== digest || !sameResult(previous, executed);
|
|
449
|
+
const retained = cloneResult(executed);
|
|
450
|
+
group.result = retained;
|
|
451
|
+
group.digest = digest;
|
|
452
|
+
return { result: retained, changed };
|
|
453
|
+
})();
|
|
454
|
+
group.execution = execution;
|
|
455
|
+
try {
|
|
456
|
+
return await execution;
|
|
457
|
+
}
|
|
458
|
+
finally {
|
|
459
|
+
if (group.execution === execution)
|
|
460
|
+
group.execution = undefined;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
#deliverResult(subscriber, result) {
|
|
464
|
+
if (subscriber.closed)
|
|
465
|
+
return;
|
|
466
|
+
subscriber.delivered = true;
|
|
467
|
+
subscriber.options.onChange(cloneResult(result));
|
|
468
|
+
}
|
|
469
|
+
#deliverInvalidation(subscriber, invalidation) {
|
|
470
|
+
if (subscriber.closed)
|
|
471
|
+
return;
|
|
472
|
+
subscriber.delivered = true;
|
|
473
|
+
subscriber.options.onInvalidate(invalidation);
|
|
474
|
+
}
|
|
152
475
|
/** Called by the owning database after each local write commit; also hints other tabs. */
|
|
153
476
|
notifyLocalCommit() {
|
|
154
477
|
this.#channel?.postMessage("minnow-commit");
|
|
155
478
|
this.#hint();
|
|
156
479
|
}
|
|
157
|
-
/** Runs one authoritative
|
|
480
|
+
/** Runs one authoritative probe and selective re-execution sweep. */
|
|
158
481
|
async refresh() {
|
|
159
482
|
this.#hint();
|
|
160
483
|
await this.#sweepChain;
|
|
@@ -168,14 +491,19 @@ export class LiveQuerySet {
|
|
|
168
491
|
this.#channel?.close?.();
|
|
169
492
|
if (this.#pollTimer !== undefined)
|
|
170
493
|
clearInterval(this.#pollTimer);
|
|
171
|
-
const
|
|
172
|
-
this.#
|
|
173
|
-
|
|
174
|
-
for (const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
494
|
+
const groups = [...this.#groups.values()];
|
|
495
|
+
this.#groups.clear();
|
|
496
|
+
this.#groupsByTable.clear();
|
|
497
|
+
for (const group of groups) {
|
|
498
|
+
const subscribers = [...group.subscribers];
|
|
499
|
+
group.subscribers.clear();
|
|
500
|
+
for (const subscriber of subscribers) {
|
|
501
|
+
if (subscriber.closed)
|
|
502
|
+
continue;
|
|
503
|
+
subscriber.closed = true;
|
|
504
|
+
this.#subscriptionCount -= 1;
|
|
505
|
+
subscriber.options.onComplete?.();
|
|
506
|
+
}
|
|
179
507
|
}
|
|
180
508
|
this.#onClosed?.();
|
|
181
509
|
}
|
|
@@ -192,107 +520,152 @@ export class LiveQuerySet {
|
|
|
192
520
|
await this.#sweep();
|
|
193
521
|
}
|
|
194
522
|
catch (error) {
|
|
195
|
-
for (const
|
|
196
|
-
|
|
197
|
-
}
|
|
523
|
+
for (const group of this.#groups.values())
|
|
524
|
+
this.#notifyGroupError(group, error);
|
|
198
525
|
}
|
|
199
526
|
});
|
|
200
527
|
}
|
|
201
|
-
/**
|
|
202
|
-
* Re-reads the closed flags through calls. `close()` runs while a sweep is suspended at an
|
|
203
|
-
* await, but control-flow analysis assumes flags cannot change across one, so a direct read
|
|
204
|
-
* would be narrowed to its pre-await value and the guard silently dropped.
|
|
205
|
-
*/
|
|
206
528
|
#stillOpen() {
|
|
207
529
|
return !this.#closed;
|
|
208
530
|
}
|
|
209
531
|
async #sweep() {
|
|
210
|
-
if (this.#closed || this.#
|
|
532
|
+
if (this.#closed || this.#groups.size === 0)
|
|
211
533
|
return;
|
|
212
534
|
this.#stats.versionChecks += 1;
|
|
213
|
-
const current = await this.#host.
|
|
214
|
-
if (!this.#stillOpen()
|
|
535
|
+
const current = await this.#host.currentProbe();
|
|
536
|
+
if (!this.#stillOpen())
|
|
215
537
|
return;
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
if (current
|
|
538
|
+
const last = this.#lastProbe ?? current;
|
|
539
|
+
const anyLagging = [...this.#groups.values()].some((group) => !sameProbe(group.seenProbe, current));
|
|
540
|
+
if (sameProbe(last, current) && !anyLagging)
|
|
219
541
|
return;
|
|
220
542
|
const started = performance.now();
|
|
221
543
|
this.#stats.sweeps += 1;
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
544
|
+
const windowCache = new Map();
|
|
545
|
+
const changedSince = (after) => {
|
|
546
|
+
const key = versionOrdinal(after);
|
|
547
|
+
let pending = windowCache.get(key);
|
|
548
|
+
if (pending === undefined) {
|
|
549
|
+
pending = this.#changedTablesSince(after, current.manifestVersion);
|
|
550
|
+
windowCache.set(key, pending);
|
|
551
|
+
}
|
|
552
|
+
return pending;
|
|
553
|
+
};
|
|
554
|
+
for (const group of [...this.#groups.values()]) {
|
|
555
|
+
if (!this.#stillOpen())
|
|
556
|
+
return;
|
|
557
|
+
if (group.subscribers.size === 0 || sameProbe(group.seenProbe, current))
|
|
232
558
|
continue;
|
|
233
|
-
|
|
559
|
+
const prior = group.seenProbe;
|
|
560
|
+
const catalogChanged = catalogChangedBetween(prior, current);
|
|
561
|
+
if (catalogChanged) {
|
|
562
|
+
try {
|
|
563
|
+
await this.#refreshDependencies(group);
|
|
564
|
+
}
|
|
565
|
+
catch (error) {
|
|
566
|
+
this.#notifyGroupError(group, error);
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
let relevant = [];
|
|
571
|
+
let affected = catalogChanged;
|
|
572
|
+
if (!affected && prior.manifestVersion !== current.manifestVersion) {
|
|
573
|
+
const changed = await changedSince(prior.manifestVersion);
|
|
574
|
+
if (!this.#stillOpen())
|
|
575
|
+
return;
|
|
576
|
+
if (changed === "all")
|
|
577
|
+
affected = true;
|
|
578
|
+
else {
|
|
579
|
+
relevant = [...group.dependencies].filter((tableId) => changed.has(tableId));
|
|
580
|
+
affected = relevant.length > 0;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
if (!affected) {
|
|
584
|
+
this.#stats.rerunsAvoided += 1;
|
|
585
|
+
group.seenProbe = current;
|
|
234
586
|
continue;
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
587
|
+
}
|
|
588
|
+
if (!catalogChanged &&
|
|
589
|
+
relevant.length > 0 &&
|
|
590
|
+
current.manifestVersion !== null &&
|
|
591
|
+
this.#host.changeCanAffect !== undefined) {
|
|
592
|
+
let canAffect;
|
|
593
|
+
try {
|
|
594
|
+
canAffect = await this.#host.changeCanAffect(group.query, relevant, prior.manifestVersion, current.manifestVersion);
|
|
595
|
+
}
|
|
596
|
+
catch {
|
|
597
|
+
canAffect = true;
|
|
598
|
+
}
|
|
599
|
+
if (!this.#stillOpen())
|
|
600
|
+
return;
|
|
601
|
+
if (!canAffect) {
|
|
239
602
|
this.#stats.rerunsAvoided += 1;
|
|
240
|
-
|
|
603
|
+
this.#stats.zoneSkips += 1;
|
|
604
|
+
group.seenProbe = current;
|
|
241
605
|
continue;
|
|
242
606
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if (isSubscriptionClosed(subscription))
|
|
607
|
+
}
|
|
608
|
+
const resultSubscribers = [...group.subscribers].filter((subscriber) => subscriber.kind === "result" && !subscriber.closed);
|
|
609
|
+
const observers = [...group.subscribers].filter((subscriber) => subscriber.kind === "observer" && !subscriber.closed);
|
|
610
|
+
try {
|
|
611
|
+
let execution;
|
|
612
|
+
if (resultSubscribers.length > 0) {
|
|
613
|
+
// An initial delivery may be executing against the pre-sweep snapshot. Waiting for it
|
|
614
|
+
// here can deadlock callers that intentionally hold that execute while awaiting this
|
|
615
|
+
// refresh. Leave the group dirty; once the initial delivery lands, the next refresh
|
|
616
|
+
// re-runs it against `current`.
|
|
617
|
+
if (group.result === undefined && group.execution !== undefined)
|
|
255
618
|
continue;
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
subscription.seenVersion = current;
|
|
619
|
+
this.#stats.reruns += 1;
|
|
620
|
+
execution = await this.#executeGroup(group);
|
|
621
|
+
if (!this.#stillOpen() || !this.#groups.has(group.key))
|
|
260
622
|
continue;
|
|
623
|
+
}
|
|
624
|
+
for (const observer of observers) {
|
|
625
|
+
try {
|
|
626
|
+
this.#deliverInvalidation(observer, { ...current, initial: false });
|
|
627
|
+
this.#stats.invalidations += 1;
|
|
628
|
+
}
|
|
629
|
+
catch (error) {
|
|
630
|
+
observer.options.onError?.(error);
|
|
261
631
|
}
|
|
262
632
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
633
|
+
if (execution !== undefined) {
|
|
634
|
+
if (execution.changed) {
|
|
635
|
+
for (const subscriber of resultSubscribers) {
|
|
636
|
+
try {
|
|
637
|
+
this.#deliverResult(subscriber, execution.result);
|
|
638
|
+
}
|
|
639
|
+
catch (error) {
|
|
640
|
+
subscriber.options.onError?.(error);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
else {
|
|
645
|
+
this.#stats.notificationsSuppressed += resultSubscribers.length;
|
|
646
|
+
}
|
|
276
647
|
}
|
|
277
|
-
|
|
278
|
-
subscription.options.onChange(result);
|
|
648
|
+
group.seenProbe = current;
|
|
279
649
|
}
|
|
280
650
|
catch (error) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
subscription.seenVersion = current;
|
|
284
|
-
subscription.options.onError?.(error);
|
|
651
|
+
// Do not advance seenProbe. A refresh with no newer commit retries the dirty group.
|
|
652
|
+
this.#notifyGroupError(group, error);
|
|
285
653
|
}
|
|
286
654
|
}
|
|
287
|
-
this.#
|
|
655
|
+
this.#lastProbe = current;
|
|
288
656
|
this.#stats.lastSweepMs = performance.now() - started;
|
|
289
657
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
658
|
+
#notifyGroupError(group, error) {
|
|
659
|
+
for (const subscriber of group.subscribers) {
|
|
660
|
+
if (subscriber.closed)
|
|
661
|
+
continue;
|
|
662
|
+
subscriber.options.onError?.(error);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
/** Unions persisted table change sets in (after, until], widening on any history gap. */
|
|
295
666
|
async #changedTablesSince(after, until) {
|
|
667
|
+
if (until === null || after === until)
|
|
668
|
+
return new Set();
|
|
296
669
|
const changed = new Set();
|
|
297
670
|
let cursor = after;
|
|
298
671
|
let expected = (after ?? -1) + 1;
|
|
@@ -304,18 +677,15 @@ export class LiveQuerySet {
|
|
|
304
677
|
if (manifest.version !== expected)
|
|
305
678
|
return "all";
|
|
306
679
|
expected += 1;
|
|
307
|
-
if (manifest.changedTableIds === undefined)
|
|
308
|
-
return "all";
|
|
309
680
|
for (const tableId of manifest.changedTableIds)
|
|
310
681
|
changed.add(tableId);
|
|
311
682
|
if (manifest.version === until)
|
|
312
683
|
return changed;
|
|
313
684
|
}
|
|
314
|
-
if (page.nextCursor === null)
|
|
685
|
+
if (page.nextCursor === null)
|
|
315
686
|
return expected > until ? changed : "all";
|
|
316
|
-
}
|
|
317
687
|
cursor = page.nextCursor;
|
|
318
688
|
}
|
|
319
689
|
}
|
|
320
690
|
}
|
|
321
|
-
|
|
691
|
+
import { copyDate, dateMilliseconds } from "../date-value.js";
|