@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
package/dist/engine/client.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createLiveQueryPatch } from "./live-patch.js";
|
|
2
|
-
import { BlockReadBatchTooLargeError, CompactionBacklogError, CompactionJobConflictError, GarbageCollectionJobConflictError, IndexedDbSchemaUpgradeBlockedError, LeaseConflictError, LeaseExpiredError, LeaseOwnerConflictError,
|
|
2
|
+
import { BlockReadBatchTooLargeError, CompactionBacklogError, CompactionJobConflictError, ConnectionLostError, GarbageCollectionJobConflictError, IndexedDbSchemaUpgradeBlockedError, LeaseConflictError, LeaseExpiredError, LeaseOwnerConflictError, OpfsCoordinationError, OpfsDatabaseInUseError, OpfsUncertainOutcomeError, PostingBuildConflictError, SchemaConflictError, SnapshotImportConflictError, SnapshotManifestMissingError, StorageCorruptionError, StorageFormatVersionError, StorageResourceLimitError, TableInUseError, TableRecordConflictError, TempOwnerConflictError, TransactionRecordConflictError, UniqueIndexCoverageError, UniqueKeyBuildConflictError, UniqueKeyConflictError, UnknownOutcomeError, WriteConflictError } from "../storage/types.js";
|
|
3
3
|
import { MAX_SNAPSHOT_STREAM_CHUNK_BYTES } from "../storage/snapshot.js";
|
|
4
|
-
import { parseRpcResponse,
|
|
4
|
+
import { MAX_DATABASE_RPC_IN_FLIGHT, WORKER_DIAGNOSTIC_HANDLE_ID, isWorkerErrorReport, parseRpcResponse, MIN_WORKER_KEEPALIVE_INTERVAL_MS, WORKER_KEEPALIVE_INTERVAL_MS, protocolVersion, rehydrateError as rehydrateSerializedError } from "../worker-protocol/index.js";
|
|
5
5
|
import { definedVectors, toColumnarBatch } from "./batch.js";
|
|
6
|
-
import { CompactionJobCancelledError, CompactionMemoryBudgetError, CompactionWriteAmplificationError, MaintenanceBacklogError, DatabaseReadBacklogError, TransactionExpiredError, DatabaseWorkerTimeoutError, DatabaseWorkerOutcomeUnknownError, LiveQueryLimitError, MissingKeyError, SqlCompileError, UnknownTableError, UniqueConstraintError, VisibleSegmentCursorStaleError } from "./errors.js";
|
|
6
|
+
import { CompactionJobCancelledError, CompactionMemoryBudgetError, CompactionWriteAmplificationError, MaintenanceBacklogError, DatabaseReadBacklogError, TransactionExpiredError, DatabaseWorkerTimeoutError, DatabaseWorkerFailedError, DatabaseStoreUnavailableError, DatabaseWorkerOutcomeUnknownError, LiveQueryLimitError, MissingKeyError, SqlCompileError, UnknownTableError, UniqueConstraintError, VisibleSegmentCursorStaleError } from "./errors.js";
|
|
7
7
|
import { QueryMemoryBudgetError } from "./memory.js";
|
|
8
8
|
import { decodeQueryResult } from "./result-wire.js";
|
|
9
9
|
import { serializeSchema } from "./schema-wire.js";
|
|
@@ -23,6 +23,7 @@ function throwIfClientSnapshotAborted(signal) {
|
|
|
23
23
|
error.name = "AbortError";
|
|
24
24
|
throw error;
|
|
25
25
|
}
|
|
26
|
+
const MAX_KEEPALIVE_EXTENSION = 10;
|
|
26
27
|
const errorRegistry = new Map([
|
|
27
28
|
UniqueConstraintError,
|
|
28
29
|
MissingKeyError,
|
|
@@ -36,6 +37,8 @@ const errorRegistry = new Map([
|
|
|
36
37
|
TransactionExpiredError,
|
|
37
38
|
DatabaseWorkerTimeoutError,
|
|
38
39
|
DatabaseWorkerOutcomeUnknownError,
|
|
40
|
+
DatabaseWorkerFailedError,
|
|
41
|
+
DatabaseStoreUnavailableError,
|
|
39
42
|
LiveQueryLimitError,
|
|
40
43
|
SqlCompileError,
|
|
41
44
|
QueryMemoryBudgetError,
|
|
@@ -64,31 +67,12 @@ const errorRegistry = new Map([
|
|
|
64
67
|
StorageFormatVersionError,
|
|
65
68
|
OpfsCoordinationError,
|
|
66
69
|
OpfsDatabaseInUseError,
|
|
67
|
-
OpfsUncertainOutcomeError
|
|
70
|
+
OpfsUncertainOutcomeError,
|
|
71
|
+
UnknownOutcomeError,
|
|
72
|
+
ConnectionLostError
|
|
68
73
|
].map((constructor) => [constructor.name, constructor]));
|
|
69
74
|
function rehydrateError(serialized) {
|
|
70
|
-
|
|
71
|
-
const error = constructor === void 0 ? new Error(serialized.message) : Object.create(constructor.prototype);
|
|
72
|
-
Object.defineProperty(error, "message", {
|
|
73
|
-
value: serialized.message,
|
|
74
|
-
writable: true,
|
|
75
|
-
configurable: true
|
|
76
|
-
});
|
|
77
|
-
Object.defineProperty(error, "name", {
|
|
78
|
-
value: serialized.name,
|
|
79
|
-
writable: true,
|
|
80
|
-
configurable: true
|
|
81
|
-
});
|
|
82
|
-
if (serialized.stack !== void 0) {
|
|
83
|
-
Object.defineProperty(error, "stack", {
|
|
84
|
-
value: serialized.stack,
|
|
85
|
-
writable: true,
|
|
86
|
-
configurable: true
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
if (serialized.props !== void 0)
|
|
90
|
-
Object.assign(error, serialized.props);
|
|
91
|
-
return error;
|
|
75
|
+
return rehydrateSerializedError(serialized, errorRegistry);
|
|
92
76
|
}
|
|
93
77
|
function rehydrateResponseError(payload) {
|
|
94
78
|
const candidate = payload;
|
|
@@ -103,36 +87,57 @@ class MinnowDatabaseClient {
|
|
|
103
87
|
return this;
|
|
104
88
|
}
|
|
105
89
|
#transport;
|
|
90
|
+
#transportFactory;
|
|
91
|
+
#initPayload;
|
|
92
|
+
#onConnectionLost;
|
|
106
93
|
#requestTimeoutMs;
|
|
107
94
|
#closePromise;
|
|
108
95
|
#pending = /* @__PURE__ */ new Map();
|
|
109
96
|
#events = /* @__PURE__ */ new Map();
|
|
110
97
|
#ready;
|
|
98
|
+
#storeKind;
|
|
111
99
|
#fatal;
|
|
112
100
|
#closed = false;
|
|
113
101
|
#onVisibilityChange;
|
|
114
102
|
#onMessage = (event) => {
|
|
115
103
|
this.#receive(event.data);
|
|
116
104
|
};
|
|
117
|
-
#
|
|
118
|
-
|
|
105
|
+
#onWorkerError;
|
|
106
|
+
#onError = (event) => {
|
|
107
|
+
const detail = event !== void 0 && "filename" in event ? event : void 0;
|
|
108
|
+
const where = detail?.filename === void 0 || detail.filename === "" ? "" : ` at ${detail.filename}:${String(detail.lineno)}:${String(detail.colno)}`;
|
|
109
|
+
const message = detail?.message === void 0 || detail.message === "" ? "" : `: ${detail.message}`;
|
|
110
|
+
const error = new DatabaseWorkerFailedError("error", `The database worker failed: it raised an error it did not handle${message}${where}`, detail?.error === void 0 ? void 0 : { cause: detail.error });
|
|
111
|
+
this.#reportWorkerError({ kind: "transport", context: "worker error event", error });
|
|
112
|
+
this.#fail(error);
|
|
119
113
|
};
|
|
120
114
|
#onMessageError = () => {
|
|
121
|
-
|
|
115
|
+
const error = new DatabaseWorkerFailedError("messageerror", "A database worker message could not be deserialized");
|
|
116
|
+
this.#reportWorkerError({ kind: "transport", context: "worker messageerror event", error });
|
|
117
|
+
this.#fail(error);
|
|
122
118
|
};
|
|
119
|
+
#reportWorkerError(event) {
|
|
120
|
+
if (this.#onWorkerError !== void 0) {
|
|
121
|
+
this.#onWorkerError(event);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (typeof console === "undefined")
|
|
125
|
+
return;
|
|
126
|
+
console.error(`[minnowdb] worker ${event.kind} (${event.context}):`, event.error);
|
|
127
|
+
}
|
|
123
128
|
constructor(transport, options = {}) {
|
|
124
129
|
this.#requestTimeoutMs = clientDeadline(options.requestTimeoutMs ?? 6e4);
|
|
125
130
|
this.#schema = options.schema;
|
|
126
|
-
this.#
|
|
127
|
-
|
|
128
|
-
transport
|
|
129
|
-
transport
|
|
130
|
-
|
|
131
|
+
this.#onWorkerError = options.onWorkerError;
|
|
132
|
+
this.#onConnectionLost = options.onConnectionLost;
|
|
133
|
+
this.#transportFactory = typeof transport === "function" ? transport : void 0;
|
|
134
|
+
this.#transport = typeof transport === "function" ? transport() : transport;
|
|
135
|
+
this.#initPayload = {
|
|
131
136
|
store: options.store ?? { kind: "indexeddb", name: "minnow" },
|
|
132
|
-
...options.databaseOptions === void 0 ? {} : { options: options.databaseOptions }
|
|
137
|
+
...options.databaseOptions === void 0 ? {} : { options: options.databaseOptions },
|
|
138
|
+
keepaliveIntervalMs: keepalivePace(this.#requestTimeoutMs)
|
|
133
139
|
};
|
|
134
|
-
this.#ready = this.#
|
|
135
|
-
this.#ready.catch(() => void 0);
|
|
140
|
+
this.#ready = this.#attach(this.#transport);
|
|
136
141
|
if (typeof document !== "undefined" && typeof document.addEventListener === "function") {
|
|
137
142
|
const report = () => {
|
|
138
143
|
if (this.#closed)
|
|
@@ -146,9 +151,56 @@ class MinnowDatabaseClient {
|
|
|
146
151
|
report();
|
|
147
152
|
}
|
|
148
153
|
}
|
|
154
|
+
#attach(transport) {
|
|
155
|
+
transport.addEventListener("message", this.#onMessage);
|
|
156
|
+
transport.addEventListener("error", this.#onError);
|
|
157
|
+
transport.addEventListener("messageerror", this.#onMessageError);
|
|
158
|
+
const ready = this.#post("rpc-init", null, "init", [this.#initPayload]).then((result) => {
|
|
159
|
+
const kind = result?.store;
|
|
160
|
+
this.#storeKind = kind === "indexeddb" || kind === "opfs" || kind === "memory" ? kind : void 0;
|
|
161
|
+
});
|
|
162
|
+
ready.catch(() => void 0);
|
|
163
|
+
return ready;
|
|
164
|
+
}
|
|
165
|
+
#detach(transport) {
|
|
166
|
+
transport.removeEventListener?.("message", this.#onMessage);
|
|
167
|
+
transport.removeEventListener?.("error", this.#onError);
|
|
168
|
+
transport.removeEventListener?.("messageerror", this.#onMessageError);
|
|
169
|
+
}
|
|
170
|
+
async reopen(transport) {
|
|
171
|
+
const next = transport ?? this.#transportFactory?.();
|
|
172
|
+
if (next === void 0) {
|
|
173
|
+
throw new TypeError("reopen() needs a transport: pass one, or construct the client with a transport factory");
|
|
174
|
+
}
|
|
175
|
+
const previous = this.#transport;
|
|
176
|
+
this.#detach(previous);
|
|
177
|
+
this.#transport = next;
|
|
178
|
+
if (this.#fatal === void 0) {
|
|
179
|
+
this.#fail(new DatabaseWorkerFailedError("reopened", "The database client was reopened"));
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
182
|
+
previous.terminate?.();
|
|
183
|
+
} catch {
|
|
184
|
+
}
|
|
185
|
+
this.#fatal = void 0;
|
|
186
|
+
this.#closed = false;
|
|
187
|
+
this.#closePromise = void 0;
|
|
188
|
+
this.#events.clear();
|
|
189
|
+
this.#transport = next;
|
|
190
|
+
this.#ready = this.#attach(next);
|
|
191
|
+
if (this.#onVisibilityChange !== void 0 && typeof document !== "undefined") {
|
|
192
|
+
document.addEventListener("visibilitychange", this.#onVisibilityChange);
|
|
193
|
+
}
|
|
194
|
+
this.#onVisibilityChange?.();
|
|
195
|
+
await this.#ready;
|
|
196
|
+
}
|
|
149
197
|
async ready() {
|
|
150
198
|
return this.#ready;
|
|
151
199
|
}
|
|
200
|
+
async storeKind() {
|
|
201
|
+
await this.#ready;
|
|
202
|
+
return this.#storeKind;
|
|
203
|
+
}
|
|
152
204
|
async createTable(input) {
|
|
153
205
|
await this.#call("createTable", [input]);
|
|
154
206
|
}
|
|
@@ -223,7 +275,24 @@ class MinnowDatabaseClient {
|
|
|
223
275
|
...onError === void 0 ? {} : { onError }
|
|
224
276
|
});
|
|
225
277
|
const created = this.#call("bufferedWriter", [handleId, tableName, wireOptions]);
|
|
226
|
-
|
|
278
|
+
const writer = new ClientBufferedWriter(this.#erased, handleId, created);
|
|
279
|
+
if (typeof document !== "undefined" && typeof document.addEventListener === "function") {
|
|
280
|
+
const onHidden = () => {
|
|
281
|
+
if (document.visibilityState === "hidden")
|
|
282
|
+
writer.requestFlush();
|
|
283
|
+
};
|
|
284
|
+
const onPageHide = () => {
|
|
285
|
+
writer.requestFlush();
|
|
286
|
+
};
|
|
287
|
+
document.addEventListener("visibilitychange", onHidden);
|
|
288
|
+
const page = typeof window === "undefined" ? void 0 : window;
|
|
289
|
+
page?.addEventListener("pagehide", onPageHide);
|
|
290
|
+
writer._onClose(() => {
|
|
291
|
+
document.removeEventListener("visibilitychange", onHidden);
|
|
292
|
+
page?.removeEventListener("pagehide", onPageHide);
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
return writer;
|
|
227
296
|
}
|
|
228
297
|
async readTable(tableName, versionOrOptions) {
|
|
229
298
|
return decodeQueryResult(await this.#call("readTable", versionOrOptions === void 0 ? [tableName] : [tableName, versionOrOptions])).rows;
|
|
@@ -512,7 +581,7 @@ class MinnowDatabaseClient {
|
|
|
512
581
|
try {
|
|
513
582
|
await this.#post("rpc-call", null, "dispose", [], void 0, true, { timeoutMs });
|
|
514
583
|
} finally {
|
|
515
|
-
this.#fail(new Error("Database client is closed"));
|
|
584
|
+
this.#fail(new Error("Database client is closed"), true);
|
|
516
585
|
this.#transport.removeEventListener?.("message", this.#onMessage);
|
|
517
586
|
this.#transport.removeEventListener?.("error", this.#onError);
|
|
518
587
|
this.#transport.removeEventListener?.("messageerror", this.#onMessageError);
|
|
@@ -552,8 +621,19 @@ class MinnowDatabaseClient {
|
|
|
552
621
|
const timeoutMs = controls.timeoutMs ?? this.#requestTimeoutMs;
|
|
553
622
|
const mayPublish = rpcMayPublish(method, args);
|
|
554
623
|
return new Promise((resolve, reject) => {
|
|
555
|
-
const
|
|
624
|
+
const startedAt = Date.now();
|
|
625
|
+
const expire = () => this.#fail(new DatabaseWorkerTimeoutError(method, timeoutMs));
|
|
626
|
+
let timer = setTimeout(expire, timeoutMs);
|
|
556
627
|
timer.unref?.();
|
|
628
|
+
const keepalive = () => {
|
|
629
|
+
const elapsed = Date.now() - startedAt;
|
|
630
|
+
const remaining = timeoutMs * MAX_KEEPALIVE_EXTENSION - elapsed;
|
|
631
|
+
if (remaining <= 0)
|
|
632
|
+
return;
|
|
633
|
+
clearTimeout(timer);
|
|
634
|
+
timer = setTimeout(expire, Math.min(timeoutMs, remaining));
|
|
635
|
+
timer.unref?.();
|
|
636
|
+
};
|
|
557
637
|
const onAbort = () => {
|
|
558
638
|
try {
|
|
559
639
|
this.#transport.postMessage({ version: protocolVersion, requestId, kind: "rpc-cancel" });
|
|
@@ -580,7 +660,15 @@ class MinnowDatabaseClient {
|
|
|
580
660
|
if (controls.onStats !== void 0) {
|
|
581
661
|
this.#events.set(requestId, { onStats: controls.onStats });
|
|
582
662
|
}
|
|
583
|
-
this.#pending.set(requestId, {
|
|
663
|
+
this.#pending.set(requestId, {
|
|
664
|
+
resolve,
|
|
665
|
+
reject,
|
|
666
|
+
cleanup,
|
|
667
|
+
method,
|
|
668
|
+
requestId,
|
|
669
|
+
mayPublish,
|
|
670
|
+
keepalive
|
|
671
|
+
});
|
|
584
672
|
try {
|
|
585
673
|
this.#transport.postMessage(kind === "rpc-init" ? { version: protocolVersion, requestId, kind, payload: args[0] } : { version: protocolVersion, requestId, kind, handleId, method, args }, transfer === void 0 ? void 0 : { transfer });
|
|
586
674
|
} catch (error) {
|
|
@@ -601,6 +689,20 @@ class MinnowDatabaseClient {
|
|
|
601
689
|
if (response === null)
|
|
602
690
|
return;
|
|
603
691
|
if (response.kind === "rpc-event") {
|
|
692
|
+
if (response.handleId === WORKER_DIAGNOSTIC_HANDLE_ID) {
|
|
693
|
+
if (response.event === "keepalive" && typeof response.requestId === "string") {
|
|
694
|
+
this.#pending.get(response.requestId)?.keepalive?.();
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
if (response.event === "error" && isWorkerErrorReport(response.payload)) {
|
|
698
|
+
this.#reportWorkerError({
|
|
699
|
+
kind: response.payload.kind,
|
|
700
|
+
context: response.payload.context,
|
|
701
|
+
error: rehydrateError(response.payload.error)
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
604
706
|
const route = this.#events.get(response.handleId);
|
|
605
707
|
if (route === void 0)
|
|
606
708
|
return;
|
|
@@ -651,30 +753,52 @@ class MinnowDatabaseClient {
|
|
|
651
753
|
}
|
|
652
754
|
#rejectUnreadable(message, cause) {
|
|
653
755
|
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
654
|
-
const
|
|
655
|
-
cause
|
|
656
|
-
});
|
|
756
|
+
const text = `The database worker sent a frame this client cannot read: ${reason}`;
|
|
657
757
|
const requestId = message.requestId;
|
|
658
758
|
const pending = typeof requestId === "string" ? this.#pending.get(requestId) : void 0;
|
|
659
759
|
if (pending === void 0 || typeof requestId !== "string") {
|
|
660
|
-
this.#fail(
|
|
760
|
+
this.#fail(new DatabaseWorkerFailedError("messageerror", text, { cause }));
|
|
661
761
|
return;
|
|
662
762
|
}
|
|
763
|
+
const error = new Error(text, { cause });
|
|
663
764
|
this.#pending.delete(requestId);
|
|
664
765
|
pending.cleanup?.();
|
|
665
766
|
pending.reject(pending.mayPublish ? new DatabaseWorkerOutcomeUnknownError(pending.method, requestId, { cause: error }) : error);
|
|
666
767
|
}
|
|
667
|
-
#fail(error) {
|
|
768
|
+
#fail(error, closing = false) {
|
|
769
|
+
const first = this.#fatal === void 0;
|
|
668
770
|
this.#fatal = error;
|
|
669
771
|
const pending = [...this.#pending.values()];
|
|
670
772
|
this.#pending.clear();
|
|
773
|
+
const routes = [...this.#events.values()];
|
|
671
774
|
this.#events.clear();
|
|
775
|
+
for (const route of routes) {
|
|
776
|
+
if (!closing) {
|
|
777
|
+
try {
|
|
778
|
+
route.onError?.(error);
|
|
779
|
+
} catch {
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
try {
|
|
783
|
+
route.onComplete?.();
|
|
784
|
+
} catch {
|
|
785
|
+
}
|
|
786
|
+
}
|
|
672
787
|
for (const call of pending) {
|
|
673
788
|
call.cleanup?.();
|
|
674
789
|
call.reject(call.mayPublish ? new DatabaseWorkerOutcomeUnknownError(call.method, call.requestId, { cause: error }) : error);
|
|
675
790
|
}
|
|
791
|
+
if (first && error instanceof ConnectionLostError && !(error instanceof DatabaseWorkerFailedError && error.reason === "reopened") && this.#onConnectionLost !== void 0) {
|
|
792
|
+
try {
|
|
793
|
+
this.#onConnectionLost(error);
|
|
794
|
+
} catch {
|
|
795
|
+
}
|
|
796
|
+
}
|
|
676
797
|
}
|
|
677
798
|
}
|
|
799
|
+
function keepalivePace(deadlineMs) {
|
|
800
|
+
return Math.min(WORKER_KEEPALIVE_INTERVAL_MS, Math.max(MIN_WORKER_KEEPALIVE_INTERVAL_MS, Math.floor(deadlineMs / 3)));
|
|
801
|
+
}
|
|
678
802
|
function clientDeadline(value) {
|
|
679
803
|
if (!Number.isSafeInteger(value) || value < 1 || value > 2147483647)
|
|
680
804
|
throw new RangeError("Worker timeout must be a positive timer interval");
|
|
@@ -695,6 +819,10 @@ class ClientBufferedWriter {
|
|
|
695
819
|
created.catch(() => void 0);
|
|
696
820
|
}
|
|
697
821
|
#created;
|
|
822
|
+
#onClose;
|
|
823
|
+
_onClose(cleanup) {
|
|
824
|
+
this.#onClose = cleanup;
|
|
825
|
+
}
|
|
698
826
|
async add(row) {
|
|
699
827
|
await this.#created;
|
|
700
828
|
return await this.client._invoke(this.handleId, "add", [row]);
|
|
@@ -720,6 +848,8 @@ class ClientBufferedWriter {
|
|
|
720
848
|
return await this.client._invoke(this.handleId, "close", []);
|
|
721
849
|
} finally {
|
|
722
850
|
this.client._unrouteEvents(this.handleId);
|
|
851
|
+
this.#onClose?.();
|
|
852
|
+
this.#onClose = void 0;
|
|
723
853
|
}
|
|
724
854
|
}
|
|
725
855
|
}
|
|
@@ -4,7 +4,7 @@ export { attachLifecycleFlush, BufferedTableWriter, MAX_BUFFERED_WRITER_PENDING_
|
|
|
4
4
|
import { CompactionJobCancelledError, CompactionMemoryBudgetError, CompactionWriteAmplificationError, MaintenanceBacklogError, MissingKeyError, SqlCompileError, UnknownTableError, UniqueConstraintError, VisibleSegmentCursorStaleError } from "./errors.js";
|
|
5
5
|
export { DatabaseReadBacklogError } from "./errors.js";
|
|
6
6
|
import { type Compression } from "../block-format/index.js";
|
|
7
|
-
import { type BlockStore, type ColumnDefault, type ColumnGenerated, CompactionBacklogError, type CompactionJobRecord, type CompactionJobState, type GarbageCollectionJobRecord, type GarbageCollectionJobState, type SimpleDataType, type SqlDomain, type StorageIntegrityMode, type StorageIntegrityReport, type StorageStats, type InterruptedSnapshotImport, type InterruptedSnapshotImportAbortResult, TableInUseError } from "../storage/types.js";
|
|
7
|
+
import { type BlockStore, type ColumnDefault, type ColumnGenerated, CompactionBacklogError, type CompactionJobRecord, type CompactionJobState, type GarbageCollectionJobRecord, type GarbageCollectionJobState, type SimpleDataType, type SqlDomain, type StorageIntegrityMode, type StorageIntegrityReport, type StorageStats, type InterruptedSnapshotImport, type InterruptedSnapshotImportAbortResult, type TableColumnRecord, type TableRecord, TableInUseError } from "../storage/types.js";
|
|
8
8
|
import type { SnapshotExportProgress, SnapshotLoadProgress } from "../storage/snapshot.js";
|
|
9
9
|
import { type ComparisonOperator, type CompiledQuery, type CompiledStatement, type ForeignKeyDefinition, type QueryResult, type QueryRow, type QueryValue, type UniqueConstraintDefinition } from "./query.js";
|
|
10
10
|
import { LiveQuerySet, type LiveQuerySetOptions } from "./live.js";
|
|
@@ -476,6 +476,13 @@ export interface MinnowDatabaseOptions<TSchema extends AnySchema = UntypedSchema
|
|
|
476
476
|
coordinateWrites?: boolean;
|
|
477
477
|
now?: () => Date;
|
|
478
478
|
createId?: () => string;
|
|
479
|
+
/**
|
|
480
|
+
* Hears failures in work no caller awaits: a background collection pass that failed, a live
|
|
481
|
+
* sweep that failed with nobody subscribed. `maintenanceStatus().lastError` still records the
|
|
482
|
+
* last one; this hook sees every one, as it happens. Inside the worker host it feeds the
|
|
483
|
+
* client's `onWorkerError`.
|
|
484
|
+
*/
|
|
485
|
+
onBackgroundError?: (error: unknown, context: string) => void;
|
|
479
486
|
/** Durable spill-owner lease lifetime; renewed while a spilling query runs. */
|
|
480
487
|
spillOwnerLeaseMs?: number;
|
|
481
488
|
/** Durable active-writer deadline; renewed every third while the writer is live. */
|
|
@@ -657,6 +664,13 @@ export interface RunStatementOptions {
|
|
|
657
664
|
*/
|
|
658
665
|
export interface StatementWriter extends WriteSession {
|
|
659
666
|
queryPlan(plan: CompiledQuery): Promise<QueryResult>;
|
|
667
|
+
/**
|
|
668
|
+
* Which of the given keys the scope sees as present — staged by it and not removed, or
|
|
669
|
+
* committed — as key tokens, answered from the scope's key ledger and a keyed probe so a plain
|
|
670
|
+
* INSERT or an `ON CONFLICT DO NOTHING` never has to stage its buffered predecessors just to
|
|
671
|
+
* find a duplicate. Undefined when the writer cannot answer without a read.
|
|
672
|
+
*/
|
|
673
|
+
stagedKeyPresence?(table: TableRecord, keyColumn: TableColumnRecord, keys: ReadonlyArray<Exclude<BatchValue, null>>): Promise<ReadonlySet<string> | undefined>;
|
|
660
674
|
}
|
|
661
675
|
export interface VisibleSegment {
|
|
662
676
|
id: string;
|