@minnowdb/core 0.9.1 → 0.10.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/engine/auto-store.d.ts +52 -0
- package/dist/engine/auto-store.js +157 -0
- package/dist/engine/buffered-writer.d.ts +2 -0
- package/dist/engine/buffered-writer.js +15 -2
- package/dist/engine/client-audit-harness.js +123 -0
- package/dist/engine/client.d.ts +55 -6
- package/dist/engine/client.js +176 -46
- package/dist/engine/database.d.ts +15 -1
- package/dist/engine/database.js +1276 -251
- package/dist/engine/errors.d.ts +61 -2
- package/dist/engine/errors.js +116 -3
- package/dist/engine/index.d.ts +1 -0
- package/dist/engine/index.js +2 -0
- package/dist/engine/live.d.ts +24 -1
- package/dist/engine/live.js +33 -9
- package/dist/engine/scope-write-set.js +36 -0
- package/dist/engine/worker-auto.d.ts +1 -0
- package/dist/engine/worker-auto.js +3 -0
- package/dist/engine/worker-host.d.ts +2 -1
- package/dist/engine/worker-host.js +19 -1
- package/dist/engine/worker-server.d.ts +53 -1
- package/dist/engine/worker-server.js +122 -19
- package/dist/engine/worker-store-auto.js +36 -0
- package/dist/engine/worker-store-opfs.js +3 -2
- package/dist/engine/write-coordinator.js +44 -2
- package/dist/storage/indexeddb-audit-helpers.js +269 -0
- package/dist/storage/indexeddb.js +599 -374
- package/dist/storage/opfs/coordination-helpers.js +54 -0
- package/dist/storage/opfs/index.d.ts +1 -1
- package/dist/storage/opfs/index.js +3 -2
- package/dist/storage/opfs/leader.js +243 -17
- package/dist/storage/opfs/power-loss-model.js +62 -0
- package/dist/storage/opfs/rpc.js +24 -43
- package/dist/storage/opfs/store.d.ts +32 -0
- package/dist/storage/opfs/store.js +531 -65
- package/dist/storage/toolkit/record-core.js +67 -38
- package/dist/storage/toolkit/wal.js +16 -0
- package/dist/storage/toolkit/wire.d.ts +1 -1
- package/dist/storage/toolkit/wire.js +4 -4
- package/dist/storage/types.d.ts +31 -10
- package/dist/storage/types.js +27 -16
- package/dist/testing/opfs-shim.js +14 -6
- package/dist/transactions/index.d.ts +19 -0
- package/dist/transactions/index.js +99 -25
- package/dist/worker-protocol/index.d.ts +50 -2
- package/dist/worker-protocol/index.js +106 -4
- package/package.json +7 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { dateIsoString, dateMilliseconds } from "../date-value.js";
|
|
2
|
-
import { assertTransactionArtifactBatchLimits,
|
|
2
|
+
import { assertTransactionArtifactBatchLimits, GarbageCollectionJobConflictError, LeaseConflictError, LeaseExpiredError, MAX_LEVEL_ZERO_SEGMENTS, MAX_MANIFEST_BLOCK_PRESENCE_IDS, MAX_LEASE_TTL_MS, MAX_TRANSACTION_STAGE_BLOCKS, MAX_TRANSACTION_STAGE_BYTES, MAX_TRANSACTION_STAGE_SEGMENTS, MAX_TRANSACTION_COMMIT_DELTA_BYTES, MAX_TRANSACTION_COMMIT_DELTA_ENTRIES, transactionCommitDeltaRetainedBytes, SnapshotManifestMissingError, TransactionRecordConflictError, UnknownOutcomeError, WriteConflictError } from "../storage/types.js";
|
|
3
3
|
const DEFAULT_TRANSACTION_TTL_MS = 3e4;
|
|
4
4
|
class TransactionClosedError extends Error {
|
|
5
5
|
transactionId;
|
|
@@ -118,6 +118,7 @@ class DatabaseTransaction {
|
|
|
118
118
|
#deferredBlocks = [];
|
|
119
119
|
#deferredSegments = [];
|
|
120
120
|
#knownSegments = /* @__PURE__ */ new Map();
|
|
121
|
+
#stagedSegmentsView;
|
|
121
122
|
#persisted;
|
|
122
123
|
#coalesceArtifacts;
|
|
123
124
|
#snapshotLease;
|
|
@@ -156,14 +157,56 @@ class DatabaseTransaction {
|
|
|
156
157
|
get snapshotVersion() {
|
|
157
158
|
return this.#record.snapshotVersion;
|
|
158
159
|
}
|
|
160
|
+
#journalView;
|
|
161
|
+
#journal() {
|
|
162
|
+
const view = this.#journalView;
|
|
163
|
+
if (view?.record === this.#record && view.deferredBlocks === this.#deferredBlocks.length && view.deferredSegments === this.#deferredSegments.length) {
|
|
164
|
+
return view;
|
|
165
|
+
}
|
|
166
|
+
const journaledBlocks = view?.record === this.#record ? view.journaledBlocks : new Set(this.#record.pendingBlockIds);
|
|
167
|
+
const next = {
|
|
168
|
+
record: this.#record,
|
|
169
|
+
deferredBlocks: this.#deferredBlocks.length,
|
|
170
|
+
deferredSegments: this.#deferredSegments.length,
|
|
171
|
+
blockIds: [...this.#record.pendingBlockIds, ...this.#deferredBlocks.map((block) => block.id)],
|
|
172
|
+
segmentIds: [
|
|
173
|
+
...this.#record.pendingSegmentIds,
|
|
174
|
+
...this.#deferredSegments.map((segment) => segment.id)
|
|
175
|
+
],
|
|
176
|
+
journaledBlocks
|
|
177
|
+
};
|
|
178
|
+
this.#journalView = next;
|
|
179
|
+
return next;
|
|
180
|
+
}
|
|
181
|
+
#journalAppended(previous, blockIds) {
|
|
182
|
+
const view = this.#journalView;
|
|
183
|
+
if (view?.record !== previous || this.#record.pendingBlockIds.length !== previous.pendingBlockIds.length + blockIds.length) {
|
|
184
|
+
this.#journalView = void 0;
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
for (const id of blockIds)
|
|
188
|
+
view.journaledBlocks.add(id);
|
|
189
|
+
view.record = this.#record;
|
|
190
|
+
view.deferredBlocks = -1;
|
|
191
|
+
}
|
|
159
192
|
get pendingBlockIds() {
|
|
160
|
-
return [...this.#
|
|
193
|
+
return [...this.#journal().blockIds];
|
|
161
194
|
}
|
|
162
195
|
get pendingSegmentIds() {
|
|
163
|
-
return [
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
196
|
+
return [...this.#journal().segmentIds];
|
|
197
|
+
}
|
|
198
|
+
hasPendingBlock(id) {
|
|
199
|
+
return this.#journal().journaledBlocks.has(id) || this.#deferredBlocks.some((block) => block.id === id);
|
|
200
|
+
}
|
|
201
|
+
get stagedSegments() {
|
|
202
|
+
this.#stagedSegmentsView ??= [...this.#knownSegments.values()];
|
|
203
|
+
return this.#stagedSegmentsView;
|
|
204
|
+
}
|
|
205
|
+
get pendingBlockCount() {
|
|
206
|
+
return this.#record.pendingBlockIds.length + this.#deferredBlocks.length;
|
|
207
|
+
}
|
|
208
|
+
get pendingSegmentCount() {
|
|
209
|
+
return this.#record.pendingSegmentIds.length + this.#deferredSegments.length;
|
|
167
210
|
}
|
|
168
211
|
get deferredSegments() {
|
|
169
212
|
return structuredClone(this.#deferredSegments);
|
|
@@ -190,8 +233,13 @@ class DatabaseTransaction {
|
|
|
190
233
|
this.#assertActive();
|
|
191
234
|
await this.#renewOwnership(true);
|
|
192
235
|
}
|
|
236
|
+
async renewIfDue() {
|
|
237
|
+
if (this.#record.status !== "active")
|
|
238
|
+
return;
|
|
239
|
+
await this.#renewOwnership(false);
|
|
240
|
+
}
|
|
193
241
|
get stagedWorkCount() {
|
|
194
|
-
return this.
|
|
242
|
+
return this.pendingBlockCount + this.pendingSegmentCount + this.#uniqueKeyChanges.length + this.#ftsChanges.size + this.#compactionSourceBlockIds.size;
|
|
195
243
|
}
|
|
196
244
|
checkpoint() {
|
|
197
245
|
this.#assertActive();
|
|
@@ -218,9 +266,9 @@ class DatabaseTransaction {
|
|
|
218
266
|
bytes = checkpointByteSum(bytes, 16 + value.length * 2);
|
|
219
267
|
};
|
|
220
268
|
addString(this.id);
|
|
221
|
-
for (const id of this.
|
|
269
|
+
for (const id of this.#journal().blockIds)
|
|
222
270
|
addString(id);
|
|
223
|
-
for (const id of this.
|
|
271
|
+
for (const id of this.#journal().segmentIds)
|
|
224
272
|
addString(id);
|
|
225
273
|
for (const change of this.#uniqueKeyChanges) {
|
|
226
274
|
addString(change.tableId);
|
|
@@ -258,9 +306,11 @@ class DatabaseTransaction {
|
|
|
258
306
|
const segmentPrefix = checkpoint.pendingSegmentIds;
|
|
259
307
|
const retainedBlocks = new Set(blockPrefix);
|
|
260
308
|
const retainedSegments = new Set(segmentPrefix);
|
|
261
|
-
const currentBlockIds = this.
|
|
262
|
-
const currentSegmentIds = this.
|
|
263
|
-
|
|
309
|
+
const currentBlockIds = this.#journal().blockIds;
|
|
310
|
+
const currentSegmentIds = this.#journal().segmentIds;
|
|
311
|
+
const currentBlocks = new Set(currentBlockIds);
|
|
312
|
+
const currentSegments = new Set(currentSegmentIds);
|
|
313
|
+
if (blockPrefix.some((id) => !currentBlocks.has(id)) || segmentPrefix.some((id) => !currentSegments.has(id))) {
|
|
264
314
|
throw new TypeError("A transaction checkpoint is no longer reachable");
|
|
265
315
|
}
|
|
266
316
|
const removedBlockIds = currentBlockIds.filter((id) => !retainedBlocks.has(id));
|
|
@@ -290,6 +340,7 @@ class DatabaseTransaction {
|
|
|
290
340
|
}
|
|
291
341
|
for (const id of removedSegmentIds)
|
|
292
342
|
this.#knownSegments.delete(id);
|
|
343
|
+
this.#stagedSegmentsView = void 0;
|
|
293
344
|
this.#uniqueKeyChanges.splice(0);
|
|
294
345
|
this.#ftsChanges.clear();
|
|
295
346
|
this.#commitDeltaBytes = 0;
|
|
@@ -319,20 +370,23 @@ class DatabaseTransaction {
|
|
|
319
370
|
const deferred = this.#deferredBlocks.find((block) => block.id === id);
|
|
320
371
|
if (deferred !== void 0)
|
|
321
372
|
return new Uint8Array(deferred.bytes);
|
|
322
|
-
if (this.#
|
|
373
|
+
if (this.#journal().journaledBlocks.has(id))
|
|
323
374
|
return this.store.getBlock(id);
|
|
324
375
|
return (await this.snapshot()).getBlock(id);
|
|
325
376
|
}
|
|
326
377
|
#assertProspectiveArtifactJournal(blocks, segments) {
|
|
327
|
-
const
|
|
328
|
-
const
|
|
378
|
+
const journal = this.#journal();
|
|
379
|
+
const blockIds = /* @__PURE__ */ new Set();
|
|
380
|
+
const deferredBlocks = new Set(this.#deferredBlocks.map((block) => block.id));
|
|
329
381
|
for (const block of blocks) {
|
|
330
382
|
if (block.id.length === 0)
|
|
331
383
|
throw new TypeError("Block ID cannot be empty");
|
|
332
|
-
if (blockIds.has(block.id))
|
|
384
|
+
if (journal.journaledBlocks.has(block.id) || deferredBlocks.has(block.id) || blockIds.has(block.id)) {
|
|
333
385
|
throw new Error(`Block already exists: ${block.id}`);
|
|
386
|
+
}
|
|
334
387
|
blockIds.add(block.id);
|
|
335
388
|
}
|
|
389
|
+
const segmentIds = new Set(journal.segmentIds);
|
|
336
390
|
for (const segment of segments) {
|
|
337
391
|
if (segment.id.length === 0)
|
|
338
392
|
throw new TypeError("Segment ID cannot be empty");
|
|
@@ -340,7 +394,6 @@ class DatabaseTransaction {
|
|
|
340
394
|
throw new Error(`Segment already exists: ${segment.id}`);
|
|
341
395
|
segmentIds.add(segment.id);
|
|
342
396
|
}
|
|
343
|
-
assertTransactionArtifactJournalLimits([...blockIds], [...segmentIds]);
|
|
344
397
|
}
|
|
345
398
|
async #stageArtifactBatch(blocks, segments) {
|
|
346
399
|
if (blocks.length === 0 && segments.length === 0)
|
|
@@ -348,6 +401,7 @@ class DatabaseTransaction {
|
|
|
348
401
|
assertTransactionArtifactBatchLimits(blocks, segments);
|
|
349
402
|
await this.#ensurePersisted();
|
|
350
403
|
await this.#renewOwnership();
|
|
404
|
+
const previous = this.#record;
|
|
351
405
|
try {
|
|
352
406
|
this.#record = await this.store.stageTransactionArtifacts({
|
|
353
407
|
transactionId: this.id,
|
|
@@ -357,8 +411,9 @@ class DatabaseTransaction {
|
|
|
357
411
|
updatedAt: dateIsoString(this.now())
|
|
358
412
|
});
|
|
359
413
|
} catch (error) {
|
|
360
|
-
await this.#recoverStagedAcknowledgement(error, blocks, segments);
|
|
414
|
+
this.#record = await this.#recoverStagedAcknowledgement(error, blocks, segments);
|
|
361
415
|
}
|
|
416
|
+
this.#journalAppended(previous, blocks.map((block) => block.id));
|
|
362
417
|
}
|
|
363
418
|
async stageBlock(id, bytes) {
|
|
364
419
|
return this.stageBlocks([{ id, bytes }]);
|
|
@@ -376,7 +431,7 @@ class DatabaseTransaction {
|
|
|
376
431
|
throw new Error(`Segment ${segment.id} belongs to another transaction`);
|
|
377
432
|
}
|
|
378
433
|
}
|
|
379
|
-
const ordinalBase = this.
|
|
434
|
+
const ordinalBase = this.pendingSegmentCount;
|
|
380
435
|
const ordered = segments.map((segment, index) => ({
|
|
381
436
|
...segment,
|
|
382
437
|
commitOrdinal: ordinalBase + index
|
|
@@ -392,6 +447,7 @@ class DatabaseTransaction {
|
|
|
392
447
|
this.#deferredSegments.push(...structuredClone(ordered));
|
|
393
448
|
for (const segment of ordered)
|
|
394
449
|
this.#knownSegments.set(segment.id, structuredClone(segment));
|
|
450
|
+
this.#stagedSegmentsView = void 0;
|
|
395
451
|
for (const segment of ordered)
|
|
396
452
|
this.#changedTableIds.add(segment.tableId);
|
|
397
453
|
return;
|
|
@@ -402,6 +458,7 @@ class DatabaseTransaction {
|
|
|
402
458
|
}
|
|
403
459
|
for (const segment of ordered)
|
|
404
460
|
this.#knownSegments.set(segment.id, structuredClone(segment));
|
|
461
|
+
this.#stagedSegmentsView = void 0;
|
|
405
462
|
for (const segment of ordered)
|
|
406
463
|
this.#changedTableIds.add(segment.tableId);
|
|
407
464
|
}
|
|
@@ -412,6 +469,17 @@ class DatabaseTransaction {
|
|
|
412
469
|
throw new Error(`Segment ${segment.id} belongs to another transaction`);
|
|
413
470
|
}
|
|
414
471
|
}
|
|
472
|
+
if (this.#deferredBlocks.length > 0 || this.#deferredSegments.length > 0) {
|
|
473
|
+
const combined = transactionArtifactBatches([...this.#deferredBlocks, ...blocks], [...this.#deferredSegments, ...segments]);
|
|
474
|
+
if (combined.length === 1) {
|
|
475
|
+
blocks = [...this.#deferredBlocks, ...blocks];
|
|
476
|
+
segments = [...this.#deferredSegments, ...segments];
|
|
477
|
+
this.#deferredBlocks.length = 0;
|
|
478
|
+
this.#deferredSegments.length = 0;
|
|
479
|
+
} else {
|
|
480
|
+
await this.#persistDeferredArtifacts();
|
|
481
|
+
}
|
|
482
|
+
}
|
|
415
483
|
const pendingBlockIds = new Set(this.#record.pendingBlockIds);
|
|
416
484
|
const pendingSegmentIds = new Set(this.#record.pendingSegmentIds);
|
|
417
485
|
await this.#assertRetainedArtifactsUnchanged(blocks.filter((block) => pendingBlockIds.has(block.id)), segments.filter((segment) => pendingSegmentIds.has(segment.id)));
|
|
@@ -446,6 +514,7 @@ class DatabaseTransaction {
|
|
|
446
514
|
const ftsChanges = this.#materializedFtsChanges();
|
|
447
515
|
for (const segment of ordered)
|
|
448
516
|
this.#knownSegments.set(segment.id, structuredClone(segment));
|
|
517
|
+
this.#stagedSegmentsView = void 0;
|
|
449
518
|
for (const segment of ordered)
|
|
450
519
|
this.#changedTableIds.add(segment.tableId);
|
|
451
520
|
try {
|
|
@@ -496,7 +565,6 @@ class DatabaseTransaction {
|
|
|
496
565
|
if (additions.some((id) => id.length === 0)) {
|
|
497
566
|
throw new TypeError("Block ID cannot be empty");
|
|
498
567
|
}
|
|
499
|
-
assertTransactionArtifactJournalLimits([...this.#record.pendingBlockIds, ...additions], this.#record.pendingSegmentIds);
|
|
500
568
|
for (const id of additions) {
|
|
501
569
|
if (await this.store.getBlock(id) === void 0) {
|
|
502
570
|
throw new Error(`Cannot stage a missing existing block: ${id}`);
|
|
@@ -530,10 +598,6 @@ class DatabaseTransaction {
|
|
|
530
598
|
if (record.commitOrdinal !== this.#record.pendingSegmentIds.length) {
|
|
531
599
|
throw new Error(`Existing segment ${segmentId} is not the next journal ordinal`);
|
|
532
600
|
}
|
|
533
|
-
assertTransactionArtifactJournalLimits(this.#record.pendingBlockIds, [
|
|
534
|
-
...this.#record.pendingSegmentIds,
|
|
535
|
-
segmentId
|
|
536
|
-
]);
|
|
537
601
|
this.#registerLevelZeroSegments([record]);
|
|
538
602
|
this.#changedTableIds.add(record.tableId);
|
|
539
603
|
await this.#ensurePersisted();
|
|
@@ -821,6 +885,7 @@ class DatabaseTransaction {
|
|
|
821
885
|
this.#deferredBlocks.length = 0;
|
|
822
886
|
this.#deferredSegments.length = 0;
|
|
823
887
|
this.#knownSegments.clear();
|
|
888
|
+
this.#stagedSegmentsView = void 0;
|
|
824
889
|
this.#record = { ...this.#record, status: "aborted", updatedAt: dateIsoString(this.now()) };
|
|
825
890
|
this.#stopHeartbeat();
|
|
826
891
|
await this.#releaseSnapshotLease().catch(() => void 0);
|
|
@@ -852,7 +917,14 @@ class DatabaseTransaction {
|
|
|
852
917
|
if (error instanceof SnapshotManifestMissingError) {
|
|
853
918
|
throw new WriteConflictError(this.#record.snapshotVersion, await this.store.getCurrentManifestVersion());
|
|
854
919
|
}
|
|
855
|
-
|
|
920
|
+
const persisted = await this.store.getTransaction(this.id).catch(() => void 0);
|
|
921
|
+
if (persisted?.status !== "active" || persisted.ownerId !== this.#record.ownerId || persisted.revision !== this.#record.revision || persisted.snapshotVersion !== this.#record.snapshotVersion || persisted.pendingBlockIds.length !== 0 || persisted.pendingSegmentIds.length !== 0) {
|
|
922
|
+
throw error;
|
|
923
|
+
}
|
|
924
|
+
this.#record = persisted;
|
|
925
|
+
this.#persisted = true;
|
|
926
|
+
if (!(error instanceof UnknownOutcomeError))
|
|
927
|
+
throw error;
|
|
856
928
|
}
|
|
857
929
|
this.#persisted = true;
|
|
858
930
|
void this.#releaseSnapshotLease().catch(() => void 0);
|
|
@@ -1010,6 +1082,8 @@ class DatabaseTransaction {
|
|
|
1010
1082
|
})) {
|
|
1011
1083
|
this.#record = persisted;
|
|
1012
1084
|
this.#persisted = true;
|
|
1085
|
+
if (error instanceof UnknownOutcomeError)
|
|
1086
|
+
return persisted;
|
|
1013
1087
|
}
|
|
1014
1088
|
}
|
|
1015
1089
|
} catch {
|
|
@@ -40,15 +40,63 @@ export type RpcResponse = {
|
|
|
40
40
|
};
|
|
41
41
|
/**
|
|
42
42
|
* A structured-clone-safe error carrying the constructor name and every cloneable own property,
|
|
43
|
-
* so typed engine errors survive the channel and can be rehydrated for instanceof checks.
|
|
43
|
+
* so typed engine errors survive the channel and can be rehydrated for instanceof checks. The
|
|
44
|
+
* `cause` chain travels to a bounded depth, and a platform exception keeps its `DOMException`
|
|
45
|
+
* identity, so a quota refusal is still a `DOMException` named `QuotaExceededError` on the
|
|
46
|
+
* other side.
|
|
44
47
|
*/
|
|
45
48
|
export interface SerializedError {
|
|
46
49
|
name: string;
|
|
47
50
|
message: string;
|
|
48
51
|
stack?: string;
|
|
49
52
|
props?: Record<string, unknown>;
|
|
53
|
+
/** True for platform exceptions, which rehydrate as `DOMException` rather than `Error`. */
|
|
54
|
+
domException?: true;
|
|
55
|
+
cause?: SerializedError;
|
|
50
56
|
}
|
|
51
|
-
|
|
57
|
+
/** How many `cause` links a serialized error carries before the chain is cut. */
|
|
58
|
+
export declare const MAX_SERIALIZED_CAUSE_DEPTH = 8;
|
|
59
|
+
export declare function serializeError(error: unknown, depth?: number): SerializedError;
|
|
60
|
+
/** Constructors whose prototype a rehydrated error adopts, keyed by the `name` they carry. */
|
|
61
|
+
export type ErrorRegistry = ReadonlyMap<string, new (...args: never[]) => Error>;
|
|
62
|
+
/**
|
|
63
|
+
* Rebuilds an `Error` from its serialized form: a registry hit adopts that class's prototype,
|
|
64
|
+
* a platform exception becomes a `DOMException`, a built-in subclass name (`TypeError`) keeps
|
|
65
|
+
* its built-in prototype, and anything else is a plain `Error` carrying the original name.
|
|
66
|
+
* Own properties and the `cause` chain are restored; the stack stays the worker's.
|
|
67
|
+
*/
|
|
68
|
+
export declare function rehydrateError(serialized: SerializedError, registry: ErrorRegistry): Error;
|
|
69
|
+
/** The event-frame handle id that carries worker diagnostics. Never issued for a real handle. */
|
|
70
|
+
export declare const WORKER_DIAGNOSTIC_HANDLE_ID = "$worker";
|
|
71
|
+
export type WorkerErrorKind =
|
|
72
|
+
/** A script error nobody caught, in a timer, a channel listener, or another callback. */
|
|
73
|
+
"uncaught"
|
|
74
|
+
/** A promise rejected with no handler attached. */
|
|
75
|
+
| "unhandled-rejection"
|
|
76
|
+
/** A frame the worker could not deserialize. */
|
|
77
|
+
| "messageerror"
|
|
78
|
+
/** Background maintenance: a checkpoint, cleanup, or collection step that failed. */
|
|
79
|
+
| "maintenance"
|
|
80
|
+
/** Multi-tab coordination: an election, handover, or served request that failed. */
|
|
81
|
+
| "coordination";
|
|
82
|
+
export interface WorkerErrorReport {
|
|
83
|
+
kind: WorkerErrorKind;
|
|
84
|
+
/** Where in the worker it happened, for a log line: "opfs election", "auto collection". */
|
|
85
|
+
context: string;
|
|
86
|
+
error: SerializedError;
|
|
87
|
+
}
|
|
88
|
+
export declare function workerErrorEvent(report: WorkerErrorReport): RpcResponse;
|
|
89
|
+
/**
|
|
90
|
+
* How often the worker tells the client that a call is still being worked on. The client's
|
|
91
|
+
* request deadline counts silence, not wall time, so a large batch write is not cut off — and
|
|
92
|
+
* its client left permanently failed — merely for being large.
|
|
93
|
+
*/
|
|
94
|
+
export declare const WORKER_KEEPALIVE_INTERVAL_MS = 5000;
|
|
95
|
+
/** The shortest pace a client may ask for; below this the reports would be the load. */
|
|
96
|
+
export declare const MIN_WORKER_KEEPALIVE_INTERVAL_MS = 100;
|
|
97
|
+
/** A keepalive for one in-flight request: the worker is alive and still holds it. */
|
|
98
|
+
export declare function workerKeepaliveEvent(requestId: string): RpcResponse;
|
|
99
|
+
export declare function isWorkerErrorReport(value: unknown): value is WorkerErrorReport;
|
|
52
100
|
export declare function rpcResult(requestId: string, result: unknown): RpcResponse;
|
|
53
101
|
export declare function rpcFailure(requestId: string, error: unknown): RpcResponse;
|
|
54
102
|
export declare function rpcEvent(handleId: string, event: string, payload: unknown): RpcResponse;
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
const protocolVersion = 7;
|
|
2
2
|
const MAX_DATABASE_RPC_IN_FLIGHT = 256;
|
|
3
|
-
|
|
3
|
+
const MAX_SERIALIZED_CAUSE_DEPTH = 8;
|
|
4
|
+
function serializeError(error, depth = 0) {
|
|
5
|
+
if (typeof DOMException !== "undefined" && error instanceof DOMException) {
|
|
6
|
+
return {
|
|
7
|
+
name: error.name,
|
|
8
|
+
message: error.message,
|
|
9
|
+
domException: true,
|
|
10
|
+
...typeof error.stack === "string" ? { stack: error.stack } : {}
|
|
11
|
+
};
|
|
12
|
+
}
|
|
4
13
|
if (!(error instanceof Error)) {
|
|
5
14
|
return { name: "Error", message: String(error) };
|
|
6
15
|
}
|
|
7
16
|
const props = {};
|
|
8
17
|
for (const key of Object.keys(error)) {
|
|
9
|
-
if (key === "name" || key === "message" || key === "stack")
|
|
18
|
+
if (key === "name" || key === "message" || key === "stack" || key === "cause")
|
|
10
19
|
continue;
|
|
11
20
|
const value = error[key];
|
|
12
21
|
try {
|
|
@@ -15,13 +24,98 @@ function serializeError(error) {
|
|
|
15
24
|
} catch {
|
|
16
25
|
}
|
|
17
26
|
}
|
|
27
|
+
const cause = error.cause === void 0 || depth >= MAX_SERIALIZED_CAUSE_DEPTH ? void 0 : serializeError(error.cause, depth + 1);
|
|
18
28
|
return {
|
|
19
29
|
name: error.name,
|
|
20
30
|
message: error.message,
|
|
21
31
|
...error.stack === void 0 ? {} : { stack: error.stack },
|
|
22
|
-
...Object.keys(props).length === 0 ? {} : { props }
|
|
32
|
+
...Object.keys(props).length === 0 ? {} : { props },
|
|
33
|
+
...cause === void 0 ? {} : { cause }
|
|
23
34
|
};
|
|
24
35
|
}
|
|
36
|
+
const builtinErrorNames = /* @__PURE__ */ new Set([
|
|
37
|
+
"TypeError",
|
|
38
|
+
"RangeError",
|
|
39
|
+
"SyntaxError",
|
|
40
|
+
"ReferenceError",
|
|
41
|
+
"EvalError",
|
|
42
|
+
"URIError",
|
|
43
|
+
"AggregateError"
|
|
44
|
+
]);
|
|
45
|
+
function rehydrateError(serialized, registry) {
|
|
46
|
+
const cause = serialized.cause === void 0 ? void 0 : rehydrateError(serialized.cause, registry);
|
|
47
|
+
if (serialized.domException === true) {
|
|
48
|
+
const exception = new DOMException(serialized.message, serialized.name);
|
|
49
|
+
if (cause !== void 0) {
|
|
50
|
+
Object.defineProperty(exception, "cause", {
|
|
51
|
+
value: cause,
|
|
52
|
+
writable: true,
|
|
53
|
+
configurable: true
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return exception;
|
|
57
|
+
}
|
|
58
|
+
const constructor = registry.get(serialized.name);
|
|
59
|
+
let error;
|
|
60
|
+
if (constructor !== void 0) {
|
|
61
|
+
error = Object.create(constructor.prototype);
|
|
62
|
+
} else if (builtinErrorNames.has(serialized.name)) {
|
|
63
|
+
const builtin = globalThis[serialized.name];
|
|
64
|
+
error = typeof builtin === "function" ? Object.create(builtin.prototype) : new Error(serialized.message);
|
|
65
|
+
} else {
|
|
66
|
+
error = new Error(serialized.message);
|
|
67
|
+
}
|
|
68
|
+
Object.defineProperty(error, "message", {
|
|
69
|
+
value: serialized.message,
|
|
70
|
+
writable: true,
|
|
71
|
+
configurable: true
|
|
72
|
+
});
|
|
73
|
+
Object.defineProperty(error, "name", {
|
|
74
|
+
value: serialized.name,
|
|
75
|
+
writable: true,
|
|
76
|
+
configurable: true
|
|
77
|
+
});
|
|
78
|
+
if (serialized.stack !== void 0) {
|
|
79
|
+
Object.defineProperty(error, "stack", {
|
|
80
|
+
value: serialized.stack,
|
|
81
|
+
writable: true,
|
|
82
|
+
configurable: true
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (cause !== void 0) {
|
|
86
|
+
Object.defineProperty(error, "cause", { value: cause, writable: true, configurable: true });
|
|
87
|
+
}
|
|
88
|
+
if (serialized.props !== void 0)
|
|
89
|
+
Object.assign(error, serialized.props);
|
|
90
|
+
return error;
|
|
91
|
+
}
|
|
92
|
+
const WORKER_DIAGNOSTIC_HANDLE_ID = "$worker";
|
|
93
|
+
function workerErrorEvent(report) {
|
|
94
|
+
return rpcEvent(WORKER_DIAGNOSTIC_HANDLE_ID, "error", report);
|
|
95
|
+
}
|
|
96
|
+
const WORKER_KEEPALIVE_INTERVAL_MS = 5e3;
|
|
97
|
+
const MIN_WORKER_KEEPALIVE_INTERVAL_MS = 100;
|
|
98
|
+
function workerKeepaliveEvent(requestId) {
|
|
99
|
+
return {
|
|
100
|
+
version: protocolVersion,
|
|
101
|
+
requestId,
|
|
102
|
+
kind: "rpc-event",
|
|
103
|
+
handleId: WORKER_DIAGNOSTIC_HANDLE_ID,
|
|
104
|
+
event: "keepalive",
|
|
105
|
+
payload: null
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function isWorkerErrorReport(value) {
|
|
109
|
+
if (typeof value !== "object" || value === null)
|
|
110
|
+
return false;
|
|
111
|
+
const report = value;
|
|
112
|
+
if (typeof report.kind !== "string" || typeof report.context !== "string")
|
|
113
|
+
return false;
|
|
114
|
+
if (typeof report.error !== "object" || report.error === null)
|
|
115
|
+
return false;
|
|
116
|
+
const error = report.error;
|
|
117
|
+
return typeof error.name === "string" && typeof error.message === "string";
|
|
118
|
+
}
|
|
25
119
|
function rpcResult(requestId, result) {
|
|
26
120
|
return { version: protocolVersion, requestId, kind: "rpc-result", result };
|
|
27
121
|
}
|
|
@@ -69,11 +163,19 @@ function parseRpcResponse(value) {
|
|
|
69
163
|
}
|
|
70
164
|
export {
|
|
71
165
|
MAX_DATABASE_RPC_IN_FLIGHT,
|
|
166
|
+
MAX_SERIALIZED_CAUSE_DEPTH,
|
|
167
|
+
MIN_WORKER_KEEPALIVE_INTERVAL_MS,
|
|
168
|
+
WORKER_DIAGNOSTIC_HANDLE_ID,
|
|
169
|
+
WORKER_KEEPALIVE_INTERVAL_MS,
|
|
170
|
+
isWorkerErrorReport,
|
|
72
171
|
parseRpcRequest,
|
|
73
172
|
parseRpcResponse,
|
|
74
173
|
protocolVersion,
|
|
174
|
+
rehydrateError,
|
|
75
175
|
rpcEvent,
|
|
76
176
|
rpcFailure,
|
|
77
177
|
rpcResult,
|
|
78
|
-
serializeError
|
|
178
|
+
serializeError,
|
|
179
|
+
workerErrorEvent,
|
|
180
|
+
workerKeepaliveEvent
|
|
79
181
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@minnowdb/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "A columnar SQL database for the browser: PostgreSQL-style SQL over durable IndexedDB or OPFS data, with no server or WebAssembly module.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Eric Wilhite",
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
"./dist/engine/worker.js",
|
|
30
30
|
"./dist/engine/worker-indexeddb.js",
|
|
31
31
|
"./dist/engine/worker-opfs.js",
|
|
32
|
-
"./dist/engine/worker-memory.js"
|
|
32
|
+
"./dist/engine/worker-memory.js",
|
|
33
|
+
"./dist/engine/worker-auto.js"
|
|
33
34
|
],
|
|
34
35
|
"scripts": {
|
|
35
36
|
"prepack": "node ../../scripts/prepare-package.mjs --strip-comments"
|
|
@@ -79,6 +80,10 @@
|
|
|
79
80
|
"types": "./dist/engine/worker-memory.d.ts",
|
|
80
81
|
"default": "./dist/engine/worker-memory.js"
|
|
81
82
|
},
|
|
83
|
+
"./worker/auto": {
|
|
84
|
+
"types": "./dist/engine/worker-auto.d.ts",
|
|
85
|
+
"default": "./dist/engine/worker-auto.js"
|
|
86
|
+
},
|
|
82
87
|
"./worker-host": {
|
|
83
88
|
"types": "./dist/engine/worker-host.d.ts",
|
|
84
89
|
"default": "./dist/engine/worker-host.js"
|