@minnowdb/core 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -32
- 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 +2 -1
- package/dist/engine/artifact-cache.js +5 -1
- package/dist/engine/batch.d.ts +11 -3
- package/dist/engine/batch.js +17 -7
- 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 +24 -5
- package/dist/engine/catalog.js +34 -7
- package/dist/engine/client.d.ts +29 -7
- package/dist/engine/client.js +216 -40
- package/dist/engine/database.d.ts +224 -39
- package/dist/engine/database.js +7475 -1539
- package/dist/engine/defaults.d.ts +4 -8
- package/dist/engine/defaults.js +46 -22
- 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 +41 -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 -10
- package/dist/engine/index.js +12 -9
- 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 +504 -126
- 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 +262 -27
- package/dist/engine/query-api.d.ts +3 -0
- package/dist/engine/query-api.js +3 -0
- package/dist/engine/query-cache.d.ts +1 -2
- package/dist/engine/query-cache.js +7 -5
- package/dist/engine/query.d.ts +134 -29
- package/dist/engine/query.js +1668 -372
- package/dist/engine/result-wire.d.ts +0 -1
- package/dist/engine/result-wire.js +2 -2
- package/dist/engine/schema-wire.d.ts +6 -3
- package/dist/engine/schema-wire.js +40 -34
- package/dist/engine/schema.d.ts +156 -80
- package/dist/engine/schema.js +570 -108
- package/dist/engine/sort-keys.d.ts +4 -4
- package/dist/engine/sort-keys.js +44 -29
- package/dist/engine/sql-domains.d.ts +31 -0
- package/dist/engine/sql-domains.js +585 -0
- package/dist/engine/sql-driver.d.ts +19 -0
- package/dist/engine/sql-driver.js +1 -0
- package/dist/engine/sql-json.d.ts +7 -9
- package/dist/engine/sql-json.js +75 -15
- package/dist/engine/sql-semantics.d.ts +8 -3
- package/dist/engine/sql-semantics.js +458 -30
- package/dist/engine/typed-live.d.ts +67 -0
- package/dist/engine/typed-live.js +349 -0
- package/dist/engine/vector.d.ts +12 -2
- package/dist/engine/vector.js +635 -119
- package/dist/engine/worker-host.d.ts +9 -2
- package/dist/engine/worker-host.js +620 -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 +3 -4
- package/dist/plan/index.js +3 -4
- package/dist/storage/index.d.ts +1 -1
- package/dist/storage/index.js +1 -1
- package/dist/storage/indexeddb.d.ts +99 -51
- package/dist/storage/indexeddb.js +13448 -2660
- package/dist/storage/memory.d.ts +64 -57
- package/dist/storage/memory.js +1042 -128
- 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 +409 -75
- package/dist/storage/opfs/leader.js +4621 -650
- package/dist/storage/opfs/rpc.d.ts +15 -3
- package/dist/storage/opfs/rpc.js +224 -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 +33 -99
- package/dist/storage/opfs/store.js +453 -364
- 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 -85
- package/dist/storage/snapshot.js +33 -265
- package/dist/storage/toolkit/extents.d.ts +41 -7
- package/dist/storage/toolkit/extents.js +402 -39
- package/dist/storage/toolkit/index.d.ts +4 -5
- package/dist/storage/toolkit/index.js +28 -4
- package/dist/storage/toolkit/record-core.d.ts +182 -55
- package/dist/storage/toolkit/record-core.js +6158 -1331
- 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 +15 -2
- package/dist/storage/toolkit/wire.js +199 -10
- package/dist/storage/types.d.ts +1439 -204
- package/dist/storage/types.js +1745 -141
- package/dist/testing/block-store-conformance.d.ts +0 -1
- package/dist/testing/block-store-conformance.js +1009 -119
- package/dist/testing/index.d.ts +46 -26
- package/dist/testing/index.js +112 -57
- package/dist/testing/opfs-shim.d.ts +13 -3
- package/dist/testing/opfs-shim.js +40 -6
- package/dist/testing/simulator.d.ts +97 -0
- package/dist/testing/simulator.js +591 -0
- package/dist/testing/sqllogictest.d.ts +89 -0
- package/dist/testing/sqllogictest.js +434 -0
- package/dist/transactions/index.d.ts +92 -16
- package/dist/transactions/index.js +1074 -236
- package/dist/worker-protocol/index.d.ts +3 -2
- package/dist/worker-protocol/index.js +2 -5
- package/package.json +53 -3
- package/postgres-feature-profile.json +223 -0
- package/sql-feature-matrix.json +172 -252
- 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-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/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;
|
|
@@ -101,52 +241,243 @@ export class LiveQuerySet {
|
|
|
101
241
|
get stats() {
|
|
102
242
|
return { ...this.#stats };
|
|
103
243
|
}
|
|
104
|
-
|
|
105
|
-
async subscribe(query, options) {
|
|
244
|
+
#throwIfClosed() {
|
|
106
245
|
if (this.#closed)
|
|
107
246
|
throw new Error("Live query set is closed");
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
247
|
+
}
|
|
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. */
|
|
255
|
+
async subscribe(query, options) {
|
|
256
|
+
this.#throwIfClosed();
|
|
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;
|
|
120
290
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
291
|
+
return this.#subscriptionHandle(group, subscriber);
|
|
292
|
+
}
|
|
293
|
+
/** Observes invalidation without executing the statement inside the set. */
|
|
294
|
+
async observe(query, options) {
|
|
295
|
+
this.#throwIfClosed();
|
|
296
|
+
this.#reserveSubscription();
|
|
297
|
+
let group;
|
|
298
|
+
let subscriber;
|
|
124
299
|
try {
|
|
125
|
-
|
|
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
|
+
}
|
|
126
314
|
}
|
|
127
315
|
catch (error) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
+
}
|
|
131
324
|
throw error;
|
|
132
325
|
}
|
|
326
|
+
return this.#subscriptionHandle(group, subscriber);
|
|
327
|
+
}
|
|
328
|
+
#subscriptionHandle(group, subscriber) {
|
|
133
329
|
return {
|
|
134
|
-
dependencyTableIds: [...dependencies],
|
|
330
|
+
dependencyTableIds: [...group.dependencies],
|
|
135
331
|
close: () => {
|
|
136
|
-
|
|
137
|
-
return;
|
|
138
|
-
subscription.closed = true;
|
|
139
|
-
this.#subscriptions.delete(subscription);
|
|
140
|
-
subscription.options.onComplete?.();
|
|
332
|
+
this.#removeSubscriber(group, subscriber, true);
|
|
141
333
|
},
|
|
142
334
|
};
|
|
143
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
|
+
}
|
|
144
475
|
/** Called by the owning database after each local write commit; also hints other tabs. */
|
|
145
476
|
notifyLocalCommit() {
|
|
146
477
|
this.#channel?.postMessage("minnow-commit");
|
|
147
478
|
this.#hint();
|
|
148
479
|
}
|
|
149
|
-
/** Runs one authoritative
|
|
480
|
+
/** Runs one authoritative probe and selective re-execution sweep. */
|
|
150
481
|
async refresh() {
|
|
151
482
|
this.#hint();
|
|
152
483
|
await this.#sweepChain;
|
|
@@ -160,14 +491,19 @@ export class LiveQuerySet {
|
|
|
160
491
|
this.#channel?.close?.();
|
|
161
492
|
if (this.#pollTimer !== undefined)
|
|
162
493
|
clearInterval(this.#pollTimer);
|
|
163
|
-
const
|
|
164
|
-
this.#
|
|
165
|
-
|
|
166
|
-
for (const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
+
}
|
|
171
507
|
}
|
|
172
508
|
this.#onClosed?.();
|
|
173
509
|
}
|
|
@@ -184,107 +520,152 @@ export class LiveQuerySet {
|
|
|
184
520
|
await this.#sweep();
|
|
185
521
|
}
|
|
186
522
|
catch (error) {
|
|
187
|
-
for (const
|
|
188
|
-
|
|
189
|
-
}
|
|
523
|
+
for (const group of this.#groups.values())
|
|
524
|
+
this.#notifyGroupError(group, error);
|
|
190
525
|
}
|
|
191
526
|
});
|
|
192
527
|
}
|
|
193
|
-
/**
|
|
194
|
-
* Re-reads the closed flags through calls. `close()` runs while a sweep is suspended at an
|
|
195
|
-
* await, but control-flow analysis assumes flags cannot change across one, so a direct read
|
|
196
|
-
* would be narrowed to its pre-await value and the guard silently dropped.
|
|
197
|
-
*/
|
|
198
528
|
#stillOpen() {
|
|
199
529
|
return !this.#closed;
|
|
200
530
|
}
|
|
201
531
|
async #sweep() {
|
|
202
|
-
if (this.#closed || this.#
|
|
532
|
+
if (this.#closed || this.#groups.size === 0)
|
|
203
533
|
return;
|
|
204
534
|
this.#stats.versionChecks += 1;
|
|
205
|
-
const current = await this.#host.
|
|
206
|
-
if (!this.#stillOpen()
|
|
535
|
+
const current = await this.#host.currentProbe();
|
|
536
|
+
if (!this.#stillOpen())
|
|
207
537
|
return;
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
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)
|
|
211
541
|
return;
|
|
212
542
|
const started = performance.now();
|
|
213
543
|
this.#stats.sweeps += 1;
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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))
|
|
224
558
|
continue;
|
|
225
|
-
|
|
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;
|
|
226
586
|
continue;
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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) {
|
|
231
602
|
this.#stats.rerunsAvoided += 1;
|
|
232
|
-
|
|
603
|
+
this.#stats.zoneSkips += 1;
|
|
604
|
+
group.seenProbe = current;
|
|
233
605
|
continue;
|
|
234
606
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
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)
|
|
247
618
|
continue;
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
subscription.seenVersion = current;
|
|
619
|
+
this.#stats.reruns += 1;
|
|
620
|
+
execution = await this.#executeGroup(group);
|
|
621
|
+
if (!this.#stillOpen() || !this.#groups.has(group.key))
|
|
252
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);
|
|
253
631
|
}
|
|
254
632
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
+
}
|
|
268
647
|
}
|
|
269
|
-
|
|
270
|
-
subscription.options.onChange(result);
|
|
648
|
+
group.seenProbe = current;
|
|
271
649
|
}
|
|
272
650
|
catch (error) {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
subscription.seenVersion = current;
|
|
276
|
-
subscription.options.onError?.(error);
|
|
651
|
+
// Do not advance seenProbe. A refresh with no newer commit retries the dirty group.
|
|
652
|
+
this.#notifyGroupError(group, error);
|
|
277
653
|
}
|
|
278
654
|
}
|
|
279
|
-
this.#
|
|
655
|
+
this.#lastProbe = current;
|
|
280
656
|
this.#stats.lastSweepMs = performance.now() - started;
|
|
281
657
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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. */
|
|
287
666
|
async #changedTablesSince(after, until) {
|
|
667
|
+
if (until === null || after === until)
|
|
668
|
+
return new Set();
|
|
288
669
|
const changed = new Set();
|
|
289
670
|
let cursor = after;
|
|
290
671
|
let expected = (after ?? -1) + 1;
|
|
@@ -296,18 +677,15 @@ export class LiveQuerySet {
|
|
|
296
677
|
if (manifest.version !== expected)
|
|
297
678
|
return "all";
|
|
298
679
|
expected += 1;
|
|
299
|
-
if (manifest.changedTableIds === undefined)
|
|
300
|
-
return "all";
|
|
301
680
|
for (const tableId of manifest.changedTableIds)
|
|
302
681
|
changed.add(tableId);
|
|
303
682
|
if (manifest.version === until)
|
|
304
683
|
return changed;
|
|
305
684
|
}
|
|
306
|
-
if (page.nextCursor === null)
|
|
685
|
+
if (page.nextCursor === null)
|
|
307
686
|
return expected > until ? changed : "all";
|
|
308
|
-
}
|
|
309
687
|
cursor = page.nextCursor;
|
|
310
688
|
}
|
|
311
689
|
}
|
|
312
690
|
}
|
|
313
|
-
|
|
691
|
+
import { copyDate, dateMilliseconds } from "../date-value.js";
|