@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.
Files changed (47) hide show
  1. package/dist/engine/auto-store.d.ts +52 -0
  2. package/dist/engine/auto-store.js +157 -0
  3. package/dist/engine/buffered-writer.d.ts +2 -0
  4. package/dist/engine/buffered-writer.js +15 -2
  5. package/dist/engine/client-audit-harness.js +123 -0
  6. package/dist/engine/client.d.ts +55 -6
  7. package/dist/engine/client.js +176 -46
  8. package/dist/engine/database.d.ts +15 -1
  9. package/dist/engine/database.js +1276 -251
  10. package/dist/engine/errors.d.ts +61 -2
  11. package/dist/engine/errors.js +116 -3
  12. package/dist/engine/index.d.ts +1 -0
  13. package/dist/engine/index.js +2 -0
  14. package/dist/engine/live.d.ts +24 -1
  15. package/dist/engine/live.js +33 -9
  16. package/dist/engine/scope-write-set.js +36 -0
  17. package/dist/engine/worker-auto.d.ts +1 -0
  18. package/dist/engine/worker-auto.js +3 -0
  19. package/dist/engine/worker-host.d.ts +2 -1
  20. package/dist/engine/worker-host.js +19 -1
  21. package/dist/engine/worker-server.d.ts +53 -1
  22. package/dist/engine/worker-server.js +122 -19
  23. package/dist/engine/worker-store-auto.js +36 -0
  24. package/dist/engine/worker-store-opfs.js +3 -2
  25. package/dist/engine/write-coordinator.js +44 -2
  26. package/dist/storage/indexeddb-audit-helpers.js +269 -0
  27. package/dist/storage/indexeddb.js +599 -374
  28. package/dist/storage/opfs/coordination-helpers.js +54 -0
  29. package/dist/storage/opfs/index.d.ts +1 -1
  30. package/dist/storage/opfs/index.js +3 -2
  31. package/dist/storage/opfs/leader.js +243 -17
  32. package/dist/storage/opfs/power-loss-model.js +62 -0
  33. package/dist/storage/opfs/rpc.js +24 -43
  34. package/dist/storage/opfs/store.d.ts +32 -0
  35. package/dist/storage/opfs/store.js +531 -65
  36. package/dist/storage/toolkit/record-core.js +67 -38
  37. package/dist/storage/toolkit/wal.js +16 -0
  38. package/dist/storage/toolkit/wire.d.ts +1 -1
  39. package/dist/storage/toolkit/wire.js +4 -4
  40. package/dist/storage/types.d.ts +31 -10
  41. package/dist/storage/types.js +27 -16
  42. package/dist/testing/opfs-shim.js +14 -6
  43. package/dist/transactions/index.d.ts +19 -0
  44. package/dist/transactions/index.js +99 -25
  45. package/dist/worker-protocol/index.d.ts +50 -2
  46. package/dist/worker-protocol/index.js +106 -4
  47. package/package.json +7 -2
@@ -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, UniqueIndexCoverageError, WriteConflictError, SchemaConflictError } from "../types.js";
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 allowed = record.props === void 0 ? record.domException === void 0 ? ["name", "message"] : ["name", "message", "domException"] : record.domException === void 0 ? ["name", "message", "props"] : ["name", "message", "domException", "props"];
74
- return exactKeys(record, allowed) && boundedRpcString(record.name) && typeof record.message === "string" && record.message.length <= MAX_OPFS_RPC_IDENTIFIER_CHARACTERS * 4 && (record.domException === void 0 || record.domException === true) && (record.props === void 0 || typeof record.props === "object" && record.props !== null && !Array.isArray(record.props));
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
- if (error instanceof DOMException) {
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
- if (serialized.domException === true) {
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>;