@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
@@ -1,3 +1,5 @@
1
+ import { dateMilliseconds } from "../date-value.js";
2
+ import { MAX_TEMP_RUN_BATCH_BYTES, MAX_TEMP_RUN_PAGE_BYTES, MAX_TEMP_RUN_PAGES_PER_BATCH, } from "../storage/types.js";
1
3
  import { cachedListMembership, childExpressions, distinctFromComparison, nullOrder, isScalarFunctionName, likeMatches, orderOutputName, parseQuantified, quantifiedComparison, scalarFunctionValue, } from "./query.js";
2
4
  import { jsonValueOf } from "./sql-json.js";
3
5
  import { bm25DocumentScore, cachedQueryTerms, FtsStatsAccumulator, fullTermsMask, renderDocumentValue, termFrequencies, termsMask, tokenize, } from "./fts.js";
@@ -27,7 +29,17 @@ const SPILL_PAGE_MAGIC = 0x5350494c;
27
29
  const SPILL_PAGE_HEADER_BYTES = 8;
28
30
  const vectorTextEncoder = new TextEncoder();
29
31
  /** Pages per batched spill write: bounds the transient encoded copies a batch holds. */
30
- const SPILL_WRITE_BATCH_PAGES = 8;
32
+ const SPILL_WRITE_BATCH_PAGES = Math.min(8, MAX_TEMP_RUN_PAGES_PER_BATCH);
33
+ function boundedSpillPageRows(value) {
34
+ const rows = value ?? DEFAULT_BATCH_ROWS;
35
+ if (!Number.isSafeInteger(rows) || rows <= 0) {
36
+ throw new RangeError("Query spill page rows must be a positive whole number");
37
+ }
38
+ // Page rows are a tuning target, not a request to buffer an unbounded number of rows. Larger
39
+ // pages do not improve the block-store round trip because byte-bounded batches provide that
40
+ // amortization independently.
41
+ return Math.min(rows, DEFAULT_BATCH_ROWS);
42
+ }
31
43
  /** Writes already-materialized pages through the batch method when the store offers one. */
32
44
  async function writeSpillPages(store, pages) {
33
45
  const batched = store.putPages?.bind(store);
@@ -37,9 +49,43 @@ async function writeSpillPages(store, pages) {
37
49
  }
38
50
  return;
39
51
  }
40
- for (let start = 0; start < pages.length; start += SPILL_WRITE_BATCH_PAGES) {
41
- await batched(pages.slice(start, start + SPILL_WRITE_BATCH_PAGES));
52
+ let batch = [];
53
+ let batchBytes = 0;
54
+ for (const page of pages) {
55
+ if (page.bytes.byteLength > MAX_TEMP_RUN_PAGE_BYTES) {
56
+ throw new RangeError("Query spill page exceeds the storage limit");
57
+ }
58
+ if (batch.length > 0 &&
59
+ (batch.length === SPILL_WRITE_BATCH_PAGES ||
60
+ batchBytes + page.bytes.byteLength > MAX_TEMP_RUN_BATCH_BYTES)) {
61
+ await batched(batch);
62
+ batch = [];
63
+ batchBytes = 0;
64
+ }
65
+ batch.push(page);
66
+ batchBytes += page.bytes.byteLength;
42
67
  }
68
+ if (batch.length > 0)
69
+ await batched(batch);
70
+ }
71
+ async function writeSpillRowPages(store, ownerId, runId, startPageIndex, columns, rows, pageRows) {
72
+ let pending = [];
73
+ let pendingBytes = 0;
74
+ let pageCount = 0;
75
+ for (const bytes of encodeSpillRowPages(columns, rows, pageRows)) {
76
+ if (pending.length > 0 &&
77
+ (pending.length === SPILL_WRITE_BATCH_PAGES ||
78
+ pendingBytes + bytes.byteLength > MAX_TEMP_RUN_BATCH_BYTES)) {
79
+ await writeSpillPages(store, pending);
80
+ pending = [];
81
+ pendingBytes = 0;
82
+ }
83
+ pending.push({ ownerId, runId, pageIndex: startPageIndex + pageCount, bytes });
84
+ pendingBytes += bytes.byteLength;
85
+ pageCount += 1;
86
+ }
87
+ await writeSpillPages(store, pending);
88
+ return pageCount;
43
89
  }
44
90
  export function createColumnarTable(name, columns, uniqueKey) {
45
91
  const first = columns.values().next().value;
@@ -135,6 +181,23 @@ export function prepareVectorQuery(plan, inputTables, options = {}) {
135
181
  executionMemory.close();
136
182
  }
137
183
  },
184
+ async executeBatches(executionOptions, consume) {
185
+ if (closed)
186
+ throw new Error("Prepared vector query is closed");
187
+ if (bound.grouped || bound.orderBy.length > 0 || bound.joins.length > 0) {
188
+ throw new TypeError("Native query batching requires an ungrouped, unordered, single-source plan");
189
+ }
190
+ if (!Number.isSafeInteger(executionOptions.batchRows) || executionOptions.batchRows <= 0) {
191
+ throw new RangeError("Query batch rows must be a positive whole number");
192
+ }
193
+ const executionMemory = retainedMemory.createChild();
194
+ try {
195
+ return await executeBoundPlanBatches(bound, executionMemory, executionOptions, consume);
196
+ }
197
+ finally {
198
+ executionMemory.close();
199
+ }
200
+ },
138
201
  close() {
139
202
  if (closed)
140
203
  return;
@@ -198,7 +261,7 @@ function createVector(input) {
198
261
  continue;
199
262
  const numericValue = input.type === "datetime"
200
263
  ? value instanceof Date
201
- ? value.getTime()
264
+ ? dateMilliseconds(value)
202
265
  : Number.NaN
203
266
  : typeof value === "number"
204
267
  ? value
@@ -1202,15 +1265,61 @@ async function executeBoundPlanAsync(plan, memory, options) {
1202
1265
  const rows = plan.grouped ? finishGroups(plan, groups.values(), memory) : output.finish();
1203
1266
  return finishResult(plan, rows, memory);
1204
1267
  }
1268
+ /**
1269
+ * Pull-driven scan execution for cursor-safe plans. A result page owns a child memory context
1270
+ * that closes after the consumer settles, so both modeled memory and live row objects are
1271
+ * bounded by one page. OFFSET/LIMIT are applied once across the whole scan, never per page.
1272
+ */
1273
+ async function executeBoundPlanBatches(plan, memory, options, consume) {
1274
+ const columns = plan.wildcard ? wildcardColumnNames(plan) : plan.select.map((item) => item.alias);
1275
+ const { limit, offset, ...unbounded } = plan;
1276
+ const scanPlan = unbounded;
1277
+ let skipped = 0;
1278
+ let emitted = 0;
1279
+ const scanRows = scanPlan.sourceTables[scanPlan.scanSource]?.rowCount ?? 0;
1280
+ const step = Math.min(DEFAULT_BATCH_ROWS, options.batchRows);
1281
+ for (let start = 0; start < scanRows && (limit === undefined || emitted < limit);) {
1282
+ options.signal?.throwIfAborted();
1283
+ let length = Math.min(step, scanRows - start);
1284
+ const loaded = options.loadScanWindow?.(start, length);
1285
+ const residentEnd = typeof loaded === "number" || loaded === undefined ? loaded : await loaded;
1286
+ options.signal?.throwIfAborted();
1287
+ if (typeof residentEnd === "number" && residentEnd > start) {
1288
+ length = Math.min(length, residentEnd - start);
1289
+ }
1290
+ const pageMemory = memory.createChild();
1291
+ try {
1292
+ const groups = new GroupAccumulator(scanPlan, pageMemory);
1293
+ const output = new ResultSink(scanPlan, pageMemory, options.loadScanWindow === undefined);
1294
+ runScanBatch(scanPlan, start, length, groups, output, pageMemory);
1295
+ let rows = output.finish();
1296
+ const remainingOffset = Math.max(0, (offset ?? 0) - skipped);
1297
+ if (remainingOffset > 0) {
1298
+ const remove = Math.min(remainingOffset, rows.length);
1299
+ rows = rows.slice(remove);
1300
+ skipped += remove;
1301
+ }
1302
+ if (limit !== undefined && rows.length > limit - emitted) {
1303
+ rows = rows.slice(0, limit - emitted);
1304
+ }
1305
+ if (rows.length > 0) {
1306
+ emitted += rows.length;
1307
+ await consume({ columns: [...columns], rows });
1308
+ }
1309
+ }
1310
+ finally {
1311
+ pageMemory.close();
1312
+ }
1313
+ start += length;
1314
+ }
1315
+ return columns;
1316
+ }
1205
1317
  function createSpillOwnerId() {
1206
1318
  return `query-${globalThis.crypto.randomUUID()}`;
1207
1319
  }
1208
1320
  async function executeBoundPlanWithSortSpill(plan, memory, options) {
1209
1321
  const store = required(options.spillStore, "Query spill store is missing");
1210
- const pageRows = options.spillPageRows ?? DEFAULT_BATCH_ROWS;
1211
- if (!Number.isSafeInteger(pageRows) || pageRows <= 0) {
1212
- throw new RangeError("Query spill page rows must be a positive whole number");
1213
- }
1322
+ const pageRows = boundedSpillPageRows(options.spillPageRows);
1214
1323
  const columns = plan.wildcard ? wildcardColumnNames(plan) : plan.select.map((item) => item.alias);
1215
1324
  const ownerId = createSpillOwnerId();
1216
1325
  const runs = [];
@@ -1251,8 +1360,8 @@ async function executeBoundPlanWithSortSpill(plan, memory, options) {
1251
1360
  ordering.release();
1252
1361
  }
1253
1362
  const runId = `run-${String(runSequence++)}`;
1254
- await store.putPage(ownerId, runId, 0, encodeSpillRows(columns, rows));
1255
- runs.push({ id: runId, pageCount: 1 });
1363
+ const pageCount = await writeSpillRowPages(store, ownerId, runId, 0, columns, rows, pageRows);
1364
+ runs.push({ id: runId, pageCount });
1256
1365
  }
1257
1366
  finally {
1258
1367
  outputMemory.close();
@@ -1307,10 +1416,7 @@ async function executeBoundPlanWithSortSpill(plan, memory, options) {
1307
1416
  }
1308
1417
  async function executeBoundPlanWithHashSpill(plan, memory, options) {
1309
1418
  const store = required(options.spillStore, "Query spill store is missing");
1310
- const pageRows = options.spillPageRows ?? DEFAULT_BATCH_ROWS;
1311
- if (!Number.isSafeInteger(pageRows) || pageRows <= 0) {
1312
- throw new RangeError("Query spill page rows must be a positive whole number");
1313
- }
1419
+ const pageRows = boundedSpillPageRows(options.spillPageRows);
1314
1420
  const partitionCount = 64;
1315
1421
  const columns = plan.select.map((item) => item.alias);
1316
1422
  const groupColumnNames = plan.groupBy.map((_, index) => `g${String(index)}`);
@@ -1382,15 +1488,26 @@ async function executeBoundPlanWithHashSpill(plan, memory, options) {
1382
1488
  }
1383
1489
  });
1384
1490
  const flush = [];
1491
+ let flushBytes = 0;
1385
1492
  for (const [partition, rows] of partitionBuffers) {
1386
- const pageIndex = partitionPages[partition] ?? 0;
1387
- flush.push({
1388
- ownerId,
1389
- runId: `partition-${String(partition)}`,
1390
- pageIndex,
1391
- bytes: encodeSpillRows(spillColumns, rows),
1392
- });
1393
- partitionPages[partition] = pageIndex + 1;
1493
+ for (const bytes of encodeSpillRowPages(spillColumns, rows, pageRows)) {
1494
+ if (flush.length > 0 &&
1495
+ (flush.length === SPILL_WRITE_BATCH_PAGES ||
1496
+ flushBytes + bytes.byteLength > MAX_TEMP_RUN_BATCH_BYTES)) {
1497
+ await writeSpillPages(store, flush);
1498
+ flush.length = 0;
1499
+ flushBytes = 0;
1500
+ }
1501
+ const pageIndex = partitionPages[partition] ?? 0;
1502
+ flush.push({
1503
+ ownerId,
1504
+ runId: `partition-${String(partition)}`,
1505
+ pageIndex,
1506
+ bytes,
1507
+ });
1508
+ flushBytes += bytes.byteLength;
1509
+ partitionPages[partition] = pageIndex + 1;
1510
+ }
1394
1511
  }
1395
1512
  await writeSpillPages(store, flush);
1396
1513
  }
@@ -1437,23 +1554,8 @@ async function executeBoundPlanWithHashSpill(plan, memory, options) {
1437
1554
  }
1438
1555
  }
1439
1556
  const runId = `group-${String(runSequence++)}`;
1440
- let outputPage = 0;
1441
- let pending = [];
1442
- for (let start = 0; start < rows.length; start += pageRows) {
1443
- pending.push({
1444
- ownerId,
1445
- runId,
1446
- pageIndex: outputPage,
1447
- bytes: encodeSpillRows(columns, rows.slice(start, start + pageRows)),
1448
- });
1449
- outputPage += 1;
1450
- if (pending.length >= SPILL_WRITE_BATCH_PAGES) {
1451
- await writeSpillPages(store, pending);
1452
- pending = [];
1453
- }
1454
- }
1455
- await writeSpillPages(store, pending);
1456
- runs.push({ id: runId, pageCount: outputPage });
1557
+ const pageCount = await writeSpillRowPages(store, ownerId, runId, 0, columns, rows, pageRows);
1558
+ runs.push({ id: runId, pageCount });
1457
1559
  }
1458
1560
  finally {
1459
1561
  partitionMemory.close();
@@ -1589,11 +1691,10 @@ async function mergeSpillRuns(store, ownerId, left, right, outputId, columns, or
1589
1691
  const flush = async () => {
1590
1692
  if (outputPage.length === 0)
1591
1693
  return;
1592
- await store.putPage(ownerId, outputId, pageIndex, encodeSpillRows(columns, outputPage));
1694
+ pageIndex += await writeSpillRowPages(store, ownerId, outputId, pageIndex, columns, outputPage, pageRows);
1593
1695
  outputPage = [];
1594
1696
  outputMemory.close();
1595
1697
  outputMemory = mergeMemory.createChild();
1596
- pageIndex += 1;
1597
1698
  };
1598
1699
  try {
1599
1700
  let leftRow = await leftReader.next();
@@ -1665,19 +1766,63 @@ function compareOrderedRows(left, right, orderBy) {
1665
1766
  }
1666
1767
  return 0;
1667
1768
  }
1668
- function encodeSpillRows(columns, rows) {
1669
- const encoded = rows.map((row) => columns.map((column) => encodeSpillValue(row[column] ?? null)));
1670
- const payload = vectorTextEncoder.encode(JSON.stringify(encoded));
1671
- const modeledBytes = rows.reduce((total, row) => safeMemorySum(total, queryRowPayloadBytes(row), "Spill page rows"), 0);
1672
- if (modeledBytes > 0xffffffff)
1673
- throw new RangeError("Spill page modeled bytes exceed uint32");
1674
- const bytes = new Uint8Array(SPILL_PAGE_HEADER_BYTES + payload.byteLength);
1769
+ function encodeSpillRow(columns, row) {
1770
+ return {
1771
+ bytes: vectorTextEncoder.encode(JSON.stringify(columns.map((column) => encodeSpillValue(row[column] ?? null)))),
1772
+ modeledBytes: queryRowPayloadBytes(row),
1773
+ };
1774
+ }
1775
+ function buildSpillPage(rows, byteLength, modeledBytes) {
1776
+ const bytes = new Uint8Array(byteLength);
1675
1777
  const header = new DataView(bytes.buffer);
1676
1778
  header.setUint32(0, SPILL_PAGE_MAGIC, true);
1677
1779
  header.setUint32(4, modeledBytes, true);
1678
- bytes.set(payload, SPILL_PAGE_HEADER_BYTES);
1780
+ let offset = SPILL_PAGE_HEADER_BYTES;
1781
+ bytes[offset++] = 0x5b;
1782
+ for (let index = 0; index < rows.length; index += 1) {
1783
+ if (index > 0)
1784
+ bytes[offset++] = 0x2c;
1785
+ const row = required(rows[index], "Encoded spill row is missing");
1786
+ bytes.set(row.bytes, offset);
1787
+ offset += row.bytes.byteLength;
1788
+ }
1789
+ bytes[offset] = 0x5d;
1679
1790
  return bytes;
1680
1791
  }
1792
+ /** Encodes pages incrementally and splits on both the requested row target and the hard byte cap. */
1793
+ function* encodeSpillRowPages(columns, rows, pageRows) {
1794
+ let pending = [];
1795
+ let byteLength = SPILL_PAGE_HEADER_BYTES + 2;
1796
+ let modeledBytes = 0;
1797
+ const flush = () => {
1798
+ const page = buildSpillPage(pending, byteLength, modeledBytes);
1799
+ pending = [];
1800
+ byteLength = SPILL_PAGE_HEADER_BYTES + 2;
1801
+ modeledBytes = 0;
1802
+ return page;
1803
+ };
1804
+ for (const row of rows) {
1805
+ const encoded = encodeSpillRow(columns, row);
1806
+ const separatorBytes = pending.length === 0 ? 0 : 1;
1807
+ const nextByteLength = byteLength + separatorBytes + encoded.bytes.byteLength;
1808
+ const nextModeledBytes = modeledBytes + encoded.modeledBytes;
1809
+ if (pending.length > 0 &&
1810
+ (pending.length === pageRows ||
1811
+ nextByteLength > MAX_TEMP_RUN_PAGE_BYTES ||
1812
+ nextModeledBytes > 0xffffffff)) {
1813
+ yield flush();
1814
+ }
1815
+ const addedSeparatorBytes = pending.length === 0 ? 0 : 1;
1816
+ byteLength += addedSeparatorBytes + encoded.bytes.byteLength;
1817
+ modeledBytes += encoded.modeledBytes;
1818
+ if (byteLength > MAX_TEMP_RUN_PAGE_BYTES || modeledBytes > 0xffffffff) {
1819
+ throw new RangeError("One query result row exceeds the spill page storage limit");
1820
+ }
1821
+ pending.push(encoded);
1822
+ }
1823
+ if (pending.length > 0)
1824
+ yield flush();
1825
+ }
1681
1826
  function decodeSpillRows(columns, bytes) {
1682
1827
  spillRowsModeledBytes(bytes);
1683
1828
  const value = JSON.parse(new TextDecoder().decode(bytes.subarray(SPILL_PAGE_HEADER_BYTES)));
@@ -1708,7 +1853,7 @@ function encodeSpillValue(value) {
1708
1853
  return [2, Object.is(value, -0) ? "-0" : String(value)];
1709
1854
  if (typeof value === "string")
1710
1855
  return [3, value];
1711
- return [4, value.getTime()];
1856
+ return [4, dateMilliseconds(value)];
1712
1857
  }
1713
1858
  function decodeSpillValue(value) {
1714
1859
  if (!Array.isArray(value))
@@ -2851,7 +2996,7 @@ class ResultSink {
2851
2996
  : typeof first === "number"
2852
2997
  ? first
2853
2998
  : first instanceof Date
2854
- ? first.getTime()
2999
+ ? dateMilliseconds(first)
2855
3000
  : undefined;
2856
3001
  }
2857
3002
  /**
@@ -3404,7 +3549,7 @@ function createGroupState(groupValues, plan, memory) {
3404
3549
  * string a row genuinely holds.
3405
3550
  */
3406
3551
  function distinctKey(value) {
3407
- return value instanceof Date ? `d${String(value.getTime())}` : value;
3552
+ return value instanceof Date ? `\0d${String(dateMilliseconds(value))}` : value;
3408
3553
  }
3409
3554
  /**
3410
3555
  * Whether this value is the first of its kind for the slot, folding it into the set when it is.
@@ -3799,7 +3944,7 @@ function detectPrimitiveComparison(predicate) {
3799
3944
  return { source: column.source, vector, operator, value };
3800
3945
  }
3801
3946
  if (vector.kind === "datetime" && value instanceof Date) {
3802
- return { source: column.source, vector, operator, value: value.getTime() };
3947
+ return { source: column.source, vector, operator, value: dateMilliseconds(value) };
3803
3948
  }
3804
3949
  if (vector.kind === "boolean" &&
3805
3950
  typeof value === "boolean" &&
@@ -3847,7 +3992,7 @@ function detectPrimitiveInList(predicate) {
3847
3992
  else {
3848
3993
  if (!(value instanceof Date))
3849
3994
  return undefined;
3850
- members.add(value.getTime());
3995
+ members.add(dateMilliseconds(value));
3851
3996
  }
3852
3997
  }
3853
3998
  if (members.size === 0)
@@ -4361,7 +4506,7 @@ function comparisonValue(operator, leftValue, rightValue) {
4361
4506
  return comparison <= 0;
4362
4507
  }
4363
4508
  function comparable(value) {
4364
- return value instanceof Date ? value.getTime() : value;
4509
+ return value instanceof Date ? dateMilliseconds(value) : value;
4365
4510
  }
4366
4511
  function groupKey(value) {
4367
4512
  const comparableValue = comparable(value);
@@ -4411,8 +4556,8 @@ function compareValues(left, right) {
4411
4556
  const exact = exactNumericCompare(left, right);
4412
4557
  if (exact !== undefined)
4413
4558
  return exact;
4414
- const a = left instanceof Date ? left.getTime() : left;
4415
- const b = right instanceof Date ? right.getTime() : right;
4559
+ const a = left instanceof Date ? dateMilliseconds(left) : left;
4560
+ const b = right instanceof Date ? dateMilliseconds(right) : right;
4416
4561
  if (a === b)
4417
4562
  return 0;
4418
4563
  if (a === null || a === undefined)
@@ -4492,4 +4637,3 @@ function safeMemoryProduct(left, right, label) {
4492
4637
  }
4493
4638
  return product;
4494
4639
  }
4495
- //# sourceMappingURL=vector.js.map
@@ -21,7 +21,7 @@ export type StoreDescriptor = {
21
21
  durability?: "relaxed" | "strict";
22
22
  };
23
23
  /** The cloneable subset of MinnowDatabaseOptions; function-valued seams stay worker-side. */
24
- export type WireDatabaseOptions = Pick<MinnowDatabaseOptions, "compression" | "rowsPerBlock" | "maxCommitRetries" | "spillOwnerLeaseMs" | "bufferPoolBytes">;
24
+ export type WireDatabaseOptions = Pick<MinnowDatabaseOptions, "compression" | "targetBlockBytes" | "rowsPerBlock" | "maxCommitRetries" | "spillOwnerLeaseMs" | "transactionOwnerLeaseMs" | "transactionIdleTimeoutMs" | "bufferPoolBytes" | "executionMemoryBudgetBytes" | "autoCollectDebtLimitCommits" | "autoCollect" | "autoCompact">;
25
25
  export interface DatabaseInitPayload {
26
26
  store: StoreDescriptor;
27
27
  options?: WireDatabaseOptions;
@@ -40,6 +40,11 @@ export interface RpcScope {
40
40
  export interface ExposeDatabaseOptions {
41
41
  /** Called when the client sends its dispose frame, after every handle has been closed. */
42
42
  onDispose?: () => void | Promise<void>;
43
+ /**
44
+ * Rolls back a worker write handle after this long without a handle RPC. Match the constructed
45
+ * database's `transactionIdleTimeoutMs` when it is customized. Defaults to 30 seconds.
46
+ */
47
+ writeHandleIdleTimeoutMs?: number;
43
48
  /**
44
49
  * Called when the main thread reports its page visibility. The stock entry forwards this to
45
50
  * the store's `setForeground` when it has one — the OPFS store uses it to keep leadership
@@ -51,6 +56,8 @@ export interface AttachDatabaseWorkerOptions {
51
56
  /** Test seam for environments without a global IndexedDB (e.g. fake-indexeddb). */
52
57
  indexedDB?: IDBFactory;
53
58
  }
59
+ /** Hard per-connection ceiling for resident worker resources abandoned by a client. */
60
+ export declare const MAX_WORKER_HANDLES_PER_CONNECTION = 256;
54
61
  /**
55
62
  * Answers client RPC frames against a database you constructed yourself. Use this in a custom
56
63
  * worker entry when you need non-cloneable construction options (a custom store, `now`,
@@ -62,4 +69,3 @@ export declare function exposeDatabase(database: MinnowDatabase, scope: RpcScope
62
69
  * constructs the database, and exposes it. The store is closed when the client disposes.
63
70
  */
64
71
  export declare function attachDatabaseWorker(scope: RpcScope, options?: AttachDatabaseWorkerOptions): void;
65
- //# sourceMappingURL=worker-host.d.ts.map