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