@minnowdb/core 0.10.0 → 0.10.2

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 (37) hide show
  1. package/dist/engine/auto-store.d.ts +15 -3
  2. package/dist/engine/auto-store.js +48 -6
  3. package/dist/engine/client.js +23 -7
  4. package/dist/engine/database.js +347 -862
  5. package/dist/engine/index-terms.js +627 -0
  6. package/dist/engine/index.d.ts +1 -1
  7. package/dist/engine/index.js +2 -1
  8. package/dist/engine/live-maintenance.js +220 -0
  9. package/dist/engine/schema.js +2 -1
  10. package/dist/engine/sql-functions.js +3 -2
  11. package/dist/engine/sql-quote.js +6 -0
  12. package/dist/engine/vector.js +1 -3
  13. package/dist/engine/worker-host.js +2 -0
  14. package/dist/engine/worker-server.js +4 -5
  15. package/dist/engine/worker-store-auto.js +2 -2
  16. package/dist/engine/write-coordinator.js +21 -1
  17. package/dist/storage/indexeddb.d.ts +18 -0
  18. package/dist/storage/indexeddb.js +293 -211
  19. package/dist/storage/opfs/coordination-helpers.js +54 -0
  20. package/dist/storage/opfs/index.d.ts +1 -1
  21. package/dist/storage/opfs/index.js +3 -2
  22. package/dist/storage/opfs/leader.js +44 -5
  23. package/dist/storage/opfs/rpc.js +3 -1
  24. package/dist/storage/opfs/store.d.ts +12 -0
  25. package/dist/storage/opfs/store.js +59 -12
  26. package/dist/storage/toolkit/wal.js +16 -0
  27. package/dist/storage/toolkit/wire.js +3 -3
  28. package/dist/storage/types.d.ts +35 -4
  29. package/dist/storage/types.js +17 -4
  30. package/dist/testing/block-store-conformance.js +40 -1
  31. package/dist/testing/index.d.ts +1 -0
  32. package/dist/testing/index.js +9 -0
  33. package/dist/testing/interaction-simulator.d.ts +319 -0
  34. package/dist/testing/interaction-simulator.js +1631 -0
  35. package/dist/transactions/index.d.ts +7 -0
  36. package/dist/transactions/index.js +17 -3
  37. package/package.json +4 -1
@@ -0,0 +1,627 @@
1
+ import { dateMilliseconds } from "../date-value.js";
2
+ import { renderDocumentValue, tokenize as ftsTokenize } from "./fts.js";
3
+ import { protectedSqlTextValue } from "./sql-domains.js";
4
+ import { MAX_FTS_POSTINGS_PER_CHUNK, MAX_FTS_POSTING_ROW_IDS_PER_CHUNK, MAX_FTS_POSTING_TERM_CHARACTERS, MAX_INDEXED_STRING_CHARACTERS, secondaryIndexColumnIds, secondaryIndexDirections, secondaryUniqueKeyNamespace } from "../storage/types.js";
5
+ function safeWholeNumberSum(values, name) {
6
+ let total = 0;
7
+ for (const value of values) {
8
+ if (!Number.isSafeInteger(value) || value < 0 || total > Number.MAX_SAFE_INTEGER - value) {
9
+ throw new RangeError(`${name} exceeds the safe integer range`);
10
+ }
11
+ total += value;
12
+ }
13
+ return total;
14
+ }
15
+ function assertIndexedStringLength(value) {
16
+ if (value.length > MAX_INDEXED_STRING_CHARACTERS) {
17
+ throw new RangeError(`Indexed strings cannot exceed ${String(MAX_INDEXED_STRING_CHARACTERS)} characters`);
18
+ }
19
+ }
20
+ function keyToken(type, value) {
21
+ if (value === null)
22
+ throw new TypeError("Unique key cannot be null");
23
+ switch (type) {
24
+ case "boolean":
25
+ if (typeof value !== "boolean")
26
+ throw new TypeError("Invalid boolean unique key");
27
+ return value ? "boolean:true" : "boolean:false";
28
+ case "number":
29
+ if (typeof value !== "number" || !Number.isFinite(value)) {
30
+ throw new TypeError("Invalid number unique key");
31
+ }
32
+ return `number:${String(value)}`;
33
+ case "string":
34
+ if (typeof value !== "string")
35
+ throw new TypeError("Invalid string unique key");
36
+ assertIndexedStringLength(value);
37
+ return `string:${value}`;
38
+ case "datetime":
39
+ if (!(value instanceof Date) || !Number.isFinite(dateMilliseconds(value))) {
40
+ throw new TypeError("Invalid datetime unique key");
41
+ }
42
+ return `datetime:${String(dateMilliseconds(value))}`;
43
+ }
44
+ }
45
+ function getUniqueKeyColumn(table) {
46
+ if (table.uniqueKeyColumnId === void 0)
47
+ return void 0;
48
+ return table.columns.find((column) => column.id === table.uniqueKeyColumnId);
49
+ }
50
+ function rowIdSpanEnvelope(spans) {
51
+ if (spans.length === 0)
52
+ return { start: 0n, endExclusive: 0n };
53
+ let start = spans[0]?.rowIdStart ?? 0n;
54
+ let endExclusive = start + BigInt(spans[0]?.rowCount ?? 0);
55
+ for (const span of spans.slice(1)) {
56
+ if (span.rowIdStart < start)
57
+ start = span.rowIdStart;
58
+ const spanEnd = span.rowIdStart + BigInt(span.rowCount);
59
+ if (spanEnd > endExclusive)
60
+ endExclusive = spanEnd;
61
+ }
62
+ return { start, endExclusive };
63
+ }
64
+ function mergeSourceRowIdSpans(segment, kind) {
65
+ if (kind === "update" || kind === "delete") {
66
+ if (segment.rowIdStart !== 0n || segment.rowIdEndExclusive !== 0n || segment.rowIdSpans.length !== 0) {
67
+ throw new Error(`Mutation marker unexpectedly owns row IDs: ${segment.id}`);
68
+ }
69
+ return [];
70
+ }
71
+ const spans = segment.rowIdSpans.length === 0 ? [{ rowStart: 0, rowCount: segment.rowCount, rowIdStart: segment.rowIdStart }] : segment.rowIdSpans.map((span) => ({ ...span }));
72
+ const envelope = rowIdSpanEnvelope(spans);
73
+ let rowStart = 0;
74
+ for (const [index, span] of spans.entries()) {
75
+ if (span.rowStart !== rowStart || span.rowCount <= 0) {
76
+ throw new Error(`Segment row ID spans are not contiguous: ${segment.id}`);
77
+ }
78
+ const previous = spans[index - 1];
79
+ if (previous !== void 0 && previous.rowIdStart + BigInt(previous.rowCount) === span.rowIdStart) {
80
+ throw new Error(`Segment row ID spans are not coalesced: ${segment.id}`);
81
+ }
82
+ rowStart = safeWholeNumberSum([rowStart, span.rowCount], "Segment row ID span rows");
83
+ }
84
+ if (rowStart !== segment.rowCount || envelope.start !== segment.rowIdStart || envelope.endExclusive !== segment.rowIdEndExclusive) {
85
+ throw new Error(`Segment row ID spans differ from their envelope: ${segment.id}`);
86
+ }
87
+ const intervals = spans.map((span) => ({
88
+ start: span.rowIdStart,
89
+ end: span.rowIdStart + BigInt(span.rowCount)
90
+ })).sort((left, right) => left.start < right.start ? -1 : left.start > right.start ? 1 : 0);
91
+ for (let index = 1; index < intervals.length; index += 1) {
92
+ const previous = intervals[index - 1];
93
+ const current = intervals[index];
94
+ if (previous !== void 0 && current !== void 0 && current.start < previous.end) {
95
+ throw new Error(`Segment row IDs overlap: ${segment.id}`);
96
+ }
97
+ }
98
+ return spans;
99
+ }
100
+ function addFtsDocument(byTerm, value, rowId) {
101
+ const rendered = renderDocumentValue(value);
102
+ if (rendered === void 0)
103
+ return 0;
104
+ const tokens = ftsTokenize(rendered);
105
+ const counts = /* @__PURE__ */ new Map();
106
+ for (const token of tokens)
107
+ counts.set(token, (counts.get(token) ?? 0) + 1);
108
+ for (const [term, tf] of counts) {
109
+ const posting = byTerm.get(term) ?? { rowIds: [], tf: [] };
110
+ posting.rowIds.push(rowId);
111
+ posting.tf.push(tf);
112
+ byTerm.set(term, posting);
113
+ }
114
+ return tokens.length;
115
+ }
116
+ function sortedFtsPostings(byTerm) {
117
+ return [...byTerm.entries()].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([term, posting]) => ({ term, rowIds: posting.rowIds, tf: posting.tf }));
118
+ }
119
+ function postingFrequencyTotal(postings) {
120
+ let total = 0;
121
+ for (const posting of postings) {
122
+ for (const frequency of posting.tf) {
123
+ total = safeWholeNumberSum([total, frequency], "Posting term-frequency total");
124
+ }
125
+ }
126
+ return total;
127
+ }
128
+ const secondaryNumberBits = new DataView(new ArrayBuffer(8));
129
+ function secondaryIndexTerm(type, value) {
130
+ if (value === null)
131
+ throw new TypeError("A NULL has no secondary-index comparison term");
132
+ if (type === "string") {
133
+ if (typeof value !== "string")
134
+ throw new TypeError("Invalid string index value");
135
+ assertIndexedStringLength(value);
136
+ return value;
137
+ }
138
+ if (type === "boolean") {
139
+ if (typeof value !== "boolean")
140
+ throw new TypeError("Invalid boolean index value");
141
+ return value ? "1" : "0";
142
+ }
143
+ const numeric = type === "datetime" && value instanceof Date ? dateMilliseconds(value) : value;
144
+ if (typeof numeric !== "number" || !Number.isFinite(numeric)) {
145
+ throw new TypeError(`Invalid ${type} index value`);
146
+ }
147
+ secondaryNumberBits.setFloat64(0, numeric === 0 ? 0 : numeric, false);
148
+ const bits = secondaryNumberBits.getBigUint64(0, false);
149
+ const sortable = (bits & 0x8000000000000000n) === 0n ? bits ^ 0x8000000000000000n : ~bits & 0xffffffffffffffffn;
150
+ return sortable.toString(16).padStart(16, "0");
151
+ }
152
+ const ASCENDING_NULL_COMPONENT = "g";
153
+ const DESCENDING_NULL_COMPONENT = "/";
154
+ const LOWEST_SECONDARY_COMPONENT = "0";
155
+ function secondaryNullComponent(direction) {
156
+ return direction === "desc" ? DESCENDING_NULL_COMPONENT : ASCENDING_NULL_COMPONENT;
157
+ }
158
+ function secondaryIndexTermsCoverNulls(index) {
159
+ return index.termEncoding === "tuple-v2";
160
+ }
161
+ function secondaryTupleComponent(type, value) {
162
+ if (value === null)
163
+ throw new TypeError("A NULL has no secondary-index comparison term");
164
+ if (type === "string") {
165
+ if (typeof value !== "string")
166
+ throw new TypeError("Invalid string index value");
167
+ assertIndexedStringLength(value);
168
+ if (value.length * 5 + 5 > MAX_FTS_POSTING_TERM_CHARACTERS) {
169
+ throw new RangeError("Composite index term exceeds the persisted term limit");
170
+ }
171
+ let encoded = "";
172
+ for (let index = 0; index < value.length; index += 1) {
173
+ encoded += (value.charCodeAt(index) + 1).toString(16).padStart(5, "0");
174
+ }
175
+ return `${encoded}00000`;
176
+ }
177
+ if (type === "boolean") {
178
+ if (typeof value !== "boolean")
179
+ throw new TypeError("Invalid boolean index value");
180
+ return value ? "1" : "0";
181
+ }
182
+ return secondaryIndexTerm(type, value);
183
+ }
184
+ const reversedHex = new Map(Array.from("0123456789abcdef", (character, index) => [
185
+ character,
186
+ "fedcba9876543210"[index] ?? ""
187
+ ]));
188
+ function reverseSecondaryHex(input) {
189
+ let output = "";
190
+ for (let index = 0; index < input.length; index += 1) {
191
+ const character = input.charAt(index);
192
+ const reversed = reversedHex.get(character);
193
+ if (reversed === void 0)
194
+ throw new Error("Secondary index has a non-hexadecimal term");
195
+ output += reversed;
196
+ }
197
+ return output;
198
+ }
199
+ function secondaryTupleIndexTerm(index, columns, values) {
200
+ if (columns.length !== values.length)
201
+ throw new TypeError("Index key has the wrong arity");
202
+ if (values[0] === null)
203
+ return void 0;
204
+ if (!secondaryIndexTermsCoverNulls(index) && values.some((value) => value === null)) {
205
+ return void 0;
206
+ }
207
+ const directions = secondaryIndexDirections(index);
208
+ let term = "";
209
+ for (const [position, column] of columns.entries()) {
210
+ const direction = directions[position] ?? "asc";
211
+ const value = values[position] ?? null;
212
+ const encoded = value === null ? secondaryNullComponent(direction) : secondaryTupleComponent(column.type, value);
213
+ const component = value !== null && direction === "desc" ? reverseSecondaryHex(encoded) : encoded;
214
+ if (term.length + component.length > MAX_FTS_POSTING_TERM_CHARACTERS) {
215
+ throw new RangeError("Composite index term exceeds the persisted term limit");
216
+ }
217
+ term += component;
218
+ }
219
+ return term;
220
+ }
221
+ function secondaryIndexComponentTerm(index, column, position, value) {
222
+ const component = secondaryTupleComponent(column.type, value);
223
+ if (secondaryIndexDirections(index)[position] !== "desc")
224
+ return component;
225
+ return reverseSecondaryHex(component);
226
+ }
227
+ function decodeSecondaryTupleTerm(index, columns, term) {
228
+ const directions = secondaryIndexDirections(index);
229
+ let offset = 0;
230
+ const values = columns.map((column, position) => {
231
+ const direction = directions[position] ?? "asc";
232
+ const descending = direction === "desc";
233
+ const restore = (encoded2) => descending ? reverseSecondaryHex(encoded2) : encoded2;
234
+ if (term.startsWith(secondaryNullComponent(direction), offset)) {
235
+ offset += 1;
236
+ return null;
237
+ }
238
+ if (column.type === "string") {
239
+ let value2 = "";
240
+ for (; ; ) {
241
+ const group = restore(term.slice(offset, offset + 5));
242
+ if (group.length !== 5)
243
+ throw new Error(`Secondary index ${index.name} has a bad term`);
244
+ offset += 5;
245
+ if (group === "00000")
246
+ return value2;
247
+ const code = Number.parseInt(group, 16) - 1;
248
+ if (!Number.isInteger(code) || code < 0 || code > 65535) {
249
+ throw new Error(`Secondary index ${index.name} has a bad string term`);
250
+ }
251
+ value2 += String.fromCharCode(code);
252
+ }
253
+ }
254
+ if (column.type === "boolean") {
255
+ const encoded2 = restore(term.slice(offset, offset + 1));
256
+ offset += 1;
257
+ if (encoded2 !== "0" && encoded2 !== "1") {
258
+ throw new Error(`Secondary index ${index.name} has a bad boolean term`);
259
+ }
260
+ return encoded2 === "1";
261
+ }
262
+ const encoded = restore(term.slice(offset, offset + 16));
263
+ if (encoded.length !== 16)
264
+ throw new Error(`Secondary index ${index.name} has a bad term`);
265
+ offset += 16;
266
+ const sortable = BigInt(`0x${encoded}`);
267
+ const bits = (sortable & 0x8000000000000000n) === 0n ? ~sortable & 0xffffffffffffffffn : sortable ^ 0x8000000000000000n;
268
+ secondaryNumberBits.setBigUint64(0, bits, false);
269
+ const value = secondaryNumberBits.getFloat64(0, false);
270
+ return column.type === "datetime" ? new Date(value) : value;
271
+ });
272
+ if (offset !== term.length)
273
+ throw new Error(`Secondary index ${index.name} has a bad term`);
274
+ return values;
275
+ }
276
+ function secondaryKeyLocator(type, value) {
277
+ const token = keyToken(type, value);
278
+ let hash = 2166136261;
279
+ for (let index = 0; index < token.length; index += 1) {
280
+ const code = token.charCodeAt(index);
281
+ hash = Math.imul(hash ^ code & 255, 16777619) >>> 0;
282
+ hash = Math.imul(hash ^ code >>> 8, 16777619) >>> 0;
283
+ }
284
+ return BigInt(hash >>> 0);
285
+ }
286
+ function addSecondaryPosting(byTerm, index, columns, values, locator, uniqueTerms) {
287
+ const first = columns[0];
288
+ if (first === void 0)
289
+ return;
290
+ const term = secondaryTupleIndexTerm(index, columns, values);
291
+ if (term === void 0)
292
+ return;
293
+ if (!values.some((value) => value === null)) {
294
+ if (uniqueTerms?.has(term) === true) {
295
+ throw new TypeError(`UNIQUE index ${index.name} has a duplicate key`);
296
+ }
297
+ uniqueTerms?.add(term);
298
+ }
299
+ const posting = byTerm.get(term) ?? { rowIds: [], tf: [] };
300
+ posting.rowIds.push(locator);
301
+ posting.tf.push(1);
302
+ byTerm.set(term, posting);
303
+ }
304
+ function sortedSecondaryPostings(byTerm) {
305
+ return [...byTerm.entries()].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([term, posting]) => {
306
+ const rowIds = [...new Set(posting.rowIds)].sort((left, right) => left < right ? -1 : left > right ? 1 : 0);
307
+ return { term, rowIds, tf: rowIds.map(() => 1) };
308
+ });
309
+ }
310
+ function appendRowIdLocator(segments, expectedRows) {
311
+ const spans = appendRowIdSpans(segments, expectedRows);
312
+ let spanIndex = 0;
313
+ return (row) => {
314
+ while (spanIndex < spans.length && row >= (spans[spanIndex]?.rowStart ?? 0) + (spans[spanIndex]?.rowCount ?? 0)) {
315
+ spanIndex += 1;
316
+ }
317
+ const span = spans[spanIndex];
318
+ if (span === void 0 || row < span.rowStart) {
319
+ throw new Error(`Secondary-index row ID is missing: ${String(row)}`);
320
+ }
321
+ return span.rowIdStart + BigInt(row - span.rowStart);
322
+ };
323
+ }
324
+ function appendRowIdSpans(segments, expectedRows) {
325
+ const spans = [];
326
+ let outputStart = 0;
327
+ for (const segment of segments) {
328
+ const kind = segment.kind;
329
+ if (kind !== "insert" && kind !== "base")
330
+ continue;
331
+ for (const span of mergeSourceRowIdSpans(segment, kind)) {
332
+ spans.push({ ...span, rowStart: outputStart + span.rowStart });
333
+ }
334
+ outputStart += segment.rowCount;
335
+ }
336
+ const rows = spans.reduce((total, span) => total + span.rowCount, 0);
337
+ if (rows !== expectedRows)
338
+ throw new Error("Secondary-index row IDs differ from table rows");
339
+ return spans;
340
+ }
341
+ function appendRowForLocator(segments, expectedRows) {
342
+ const spans = appendRowIdSpans(segments, expectedRows).sort((left, right) => left.rowIdStart < right.rowIdStart ? -1 : left.rowIdStart > right.rowIdStart ? 1 : 0);
343
+ return (locator) => {
344
+ let low = 0;
345
+ let high = spans.length;
346
+ while (low < high) {
347
+ const middle = low + high >>> 1;
348
+ if ((spans[middle]?.rowIdStart ?? 0n) <= locator)
349
+ low = middle + 1;
350
+ else
351
+ high = middle;
352
+ }
353
+ const span = spans[low - 1];
354
+ if (span === void 0)
355
+ return void 0;
356
+ const offset = locator - span.rowIdStart;
357
+ if (offset < 0n || offset >= BigInt(span.rowCount))
358
+ return void 0;
359
+ return span.rowStart + Number(offset);
360
+ };
361
+ }
362
+ function chunkFtsPostings(postings, size = 128) {
363
+ const chunks = [];
364
+ let chunk = [];
365
+ let rowIds = 0;
366
+ const flush = () => {
367
+ if (chunk.length === 0)
368
+ return;
369
+ chunks.push(chunk);
370
+ chunk = [];
371
+ rowIds = 0;
372
+ };
373
+ for (const posting of postings) {
374
+ if (posting.rowIds.length > MAX_FTS_POSTING_ROW_IDS_PER_CHUNK) {
375
+ throw new RangeError("One posting exceeds the persisted row-id chunk limit");
376
+ }
377
+ if (chunk.length >= Math.min(size, MAX_FTS_POSTINGS_PER_CHUNK) || rowIds + posting.rowIds.length > MAX_FTS_POSTING_ROW_IDS_PER_CHUNK) {
378
+ flush();
379
+ }
380
+ chunk.push(posting);
381
+ rowIds += posting.rowIds.length;
382
+ }
383
+ flush();
384
+ return chunks;
385
+ }
386
+ function buildFtsColumnDeltas(table, input, rowIdStart) {
387
+ const active = Object.entries(table.ftsColumns ?? {}).filter(([, record]) => record.state !== "invalid");
388
+ if (active.length === 0)
389
+ return [];
390
+ const columnsById = new Map(table.columns.map((column) => [column.id, column]));
391
+ return active.flatMap(([columnId]) => {
392
+ const column = columnsById.get(columnId);
393
+ if (column === void 0)
394
+ return [];
395
+ const byTerm = /* @__PURE__ */ new Map();
396
+ let totalTokens = 0;
397
+ (input.columns[column.name] ?? []).forEach((value, index) => {
398
+ const documentValue = column.type === "string" && column.sqlDomain === void 0 && typeof value === "string" ? protectedSqlTextValue(value) : value;
399
+ totalTokens += addFtsDocument(byTerm, documentValue, rowIdStart + BigInt(index));
400
+ });
401
+ return [{ columnId, postings: sortedFtsPostings(byTerm), totalTokens }];
402
+ });
403
+ }
404
+ function buildSecondaryInsertDeltas(table, input, rowIdStart) {
405
+ const active = Object.values(table.secondaryIndexes ?? {}).filter((index) => index.state !== "invalid");
406
+ if (active.length === 0)
407
+ return [];
408
+ const columnsById = new Map(table.columns.map((column) => [column.id, column]));
409
+ const keyColumn = getUniqueKeyColumn(table);
410
+ return active.flatMap((index) => {
411
+ const columns = secondaryIndexColumnIds(index).map((columnId) => columnsById.get(columnId));
412
+ if (columns.some((column) => column === void 0))
413
+ return [];
414
+ const indexedColumns = columns;
415
+ const byTerm = /* @__PURE__ */ new Map();
416
+ const keys = keyColumn === void 0 ? void 0 : input.columns[keyColumn.name] ?? [];
417
+ const rowCount = input.rowCount ?? Object.values(input.columns)[0]?.length ?? 0;
418
+ try {
419
+ for (let row = 0; row < rowCount; row += 1) {
420
+ const values = indexedColumns.map((column) => input.columns[column.name]?.[row] ?? null);
421
+ const locator = keyColumn === void 0 ? rowIdStart + BigInt(row) : secondaryKeyLocator(keyColumn.type, keys?.[row] ?? null);
422
+ addSecondaryPosting(byTerm, index, indexedColumns, values, locator);
423
+ }
424
+ } catch (error) {
425
+ if (error instanceof RangeError && index.unique !== true)
426
+ return [];
427
+ throw error;
428
+ }
429
+ const postings = sortedSecondaryPostings(byTerm);
430
+ return [
431
+ {
432
+ columnId: index.storageColumnId,
433
+ postings,
434
+ totalTokens: postingFrequencyTotal(postings)
435
+ }
436
+ ];
437
+ });
438
+ }
439
+ function buildSecondaryUpdateDeltas(table, input, preImages = []) {
440
+ const active = Object.values(table.secondaryIndexes ?? {}).filter((index) => index.state !== "invalid");
441
+ if (active.length === 0)
442
+ return [];
443
+ const keyColumn = getUniqueKeyColumn(table);
444
+ if (keyColumn === void 0)
445
+ return [];
446
+ const columnsById = new Map(table.columns.map((column) => [column.id, column]));
447
+ return active.flatMap((index) => {
448
+ const columns = secondaryIndexColumnIds(index).map((columnId) => columnsById.get(columnId));
449
+ if (columns.some((column) => column === void 0))
450
+ return [];
451
+ const indexedColumns = columns;
452
+ const byTerm = /* @__PURE__ */ new Map();
453
+ const affected = indexedColumns.some((column) => input.changes[column.name] !== void 0);
454
+ if (affected) {
455
+ try {
456
+ for (let row = 0; row < input.keys.length; row += 1) {
457
+ const values = indexedColumns.map((column) => input.changes[column.name] === void 0 ? preImages[row]?.[column.name] ?? null : input.changes[column.name]?.[row] ?? null);
458
+ addSecondaryPosting(byTerm, index, indexedColumns, values, secondaryKeyLocator(keyColumn.type, input.keys[row] ?? null));
459
+ }
460
+ } catch (error) {
461
+ if (error instanceof RangeError && index.unique !== true)
462
+ return [];
463
+ throw error;
464
+ }
465
+ }
466
+ const postings = sortedSecondaryPostings(byTerm);
467
+ return [
468
+ {
469
+ columnId: index.storageColumnId,
470
+ postings,
471
+ totalTokens: postingFrequencyTotal(postings)
472
+ }
473
+ ];
474
+ });
475
+ }
476
+ function secondaryIndexUpdateNeedsPreImages(table, input) {
477
+ const changed = new Set(Object.keys(input.changes));
478
+ const columnsById = new Map(table.columns.map((column) => [column.id, column.name]));
479
+ return Object.values(table.secondaryIndexes ?? {}).some((index) => {
480
+ if (index.state === "invalid")
481
+ return false;
482
+ const names = secondaryIndexColumnIds(index).map((columnId) => columnsById.get(columnId));
483
+ return names.some((name) => name !== void 0 && changed.has(name)) && names.some((name) => name === void 0 || !changed.has(name));
484
+ });
485
+ }
486
+ function buildSecondaryDeleteCoverage(table) {
487
+ return Object.values(table.secondaryIndexes ?? {}).flatMap((index) => index.state === "invalid" ? [] : [
488
+ {
489
+ columnId: index.storageColumnId,
490
+ postings: [],
491
+ totalTokens: 0
492
+ }
493
+ ]);
494
+ }
495
+ function readyUniqueSecondaryIndexes(table) {
496
+ const columnsById = new Map(table.columns.map((column) => [column.id, column]));
497
+ return Object.entries(table.secondaryIndexes ?? {}).flatMap(([indexId, index]) => {
498
+ if (index.unique !== true || index.uniqueEnforced !== true)
499
+ return [];
500
+ const columns = secondaryIndexColumnIds(index).map((columnId) => columnsById.get(columnId));
501
+ return columns.some((column) => column === void 0) ? [] : [{ indexId, index, columns }];
502
+ });
503
+ }
504
+ function secondaryUniqueTerm(index, columns, values) {
505
+ if (values.some((value) => value === null))
506
+ return void 0;
507
+ return secondaryTupleIndexTerm(index, columns, values);
508
+ }
509
+ function assertNoDuplicateUniqueTerms(index, terms) {
510
+ const seen = /* @__PURE__ */ new Set();
511
+ for (const term of terms) {
512
+ if (seen.has(term))
513
+ throw new TypeError(`UNIQUE index ${index.name} has a duplicate key`);
514
+ seen.add(term);
515
+ }
516
+ }
517
+ function assertBatchSecondaryTermsDistinct(table, input) {
518
+ const rowCount = input.rowCount ?? Object.values(input.columns)[0]?.length ?? 0;
519
+ for (const { index, columns } of readyUniqueSecondaryIndexes(table)) {
520
+ const terms = [];
521
+ for (let row = 0; row < rowCount; row += 1) {
522
+ const term = secondaryUniqueTerm(index, columns, columns.map((column) => input.columns[column.name]?.[row] ?? null));
523
+ if (term !== void 0)
524
+ terms.push(term);
525
+ }
526
+ assertNoDuplicateUniqueTerms(index, terms);
527
+ }
528
+ }
529
+ function stageSecondaryUniqueInsertChanges(transaction, table, input, oldImages) {
530
+ const rowCount = input.rowCount ?? Object.values(input.columns)[0]?.length ?? 0;
531
+ for (const { indexId, index, columns } of readyUniqueSecondaryIndexes(table)) {
532
+ const namespaceId = secondaryUniqueKeyNamespace(table.id, indexId);
533
+ const removed = (oldImages ?? []).flatMap((old) => {
534
+ if (old === void 0)
535
+ return [];
536
+ const term = secondaryUniqueTerm(index, columns, columns.map((column) => old[column.name] ?? null));
537
+ return term === void 0 ? [] : [term];
538
+ });
539
+ if (removed.length > 0) {
540
+ transaction.setUniqueKeyChanges({
541
+ tableId: namespaceId,
542
+ keyTokens: removed,
543
+ requireAbsent: false,
544
+ remove: true
545
+ });
546
+ }
547
+ const added = [];
548
+ for (let row = 0; row < rowCount; row += 1) {
549
+ const term = secondaryUniqueTerm(index, columns, columns.map((column) => input.columns[column.name]?.[row] ?? null));
550
+ if (term !== void 0)
551
+ added.push(term);
552
+ }
553
+ assertNoDuplicateUniqueTerms(index, added);
554
+ transaction.setUniqueKeyChanges({
555
+ tableId: namespaceId,
556
+ keyTokens: added,
557
+ requireAbsent: true
558
+ });
559
+ }
560
+ }
561
+ function stageSecondaryUniqueMutationChanges(transaction, table, input, oldImages) {
562
+ for (const { indexId, index, columns } of readyUniqueSecondaryIndexes(table)) {
563
+ const namespaceId = secondaryUniqueKeyNamespace(table.id, indexId);
564
+ const removed = oldImages.flatMap((old) => {
565
+ if (old === void 0)
566
+ return [];
567
+ const term = secondaryUniqueTerm(index, columns, columns.map((column) => old[column.name] ?? null));
568
+ return term === void 0 ? [] : [term];
569
+ });
570
+ transaction.setUniqueKeyChanges({
571
+ tableId: namespaceId,
572
+ keyTokens: removed,
573
+ requireAbsent: false,
574
+ remove: true
575
+ });
576
+ if (input === void 0)
577
+ continue;
578
+ const added = oldImages.flatMap((old, row) => {
579
+ if (old === void 0)
580
+ return [];
581
+ const term = secondaryUniqueTerm(index, columns, columns.map((column) => {
582
+ const assigned = input.changes[column.name];
583
+ return assigned === void 0 ? old[column.name] ?? null : assigned[row] ?? null;
584
+ }));
585
+ return term === void 0 ? [] : [term];
586
+ });
587
+ assertNoDuplicateUniqueTerms(index, added);
588
+ transaction.setUniqueKeyChanges({
589
+ tableId: namespaceId,
590
+ keyTokens: added,
591
+ requireAbsent: true
592
+ });
593
+ }
594
+ }
595
+ export {
596
+ ASCENDING_NULL_COMPONENT,
597
+ LOWEST_SECONDARY_COMPONENT,
598
+ addFtsDocument,
599
+ addSecondaryPosting,
600
+ appendRowForLocator,
601
+ appendRowIdLocator,
602
+ assertBatchSecondaryTermsDistinct,
603
+ assertNoDuplicateUniqueTerms,
604
+ buildFtsColumnDeltas,
605
+ buildSecondaryDeleteCoverage,
606
+ buildSecondaryInsertDeltas,
607
+ buildSecondaryUpdateDeltas,
608
+ chunkFtsPostings,
609
+ decodeSecondaryTupleTerm,
610
+ getUniqueKeyColumn,
611
+ keyToken,
612
+ mergeSourceRowIdSpans,
613
+ postingFrequencyTotal,
614
+ readyUniqueSecondaryIndexes,
615
+ rowIdSpanEnvelope,
616
+ secondaryIndexComponentTerm,
617
+ secondaryIndexTerm,
618
+ secondaryIndexTermsCoverNulls,
619
+ secondaryIndexUpdateNeedsPreImages,
620
+ secondaryKeyLocator,
621
+ secondaryTupleComponent,
622
+ secondaryUniqueTerm,
623
+ sortedFtsPostings,
624
+ sortedSecondaryPostings,
625
+ stageSecondaryUniqueInsertChanges,
626
+ stageSecondaryUniqueMutationChanges
627
+ };
@@ -18,4 +18,4 @@ export type * from "./schema.js";
18
18
  export type { InsertValue, CompiledQuery, CompiledStatement, QueryResult, QueryRow, QueryExecutionOptions, QueryValue, SqlColumnSchema, SqlColumnType, } from "./query.js";
19
19
  export type { AsyncQueryExecutionOptions, QueryBatchExecutionOptions, QuerySpillStore, } from "./vector.js";
20
20
  export type { MinnowSqlDriver, MinnowSqlExecutor } from "./sql-driver.js";
21
- export { OpfsCoordinationError, OpfsDatabaseInUseError, OpfsUncertainOutcomeError, } from "../storage/types.js";
21
+ export { OpfsCoordinationError, OpfsDatabaseInUseError, OpfsUncertainOutcomeError, StorageUnresponsiveError, } from "../storage/types.js";
@@ -5,7 +5,7 @@ import { forgetStoreChoice } from "./auto-store.js";
5
5
  import { QueryMemoryBudgetError } from "./memory.js";
6
6
  import { MAX_SQL_NESTING_DEPTH, MAX_SQL_NUMERIC_DIGITS, MAX_SQL_PARAMETERS, MAX_SQL_PATTERN_CHARACTERS, MAX_SQL_PATTERN_MATCH_STEPS, MAX_SQL_SCALAR_RESULT_CHARACTERS, MAX_SQL_STRUCTURED_VALUE_DEPTH, MAX_SQL_STRUCTURED_VALUE_ITEMS, MAX_SQL_TEXT_CHARACTERS, MAX_SQL_TOKENS } from "./cache-limits.js";
7
7
  import { column, foreignKeyName, isDestructiveStep, planMigration, schema, table, view } from "./schema.js";
8
- import { OpfsCoordinationError, OpfsDatabaseInUseError, OpfsUncertainOutcomeError } from "../storage/types.js";
8
+ import { OpfsCoordinationError, OpfsDatabaseInUseError, OpfsUncertainOutcomeError, StorageUnresponsiveError } from "../storage/types.js";
9
9
  export {
10
10
  MAX_SQL_NESTING_DEPTH,
11
11
  MAX_SQL_NUMERIC_DIGITS,
@@ -21,6 +21,7 @@ export {
21
21
  OpfsDatabaseInUseError,
22
22
  OpfsUncertainOutcomeError,
23
23
  QueryMemoryBudgetError,
24
+ StorageUnresponsiveError,
24
25
  column,
25
26
  foreignKeyName,
26
27
  forgetStoreChoice,