@minnowdb/core 0.6.9 → 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.
Files changed (103) hide show
  1. package/dist/block-format/block.js +254 -325
  2. package/dist/block-format/checksum.js +30 -52
  3. package/dist/block-format/codecs.js +112 -125
  4. package/dist/block-format/column.js +187 -205
  5. package/dist/block-format/index.js +5 -1
  6. package/dist/block-format/physical.js +371 -387
  7. package/dist/block-format/types.js +4 -1
  8. package/dist/block-format/unicode.js +30 -39
  9. package/dist/date-value.d.ts +5 -0
  10. package/dist/date-value.js +71 -41
  11. package/dist/engine/artifact-cache.js +106 -116
  12. package/dist/engine/batch.js +36 -44
  13. package/dist/engine/buffered-writer.js +156 -158
  14. package/dist/engine/byte-estimates.js +20 -22
  15. package/dist/engine/cache-limits.js +24 -25
  16. package/dist/engine/cancellation.js +5 -3
  17. package/dist/engine/catalog.js +64 -73
  18. package/dist/engine/client.js +715 -780
  19. package/dist/engine/database.js +15952 -18465
  20. package/dist/engine/defaults.js +104 -111
  21. package/dist/engine/errors.js +113 -128
  22. package/dist/engine/fts.js +233 -280
  23. package/dist/engine/group-index.js +287 -321
  24. package/dist/engine/index.js +23 -12
  25. package/dist/engine/join-index.js +177 -186
  26. package/dist/engine/keyed-live.js +168 -173
  27. package/dist/engine/live-api.js +0 -1
  28. package/dist/engine/live-equal.js +32 -37
  29. package/dist/engine/live.js +603 -647
  30. package/dist/engine/memory.js +109 -111
  31. package/dist/engine/optimizer.d.ts +19 -1
  32. package/dist/engine/optimizer.js +2642 -2515
  33. package/dist/engine/point-read.d.ts +4 -2
  34. package/dist/engine/point-read.js +121 -171
  35. package/dist/engine/query-api.js +12 -3
  36. package/dist/engine/query-cache.js +61 -69
  37. package/dist/engine/query.d.ts +45 -0
  38. package/dist/engine/query.js +8506 -9375
  39. package/dist/engine/result-wire.js +221 -252
  40. package/dist/engine/schema-wire.js +97 -112
  41. package/dist/engine/schema.js +1099 -1254
  42. package/dist/engine/sort-keys.js +315 -389
  43. package/dist/engine/sql-domains.d.ts +7 -1
  44. package/dist/engine/sql-domains.js +648 -734
  45. package/dist/engine/sql-driver.js +0 -1
  46. package/dist/engine/sql-functions.d.ts +11 -0
  47. package/dist/engine/sql-functions.js +1092 -0
  48. package/dist/engine/sql-json.js +199 -218
  49. package/dist/engine/sql-semantics.d.ts +25 -4
  50. package/dist/engine/sql-semantics.js +579 -513
  51. package/dist/engine/typed-live.js +271 -293
  52. package/dist/engine/vector.d.ts +7 -1
  53. package/dist/engine/vector.js +4513 -4580
  54. package/dist/engine/worker-host.d.ts +8 -63
  55. package/dist/engine/worker-host.js +28 -1130
  56. package/dist/engine/worker-indexeddb.d.ts +1 -0
  57. package/dist/engine/worker-indexeddb.js +3 -0
  58. package/dist/engine/worker-memory.d.ts +1 -0
  59. package/dist/engine/worker-memory.js +3 -0
  60. package/dist/engine/worker-opfs.d.ts +1 -0
  61. package/dist/engine/worker-opfs.js +3 -0
  62. package/dist/engine/worker-server.d.ts +93 -0
  63. package/dist/engine/worker-server.js +999 -0
  64. package/dist/engine/worker-store-indexeddb.d.ts +2 -0
  65. package/dist/engine/worker-store-indexeddb.js +11 -0
  66. package/dist/engine/worker-store-memory.d.ts +2 -0
  67. package/dist/engine/worker-store-memory.js +6 -0
  68. package/dist/engine/worker-store-opfs.d.ts +2 -0
  69. package/dist/engine/worker-store-opfs.js +9 -0
  70. package/dist/engine/worker.js +0 -12
  71. package/dist/engine/write-block-planner.js +93 -100
  72. package/dist/plan/index.js +15 -16
  73. package/dist/plan/model.d.ts +9 -1
  74. package/dist/plan/model.js +20 -27
  75. package/dist/storage/index.js +0 -12
  76. package/dist/storage/indexeddb.js +11776 -13171
  77. package/dist/storage/memory.js +1198 -1241
  78. package/dist/storage/opfs/files.js +238 -295
  79. package/dist/storage/opfs/index.js +9 -3
  80. package/dist/storage/opfs/leader.js +4386 -4856
  81. package/dist/storage/opfs/rpc.js +223 -259
  82. package/dist/storage/opfs/snapshot-ledger.js +219 -260
  83. package/dist/storage/opfs/store.js +884 -1003
  84. package/dist/storage/persistence.js +59 -74
  85. package/dist/storage/snapshot-stream.js +788 -826
  86. package/dist/storage/snapshot.js +36 -37
  87. package/dist/storage/toolkit/extents.js +444 -520
  88. package/dist/storage/toolkit/index.js +28 -29
  89. package/dist/storage/toolkit/record-core.js +5792 -6377
  90. package/dist/storage/toolkit/sync-file.js +34 -42
  91. package/dist/storage/toolkit/wal.js +92 -127
  92. package/dist/storage/toolkit/wire.js +232 -256
  93. package/dist/storage/types.js +2952 -3228
  94. package/dist/testing/block-store-conformance.js +1556 -1629
  95. package/dist/testing/index.js +310 -291
  96. package/dist/testing/opfs-shim.js +280 -312
  97. package/dist/testing/simulator.js +496 -534
  98. package/dist/testing/sqllogictest.js +425 -365
  99. package/dist/transactions/index.js +1464 -1712
  100. package/dist/worker-protocol/index.js +70 -69
  101. package/package.json +20 -2
  102. package/postgres-feature-profile.json +15 -5
  103. package/sql-feature-matrix.json +252 -2
@@ -1,690 +1,646 @@
1
1
  import { LiveQueryLimitError } from "./errors.js";
2
- export const DEFAULT_LIVE_QUERY_MAX_GROUPS = 256;
3
- export const DEFAULT_LIVE_QUERY_MAX_SUBSCRIPTIONS = 1_024;
4
- export const MAX_LIVE_QUERY_GROUPS = 4_096;
5
- export const MAX_LIVE_QUERY_SUBSCRIPTIONS = 16_384;
6
- export const MAX_LIVE_QUERY_SETS_PER_DATABASE = 256;
7
- export { LiveQueryLimitError } from "./errors.js";
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
- let hash = 0x811c9dc5;
12
- const mixByte = (byte) => {
13
- hash = Math.imul(hash ^ byte, 0x01000193) >>> 0;
14
- };
15
- const mixNumber = (value) => {
16
- digestScratch.setFloat64(0, value);
17
- for (let index = 0; index < 8; index += 1)
18
- mixByte(digestScratch.getUint8(index));
19
- };
20
- const mixString = (value) => {
21
- for (let index = 0; index < value.length; index += 1) {
22
- const code = value.charCodeAt(index);
23
- mixByte(code & 0xff);
24
- mixByte(code >>> 8);
25
- }
26
- mixByte(0xff);
27
- };
28
- for (const column of result.columns)
29
- mixString(column);
30
- for (const domain of result.columnDomains)
31
- mixString(JSON.stringify(domain));
32
- for (const row of result.rows) {
33
- for (const column of result.columns) {
34
- const value = row[column] ?? null;
35
- if (value === null)
36
- mixByte(1);
37
- else if (typeof value === "number") {
38
- mixByte(2);
39
- mixNumber(value);
40
- }
41
- else if (typeof value === "string") {
42
- mixByte(3);
43
- mixString(value);
44
- }
45
- else if (typeof value === "boolean")
46
- mixByte(value ? 4 : 5);
47
- else {
48
- mixByte(6);
49
- mixNumber(dateMilliseconds(value));
50
- }
51
- }
52
- mixByte(0xfe);
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
- if (left instanceof Date || right instanceof Date) {
58
- return (left instanceof Date &&
59
- right instanceof Date &&
60
- Object.is(dateMilliseconds(left), dateMilliseconds(right)));
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
- if (left.columns.length !== right.columns.length || left.rows.length !== right.rows.length) {
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
- for (let index = 0; index < left.columns.length; index += 1) {
69
- if (left.columns[index] !== right.columns[index])
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
- const cloned = {};
89
- for (const column of columns) {
90
- const value = row[column] ?? null;
91
- const copy = value instanceof Date ? copyDate(value) : value;
92
- if (column === "__proto__") {
93
- Object.defineProperty(cloned, column, {
94
- value: copy,
95
- enumerable: true,
96
- configurable: true,
97
- writable: true,
98
- });
99
- }
100
- else
101
- cloned[column] = copy;
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
- const columns = [...result.columns];
107
- return {
108
- columns,
109
- columnDomains: structuredClone(result.columnDomains),
110
- rows: result.rows.map((row) => cloneRow(row, columns)),
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
- /** Type-tagged, length-delimited structural identity. Unlike JSON, it preserves Date vs string,
114
- * -0, non-finite numbers, and undefined fields, so deduplication cannot merge distinct plans. */
115
- function encodeQueryIdentity(value, ancestors = new Set()) {
116
- if (value === null)
117
- return "z";
118
- if (typeof value === "undefined")
119
- return "u";
120
- if (typeof value === "boolean")
121
- return value ? "b1" : "b0";
122
- if (typeof value === "number") {
123
- if (Number.isNaN(value))
124
- return "nNaN;";
125
- if (Object.is(value, -0))
126
- return "n-0;";
127
- return `n${String(value)};`;
128
- }
129
- if (typeof value === "string")
130
- return `s${String(value.length)}:${value}`;
131
- if (value instanceof Date)
132
- return `d${String(dateMilliseconds(value))};`;
133
- if (typeof value !== "object") {
134
- throw new TypeError(`Unsupported live-query identity value: ${typeof value}`);
135
- }
136
- if (ancestors.has(value))
137
- throw new TypeError("Live-query identity contains a cycle");
138
- ancestors.add(value);
139
- let encoded;
140
- if (Array.isArray(value)) {
141
- encoded = `a${String(value.length)}[${value
142
- .map((item) => encodeQueryIdentity(item, ancestors))
143
- .join("")}]`;
144
- }
145
- else {
146
- const record = value;
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
- return encodeQueryIdentity(typeof query === "string"
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
- return left.manifestVersion === right.manifestVersion && left.schemaEpoch === right.schemaEpoch;
146
+ return left.manifestVersion === right.manifestVersion && left.schemaEpoch === right.schemaEpoch;
164
147
  }
165
148
  function versionOrdinal(version) {
166
- return version ?? -1;
149
+ return version ?? -1;
167
150
  }
168
151
  function boundedLiveLimit(value, maximum, label) {
169
- if (!Number.isSafeInteger(value) || value < 1 || value > maximum) {
170
- throw new RangeError(`Live query ${label} limit must be between 1 and ${String(maximum)}`);
171
- }
172
- return value;
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
- return (versionOrdinal(current.manifestVersion) < versionOrdinal(previous.manifestVersion) ||
179
- current.schemaEpoch !== previous.schemaEpoch);
158
+ return versionOrdinal(current.manifestVersion) < versionOrdinal(previous.manifestVersion) || current.schemaEpoch !== previous.schemaEpoch;
180
159
  }
181
- export class LiveQuerySet {
182
- #host;
183
- #channel;
184
- #channelListener = () => {
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
- #groups = new Map();
188
- #opening = new Map();
189
- #groupsByTable = new Map();
190
- #maxGroups;
191
- #maxSubscriptions;
192
- #stats = {
193
- hints: 0,
194
- versionChecks: 0,
195
- sweeps: 0,
196
- reruns: 0,
197
- rerunsAvoided: 0,
198
- zoneSkips: 0,
199
- notificationsSuppressed: 0,
200
- invalidations: 0,
201
- sharedExecutions: 0,
202
- lastSweepMs: 0,
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
- #lastProbe;
205
- #sweepChain = Promise.resolve();
206
- #sweepQueued = false;
207
- #pollTimer;
208
- #closed = false;
209
- #subscriptionCount = 0;
210
- #ownsChannel;
211
- #onClosed;
212
- constructor(host, options = {}) {
213
- if (options.pollIntervalMs !== undefined &&
214
- (!Number.isSafeInteger(options.pollIntervalMs) || options.pollIntervalMs <= 0)) {
215
- throw new RangeError("Live query poll interval must be a positive whole number");
216
- }
217
- this.#maxGroups = boundedLiveLimit(options.maxGroups ?? DEFAULT_LIVE_QUERY_MAX_GROUPS, MAX_LIVE_QUERY_GROUPS, "group");
218
- this.#maxSubscriptions = boundedLiveLimit(options.maxSubscriptions ?? DEFAULT_LIVE_QUERY_MAX_SUBSCRIPTIONS, MAX_LIVE_QUERY_SUBSCRIPTIONS, "subscription");
219
- this.#host = host;
220
- if (options.channel !== undefined) {
221
- this.#channel = options.channel;
222
- this.#ownsChannel = false;
223
- }
224
- else if (options.channelName !== undefined && typeof BroadcastChannel !== "undefined") {
225
- this.#channel = new BroadcastChannel(options.channelName);
226
- this.#ownsChannel = true;
227
- }
228
- else {
229
- this.#channel = undefined;
230
- this.#ownsChannel = false;
231
- }
232
- this.#onClosed = options.onClosed;
233
- this.#channel?.addEventListener("message", this.#channelListener);
234
- if (options.pollIntervalMs !== undefined) {
235
- this.#pollTimer = setInterval(() => {
236
- this.#hint();
237
- }, options.pollIntervalMs);
238
- }
239
- }
240
- get stats() {
241
- return { ...this.#stats };
242
- }
243
- #throwIfClosed() {
244
- if (this.#closed)
245
- throw new Error("Live query set is closed");
246
- }
247
- #reserveSubscription() {
248
- if (this.#subscriptionCount >= this.#maxSubscriptions) {
249
- throw new LiveQueryLimitError("subscription", this.#maxSubscriptions);
250
- }
251
- this.#subscriptionCount += 1;
252
- }
253
- /** Registers a query, delivers its current result, and shares work with equal statements. */
254
- async subscribe(query, options) {
255
- this.#throwIfClosed();
256
- this.#reserveSubscription();
257
- let group;
258
- let subscriber;
259
- try {
260
- group = await this.#getOrOpenGroup(query);
261
- this.#throwIfClosed();
262
- subscriber = {
263
- kind: "result",
264
- options,
265
- delivered: false,
266
- closed: false,
267
- };
268
- group.subscribers.add(subscriber);
269
- // Reconcile before exposing a shared cached result to a late subscriber.
270
- await this.refresh();
271
- this.#throwIfClosed();
272
- if (!subscriber.delivered) {
273
- const result = group.result ?? (await this.#executeGroup(group)).result;
274
- this.#throwIfClosed();
275
- if (!subscriber.closed)
276
- this.#deliverResult(subscriber, result);
277
- }
278
- }
279
- catch (error) {
280
- if (group !== undefined && subscriber !== undefined) {
281
- this.#removeSubscriber(group, subscriber, false);
282
- }
283
- else {
284
- this.#subscriptionCount -= 1;
285
- if (group !== undefined)
286
- this.#removeEmptyGroup(group);
287
- }
288
- throw error;
289
- }
290
- return this.#subscriptionHandle(group, subscriber);
291
- }
292
- /** Observes invalidation without executing the statement inside the set. */
293
- async observe(query, options) {
294
- this.#throwIfClosed();
295
- this.#reserveSubscription();
296
- let group;
297
- let subscriber;
298
- try {
299
- group = await this.#getOrOpenGroup(query);
300
- this.#throwIfClosed();
301
- subscriber = {
302
- kind: "observer",
303
- options,
304
- delivered: false,
305
- closed: false,
306
- };
307
- group.subscribers.add(subscriber);
308
- await this.refresh();
309
- this.#throwIfClosed();
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
- return;
464
+ continue;
338
465
  subscriber.closed = true;
339
466
  this.#subscriptionCount -= 1;
340
- group.subscribers.delete(subscriber);
341
- if (complete)
342
- subscriber.options.onComplete?.();
343
- this.#removeEmptyGroup(group);
344
- }
345
- #removeEmptyGroup(group) {
346
- if (group.subscribers.size !== 0)
347
- return;
348
- if (this.#groups.get(group.key) !== group)
349
- return;
350
- this.#groups.delete(group.key);
351
- this.#unindexGroup(group, group.dependencies);
352
- }
353
- async #getOrOpenGroup(query) {
354
- const key = queryKey(query);
355
- const existing = this.#groups.get(key);
356
- if (existing !== undefined) {
357
- this.#stats.sharedExecutions += 1;
358
- return existing;
359
- }
360
- const opening = this.#opening.get(key);
361
- if (opening !== undefined) {
362
- this.#stats.sharedExecutions += 1;
363
- return opening;
364
- }
365
- if (this.#groups.size + this.#opening.size >= this.#maxGroups) {
366
- throw new LiveQueryLimitError("group", this.#maxGroups);
367
- }
368
- const created = this.#openGroup(key, query);
369
- this.#opening.set(key, created);
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
- return await created;
372
- }
373
- finally {
374
- this.#opening.delete(key);
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
- async #refreshDependencies(group) {
416
- const { dependencies: next } = await this.#stableDependencies(group.query);
417
- if (!this.#stillOpen() || !this.#groups.has(group.key))
418
- return;
419
- this.#unindexGroup(group, group.dependencies);
420
- group.dependencies = next;
421
- this.#indexGroup(group, next);
422
- }
423
- /** Resolve a view/table set inside a catalog-stable probe window or refuse stale indexing. */
424
- async #stableDependencies(query) {
425
- let before = await this.#host.currentProbe();
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
- const execution = (async () => {
444
- const executed = await this.#host.execute(group.query);
445
- const digest = digestResult(executed);
446
- const previous = group.result;
447
- const changed = previous === undefined || group.digest !== digest || !sameResult(previous, executed);
448
- const retained = cloneResult(executed);
449
- group.result = retained;
450
- group.digest = digest;
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
- return await execution;
456
- }
457
- finally {
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
- return;
537
- const last = this.#lastProbe ?? current;
538
- const anyLagging = [...this.#groups.values()].some((group) => !sameProbe(group.seenProbe, current));
539
- if (sameProbe(last, current) && !anyLagging)
540
- return;
541
- const started = performance.now();
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
- this.#lastProbe = current;
655
- this.#stats.lastSweepMs = performance.now() - started;
656
- }
657
- #notifyGroupError(group, error) {
658
- for (const subscriber of group.subscribers) {
659
- if (subscriber.closed)
660
- continue;
661
- subscriber.options.onError?.(error);
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
- /** Unions persisted table change sets in (after, until], widening on any history gap. */
665
- async #changedTablesSince(after, until) {
666
- if (until === null || after === until)
667
- return new Set();
668
- const changed = new Set();
669
- let cursor = after;
670
- let expected = (after ?? -1) + 1;
671
- for (;;) {
672
- const page = await this.#host.manifestPage(cursor, 64);
673
- for (const manifest of page.records) {
674
- if (manifest.version > until)
675
- return changed;
676
- if (manifest.version !== expected)
677
- return "all";
678
- expected += 1;
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
- if (page.nextCursor === null)
685
- return expected > until ? changed : "all";
686
- cursor = page.nextCursor;
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
+ };