@minnowdb/core 0.2.1 → 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 (270) hide show
  1. package/README.md +23 -32
  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 +2 -1
  21. package/dist/engine/artifact-cache.js +5 -1
  22. package/dist/engine/batch.d.ts +11 -3
  23. package/dist/engine/batch.js +17 -7
  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 +24 -5
  29. package/dist/engine/catalog.js +34 -7
  30. package/dist/engine/client.d.ts +29 -7
  31. package/dist/engine/client.js +216 -40
  32. package/dist/engine/database.d.ts +224 -39
  33. package/dist/engine/database.js +7475 -1539
  34. package/dist/engine/defaults.d.ts +4 -8
  35. package/dist/engine/defaults.js +46 -22
  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 +41 -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 -10
  43. package/dist/engine/index.js +12 -9
  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 +504 -126
  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 +262 -27
  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 +1 -2
  59. package/dist/engine/query-cache.js +7 -5
  60. package/dist/engine/query.d.ts +134 -29
  61. package/dist/engine/query.js +1668 -372
  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 +6 -3
  65. package/dist/engine/schema-wire.js +40 -34
  66. package/dist/engine/schema.d.ts +156 -80
  67. package/dist/engine/schema.js +570 -108
  68. package/dist/engine/sort-keys.d.ts +4 -4
  69. package/dist/engine/sort-keys.js +44 -29
  70. package/dist/engine/sql-domains.d.ts +31 -0
  71. package/dist/engine/sql-domains.js +585 -0
  72. package/dist/engine/sql-driver.d.ts +19 -0
  73. package/dist/engine/sql-driver.js +1 -0
  74. package/dist/engine/sql-json.d.ts +7 -9
  75. package/dist/engine/sql-json.js +75 -15
  76. package/dist/engine/sql-semantics.d.ts +8 -3
  77. package/dist/engine/sql-semantics.js +458 -30
  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 +12 -2
  81. package/dist/engine/vector.js +635 -119
  82. package/dist/engine/worker-host.d.ts +9 -2
  83. package/dist/engine/worker-host.js +620 -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 +3 -4
  91. package/dist/plan/index.js +3 -4
  92. package/dist/storage/index.d.ts +1 -1
  93. package/dist/storage/index.js +1 -1
  94. package/dist/storage/indexeddb.d.ts +99 -51
  95. package/dist/storage/indexeddb.js +13448 -2660
  96. package/dist/storage/memory.d.ts +64 -57
  97. package/dist/storage/memory.js +1042 -128
  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 +409 -75
  103. package/dist/storage/opfs/leader.js +4621 -650
  104. package/dist/storage/opfs/rpc.d.ts +15 -3
  105. package/dist/storage/opfs/rpc.js +224 -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 +33 -99
  109. package/dist/storage/opfs/store.js +453 -364
  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 -85
  115. package/dist/storage/snapshot.js +33 -265
  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 +4 -5
  119. package/dist/storage/toolkit/index.js +28 -4
  120. package/dist/storage/toolkit/record-core.d.ts +182 -55
  121. package/dist/storage/toolkit/record-core.js +6158 -1331
  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 +15 -2
  127. package/dist/storage/toolkit/wire.js +199 -10
  128. package/dist/storage/types.d.ts +1439 -204
  129. package/dist/storage/types.js +1745 -141
  130. package/dist/testing/block-store-conformance.d.ts +0 -1
  131. package/dist/testing/block-store-conformance.js +1009 -119
  132. package/dist/testing/index.d.ts +46 -26
  133. package/dist/testing/index.js +112 -57
  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 +97 -0
  137. package/dist/testing/simulator.js +591 -0
  138. package/dist/testing/sqllogictest.d.ts +89 -0
  139. package/dist/testing/sqllogictest.js +434 -0
  140. package/dist/transactions/index.d.ts +92 -16
  141. package/dist/transactions/index.js +1074 -236
  142. package/dist/worker-protocol/index.d.ts +3 -2
  143. package/dist/worker-protocol/index.js +2 -5
  144. package/package.json +53 -3
  145. package/postgres-feature-profile.json +223 -0
  146. package/sql-feature-matrix.json +172 -252
  147. package/dist/block-format/block.d.ts.map +0 -1
  148. package/dist/block-format/block.js.map +0 -1
  149. package/dist/block-format/checksum.d.ts.map +0 -1
  150. package/dist/block-format/checksum.js.map +0 -1
  151. package/dist/block-format/codecs.d.ts.map +0 -1
  152. package/dist/block-format/codecs.js.map +0 -1
  153. package/dist/block-format/column.d.ts.map +0 -1
  154. package/dist/block-format/column.js.map +0 -1
  155. package/dist/block-format/index.d.ts.map +0 -1
  156. package/dist/block-format/index.js.map +0 -1
  157. package/dist/block-format/physical.d.ts.map +0 -1
  158. package/dist/block-format/physical.js.map +0 -1
  159. package/dist/block-format/types.d.ts.map +0 -1
  160. package/dist/block-format/types.js.map +0 -1
  161. package/dist/engine/artifact-cache.d.ts.map +0 -1
  162. package/dist/engine/artifact-cache.js.map +0 -1
  163. package/dist/engine/batch.d.ts.map +0 -1
  164. package/dist/engine/batch.js.map +0 -1
  165. package/dist/engine/buffered-writer.d.ts.map +0 -1
  166. package/dist/engine/buffered-writer.js.map +0 -1
  167. package/dist/engine/catalog.d.ts.map +0 -1
  168. package/dist/engine/catalog.js.map +0 -1
  169. package/dist/engine/client.d.ts.map +0 -1
  170. package/dist/engine/client.js.map +0 -1
  171. package/dist/engine/coordinator.d.ts +0 -17
  172. package/dist/engine/coordinator.d.ts.map +0 -1
  173. package/dist/engine/coordinator.js +0 -60
  174. package/dist/engine/coordinator.js.map +0 -1
  175. package/dist/engine/database.d.ts.map +0 -1
  176. package/dist/engine/database.js.map +0 -1
  177. package/dist/engine/defaults.d.ts.map +0 -1
  178. package/dist/engine/defaults.js.map +0 -1
  179. package/dist/engine/errors.d.ts.map +0 -1
  180. package/dist/engine/errors.js.map +0 -1
  181. package/dist/engine/fts.d.ts.map +0 -1
  182. package/dist/engine/fts.js.map +0 -1
  183. package/dist/engine/group-index.d.ts.map +0 -1
  184. package/dist/engine/group-index.js.map +0 -1
  185. package/dist/engine/index.d.ts.map +0 -1
  186. package/dist/engine/index.js.map +0 -1
  187. package/dist/engine/join-index.d.ts.map +0 -1
  188. package/dist/engine/join-index.js.map +0 -1
  189. package/dist/engine/live.d.ts.map +0 -1
  190. package/dist/engine/live.js.map +0 -1
  191. package/dist/engine/memory.d.ts.map +0 -1
  192. package/dist/engine/memory.js.map +0 -1
  193. package/dist/engine/optimizer.d.ts.map +0 -1
  194. package/dist/engine/optimizer.js.map +0 -1
  195. package/dist/engine/query-cache.d.ts.map +0 -1
  196. package/dist/engine/query-cache.js.map +0 -1
  197. package/dist/engine/query.d.ts.map +0 -1
  198. package/dist/engine/query.js.map +0 -1
  199. package/dist/engine/result-wire.d.ts.map +0 -1
  200. package/dist/engine/result-wire.js.map +0 -1
  201. package/dist/engine/schema-wire.d.ts.map +0 -1
  202. package/dist/engine/schema-wire.js.map +0 -1
  203. package/dist/engine/schema.d.ts.map +0 -1
  204. package/dist/engine/schema.js.map +0 -1
  205. package/dist/engine/sort-keys.d.ts.map +0 -1
  206. package/dist/engine/sort-keys.js.map +0 -1
  207. package/dist/engine/sql-json.d.ts.map +0 -1
  208. package/dist/engine/sql-json.js.map +0 -1
  209. package/dist/engine/sql-semantics.d.ts.map +0 -1
  210. package/dist/engine/sql-semantics.js.map +0 -1
  211. package/dist/engine/vector.d.ts.map +0 -1
  212. package/dist/engine/vector.js.map +0 -1
  213. package/dist/engine/worker-host.d.ts.map +0 -1
  214. package/dist/engine/worker-host.js.map +0 -1
  215. package/dist/engine/worker.d.ts.map +0 -1
  216. package/dist/engine/worker.js.map +0 -1
  217. package/dist/index.d.ts.map +0 -1
  218. package/dist/index.js.map +0 -1
  219. package/dist/plan/index.d.ts.map +0 -1
  220. package/dist/plan/index.js.map +0 -1
  221. package/dist/storage/fixture-shape.d.ts +0 -42
  222. package/dist/storage/fixture-shape.d.ts.map +0 -1
  223. package/dist/storage/fixture-shape.js +0 -146
  224. package/dist/storage/fixture-shape.js.map +0 -1
  225. package/dist/storage/index.d.ts.map +0 -1
  226. package/dist/storage/index.js.map +0 -1
  227. package/dist/storage/indexeddb.d.ts.map +0 -1
  228. package/dist/storage/indexeddb.js.map +0 -1
  229. package/dist/storage/memory.d.ts.map +0 -1
  230. package/dist/storage/memory.js.map +0 -1
  231. package/dist/storage/opfs/files.d.ts.map +0 -1
  232. package/dist/storage/opfs/files.js.map +0 -1
  233. package/dist/storage/opfs/index.d.ts.map +0 -1
  234. package/dist/storage/opfs/index.js.map +0 -1
  235. package/dist/storage/opfs/leader.d.ts.map +0 -1
  236. package/dist/storage/opfs/leader.js.map +0 -1
  237. package/dist/storage/opfs/rpc.d.ts.map +0 -1
  238. package/dist/storage/opfs/rpc.js.map +0 -1
  239. package/dist/storage/opfs/store.d.ts.map +0 -1
  240. package/dist/storage/opfs/store.js.map +0 -1
  241. package/dist/storage/snapshot.d.ts.map +0 -1
  242. package/dist/storage/snapshot.js.map +0 -1
  243. package/dist/storage/toolkit/extents.d.ts.map +0 -1
  244. package/dist/storage/toolkit/extents.js.map +0 -1
  245. package/dist/storage/toolkit/index.d.ts.map +0 -1
  246. package/dist/storage/toolkit/index.js.map +0 -1
  247. package/dist/storage/toolkit/record-core.d.ts.map +0 -1
  248. package/dist/storage/toolkit/record-core.js.map +0 -1
  249. package/dist/storage/toolkit/sync-file.d.ts.map +0 -1
  250. package/dist/storage/toolkit/sync-file.js.map +0 -1
  251. package/dist/storage/toolkit/wal.d.ts.map +0 -1
  252. package/dist/storage/toolkit/wal.js.map +0 -1
  253. package/dist/storage/toolkit/wire.d.ts.map +0 -1
  254. package/dist/storage/toolkit/wire.js.map +0 -1
  255. package/dist/storage/types.d.ts.map +0 -1
  256. package/dist/storage/types.js.map +0 -1
  257. package/dist/testing/block-store-conformance.d.ts.map +0 -1
  258. package/dist/testing/block-store-conformance.js.map +0 -1
  259. package/dist/testing/index.d.ts.map +0 -1
  260. package/dist/testing/index.js.map +0 -1
  261. package/dist/testing/opfs-shim.d.ts.map +0 -1
  262. package/dist/testing/opfs-shim.js.map +0 -1
  263. package/dist/testing/seeds.d.ts +0 -11
  264. package/dist/testing/seeds.d.ts.map +0 -1
  265. package/dist/testing/seeds.js +0 -50
  266. package/dist/testing/seeds.js.map +0 -1
  267. package/dist/transactions/index.d.ts.map +0 -1
  268. package/dist/transactions/index.js.map +0 -1
  269. package/dist/worker-protocol/index.d.ts.map +0 -1
  270. package/dist/worker-protocol/index.js.map +0 -1
@@ -1,7 +1,8 @@
1
- import { CompactionJobConflictError, GarbageCollectionJobConflictError, LeaseConflictError, SnapshotManifestMissingError, TableRecordConflictError, TempOwnerConflictError, TransactionRecordConflictError, UniqueKeyConflictError, WriteConflictError, } from "../storage/index.js";
2
- import { parseRpcResponse, protocolVersion, } from "../worker-protocol/index.js";
1
+ import { BlockReadBatchTooLargeError, CompactionBacklogError, CompactionJobConflictError, GarbageCollectionJobConflictError, IndexedDbSchemaUpgradeBlockedError, LeaseConflictError, LeaseExpiredError, LeaseOwnerConflictError, PostingBuildConflictError, SnapshotManifestMissingError, SnapshotImportConflictError, SchemaConflictError, StorageResourceLimitError, TableInUseError, TableRecordConflictError, TempOwnerConflictError, TransactionRecordConflictError, UniqueKeyBuildConflictError, UniqueKeyConflictError, UniqueIndexCoverageError, WriteConflictError, StorageCorruptionError, StorageFormatVersionError, OpfsUncertainOutcomeError, } from "../storage/types.js";
2
+ import { MAX_SNAPSHOT_STREAM_CHUNK_BYTES } from "../storage/snapshot.js";
3
+ import { parseRpcResponse, MAX_DATABASE_RPC_IN_FLIGHT, protocolVersion, } from "../worker-protocol/index.js";
3
4
  import { toColumnarBatch } from "./batch.js";
4
- import { CompactionJobCancelledError, CompactionMemoryBudgetError, CompactionWriteAmplificationError, MissingKeyError, SqlCompileError, UniqueConstraintError, } from "./errors.js";
5
+ import { CompactionJobCancelledError, CompactionMemoryBudgetError, CompactionWriteAmplificationError, MaintenanceBacklogError, MissingKeyError, SqlCompileError, UniqueConstraintError, VisibleSegmentCursorStaleError, } from "./errors.js";
5
6
  import { QueryMemoryBudgetError } from "./memory.js";
6
7
  import { decodeQueryResult } from "./result-wire.js";
7
8
  import { serializeSchema } from "./schema-wire.js";
@@ -10,24 +11,56 @@ import { serializeSchema } from "./schema-wire.js";
10
11
  * database is tens of round trips, small enough that no single structured clone is long enough
11
12
  * to be felt as a dropped frame.
12
13
  */
13
- const SNAPSHOT_CHUNK_BYTES = 4 * 1024 * 1024;
14
+ const SNAPSHOT_CHUNK_BYTES = MAX_SNAPSHOT_STREAM_CHUNK_BYTES;
15
+ const MAX_SNAPSHOT_BYTE_ARRAY_LENGTH = 0xffffffff;
16
+ async function* clientSnapshotChunks(bytes) {
17
+ for (let offset = 0; offset < bytes.byteLength; offset += SNAPSHOT_CHUNK_BYTES) {
18
+ yield bytes.subarray(offset, offset + SNAPSHOT_CHUNK_BYTES);
19
+ }
20
+ }
21
+ function throwIfClientSnapshotAborted(signal) {
22
+ if (signal?.aborted !== true)
23
+ return;
24
+ if (signal.reason instanceof Error)
25
+ throw signal.reason;
26
+ const error = new Error("Snapshot operation was aborted", { cause: signal.reason });
27
+ error.name = "AbortError";
28
+ throw error;
29
+ }
14
30
  const errorRegistry = new Map([
15
31
  UniqueConstraintError,
16
32
  MissingKeyError,
33
+ CompactionBacklogError,
17
34
  CompactionMemoryBudgetError,
18
35
  CompactionWriteAmplificationError,
19
36
  CompactionJobCancelledError,
37
+ MaintenanceBacklogError,
20
38
  SqlCompileError,
21
39
  QueryMemoryBudgetError,
40
+ VisibleSegmentCursorStaleError,
41
+ BlockReadBatchTooLargeError,
22
42
  WriteConflictError,
43
+ SchemaConflictError,
23
44
  UniqueKeyConflictError,
45
+ UniqueIndexCoverageError,
24
46
  TableRecordConflictError,
25
47
  CompactionJobConflictError,
26
48
  GarbageCollectionJobConflictError,
49
+ IndexedDbSchemaUpgradeBlockedError,
27
50
  SnapshotManifestMissingError,
51
+ SnapshotImportConflictError,
52
+ TableInUseError,
28
53
  TransactionRecordConflictError,
29
54
  LeaseConflictError,
55
+ LeaseExpiredError,
56
+ LeaseOwnerConflictError,
57
+ StorageResourceLimitError,
30
58
  TempOwnerConflictError,
59
+ UniqueKeyBuildConflictError,
60
+ PostingBuildConflictError,
61
+ StorageCorruptionError,
62
+ StorageFormatVersionError,
63
+ OpfsUncertainOutcomeError,
31
64
  ].map((constructor) => [constructor.name, constructor]));
32
65
  function rehydrateError(serialized) {
33
66
  const constructor = errorRegistry.get(serialized.name);
@@ -63,17 +96,20 @@ export class MinnowDatabaseClient {
63
96
  #fatal;
64
97
  #closed = false;
65
98
  #onVisibilityChange;
99
+ #onMessage = (event) => {
100
+ this.#receive(event.data);
101
+ };
102
+ #onError = () => {
103
+ this.#fail(new Error("The database worker failed; see the worker's own error output"));
104
+ };
105
+ #onMessageError = () => {
106
+ this.#fail(new Error("A database worker message could not be deserialized"));
107
+ };
66
108
  constructor(transport, options = {}) {
67
109
  this.#transport = transport;
68
- transport.addEventListener("message", (event) => {
69
- this.#receive(event.data);
70
- });
71
- transport.addEventListener("error", () => {
72
- this.#fail(new Error("The database worker failed; see the worker's own error output"));
73
- });
74
- transport.addEventListener("messageerror", () => {
75
- this.#fail(new Error("A database worker message could not be deserialized"));
76
- });
110
+ transport.addEventListener("message", this.#onMessage);
111
+ transport.addEventListener("error", this.#onError);
112
+ transport.addEventListener("messageerror", this.#onMessageError);
77
113
  const payload = {
78
114
  store: options.store ?? { kind: "indexeddb", name: "minnow" },
79
115
  ...(options.databaseOptions === undefined ? {} : { options: options.databaseOptions }),
@@ -143,6 +179,9 @@ export class MinnowDatabaseClient {
143
179
  async deleteBatch(tableName, input) {
144
180
  return (await this.#call("deleteBatch", [tableName, input]));
145
181
  }
182
+ async delete(tableName, key) {
183
+ return (await this.#call("delete", [tableName, key]));
184
+ }
146
185
  bufferedWriter(tableName, options = {}) {
147
186
  const handleId = crypto.randomUUID();
148
187
  const { onError, ...wireOptions } = options;
@@ -163,6 +202,50 @@ export class MinnowDatabaseClient {
163
202
  async query(sql, options) {
164
203
  return decodeQueryResult(await this.#call("query", options === undefined ? [sql] : [sql, options]));
165
204
  }
205
+ /** Pulls one transferred columnar page at a time, preserving worker backpressure. */
206
+ queryCursor(sql, options = {}) {
207
+ const call = this.#call.bind(this);
208
+ const invoke = this._invoke.bind(this);
209
+ const handleId = crypto.randomUUID();
210
+ const { signal, onStats, ...wireOptions } = options;
211
+ if (onStats !== undefined) {
212
+ throw new TypeError("Query cursor onStats callbacks are not available across a worker");
213
+ }
214
+ async function* batches() {
215
+ let opened = false;
216
+ let closing;
217
+ const close = () => {
218
+ if (!opened)
219
+ return Promise.resolve();
220
+ closing ??= invoke(handleId, "close", []).catch(() => undefined);
221
+ return closing;
222
+ };
223
+ const onAbort = () => {
224
+ void close();
225
+ };
226
+ signal?.addEventListener("abort", onAbort, { once: true });
227
+ try {
228
+ signal?.throwIfAborted();
229
+ await call("queryCursorOpen", [handleId, sql, wireOptions]);
230
+ opened = true;
231
+ if (signal?.aborted === true) {
232
+ await close();
233
+ signal.throwIfAborted();
234
+ }
235
+ for (;;) {
236
+ const next = (await invoke(handleId, "next", []));
237
+ if (next.done)
238
+ return undefined;
239
+ yield decodeQueryResult(next.batch);
240
+ }
241
+ }
242
+ finally {
243
+ signal?.removeEventListener("abort", onAbort);
244
+ await close();
245
+ }
246
+ }
247
+ return batches();
248
+ }
166
249
  async run(query) {
167
250
  return decodeQueryResult(await this.#call("run", [query])).rows;
168
251
  }
@@ -231,30 +314,56 @@ export class MinnowDatabaseClient {
231
314
  * database; `onProgress` reports each slice as it lands.
232
315
  */
233
316
  async exportSnapshot(options = {}) {
317
+ const chunks = [];
318
+ let byteLength = 0;
319
+ for await (const chunk of this.exportSnapshotStream(options)) {
320
+ byteLength += chunk.byteLength;
321
+ if (!Number.isSafeInteger(byteLength) || byteLength > MAX_SNAPSHOT_BYTE_ARRAY_LENGTH) {
322
+ throw new RangeError("Snapshot exceeds the byte-array API's 4 GiB limit");
323
+ }
324
+ chunks.push(chunk);
325
+ }
326
+ const bytes = new Uint8Array(byteLength);
327
+ let offset = 0;
328
+ for (const chunk of chunks) {
329
+ bytes.set(chunk, offset);
330
+ offset += chunk.byteLength;
331
+ }
332
+ return bytes;
333
+ }
334
+ /** Pulls at most one bounded worker chunk at a time; closing releases the worker-side pin. */
335
+ async *exportSnapshotStream(options = {}) {
336
+ throwIfClientSnapshotAborted(options.signal);
234
337
  options.onProgress?.({ phase: "reading", transferredBytes: 0, totalBytes: 0 });
235
338
  const opened = (await this.#call("exportSnapshotOpen", []));
236
- const totalBytes = opened.byteLength;
237
- const bytes = new Uint8Array(totalBytes);
339
+ let transferredBytes = 0;
340
+ let succeeded = false;
238
341
  try {
239
- let offset = 0;
240
- while (offset < totalBytes) {
241
- const chunk = (await this._invoke(opened.handleId, "read", [
242
- offset,
243
- Math.min(SNAPSHOT_CHUNK_BYTES, totalBytes - offset),
244
- ]));
245
- // A slice that came back empty would loop forever; the export is over either way.
246
- if (chunk.byteLength === 0)
247
- throw new Error("Snapshot transfer returned no bytes");
248
- bytes.set(chunk, offset);
249
- offset += chunk.byteLength;
250
- options.onProgress?.({ phase: "transfer", transferredBytes: offset, totalBytes });
342
+ for (;;) {
343
+ throwIfClientSnapshotAborted(options.signal);
344
+ const next = (await this._invoke(opened.handleId, "read", []));
345
+ if (next.done)
346
+ break;
347
+ throwIfClientSnapshotAborted(options.signal);
348
+ if (!(next.chunk instanceof Uint8Array) || next.chunk.byteLength === 0) {
349
+ throw new Error("Snapshot transfer returned an invalid chunk");
350
+ }
351
+ yield next.chunk;
352
+ transferredBytes += next.chunk.byteLength;
353
+ options.onProgress?.({ phase: "transfer", transferredBytes, totalBytes: 0 });
251
354
  }
355
+ succeeded = true;
252
356
  }
253
357
  finally {
254
358
  await this._invoke(opened.handleId, "close", []).catch(() => undefined);
359
+ if (succeeded) {
360
+ options.onProgress?.({
361
+ phase: "done",
362
+ transferredBytes,
363
+ totalBytes: transferredBytes,
364
+ });
365
+ }
255
366
  }
256
- options.onProgress?.({ phase: "done", transferredBytes: totalBytes, totalBytes });
257
- return bytes;
258
367
  }
259
368
  /**
260
369
  * Loads a snapshot file into the worker's store, which must be empty. The bytes go over in the
@@ -262,6 +371,10 @@ export class MinnowDatabaseClient {
262
371
  * multi-megabyte restore can be shown moving rather than as a frozen tab.
263
372
  */
264
373
  async importSnapshot(bytes, options = {}) {
374
+ await this.importSnapshotStream(clientSnapshotChunks(bytes), options);
375
+ }
376
+ /** Uploads with one acknowledged worker chunk in flight; cancellation leaves resumable state. */
377
+ async importSnapshotStream(source, options = {}) {
265
378
  const handleId = crypto.randomUUID();
266
379
  const { onProgress } = options;
267
380
  this.#events.set(handleId, {
@@ -274,17 +387,25 @@ export class MinnowDatabaseClient {
274
387
  }),
275
388
  });
276
389
  try {
390
+ throwIfClientSnapshotAborted(options.signal);
277
391
  await this.#call("importSnapshotOpen", [handleId]);
278
- for (let offset = 0; offset < bytes.byteLength; offset += SNAPSHOT_CHUNK_BYTES) {
279
- // Copied, not viewed: structured clone copies the buffer behind a typed array, so a
280
- // subarray would post the whole file on every write.
281
- await this._invoke(handleId, "write", [bytes.slice(offset, offset + SNAPSHOT_CHUNK_BYTES)]);
392
+ for await (const input of source) {
393
+ throwIfClientSnapshotAborted(options.signal);
394
+ if (!(input instanceof Uint8Array)) {
395
+ throw new TypeError("Snapshot stream chunks must be Uint8Array values");
396
+ }
397
+ for (let offset = 0; offset < input.byteLength; offset += SNAPSHOT_CHUNK_BYTES) {
398
+ // Copy the bounded window: cloning a subarray would otherwise transfer its whole backing
399
+ // file on every request.
400
+ const chunk = input.slice(offset, offset + SNAPSHOT_CHUNK_BYTES);
401
+ await this.#post("rpc-call", handleId, "write", [chunk], [chunk.buffer]);
402
+ throwIfClientSnapshotAborted(options.signal);
403
+ }
282
404
  }
283
405
  await this._invoke(handleId, "finish", []);
284
406
  }
285
407
  catch (error) {
286
- // The handle survives a failed write; drop it rather than leaving the chunks in the worker.
287
- await this._invoke(handleId, "close", []).catch(() => undefined);
408
+ await this._invoke(handleId, options.signal?.aborted === true ? "cancel" : "close", []).catch(() => undefined);
288
409
  throw error;
289
410
  }
290
411
  finally {
@@ -295,14 +416,31 @@ export class MinnowDatabaseClient {
295
416
  async bufferPoolStats() {
296
417
  return (await this.#call("bufferPoolStats", []));
297
418
  }
419
+ async maintenanceStatus() {
420
+ return (await this.#call("maintenanceStatus", []));
421
+ }
422
+ async checkIntegrity(options = {}) {
423
+ return (await this.#call("checkIntegrity", [options]));
424
+ }
425
+ async storageStats() {
426
+ return (await this.#call("storageStats", []));
427
+ }
428
+ async inspectInterruptedImport() {
429
+ return (await this.#call("inspectInterruptedImport", []));
430
+ }
431
+ async abortInterruptedImport(identity) {
432
+ return (await this.#call("abortInterruptedImport", [
433
+ identity,
434
+ ]));
435
+ }
298
436
  liveQueries(options = {}) {
299
437
  const handleId = crypto.randomUUID();
300
438
  const created = this.#call("liveQueries", [handleId, options]);
301
439
  return new ClientLiveQuerySet(this, handleId, created);
302
440
  }
303
441
  // --- Maintenance ------------------------------------------------------------------------------
304
- async listVisibleSegments(tableName, version) {
305
- return (await this.#call("listVisibleSegments", version === undefined ? [tableName] : [tableName, version]));
442
+ async listVisibleSegmentPage(tableName, options) {
443
+ return (await this.#call("listVisibleSegmentPage", options === undefined ? [tableName] : [tableName, options]));
306
444
  }
307
445
  async cleanupQuerySpill(options) {
308
446
  return (await this.#call("cleanupQuerySpill", options === undefined ? [] : [options]));
@@ -348,6 +486,9 @@ export class MinnowDatabaseClient {
348
486
  }
349
487
  finally {
350
488
  this.#events.clear();
489
+ this.#transport.removeEventListener?.("message", this.#onMessage);
490
+ this.#transport.removeEventListener?.("error", this.#onError);
491
+ this.#transport.removeEventListener?.("messageerror", this.#onMessageError);
351
492
  if (options.terminateWorker === true)
352
493
  this.#transport.terminate?.();
353
494
  }
@@ -355,6 +496,8 @@ export class MinnowDatabaseClient {
355
496
  // --- Channel internals ------------------------------------------------------------------------
356
497
  /** @internal */
357
498
  async _invoke(handleId, method, args) {
499
+ if (this.#closed)
500
+ throw new Error("Database client is closed");
358
501
  return this.#post("rpc-call", handleId, method, args);
359
502
  }
360
503
  /** @internal */
@@ -370,15 +513,18 @@ export class MinnowDatabaseClient {
370
513
  throw new Error("Database client is closed");
371
514
  return this.#post("rpc-call", null, method, args);
372
515
  }
373
- async #post(kind, handleId, method, args) {
516
+ async #post(kind, handleId, method, args, transfer, bypassLimit = kind === "rpc-init" || method === "dispose") {
374
517
  if (this.#fatal !== undefined)
375
518
  throw this.#fatal;
519
+ if (!bypassLimit && this.#pending.size >= MAX_DATABASE_RPC_IN_FLIGHT) {
520
+ throw new RangeError(`A database worker connection cannot hold more than ${String(MAX_DATABASE_RPC_IN_FLIGHT)} in-flight requests`);
521
+ }
376
522
  const requestId = crypto.randomUUID();
377
523
  return new Promise((resolve, reject) => {
378
524
  this.#pending.set(requestId, { resolve, reject });
379
525
  this.#transport.postMessage(kind === "rpc-init"
380
526
  ? { version: protocolVersion, requestId, kind, payload: args[0] }
381
- : { version: protocolVersion, requestId, kind, handleId, method, args });
527
+ : { version: protocolVersion, requestId, kind, handleId, method, args }, transfer === undefined ? undefined : { transfer });
382
528
  });
383
529
  }
384
530
  #receive(message) {
@@ -391,6 +537,9 @@ export class MinnowDatabaseClient {
391
537
  return;
392
538
  if (response.event === "change")
393
539
  route.onChange?.(decodeQueryResult(response.payload));
540
+ else if (response.event === "invalidate") {
541
+ route.onInvalidate?.(response.payload);
542
+ }
394
543
  else if (response.event === "error") {
395
544
  route.onError?.(rehydrateError(response.payload));
396
545
  }
@@ -415,6 +564,7 @@ export class MinnowDatabaseClient {
415
564
  this.#fatal = error;
416
565
  const pending = [...this.#pending.values()];
417
566
  this.#pending.clear();
567
+ this.#events.clear();
418
568
  for (const call of pending)
419
569
  call.reject(error);
420
570
  }
@@ -507,6 +657,33 @@ export class ClientLiveQuerySet {
507
657
  throw error;
508
658
  }
509
659
  }
660
+ /** Registers dependency observation while leaving execution/result mapping to an adapter. */
661
+ async observe(query, options) {
662
+ await this.#created;
663
+ const subscriptionId = crypto.randomUUID();
664
+ const state = { completed: false };
665
+ this.client._routeEvents(subscriptionId, {
666
+ onInvalidate: options.onInvalidate.bind(options),
667
+ ...(options.onError === undefined ? {} : { onError: options.onError.bind(options) }),
668
+ onComplete: () => {
669
+ state.completed = true;
670
+ this.#subscriptionIds.delete(subscriptionId);
671
+ options.onComplete?.();
672
+ },
673
+ });
674
+ try {
675
+ const created = (await this.client._invoke(this.handleId, "observe", [
676
+ subscriptionId,
677
+ query,
678
+ ]));
679
+ this.#subscriptionIds.add(subscriptionId);
680
+ return new ClientLiveSubscription(this.client, subscriptionId, created.dependencyTableIds, () => this.#subscriptionIds.delete(subscriptionId), state);
681
+ }
682
+ catch (error) {
683
+ this.client._unrouteEvents(subscriptionId);
684
+ throw error;
685
+ }
686
+ }
510
687
  async stats() {
511
688
  await this.#created;
512
689
  return (await this.client._invoke(this.handleId, "stats", []));
@@ -560,4 +737,3 @@ export class ClientLiveSubscription {
560
737
  }
561
738
  }
562
739
  }
563
- //# sourceMappingURL=client.js.map