@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/storage/opfs/rpc.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import { BlockReadBatchTooLargeError, CompactionBacklogError, CompactionJobConflictError, GarbageCollectionJobConflictError, IndexedDbSchemaUpgradeBlockedError, LeaseConflictError, LeaseExpiredError, LeaseOwnerConflictError, OpfsCoordinationError, OpfsDatabaseInUseError, OpfsUncertainOutcomeError, PostingBuildConflictError, SnapshotImportConflictError, SnapshotManifestMissingError, StorageCorruptionError, StorageFormatVersionError, StorageResourceLimitError, TableInUseError, TableRecordConflictError, TempOwnerConflictError, TransactionRecordConflictError, UniqueKeyBuildConflictError, UniqueKeyConflictError,
|
|
1
|
+
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 "../types.js";
|
|
2
2
|
import { dateIsoString } from "../../date-value.js";
|
|
3
|
+
import { MAX_SERIALIZED_CAUSE_DEPTH, rehydrateError, serializeError } from "../../worker-protocol/index.js";
|
|
4
|
+
const MAX_OPFS_RPC_STACK_CHARACTERS = 64 * 1024;
|
|
3
5
|
const MAX_OPFS_RPC_MESSAGE_BYTES = 66 * 1024 * 1024;
|
|
6
|
+
const MAX_OPFS_RPC_HOLD_MS = 6e4;
|
|
4
7
|
const MAX_OPFS_RPC_IDENTIFIER_CHARACTERS = 1024;
|
|
5
8
|
const MAX_OPFS_RPC_DEPTH = 64;
|
|
6
9
|
const MAX_OPFS_RPC_NODES = 11e5;
|
|
@@ -66,12 +69,13 @@ function estimateRpcValueBytes(value) {
|
|
|
66
69
|
}
|
|
67
70
|
return bytes;
|
|
68
71
|
}
|
|
69
|
-
function validSerializedError(value) {
|
|
72
|
+
function validSerializedError(value, depth = 0) {
|
|
70
73
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
71
74
|
return false;
|
|
72
75
|
const record = value;
|
|
73
|
-
const
|
|
74
|
-
|
|
76
|
+
const optional = ["stack", "domException", "props", "cause"];
|
|
77
|
+
const allowed = ["name", "message", ...optional.filter((key) => record[key] !== void 0)];
|
|
78
|
+
return exactKeys(record, allowed) && boundedRpcString(record.name) && typeof record.message === "string" && record.message.length <= MAX_OPFS_RPC_IDENTIFIER_CHARACTERS * 4 && (record.stack === void 0 || typeof record.stack === "string" && record.stack.length <= MAX_OPFS_RPC_STACK_CHARACTERS) && (record.domException === void 0 || record.domException === true) && (record.props === void 0 || typeof record.props === "object" && record.props !== null && !Array.isArray(record.props)) && (record.cause === void 0 || depth < MAX_SERIALIZED_CAUSE_DEPTH && validSerializedError(record.cause, depth + 1));
|
|
75
79
|
}
|
|
76
80
|
function parseStoreRpcMessage(value, knownMethods) {
|
|
77
81
|
try {
|
|
@@ -81,7 +85,7 @@ function parseStoreRpcMessage(value, knownMethods) {
|
|
|
81
85
|
estimateRpcValueBytes(record);
|
|
82
86
|
switch (record.kind) {
|
|
83
87
|
case "op":
|
|
84
|
-
if (!exactKeys(record, ["kind", "requestId", "from", "method", "args"]) || !boundedRpcString(record.requestId) || !boundedRpcString(record.from) || !boundedRpcString(record.method) || !knownMethods.has(record.method) || !Array.isArray(record.args) || estimateRpcValueBytes(record.args) > MAX_OPFS_RPC_MESSAGE_BYTES)
|
|
88
|
+
if (!exactKeys(record, ["kind", "requestId", "from", "method", "args", "sentAt"]) || !boundedRpcString(record.requestId) || !boundedRpcString(record.from) || !boundedRpcString(record.method) || !knownMethods.has(record.method) || !Array.isArray(record.args) || !Number.isSafeInteger(record.sentAt) || record.sentAt < 0 || estimateRpcValueBytes(record.args) > MAX_OPFS_RPC_MESSAGE_BYTES)
|
|
85
89
|
return void 0;
|
|
86
90
|
return record;
|
|
87
91
|
case "result":
|
|
@@ -96,7 +100,16 @@ function parseStoreRpcMessage(value, knownMethods) {
|
|
|
96
100
|
return record;
|
|
97
101
|
case "busy":
|
|
98
102
|
return exactKeys(record, ["kind", "requestId"]) && boundedRpcString(record.requestId) ? record : void 0;
|
|
103
|
+
case "hold":
|
|
104
|
+
return exactKeys(record, ["kind", "requestId", "ms"]) && boundedRpcString(record.requestId) && typeof record.ms === "number" && Number.isSafeInteger(record.ms) && record.ms >= 0 && record.ms <= MAX_OPFS_RPC_HOLD_MS ? record : void 0;
|
|
105
|
+
case "uncertain":
|
|
106
|
+
return exactKeys(record, ["kind", "requestId"]) && boundedRpcString(record.requestId) ? record : void 0;
|
|
107
|
+
case "declined":
|
|
108
|
+
return exactKeys(record, ["kind", "requestId", "reason"]) && boundedRpcString(record.requestId) && record.reason === "not-leader" ? record : void 0;
|
|
109
|
+
case "state":
|
|
110
|
+
return exactKeys(record, ["kind", "leaderId", "foreground"]) && boundedRpcString(record.leaderId) && typeof record.foreground === "boolean" ? record : void 0;
|
|
99
111
|
case "leader":
|
|
112
|
+
case "wait":
|
|
100
113
|
case "released":
|
|
101
114
|
return exactKeys(record, ["kind", "leaderId"]) && boundedRpcString(record.leaderId) ? record : void 0;
|
|
102
115
|
case "ping":
|
|
@@ -192,50 +205,18 @@ const errorRegistry = new Map([
|
|
|
192
205
|
StorageFormatVersionError,
|
|
193
206
|
OpfsCoordinationError,
|
|
194
207
|
OpfsDatabaseInUseError,
|
|
195
|
-
OpfsUncertainOutcomeError
|
|
208
|
+
OpfsUncertainOutcomeError,
|
|
209
|
+
UnknownOutcomeError,
|
|
210
|
+
ConnectionLostError
|
|
196
211
|
].map((constructor) => [constructor.name, constructor]));
|
|
197
212
|
function serializeStoreError(error) {
|
|
198
|
-
|
|
199
|
-
return { name: error.name, message: error.message, domException: true };
|
|
200
|
-
}
|
|
201
|
-
if (!(error instanceof Error))
|
|
202
|
-
return { name: "Error", message: String(error) };
|
|
203
|
-
const props = {};
|
|
204
|
-
for (const key of Object.keys(error)) {
|
|
205
|
-
const value = error[key];
|
|
206
|
-
try {
|
|
207
|
-
structuredClone(value);
|
|
208
|
-
props[key] = value;
|
|
209
|
-
} catch {
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
return {
|
|
213
|
-
name: error.name,
|
|
214
|
-
message: error.message,
|
|
215
|
-
...Object.keys(props).length === 0 ? {} : { props }
|
|
216
|
-
};
|
|
213
|
+
return serializeError(error);
|
|
217
214
|
}
|
|
218
215
|
function rehydrateStoreError(serialized) {
|
|
219
|
-
|
|
220
|
-
return new DOMException(serialized.message, serialized.name);
|
|
221
|
-
}
|
|
222
|
-
const constructor = errorRegistry.get(serialized.name);
|
|
223
|
-
const error = constructor === void 0 ? new Error(serialized.message) : Object.create(constructor.prototype);
|
|
224
|
-
Object.defineProperty(error, "message", {
|
|
225
|
-
value: serialized.message,
|
|
226
|
-
writable: true,
|
|
227
|
-
configurable: true
|
|
228
|
-
});
|
|
229
|
-
Object.defineProperty(error, "name", {
|
|
230
|
-
value: serialized.name,
|
|
231
|
-
writable: true,
|
|
232
|
-
configurable: true
|
|
233
|
-
});
|
|
234
|
-
if (serialized.props !== void 0)
|
|
235
|
-
Object.assign(error, serialized.props);
|
|
236
|
-
return error;
|
|
216
|
+
return rehydrateError(serialized, errorRegistry);
|
|
237
217
|
}
|
|
238
218
|
export {
|
|
219
|
+
MAX_OPFS_RPC_HOLD_MS,
|
|
239
220
|
MAX_OPFS_RPC_IDENTIFIER_CHARACTERS,
|
|
240
221
|
MAX_OPFS_RPC_MESSAGE_BYTES,
|
|
241
222
|
estimateRpcValueBytes,
|
|
@@ -15,8 +15,26 @@ export interface OpfsBlockStoreOptions {
|
|
|
15
15
|
checkpointEntries?: number;
|
|
16
16
|
/** @internal Test seam: cleanup-debt backpressure limit (default 64 MiB). */
|
|
17
17
|
cleanupLimitBytes?: number;
|
|
18
|
+
/** @internal Test seam: how long served requests stay answerable (default 10 minutes). */
|
|
19
|
+
servedLedgerAgeMs?: number;
|
|
20
|
+
/** @internal Test seam: the largest served result the ledger retains (default 64 KiB). */
|
|
21
|
+
servedLedgerResultBytes?: number;
|
|
18
22
|
/** @internal Test seam: how long a follower waits for the leader (default 1000ms). */
|
|
19
23
|
rpcTimeoutMs?: number;
|
|
24
|
+
/** @internal Test seam: minimum spacing between foreground yields (default 3000ms). */
|
|
25
|
+
yieldCooldownMs?: number;
|
|
26
|
+
/** @internal Test seam: idle time before a hidden leader releases (default 15000ms). */
|
|
27
|
+
hiddenIdleReleaseMs?: number;
|
|
28
|
+
/** @internal Test seam: how long an ex-leader stays out of elections (default 1500ms). */
|
|
29
|
+
handoverGraceMs?: number;
|
|
30
|
+
/** @internal Test seam: how long an operation looks for a leader (default 10000ms). */
|
|
31
|
+
dispatchBudgetMs?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Hears failures no operation reports: a background checkpoint or cleanup that failed, an
|
|
34
|
+
* election or handover that threw, a served request that could not be answered. The worker
|
|
35
|
+
* host wires it to the client's `onWorkerError`; a direct caller may log it.
|
|
36
|
+
*/
|
|
37
|
+
onDiagnostic?: (error: unknown, context: string) => void;
|
|
20
38
|
}
|
|
21
39
|
/**
|
|
22
40
|
* The OPFS block store: one leader per database holds every file handle and does all storage
|
|
@@ -50,6 +68,8 @@ export declare class OpfsBlockStore {
|
|
|
50
68
|
readonly liveQueryChannelName: string;
|
|
51
69
|
private constructor();
|
|
52
70
|
static open(options: OpfsBlockStoreOptions): Promise<OpfsBlockStore>;
|
|
71
|
+
/** @internal Simulates a leader from a release without keepalives. */
|
|
72
|
+
_suppressKeepaliveForTests(): void;
|
|
53
73
|
/** Marks this connection as the one the user is looking at — a leadership preference. */
|
|
54
74
|
setForeground(foreground: boolean): void;
|
|
55
75
|
putTempRunPage(page: TempRunPage): Promise<void>;
|
|
@@ -69,6 +89,8 @@ export declare class OpfsBlockStore {
|
|
|
69
89
|
_dropNextRpcResultForTests(): void;
|
|
70
90
|
/** Test-only: holds newly admitted served mutations until the returned release is called. */
|
|
71
91
|
_holdServedMutationsForTests(): () => void;
|
|
92
|
+
/** Test-only: the request id of the oldest pending request, for hand-posted answers. */
|
|
93
|
+
_oldestPendingRequestIdForTests(): string | undefined;
|
|
72
94
|
/** Test-only: retransmits the oldest request with its stable deduplication identity. */
|
|
73
95
|
_resendOldestPendingForTests(): void;
|
|
74
96
|
/** Test-only counters that pin the connection's bounded RPC state and close-time cleanup. */
|
|
@@ -88,3 +110,13 @@ export declare function deleteOpfsDatabase(options: {
|
|
|
88
110
|
name: string;
|
|
89
111
|
root?: FileSystemDirectoryHandle;
|
|
90
112
|
}): Promise<void>;
|
|
113
|
+
/**
|
|
114
|
+
* Whether a database directory of this name exists in the origin's private file system (or
|
|
115
|
+
* under `root`). Nothing is created: an `auto` descriptor asks this before choosing a store for
|
|
116
|
+
* a name it has no record of, so a database an explicit `opfs` descriptor created is found
|
|
117
|
+
* rather than shadowed by an empty one.
|
|
118
|
+
*/
|
|
119
|
+
export declare function opfsDatabaseExists(options: {
|
|
120
|
+
name: string;
|
|
121
|
+
root?: FileSystemDirectoryHandle;
|
|
122
|
+
}): Promise<boolean>;
|