@minnowdb/core 0.3.0 → 0.4.0

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 (276) hide show
  1. package/README.md +4 -2
  2. package/dist/block-format/block.d.ts +32 -13
  3. package/dist/block-format/block.js +179 -55
  4. package/dist/block-format/checksum.d.ts +2 -1
  5. package/dist/block-format/checksum.js +5 -2
  6. package/dist/block-format/codecs.d.ts +7 -2
  7. package/dist/block-format/codecs.js +53 -12
  8. package/dist/block-format/column.d.ts +3 -2
  9. package/dist/block-format/column.js +96 -30
  10. package/dist/block-format/index.d.ts +2 -2
  11. package/dist/block-format/index.js +2 -2
  12. package/dist/block-format/physical.d.ts +8 -2
  13. package/dist/block-format/physical.js +14 -6
  14. package/dist/block-format/types.d.ts +7 -3
  15. package/dist/block-format/types.js +0 -1
  16. package/dist/block-format/unicode.d.ts +11 -0
  17. package/dist/block-format/unicode.js +46 -0
  18. package/dist/date-value.d.ts +15 -0
  19. package/dist/date-value.js +64 -0
  20. package/dist/engine/artifact-cache.d.ts +0 -1
  21. package/dist/engine/artifact-cache.js +0 -1
  22. package/dist/engine/batch.d.ts +5 -2
  23. package/dist/engine/batch.js +0 -1
  24. package/dist/engine/buffered-writer.d.ts +2 -1
  25. package/dist/engine/buffered-writer.js +34 -9
  26. package/dist/engine/cache-limits.d.ts +25 -0
  27. package/dist/engine/cache-limits.js +25 -0
  28. package/dist/engine/catalog.d.ts +5 -6
  29. package/dist/engine/catalog.js +5 -5
  30. package/dist/engine/client.d.ts +27 -7
  31. package/dist/engine/client.js +197 -31
  32. package/dist/engine/database.d.ts +161 -38
  33. package/dist/engine/database.js +4320 -1523
  34. package/dist/engine/defaults.d.ts +0 -1
  35. package/dist/engine/defaults.js +2 -4
  36. package/dist/engine/errors.d.ts +19 -1
  37. package/dist/engine/errors.js +32 -2
  38. package/dist/engine/fts.d.ts +0 -6
  39. package/dist/engine/fts.js +39 -14
  40. package/dist/engine/group-index.d.ts +0 -1
  41. package/dist/engine/group-index.js +6 -11
  42. package/dist/engine/index.d.ts +16 -11
  43. package/dist/engine/index.js +12 -10
  44. package/dist/engine/join-index.d.ts +0 -1
  45. package/dist/engine/join-index.js +2 -2
  46. package/dist/engine/keyed-live.d.ts +57 -0
  47. package/dist/engine/keyed-live.js +226 -0
  48. package/dist/engine/live-api.d.ts +4 -0
  49. package/dist/engine/live-api.js +4 -0
  50. package/dist/engine/live.d.ts +43 -26
  51. package/dist/engine/live.js +499 -129
  52. package/dist/engine/memory.d.ts +2 -1
  53. package/dist/engine/memory.js +3 -2
  54. package/dist/engine/optimizer.d.ts +0 -1
  55. package/dist/engine/optimizer.js +4 -4
  56. package/dist/engine/query-api.d.ts +3 -0
  57. package/dist/engine/query-api.js +3 -0
  58. package/dist/engine/query-cache.d.ts +0 -1
  59. package/dist/engine/query-cache.js +4 -4
  60. package/dist/engine/query.d.ts +4 -7
  61. package/dist/engine/query.js +348 -155
  62. package/dist/engine/result-wire.d.ts +0 -1
  63. package/dist/engine/result-wire.js +2 -2
  64. package/dist/engine/schema-wire.d.ts +1 -2
  65. package/dist/engine/schema-wire.js +0 -1
  66. package/dist/engine/schema.d.ts +3 -8
  67. package/dist/engine/schema.js +41 -24
  68. package/dist/engine/sort-keys.d.ts +0 -1
  69. package/dist/engine/sort-keys.js +2 -2
  70. package/dist/engine/sql-domains.d.ts +6 -1
  71. package/dist/engine/sql-domains.js +209 -45
  72. package/dist/engine/sql-driver.d.ts +2 -2
  73. package/dist/engine/sql-driver.js +0 -1
  74. package/dist/engine/sql-json.d.ts +0 -1
  75. package/dist/engine/sql-json.js +53 -7
  76. package/dist/engine/sql-semantics.d.ts +8 -5
  77. package/dist/engine/sql-semantics.js +427 -65
  78. package/dist/engine/typed-live.d.ts +67 -0
  79. package/dist/engine/typed-live.js +349 -0
  80. package/dist/engine/vector.d.ts +7 -1
  81. package/dist/engine/vector.js +202 -58
  82. package/dist/engine/worker-host.d.ts +8 -2
  83. package/dist/engine/worker-host.js +616 -152
  84. package/dist/engine/worker.d.ts +0 -1
  85. package/dist/engine/worker.js +3 -3
  86. package/dist/engine/write-block-planner.d.ts +18 -0
  87. package/dist/engine/write-block-planner.js +112 -0
  88. package/dist/index.d.ts +0 -1
  89. package/dist/index.js +0 -1
  90. package/dist/plan/index.d.ts +0 -1
  91. package/dist/plan/index.js +0 -1
  92. package/dist/storage/index.d.ts +1 -1
  93. package/dist/storage/index.js +1 -1
  94. package/dist/storage/indexeddb.d.ts +74 -65
  95. package/dist/storage/indexeddb.js +12301 -2076
  96. package/dist/storage/memory.d.ts +56 -66
  97. package/dist/storage/memory.js +1004 -139
  98. package/dist/storage/opfs/files.d.ts +18 -7
  99. package/dist/storage/opfs/files.js +115 -28
  100. package/dist/storage/opfs/index.d.ts +1 -1
  101. package/dist/storage/opfs/index.js +1 -1
  102. package/dist/storage/opfs/leader.d.ts +403 -91
  103. package/dist/storage/opfs/leader.js +4306 -602
  104. package/dist/storage/opfs/rpc.d.ts +15 -3
  105. package/dist/storage/opfs/rpc.js +223 -2
  106. package/dist/storage/opfs/snapshot-ledger.d.ts +40 -0
  107. package/dist/storage/opfs/snapshot-ledger.js +281 -0
  108. package/dist/storage/opfs/store.d.ts +26 -121
  109. package/dist/storage/opfs/store.js +436 -388
  110. package/dist/storage/persistence.d.ts +37 -0
  111. package/dist/storage/persistence.js +78 -0
  112. package/dist/storage/snapshot-stream.d.ts +24 -0
  113. package/dist/storage/snapshot-stream.js +897 -0
  114. package/dist/storage/snapshot.d.ts +9 -99
  115. package/dist/storage/snapshot.js +33 -309
  116. package/dist/storage/toolkit/extents.d.ts +41 -7
  117. package/dist/storage/toolkit/extents.js +402 -39
  118. package/dist/storage/toolkit/index.d.ts +3 -4
  119. package/dist/storage/toolkit/index.js +27 -3
  120. package/dist/storage/toolkit/record-core.d.ts +174 -61
  121. package/dist/storage/toolkit/record-core.js +5401 -849
  122. package/dist/storage/toolkit/sync-file.d.ts +10 -1
  123. package/dist/storage/toolkit/sync-file.js +50 -2
  124. package/dist/storage/toolkit/wal.d.ts +23 -5
  125. package/dist/storage/toolkit/wal.js +89 -27
  126. package/dist/storage/toolkit/wire.d.ts +4 -6
  127. package/dist/storage/toolkit/wire.js +80 -25
  128. package/dist/storage/types.d.ts +1271 -218
  129. package/dist/storage/types.js +1471 -138
  130. package/dist/testing/block-store-conformance.d.ts +0 -1
  131. package/dist/testing/block-store-conformance.js +877 -138
  132. package/dist/testing/index.d.ts +42 -31
  133. package/dist/testing/index.js +100 -68
  134. package/dist/testing/opfs-shim.d.ts +13 -3
  135. package/dist/testing/opfs-shim.js +40 -6
  136. package/dist/testing/simulator.d.ts +0 -1
  137. package/dist/testing/simulator.js +43 -12
  138. package/dist/testing/sqllogictest.d.ts +0 -1
  139. package/dist/testing/sqllogictest.js +0 -1
  140. package/dist/transactions/index.d.ts +81 -20
  141. package/dist/transactions/index.js +1050 -268
  142. package/dist/worker-protocol/index.d.ts +3 -2
  143. package/dist/worker-protocol/index.js +2 -5
  144. package/package.json +49 -1
  145. package/dist/block-format/block.d.ts.map +0 -1
  146. package/dist/block-format/block.js.map +0 -1
  147. package/dist/block-format/checksum.d.ts.map +0 -1
  148. package/dist/block-format/checksum.js.map +0 -1
  149. package/dist/block-format/codecs.d.ts.map +0 -1
  150. package/dist/block-format/codecs.js.map +0 -1
  151. package/dist/block-format/column.d.ts.map +0 -1
  152. package/dist/block-format/column.js.map +0 -1
  153. package/dist/block-format/index.d.ts.map +0 -1
  154. package/dist/block-format/index.js.map +0 -1
  155. package/dist/block-format/physical.d.ts.map +0 -1
  156. package/dist/block-format/physical.js.map +0 -1
  157. package/dist/block-format/types.d.ts.map +0 -1
  158. package/dist/block-format/types.js.map +0 -1
  159. package/dist/engine/artifact-cache.d.ts.map +0 -1
  160. package/dist/engine/artifact-cache.js.map +0 -1
  161. package/dist/engine/batch.d.ts.map +0 -1
  162. package/dist/engine/batch.js.map +0 -1
  163. package/dist/engine/buffered-writer.d.ts.map +0 -1
  164. package/dist/engine/buffered-writer.js.map +0 -1
  165. package/dist/engine/catalog.d.ts.map +0 -1
  166. package/dist/engine/catalog.js.map +0 -1
  167. package/dist/engine/client.d.ts.map +0 -1
  168. package/dist/engine/client.js.map +0 -1
  169. package/dist/engine/coordinator.d.ts +0 -17
  170. package/dist/engine/coordinator.d.ts.map +0 -1
  171. package/dist/engine/coordinator.js +0 -60
  172. package/dist/engine/coordinator.js.map +0 -1
  173. package/dist/engine/database.d.ts.map +0 -1
  174. package/dist/engine/database.js.map +0 -1
  175. package/dist/engine/defaults.d.ts.map +0 -1
  176. package/dist/engine/defaults.js.map +0 -1
  177. package/dist/engine/errors.d.ts.map +0 -1
  178. package/dist/engine/errors.js.map +0 -1
  179. package/dist/engine/fts.d.ts.map +0 -1
  180. package/dist/engine/fts.js.map +0 -1
  181. package/dist/engine/group-index.d.ts.map +0 -1
  182. package/dist/engine/group-index.js.map +0 -1
  183. package/dist/engine/index.d.ts.map +0 -1
  184. package/dist/engine/index.js.map +0 -1
  185. package/dist/engine/join-index.d.ts.map +0 -1
  186. package/dist/engine/join-index.js.map +0 -1
  187. package/dist/engine/live.d.ts.map +0 -1
  188. package/dist/engine/live.js.map +0 -1
  189. package/dist/engine/memory.d.ts.map +0 -1
  190. package/dist/engine/memory.js.map +0 -1
  191. package/dist/engine/optimizer.d.ts.map +0 -1
  192. package/dist/engine/optimizer.js.map +0 -1
  193. package/dist/engine/query-cache.d.ts.map +0 -1
  194. package/dist/engine/query-cache.js.map +0 -1
  195. package/dist/engine/query.d.ts.map +0 -1
  196. package/dist/engine/query.js.map +0 -1
  197. package/dist/engine/result-wire.d.ts.map +0 -1
  198. package/dist/engine/result-wire.js.map +0 -1
  199. package/dist/engine/schema-wire.d.ts.map +0 -1
  200. package/dist/engine/schema-wire.js.map +0 -1
  201. package/dist/engine/schema.d.ts.map +0 -1
  202. package/dist/engine/schema.js.map +0 -1
  203. package/dist/engine/sort-keys.d.ts.map +0 -1
  204. package/dist/engine/sort-keys.js.map +0 -1
  205. package/dist/engine/sql-domains.d.ts.map +0 -1
  206. package/dist/engine/sql-domains.js.map +0 -1
  207. package/dist/engine/sql-driver.d.ts.map +0 -1
  208. package/dist/engine/sql-driver.js.map +0 -1
  209. package/dist/engine/sql-json.d.ts.map +0 -1
  210. package/dist/engine/sql-json.js.map +0 -1
  211. package/dist/engine/sql-semantics.d.ts.map +0 -1
  212. package/dist/engine/sql-semantics.js.map +0 -1
  213. package/dist/engine/vector.d.ts.map +0 -1
  214. package/dist/engine/vector.js.map +0 -1
  215. package/dist/engine/worker-host.d.ts.map +0 -1
  216. package/dist/engine/worker-host.js.map +0 -1
  217. package/dist/engine/worker.d.ts.map +0 -1
  218. package/dist/engine/worker.js.map +0 -1
  219. package/dist/index.d.ts.map +0 -1
  220. package/dist/index.js.map +0 -1
  221. package/dist/plan/index.d.ts.map +0 -1
  222. package/dist/plan/index.js.map +0 -1
  223. package/dist/storage/fixture-shape.d.ts +0 -42
  224. package/dist/storage/fixture-shape.d.ts.map +0 -1
  225. package/dist/storage/fixture-shape.js +0 -146
  226. package/dist/storage/fixture-shape.js.map +0 -1
  227. package/dist/storage/index.d.ts.map +0 -1
  228. package/dist/storage/index.js.map +0 -1
  229. package/dist/storage/indexeddb.d.ts.map +0 -1
  230. package/dist/storage/indexeddb.js.map +0 -1
  231. package/dist/storage/memory.d.ts.map +0 -1
  232. package/dist/storage/memory.js.map +0 -1
  233. package/dist/storage/opfs/files.d.ts.map +0 -1
  234. package/dist/storage/opfs/files.js.map +0 -1
  235. package/dist/storage/opfs/index.d.ts.map +0 -1
  236. package/dist/storage/opfs/index.js.map +0 -1
  237. package/dist/storage/opfs/leader.d.ts.map +0 -1
  238. package/dist/storage/opfs/leader.js.map +0 -1
  239. package/dist/storage/opfs/rpc.d.ts.map +0 -1
  240. package/dist/storage/opfs/rpc.js.map +0 -1
  241. package/dist/storage/opfs/store.d.ts.map +0 -1
  242. package/dist/storage/opfs/store.js.map +0 -1
  243. package/dist/storage/snapshot.d.ts.map +0 -1
  244. package/dist/storage/snapshot.js.map +0 -1
  245. package/dist/storage/toolkit/extents.d.ts.map +0 -1
  246. package/dist/storage/toolkit/extents.js.map +0 -1
  247. package/dist/storage/toolkit/index.d.ts.map +0 -1
  248. package/dist/storage/toolkit/index.js.map +0 -1
  249. package/dist/storage/toolkit/record-core.d.ts.map +0 -1
  250. package/dist/storage/toolkit/record-core.js.map +0 -1
  251. package/dist/storage/toolkit/sync-file.d.ts.map +0 -1
  252. package/dist/storage/toolkit/sync-file.js.map +0 -1
  253. package/dist/storage/toolkit/wal.d.ts.map +0 -1
  254. package/dist/storage/toolkit/wal.js.map +0 -1
  255. package/dist/storage/toolkit/wire.d.ts.map +0 -1
  256. package/dist/storage/toolkit/wire.js.map +0 -1
  257. package/dist/storage/types.d.ts.map +0 -1
  258. package/dist/storage/types.js.map +0 -1
  259. package/dist/testing/block-store-conformance.d.ts.map +0 -1
  260. package/dist/testing/block-store-conformance.js.map +0 -1
  261. package/dist/testing/index.d.ts.map +0 -1
  262. package/dist/testing/index.js.map +0 -1
  263. package/dist/testing/opfs-shim.d.ts.map +0 -1
  264. package/dist/testing/opfs-shim.js.map +0 -1
  265. package/dist/testing/seeds.d.ts +0 -11
  266. package/dist/testing/seeds.d.ts.map +0 -1
  267. package/dist/testing/seeds.js +0 -50
  268. package/dist/testing/seeds.js.map +0 -1
  269. package/dist/testing/simulator.d.ts.map +0 -1
  270. package/dist/testing/simulator.js.map +0 -1
  271. package/dist/testing/sqllogictest.d.ts.map +0 -1
  272. package/dist/testing/sqllogictest.js.map +0 -1
  273. package/dist/transactions/index.d.ts.map +0 -1
  274. package/dist/transactions/index.js.map +0 -1
  275. package/dist/worker-protocol/index.d.ts.map +0 -1
  276. package/dist/worker-protocol/index.js.map +0 -1
@@ -5,8 +5,9 @@
5
5
  * for the database plus the leader's instance id) and its `result` or `busy` into the
6
6
  * requester's, so block bytes are cloned once, into the tab that asked, rather than into every
7
7
  * tab. Correctness never rides on delivery: an operation is acknowledged only after the
8
- * leader's write-ahead log holds it, a lost message costs a retry, and a dead leader costs a
9
- * failover arbitrated by the storage lock, not by these messages.
8
+ * leader's write-ahead log holds it. Reads retry after failover; mutations surface an uncertain
9
+ * outcome instead of being executed twice. Leadership is arbitrated by the storage lock, not
10
+ * by these messages.
10
11
  */
11
12
  export type StoreRpcMessage =
12
13
  /**
@@ -65,6 +66,17 @@ export interface SerializedStoreError {
65
66
  domException?: boolean;
66
67
  props?: Record<string, unknown>;
67
68
  }
69
+ /** One legal maximum payload plus bounded envelope/catalog metadata. */
70
+ export declare const MAX_OPFS_RPC_MESSAGE_BYTES: number;
71
+ export declare const MAX_OPFS_RPC_IDENTIFIER_CHARACTERS = 1024;
72
+ /** Conservative retained/transfer-byte estimate with total depth/cardinality refusal. */
73
+ export declare function estimateRpcValueBytes(value: unknown): number;
74
+ /** Total non-throwing parser for same-origin-but-untrusted BroadcastChannel traffic. */
75
+ export declare function parseStoreRpcMessage(value: unknown, knownMethods: ReadonlySet<string>): StoreRpcMessage | undefined;
76
+ /** Collision-resistant bounded identity; large byte leaves become SHA-256 descriptors. */
77
+ export declare function fingerprintStoreRequest(method: string, args: unknown[]): Promise<{
78
+ signature: string;
79
+ retainedBytes: number;
80
+ }>;
68
81
  export declare function serializeStoreError(error: unknown): SerializedStoreError;
69
82
  export declare function rehydrateStoreError(serialized: SerializedStoreError): Error;
70
- //# sourceMappingURL=rpc.d.ts.map
@@ -1,15 +1,237 @@
1
- import { CompactionJobConflictError, GarbageCollectionJobConflictError, LeaseConflictError, SnapshotManifestMissingError, TableRecordConflictError, TempOwnerConflictError, TransactionRecordConflictError, UniqueKeyConflictError, UniqueIndexCoverageError, WriteConflictError, } from "../types.js";
1
+ import { BlockReadBatchTooLargeError, CompactionBacklogError, CompactionJobConflictError, GarbageCollectionJobConflictError, IndexedDbSchemaUpgradeBlockedError, LeaseConflictError, LeaseExpiredError, LeaseOwnerConflictError, OpfsUncertainOutcomeError, PostingBuildConflictError, SnapshotImportConflictError, SnapshotManifestMissingError, StorageCorruptionError, StorageFormatVersionError, StorageResourceLimitError, TableInUseError, TableRecordConflictError, TempOwnerConflictError, TransactionRecordConflictError, UniqueKeyBuildConflictError, UniqueKeyConflictError, UniqueIndexCoverageError, WriteConflictError, SchemaConflictError, } from "../types.js";
2
+ import { dateIsoString } from "../../date-value.js";
3
+ /** One legal maximum payload plus bounded envelope/catalog metadata. */
4
+ export const MAX_OPFS_RPC_MESSAGE_BYTES = 66 * 1024 * 1024;
5
+ export const MAX_OPFS_RPC_IDENTIFIER_CHARACTERS = 1024;
6
+ const MAX_OPFS_RPC_DEPTH = 64;
7
+ const MAX_OPFS_RPC_NODES = 1_100_000;
8
+ function exactKeys(value, keys) {
9
+ const actual = Object.keys(value);
10
+ return actual.length === keys.length && keys.every((key) => Object.hasOwn(value, key));
11
+ }
12
+ function boundedRpcString(value) {
13
+ return (typeof value === "string" &&
14
+ value.length > 0 &&
15
+ value.length <= MAX_OPFS_RPC_IDENTIFIER_CHARACTERS);
16
+ }
17
+ /** Conservative retained/transfer-byte estimate with total depth/cardinality refusal. */
18
+ export function estimateRpcValueBytes(value) {
19
+ const seen = new WeakSet();
20
+ let nodes = 0;
21
+ const visit = (current, depth) => {
22
+ nodes += 1;
23
+ if (nodes > MAX_OPFS_RPC_NODES || depth > MAX_OPFS_RPC_DEPTH) {
24
+ throw new RangeError("OPFS RPC value exceeds its structural limit");
25
+ }
26
+ if (current === null || current === undefined || typeof current === "boolean")
27
+ return 8;
28
+ if (typeof current === "number" || typeof current === "bigint")
29
+ return 16;
30
+ if (typeof current === "string")
31
+ return 16 + current.length * 2;
32
+ if (typeof current !== "object")
33
+ throw new TypeError("OPFS RPC value is not cloneable data");
34
+ if (seen.has(current))
35
+ throw new TypeError("OPFS RPC value must not contain cycles");
36
+ seen.add(current);
37
+ if (typeof SharedArrayBuffer !== "undefined" && current instanceof SharedArrayBuffer) {
38
+ throw new TypeError("OPFS RPC values must not use SharedArrayBuffer");
39
+ }
40
+ if (current instanceof ArrayBuffer)
41
+ return 32 + current.byteLength;
42
+ if (ArrayBuffer.isView(current)) {
43
+ if (typeof SharedArrayBuffer !== "undefined" && current.buffer instanceof SharedArrayBuffer) {
44
+ throw new TypeError("OPFS RPC values must not use SharedArrayBuffer");
45
+ }
46
+ return 48 + current.byteLength;
47
+ }
48
+ if (current instanceof Date)
49
+ return 32;
50
+ let bytes = 32;
51
+ if (Array.isArray(current)) {
52
+ for (const item of current)
53
+ bytes += visit(item, depth + 1);
54
+ }
55
+ else {
56
+ if (Object.getPrototypeOf(current) !== Object.prototype &&
57
+ Object.getPrototypeOf(current) !== null) {
58
+ throw new TypeError("OPFS RPC value has an unsupported prototype");
59
+ }
60
+ for (const [key, item] of Object.entries(current)) {
61
+ bytes += key.length * 2 + visit(item, depth + 1);
62
+ }
63
+ }
64
+ if (!Number.isSafeInteger(bytes) || bytes > MAX_OPFS_RPC_MESSAGE_BYTES) {
65
+ throw new RangeError("OPFS RPC message exceeds its byte limit");
66
+ }
67
+ return bytes;
68
+ };
69
+ const bytes = visit(value, 0);
70
+ if (bytes > MAX_OPFS_RPC_MESSAGE_BYTES) {
71
+ throw new RangeError("OPFS RPC message exceeds its byte limit");
72
+ }
73
+ return bytes;
74
+ }
75
+ function validSerializedError(value) {
76
+ if (typeof value !== "object" || value === null || Array.isArray(value))
77
+ return false;
78
+ const record = value;
79
+ const allowed = record.props === undefined
80
+ ? record.domException === undefined
81
+ ? ["name", "message"]
82
+ : ["name", "message", "domException"]
83
+ : record.domException === undefined
84
+ ? ["name", "message", "props"]
85
+ : ["name", "message", "domException", "props"];
86
+ return (exactKeys(record, allowed) &&
87
+ boundedRpcString(record.name) &&
88
+ typeof record.message === "string" &&
89
+ record.message.length <= MAX_OPFS_RPC_IDENTIFIER_CHARACTERS * 4 &&
90
+ (record.domException === undefined || record.domException === true) &&
91
+ (record.props === undefined ||
92
+ (typeof record.props === "object" && record.props !== null && !Array.isArray(record.props))));
93
+ }
94
+ /** Total non-throwing parser for same-origin-but-untrusted BroadcastChannel traffic. */
95
+ export function parseStoreRpcMessage(value, knownMethods) {
96
+ try {
97
+ if (typeof value !== "object" || value === null || Array.isArray(value))
98
+ return undefined;
99
+ const record = value;
100
+ estimateRpcValueBytes(record);
101
+ switch (record.kind) {
102
+ case "op":
103
+ if (!exactKeys(record, ["kind", "requestId", "from", "method", "args"]) ||
104
+ !boundedRpcString(record.requestId) ||
105
+ !boundedRpcString(record.from) ||
106
+ !boundedRpcString(record.method) ||
107
+ !knownMethods.has(record.method) ||
108
+ !Array.isArray(record.args) ||
109
+ estimateRpcValueBytes(record.args) > MAX_OPFS_RPC_MESSAGE_BYTES)
110
+ return undefined;
111
+ return record;
112
+ case "result":
113
+ if (!boundedRpcString(record.requestId) || typeof record.ok !== "boolean")
114
+ return undefined;
115
+ if (record.ok) {
116
+ if (!exactKeys(record, ["kind", "requestId", "ok", "value"]))
117
+ return undefined;
118
+ estimateRpcValueBytes(record.value);
119
+ }
120
+ else if (!exactKeys(record, ["kind", "requestId", "ok", "error"]) ||
121
+ !validSerializedError(record.error))
122
+ return undefined;
123
+ return record;
124
+ case "busy":
125
+ return exactKeys(record, ["kind", "requestId"]) && boundedRpcString(record.requestId)
126
+ ? record
127
+ : undefined;
128
+ case "leader":
129
+ case "released":
130
+ return exactKeys(record, ["kind", "leaderId"]) && boundedRpcString(record.leaderId)
131
+ ? record
132
+ : undefined;
133
+ case "ping":
134
+ return exactKeys(record, ["kind"]) ? record : undefined;
135
+ case "bid":
136
+ return exactKeys(record, ["kind", "bidderId", "foreground"]) &&
137
+ boundedRpcString(record.bidderId) &&
138
+ typeof record.foreground === "boolean"
139
+ ? record
140
+ : undefined;
141
+ case "yield":
142
+ return exactKeys(record, ["kind", "to"]) && boundedRpcString(record.to)
143
+ ? record
144
+ : undefined;
145
+ default:
146
+ return undefined;
147
+ }
148
+ }
149
+ catch {
150
+ return undefined;
151
+ }
152
+ }
153
+ function hex(bytes) {
154
+ let result = "";
155
+ for (const byte of bytes)
156
+ result += byte.toString(16).padStart(2, "0");
157
+ return result;
158
+ }
159
+ /** Collision-resistant bounded identity; large byte leaves become SHA-256 descriptors. */
160
+ export async function fingerprintStoreRequest(method, args) {
161
+ const retainedBytes = estimateRpcValueBytes(args);
162
+ const seen = new WeakSet();
163
+ const normalize = async (value, depth) => {
164
+ if (depth > MAX_OPFS_RPC_DEPTH)
165
+ throw new RangeError("OPFS RPC value exceeds its depth limit");
166
+ if (typeof value === "bigint")
167
+ return ["bigint", value.toString()];
168
+ if (value === undefined)
169
+ return ["undefined"];
170
+ if (typeof value === "number") {
171
+ return ["number", Object.is(value, -0) ? "-0" : String(value)];
172
+ }
173
+ if (value === null || typeof value !== "object")
174
+ return value;
175
+ if (seen.has(value))
176
+ throw new TypeError("OPFS RPC value must not contain cycles");
177
+ seen.add(value);
178
+ if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
179
+ const { byteLength } = value;
180
+ let digestInput;
181
+ if (value instanceof ArrayBuffer)
182
+ digestInput = value;
183
+ else if (!(value.buffer instanceof ArrayBuffer)) {
184
+ throw new TypeError("OPFS RPC values must not use SharedArrayBuffer");
185
+ }
186
+ else if (value.byteOffset === 0 && value.byteLength === value.buffer.byteLength) {
187
+ digestInput = value.buffer;
188
+ }
189
+ else {
190
+ digestInput = value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength);
191
+ }
192
+ const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", digestInput));
193
+ return ["bytes", value.constructor.name, byteLength, hex(digest)];
194
+ }
195
+ if (value instanceof Date)
196
+ return ["date", dateIsoString(value)];
197
+ if (Array.isArray(value)) {
198
+ return Promise.all(value.map((item) => normalize(item, depth + 1)));
199
+ }
200
+ const entries = [];
201
+ for (const key of Object.keys(value).sort()) {
202
+ entries.push([key, await normalize(value[key], depth + 1)]);
203
+ }
204
+ return ["object", entries];
205
+ };
206
+ const canonical = JSON.stringify([method, await normalize(args, 0)]);
207
+ const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(canonical)));
208
+ return { signature: `${String(retainedBytes)}:${hex(digest)}`, retainedBytes };
209
+ }
2
210
  const errorRegistry = new Map([
211
+ BlockReadBatchTooLargeError,
212
+ CompactionBacklogError,
3
213
  WriteConflictError,
214
+ SchemaConflictError,
4
215
  UniqueKeyConflictError,
5
216
  UniqueIndexCoverageError,
6
217
  TableRecordConflictError,
7
218
  TransactionRecordConflictError,
8
219
  LeaseConflictError,
220
+ LeaseExpiredError,
221
+ LeaseOwnerConflictError,
9
222
  CompactionJobConflictError,
10
223
  GarbageCollectionJobConflictError,
224
+ IndexedDbSchemaUpgradeBlockedError,
11
225
  TempOwnerConflictError,
226
+ StorageResourceLimitError,
227
+ TableInUseError,
12
228
  SnapshotManifestMissingError,
229
+ SnapshotImportConflictError,
230
+ UniqueKeyBuildConflictError,
231
+ PostingBuildConflictError,
232
+ StorageCorruptionError,
233
+ StorageFormatVersionError,
234
+ OpfsUncertainOutcomeError,
13
235
  ].map((constructor) => [constructor.name, constructor]));
14
236
  export function serializeStoreError(error) {
15
237
  if (error instanceof DOMException) {
@@ -56,4 +278,3 @@ export function rehydrateStoreError(serialized) {
56
278
  Object.assign(error, serialized.props);
57
279
  return error;
58
280
  }
59
- //# sourceMappingURL=rpc.js.map
@@ -0,0 +1,40 @@
1
+ import { type SnapshotFrame, type SnapshotFrameKind } from "../types.js";
2
+ import { type Placement } from "../toolkit/extents.js";
3
+ import { OpfsTree } from "./files.js";
4
+ export type SnapshotLedgerKind = "export" | "import" | "completed";
5
+ export interface SnapshotLedgerRecord {
6
+ readonly sequence: number;
7
+ readonly kind: SnapshotFrameKind;
8
+ readonly itemCount: number;
9
+ readonly key: string | null;
10
+ readonly payloadLength: number;
11
+ readonly checksum: number;
12
+ /** Metadata payloads are inline. Block payloads live once in an extent placement. */
13
+ readonly payload?: Uint8Array;
14
+ readonly placement?: Placement;
15
+ }
16
+ export interface SnapshotLedgerRead {
17
+ readonly record: SnapshotLedgerRecord;
18
+ readonly offset: number;
19
+ readonly nextOffset: number;
20
+ }
21
+ export declare function snapshotLedgerPath(kind: SnapshotLedgerKind, id: string): string[];
22
+ /**
23
+ * Checksummed append-only control file for one snapshot session. A checkpoint stores only the
24
+ * committed prefix length. Bytes beyond it are unpublished and truncated on recovery, so a
25
+ * large import/export never creates a database-sized WAL/checkpoint collection.
26
+ */
27
+ export declare class SnapshotFrameLedger {
28
+ #private;
29
+ private constructor();
30
+ static open(tree: OpfsTree, kind: SnapshotLedgerKind, id: string, committedLength: number, create: boolean): Promise<SnapshotFrameLedger>;
31
+ get byteLength(): number;
32
+ append(frame: SnapshotFrame, placement?: Placement): SnapshotLedgerRead;
33
+ read(offset: number, committedLength?: number): SnapshotLedgerRead;
34
+ records(committedLength?: number): IterableIterator<SnapshotLedgerRead>;
35
+ flush(): void;
36
+ truncate(length: number): void;
37
+ /** Recovery adopts the next WAL-published prefix without copying or re-appending its bytes. */
38
+ adoptLength(length: number): void;
39
+ close(): void;
40
+ }
@@ -0,0 +1,281 @@
1
+ import { crc32 } from "../../block-format/checksum.js";
2
+ import { MAX_STORED_BLOCK_BYTE_LENGTH } from "../../block-format/index.js";
3
+ import { MAX_SNAPSHOT_FRAME_ITEMS, MAX_SNAPSHOT_METADATA_FRAME_BYTES, SNAPSHOT_FRAME_KINDS, validateStorageId, } from "../types.js";
4
+ import { assertValidPlacement } from "../toolkit/extents.js";
5
+ import { readFully, writeFully } from "../toolkit/sync-file.js";
6
+ import { encodeSegment } from "./files.js";
7
+ const LEDGER_MAGIC = 0x534c_4731; // SLG1
8
+ const LEDGER_PREFIX_BYTES = 80;
9
+ const MODE_INLINE = 0;
10
+ const MODE_PLACEMENT = 1;
11
+ export function snapshotLedgerPath(kind, id) {
12
+ validateStorageId(id);
13
+ return ["snapshots-v1", kind, encodeSegment(id)];
14
+ }
15
+ /**
16
+ * Checksummed append-only control file for one snapshot session. A checkpoint stores only the
17
+ * committed prefix length. Bytes beyond it are unpublished and truncated on recovery, so a
18
+ * large import/export never creates a database-sized WAL/checkpoint collection.
19
+ */
20
+ export class SnapshotFrameLedger {
21
+ #handle;
22
+ #length;
23
+ #closed = false;
24
+ constructor(handle, length) {
25
+ this.#handle = handle;
26
+ this.#length = length;
27
+ }
28
+ static async open(tree, kind, id, committedLength, create) {
29
+ if (!Number.isSafeInteger(committedLength) || committedLength < 0) {
30
+ throw new RangeError("Snapshot ledger length is invalid");
31
+ }
32
+ const handle = await tree.openHandle(snapshotLedgerPath(kind, id), { create });
33
+ const size = handle.getSize();
34
+ if (size < committedLength) {
35
+ handle.close();
36
+ throw new Error(`Snapshot ledger is shorter than its committed prefix: ${String(size)} < ` +
37
+ String(committedLength));
38
+ }
39
+ // A checkpoint's committed prefix may have a valid WAL-covered suffix after it. Keep the
40
+ // physical suffix until recovery either adopts each frame or truncates it after replay.
41
+ return new SnapshotFrameLedger(handle, committedLength);
42
+ }
43
+ get byteLength() {
44
+ return this.#length;
45
+ }
46
+ append(frame, placement) {
47
+ this.#assertOpen();
48
+ const encoded = encodeLedgerRecord(frame, placement);
49
+ const offset = this.#length;
50
+ const nextOffset = safeLedgerSum(offset, encoded.byteLength, "Snapshot ledger length");
51
+ writeFully(this.#handle, encoded, offset, "appending snapshot frame ledger");
52
+ this.#length = nextOffset;
53
+ return { record: ledgerRecordFromFrame(frame, placement), offset, nextOffset };
54
+ }
55
+ read(offset, committedLength = this.#length) {
56
+ this.#assertOpen();
57
+ if (!Number.isSafeInteger(offset) ||
58
+ offset < 0 ||
59
+ !Number.isSafeInteger(committedLength) ||
60
+ committedLength < offset ||
61
+ committedLength > this.#length ||
62
+ committedLength - offset < LEDGER_PREFIX_BYTES) {
63
+ throw new RangeError("Snapshot ledger read offset is invalid");
64
+ }
65
+ const prefix = new Uint8Array(LEDGER_PREFIX_BYTES);
66
+ readFully(this.#handle, prefix, offset, "reading snapshot frame ledger prefix");
67
+ const view = new DataView(prefix.buffer);
68
+ if (view.getUint32(0, true) !== LEDGER_MAGIC) {
69
+ throw new Error("Snapshot ledger record marker mismatch");
70
+ }
71
+ const recordLength = view.getUint32(4, true);
72
+ if (recordLength < LEDGER_PREFIX_BYTES ||
73
+ recordLength > MAX_SNAPSHOT_METADATA_FRAME_BYTES + LEDGER_PREFIX_BYTES + 3_072 ||
74
+ recordLength > committedLength - offset) {
75
+ throw new Error("Snapshot ledger record length is invalid");
76
+ }
77
+ const encoded = new Uint8Array(recordLength);
78
+ readFully(this.#handle, encoded, offset, "reading snapshot frame ledger record");
79
+ const expectedRecordChecksum = new DataView(encoded.buffer).getUint32(76, true);
80
+ new DataView(encoded.buffer).setUint32(76, 0, true);
81
+ if (crc32(encoded) !== expectedRecordChecksum) {
82
+ throw new Error("Snapshot ledger record checksum mismatch");
83
+ }
84
+ new DataView(encoded.buffer).setUint32(76, expectedRecordChecksum, true);
85
+ const record = decodeLedgerRecord(encoded);
86
+ return {
87
+ record,
88
+ offset,
89
+ nextOffset: safeLedgerSum(offset, recordLength, "Snapshot ledger read offset"),
90
+ };
91
+ }
92
+ *records(committedLength = this.#length) {
93
+ let offset = 0;
94
+ while (offset < committedLength) {
95
+ const read = this.read(offset, committedLength);
96
+ yield read;
97
+ offset = read.nextOffset;
98
+ }
99
+ if (offset !== committedLength)
100
+ throw new Error("Snapshot ledger prefix ends mid-record");
101
+ }
102
+ flush() {
103
+ this.#assertOpen();
104
+ this.#handle.flush();
105
+ }
106
+ truncate(length) {
107
+ this.#assertOpen();
108
+ if (!Number.isSafeInteger(length) || length < 0 || length > this.#length) {
109
+ throw new RangeError("Snapshot ledger truncation is invalid");
110
+ }
111
+ this.#handle.truncate(length);
112
+ this.#length = length;
113
+ }
114
+ /** Recovery adopts the next WAL-published prefix without copying or re-appending its bytes. */
115
+ adoptLength(length) {
116
+ this.#assertOpen();
117
+ if (!Number.isSafeInteger(length) || length < this.#length || length > this.#handle.getSize()) {
118
+ throw new RangeError("Snapshot ledger recovery length is invalid");
119
+ }
120
+ this.#length = length;
121
+ }
122
+ close() {
123
+ if (this.#closed)
124
+ return;
125
+ this.#closed = true;
126
+ this.#handle.close();
127
+ }
128
+ #assertOpen() {
129
+ if (this.#closed)
130
+ throw new Error("Snapshot ledger is closed");
131
+ }
132
+ }
133
+ function encodeLedgerRecord(frame, placement) {
134
+ validateFrameScalars(frame);
135
+ const isBlock = frame.kind === "block";
136
+ if (isBlock !== (placement !== undefined)) {
137
+ throw new TypeError("Snapshot ledger block placement is inconsistent");
138
+ }
139
+ if (placement !== undefined) {
140
+ assertValidPlacement(placement);
141
+ if (placement.length !== frame.payload.byteLength || placement.checksum !== frame.checksum) {
142
+ throw new Error("Snapshot block placement disagrees with its frame");
143
+ }
144
+ }
145
+ const keyBytes = frame.key === null ? new Uint8Array() : new TextEncoder().encode(frame.key);
146
+ const inlineLength = placement === undefined ? frame.payload.byteLength : 0;
147
+ const recordLength = safeLedgerSum(LEDGER_PREFIX_BYTES + keyBytes.byteLength, inlineLength, "Snapshot ledger record length");
148
+ const encoded = new Uint8Array(recordLength);
149
+ const view = new DataView(encoded.buffer);
150
+ view.setUint32(0, LEDGER_MAGIC, true);
151
+ view.setUint32(4, recordLength, true);
152
+ view.setBigUint64(8, BigInt(frame.sequence), true);
153
+ view.setUint32(16, SNAPSHOT_FRAME_KINDS.indexOf(frame.kind), true);
154
+ view.setUint32(20, frame.itemCount, true);
155
+ view.setUint32(24, keyBytes.byteLength, true);
156
+ view.setUint32(28, placement === undefined ? MODE_INLINE : MODE_PLACEMENT, true);
157
+ view.setBigUint64(32, BigInt(frame.payload.byteLength), true);
158
+ view.setUint32(40, frame.checksum, true);
159
+ if (placement !== undefined) {
160
+ view.setBigUint64(44, BigInt(placement.extent), true);
161
+ view.setBigUint64(52, BigInt(placement.offset), true);
162
+ view.setBigUint64(60, BigInt(placement.length), true);
163
+ view.setUint32(68, placement.checksum, true);
164
+ }
165
+ // 72..75 are reserved zero; 76..79 is the record checksum itself.
166
+ encoded.set(keyBytes, LEDGER_PREFIX_BYTES);
167
+ if (placement === undefined)
168
+ encoded.set(frame.payload, LEDGER_PREFIX_BYTES + keyBytes.byteLength);
169
+ view.setUint32(76, 0, true);
170
+ view.setUint32(76, crc32(encoded), true);
171
+ return encoded;
172
+ }
173
+ function decodeLedgerRecord(encoded) {
174
+ const view = new DataView(encoded.buffer, encoded.byteOffset, encoded.byteLength);
175
+ const sequence = Number(view.getBigUint64(8, true));
176
+ const kind = SNAPSHOT_FRAME_KINDS[view.getUint32(16, true)];
177
+ const itemCount = view.getUint32(20, true);
178
+ const keyLength = view.getUint32(24, true);
179
+ const mode = view.getUint32(28, true);
180
+ const payloadLength = Number(view.getBigUint64(32, true));
181
+ const checksum = view.getUint32(40, true);
182
+ if (kind === undefined ||
183
+ !Number.isSafeInteger(sequence) ||
184
+ sequence < 0 ||
185
+ itemCount < 1 ||
186
+ itemCount > MAX_SNAPSHOT_FRAME_ITEMS ||
187
+ !Number.isSafeInteger(payloadLength) ||
188
+ payloadLength < 1 ||
189
+ (kind === "block"
190
+ ? payloadLength > MAX_STORED_BLOCK_BYTE_LENGTH
191
+ : payloadLength > MAX_SNAPSHOT_METADATA_FRAME_BYTES) ||
192
+ (mode !== MODE_INLINE && mode !== MODE_PLACEMENT) ||
193
+ encoded.subarray(72, 76).some((byte) => byte !== 0)) {
194
+ throw new TypeError("Snapshot ledger record header is invalid");
195
+ }
196
+ const inlineLength = mode === MODE_INLINE ? payloadLength : 0;
197
+ if (LEDGER_PREFIX_BYTES + keyLength + inlineLength !== encoded.byteLength) {
198
+ throw new Error("Snapshot ledger record body length is invalid");
199
+ }
200
+ let key = null;
201
+ if (keyLength > 0) {
202
+ try {
203
+ key = validateStorageId(new TextDecoder("utf-8", { fatal: true }).decode(encoded.subarray(LEDGER_PREFIX_BYTES, LEDGER_PREFIX_BYTES + keyLength)));
204
+ }
205
+ catch {
206
+ throw new TypeError("Snapshot ledger key is not canonical UTF-8");
207
+ }
208
+ }
209
+ if ((kind === "block") !== (key !== null) || (kind === "block") !== (mode === MODE_PLACEMENT)) {
210
+ throw new TypeError("Snapshot ledger frame identity is inconsistent");
211
+ }
212
+ if (mode === MODE_INLINE) {
213
+ const payload = encoded.slice(LEDGER_PREFIX_BYTES + keyLength);
214
+ if (crc32(payload) !== checksum)
215
+ throw new Error("Snapshot ledger payload checksum mismatch");
216
+ return { sequence, kind, itemCount, key, payloadLength, checksum, payload };
217
+ }
218
+ const placement = {
219
+ extent: Number(view.getBigUint64(44, true)),
220
+ offset: Number(view.getBigUint64(52, true)),
221
+ length: Number(view.getBigUint64(60, true)),
222
+ checksum: view.getUint32(68, true),
223
+ };
224
+ assertValidPlacement(placement);
225
+ if (placement.length !== payloadLength || placement.checksum !== checksum) {
226
+ throw new Error("Snapshot ledger placement disagrees with its frame");
227
+ }
228
+ return { sequence, kind, itemCount, key, payloadLength, checksum, placement };
229
+ }
230
+ function ledgerRecordFromFrame(frame, placement) {
231
+ return {
232
+ sequence: frame.sequence,
233
+ kind: frame.kind,
234
+ itemCount: frame.itemCount,
235
+ key: frame.key,
236
+ payloadLength: frame.payload.byteLength,
237
+ checksum: frame.checksum,
238
+ ...(placement === undefined ? { payload: frame.payload } : { placement }),
239
+ };
240
+ }
241
+ function validateFrameScalars(frame) {
242
+ if (!Number.isSafeInteger(frame.sequence) ||
243
+ frame.sequence < 0 ||
244
+ !SNAPSHOT_FRAME_KINDS.includes(frame.kind) ||
245
+ !Number.isSafeInteger(frame.itemCount) ||
246
+ frame.itemCount < 1 ||
247
+ frame.itemCount > MAX_SNAPSHOT_FRAME_ITEMS ||
248
+ !(frame.payload instanceof Uint8Array) ||
249
+ !Number.isSafeInteger(frame.checksum) ||
250
+ frame.checksum < 0 ||
251
+ frame.checksum > 0xffff_ffff) {
252
+ throw new TypeError("Snapshot frame is invalid");
253
+ }
254
+ if (crc32(frame.payload) !== frame.checksum) {
255
+ throw new Error("Snapshot frame checksum mismatch");
256
+ }
257
+ if (frame.kind === "block") {
258
+ if (frame.key === null || frame.itemCount !== 1) {
259
+ throw new TypeError("Snapshot block frame identity is invalid");
260
+ }
261
+ validateStorageId(frame.key);
262
+ if (frame.payload.byteLength < 1 || frame.payload.byteLength > MAX_STORED_BLOCK_BYTE_LENGTH) {
263
+ throw new RangeError("Snapshot block frame is too large");
264
+ }
265
+ }
266
+ else if (frame.key !== null ||
267
+ frame.payload.byteLength < 1 ||
268
+ frame.payload.byteLength > MAX_SNAPSHOT_METADATA_FRAME_BYTES) {
269
+ throw new TypeError("Snapshot metadata frame identity is invalid");
270
+ }
271
+ }
272
+ function safeLedgerSum(left, right, label) {
273
+ if (!Number.isSafeInteger(left) ||
274
+ left < 0 ||
275
+ !Number.isSafeInteger(right) ||
276
+ right < 0 ||
277
+ left > Number.MAX_SAFE_INTEGER - right) {
278
+ throw new RangeError(`${label} exceeds the safe integer range`);
279
+ }
280
+ return left + right;
281
+ }