@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,6 @@
1
+ import { assertWellFormedString } from "../block-format/unicode.js";
2
+ import { dateIsoString } from "../date-value.js";
3
+ import { MAX_SQL_SCALAR_RESULT_CHARACTERS, MAX_SQL_NUMERIC_DIGITS, MAX_SQL_STRUCTURED_VALUE_DEPTH, MAX_SQL_STRUCTURED_VALUE_ITEMS, } from "./cache-limits.js";
1
4
  /** Internal string encodings for PostgreSQL value domains carried by string vectors. */
2
5
  const PREFIX = "\u0000minnow-domain:";
3
6
  const TEXT_VALUE = `${PREFIX}text:`;
@@ -24,23 +27,25 @@ export function externalSqlTextValue(value) {
24
27
  ? value.slice(TEXT_VALUE.length)
25
28
  : value;
26
29
  }
27
- const powersOfTen = [1n];
28
30
  function pow10(exponent) {
29
31
  if (!Number.isSafeInteger(exponent) || exponent < 0 || exponent > 100_000) {
30
32
  throw new RangeError(`Decimal scale is outside the supported range: ${String(exponent)}`);
31
33
  }
32
- for (let index = powersOfTen.length; index <= exponent; index += 1) {
33
- powersOfTen.push((powersOfTen[index - 1] ?? 1n) * 10n);
34
- }
35
- return powersOfTen[exponent] ?? 1n;
34
+ // Retaining every intermediate power up to 100,000 consumes quadratic memory in the number
35
+ // of decimal digits. Native exponentiation constructs only the requested result.
36
+ return 10n ** BigInt(exponent);
36
37
  }
37
38
  function decimalParts(value) {
38
39
  const source = String(value).trim();
40
+ assertBoundedDomainString(source, "NUMERIC value");
39
41
  const match = /^([+-]?)(?:(\d+)(?:\.(\d*))?|\.(\d+))(?:[eE]([+-]?\d+))?$/.exec(source);
40
42
  if (match === null)
41
43
  throw new TypeError(`Invalid NUMERIC value: ${String(value)}`);
42
44
  const fraction = match[3] ?? match[4] ?? "";
43
45
  const digits = `${match[2] ?? "0"}${fraction}`.replace(/^0+(?=\d)/, "");
46
+ if (digits.length > MAX_SQL_NUMERIC_DIGITS) {
47
+ throw new RangeError(`NUMERIC value exceeds ${String(MAX_SQL_NUMERIC_DIGITS)} significant digits`);
48
+ }
44
49
  const exponent = Number(match[5] ?? 0);
45
50
  if (!Number.isSafeInteger(exponent))
46
51
  throw new RangeError(`Invalid NUMERIC exponent: ${source}`);
@@ -114,7 +119,7 @@ export function exactNumericValue(value, precision, scale) {
114
119
  throw new RangeError(`NUMERIC(${String(precision)}, ${String(declaredScale)}) overflow`);
115
120
  }
116
121
  }
117
- return NUMERIC + formatDecimal(normalizeDecimal(adjusted));
122
+ return boundedTaggedDomainValue(NUMERIC, formatDecimal(normalizeDecimal(adjusted)), "NUMERIC value");
118
123
  }
119
124
  export function exactNumericBinary(operator, left, right) {
120
125
  const leftTagged = taggedDecimalParts(left);
@@ -156,7 +161,7 @@ export function exactNumericBinary(operator, left, right) {
156
161
  }
157
162
  result = { coefficient, scale };
158
163
  }
159
- return NUMERIC + formatDecimal(normalizeDecimal(result));
164
+ return boundedTaggedDomainValue(NUMERIC, formatDecimal(normalizeDecimal(result)), "NUMERIC result");
160
165
  }
161
166
  export function exactNumericCompare(left, right) {
162
167
  const leftTagged = taggedDecimalParts(left);
@@ -174,25 +179,116 @@ export function exactNumericCompare(left, right) {
174
179
  const bc = b.coefficient * pow10(scale - b.scale);
175
180
  return ac === bc ? 0 : ac < bc ? -1 : 1;
176
181
  }
177
- function canonicalJson(value) {
178
- const normalize = (item) => {
179
- if (item === null || typeof item === "boolean" || typeof item === "string")
180
- return item;
181
- if (typeof item === "number") {
182
- if (!Number.isFinite(item))
183
- throw new TypeError("JSON numbers must be finite");
184
- return item;
182
+ /**
183
+ * Serializes a JSON value while bounding traversal, nesting, and output before concatenation.
184
+ * Canonical mode sorts object names directly in the wire text (including integer-looking names,
185
+ * which JavaScript object enumeration would otherwise silently reorder).
186
+ */
187
+ export function boundedJsonText(value, canonical, label = "JSON value") {
188
+ const state = { active: new Set(), pieces: [], items: 0, length: 0 };
189
+ encodeBoundedJson(value, canonical, label, 0, state);
190
+ return state.pieces.join("");
191
+ }
192
+ function encodeBoundedJson(value, canonical, label, depth, state) {
193
+ state.items += 1;
194
+ if (state.items > MAX_SQL_STRUCTURED_VALUE_ITEMS) {
195
+ throw new RangeError(`${label} cannot exceed ${String(MAX_SQL_STRUCTURED_VALUE_ITEMS)} values and names`);
196
+ }
197
+ if (depth > MAX_SQL_STRUCTURED_VALUE_DEPTH) {
198
+ throw new RangeError(`${label} cannot exceed ${String(MAX_SQL_STRUCTURED_VALUE_DEPTH)} levels`);
199
+ }
200
+ if (value === null) {
201
+ appendJsonPiece(state, "null", label);
202
+ return;
203
+ }
204
+ if (typeof value === "boolean") {
205
+ appendJsonPiece(state, value ? "true" : "false", label);
206
+ return;
207
+ }
208
+ if (typeof value === "number") {
209
+ if (!Number.isFinite(value))
210
+ throw new TypeError("JSON numbers must be finite");
211
+ appendJsonPiece(state, JSON.stringify(value), label);
212
+ return;
213
+ }
214
+ if (typeof value === "string") {
215
+ assertBoundedDomainString(value, label);
216
+ appendJsonPiece(state, JSON.stringify(value), label);
217
+ return;
218
+ }
219
+ if (value instanceof Date) {
220
+ const timestamp = dateIsoString(value);
221
+ appendJsonPiece(state, JSON.stringify(timestamp), label);
222
+ return;
223
+ }
224
+ if (typeof value !== "object")
225
+ throw new TypeError(`${label} cannot be represented as JSON`);
226
+ const object = value;
227
+ if (state.active.has(object))
228
+ throw new TypeError(`${label} cannot contain a cycle`);
229
+ state.active.add(object);
230
+ try {
231
+ if (Array.isArray(value)) {
232
+ appendJsonPiece(state, "[", label);
233
+ for (let index = 0; index < value.length; index += 1) {
234
+ if (index > 0)
235
+ appendJsonPiece(state, ",", label);
236
+ encodeBoundedJson(value[index], canonical, label, depth + 1, state);
237
+ }
238
+ appendJsonPiece(state, "]", label);
239
+ return;
240
+ }
241
+ const prototype = Object.getPrototypeOf(value);
242
+ if (prototype !== Object.prototype && prototype !== null) {
243
+ throw new TypeError(`${label} accepts only JSON objects, arrays, and scalar values`);
244
+ }
245
+ const record = value;
246
+ // Object.keys() would allocate one array slot per property before the item cap can fire.
247
+ // Plain JSON objects need no prototype walk, so collect at most the admitted count instead.
248
+ const keys = [];
249
+ for (const key in record) {
250
+ if (!Object.hasOwn(record, key))
251
+ continue;
252
+ if (keys.length >= MAX_SQL_STRUCTURED_VALUE_ITEMS - state.items) {
253
+ throw new RangeError(`${label} cannot exceed ${String(MAX_SQL_STRUCTURED_VALUE_ITEMS)} values and names`);
254
+ }
255
+ keys.push(key);
185
256
  }
186
- if (Array.isArray(item))
187
- return item.map(normalize);
188
- if (typeof item === "object") {
189
- return Object.fromEntries(Object.keys(item)
190
- .sort()
191
- .map((key) => [key, normalize(item[key])]));
257
+ if (canonical)
258
+ keys.sort();
259
+ appendJsonPiece(state, "{", label);
260
+ for (let index = 0; index < keys.length; index += 1) {
261
+ const key = keys[index] ?? "";
262
+ state.items += 1;
263
+ if (state.items > MAX_SQL_STRUCTURED_VALUE_ITEMS) {
264
+ throw new RangeError(`${label} cannot exceed ${String(MAX_SQL_STRUCTURED_VALUE_ITEMS)} values and names`);
265
+ }
266
+ assertBoundedDomainString(key, `${label} object name`);
267
+ if (index > 0)
268
+ appendJsonPiece(state, ",", label);
269
+ appendJsonPiece(state, JSON.stringify(key), label);
270
+ appendJsonPiece(state, ":", label);
271
+ encodeBoundedJson(record[key], canonical, label, depth + 1, state);
192
272
  }
193
- throw new TypeError("Value cannot be represented as JSON");
194
- };
195
- return JSON.stringify(normalize(value));
273
+ appendJsonPiece(state, "}", label);
274
+ }
275
+ finally {
276
+ state.active.delete(object);
277
+ }
278
+ }
279
+ function appendJsonPiece(state, piece, label) {
280
+ const next = state.length + piece.length;
281
+ if (!Number.isSafeInteger(next) || next > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
282
+ throw new RangeError(`${label} exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
283
+ }
284
+ state.length = next;
285
+ state.pieces.push(piece);
286
+ }
287
+ function assertBoundedDomainString(value, label) {
288
+ if (value.length > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
289
+ throw new RangeError(`${label} exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
290
+ }
291
+ assertWellFormedString(value, label);
196
292
  }
197
293
  export function jsonDomainValue(value, binary) {
198
294
  if (value === null || value === undefined)
@@ -205,6 +301,7 @@ export function jsonDomainValue(value, binary) {
205
301
  ? JSON_VALUE
206
302
  : undefined;
207
303
  const source = prefix === undefined ? value : value.slice(prefix.length);
304
+ assertBoundedDomainString(source, "JSON value");
208
305
  try {
209
306
  parsed = JSON.parse(source);
210
307
  }
@@ -212,14 +309,15 @@ export function jsonDomainValue(value, binary) {
212
309
  throw new TypeError("Invalid JSON value");
213
310
  }
214
311
  }
215
- const text = binary ? canonicalJson(parsed) : JSON.stringify(parsed);
216
- return (binary ? JSONB_VALUE : JSON_VALUE) + text;
312
+ const text = boundedJsonText(parsed, binary, binary ? "JSONB value" : "JSON value");
313
+ return boundedTaggedDomainValue(binary ? JSONB_VALUE : JSON_VALUE, text, binary ? "JSONB value" : "JSON value");
217
314
  }
218
315
  export function uuidDomainValue(value) {
219
316
  if (value === null || value === undefined)
220
317
  return null;
221
318
  if (typeof value !== "string")
222
319
  throw new TypeError("UUID accepts a string value");
320
+ assertBoundedDomainString(value, "UUID value");
223
321
  const source = (value.startsWith(UUID_VALUE) ? value.slice(UUID_VALUE.length) : value).toLowerCase();
224
322
  if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(source)) {
225
323
  throw new TypeError(`Invalid UUID value: ${value}`);
@@ -231,6 +329,7 @@ export function timeDomainValue(value) {
231
329
  return null;
232
330
  if (typeof value !== "string")
233
331
  throw new TypeError("TIME accepts a string value");
332
+ assertBoundedDomainString(value, "TIME value");
234
333
  const source = value.startsWith(TIME_VALUE) ? value.slice(TIME_VALUE.length) : value;
235
334
  const match = /^(\d{2}):(\d{2})(?::(\d{2})(\.\d{1,6})?)?$/.exec(source);
236
335
  const hours = match?.[1];
@@ -248,10 +347,26 @@ export function timeDomainValue(value) {
248
347
  export function intervalDomainValue(value) {
249
348
  if (value === null || value === undefined)
250
349
  return null;
251
- if (typeof value === "string" && value.startsWith(INTERVAL_VALUE))
252
- return value;
253
350
  if (typeof value !== "string")
254
351
  throw new TypeError("INTERVAL accepts a string value");
352
+ assertBoundedDomainString(value, "INTERVAL value");
353
+ if (value.startsWith(INTERVAL_VALUE)) {
354
+ const source = value.slice(INTERVAL_VALUE.length);
355
+ let decoded;
356
+ try {
357
+ decoded = JSON.parse(source);
358
+ }
359
+ catch {
360
+ throw new TypeError("Invalid tagged INTERVAL value");
361
+ }
362
+ if (!Array.isArray(decoded) ||
363
+ decoded.length !== 3 ||
364
+ !decoded.every((part) => Number.isSafeInteger(part)) ||
365
+ JSON.stringify(decoded) !== source) {
366
+ throw new TypeError("Invalid tagged INTERVAL value");
367
+ }
368
+ return value;
369
+ }
255
370
  const source = value.trim();
256
371
  let months = 0;
257
372
  let days = 0;
@@ -288,23 +403,43 @@ export function intervalDomainValue(value) {
288
403
  !Number.isSafeInteger(microseconds)) {
289
404
  throw new TypeError(`Invalid INTERVAL value: ${value}`);
290
405
  }
291
- return INTERVAL_VALUE + JSON.stringify([months, days, microseconds]);
406
+ return boundedTaggedDomainValue(INTERVAL_VALUE, JSON.stringify([months, days, microseconds]), "INTERVAL value");
292
407
  }
293
408
  export function arrayDomainValue(values) {
294
- return ARRAY_VALUE + canonicalJson(values.map(externalSqlDomainValue));
409
+ return boundedTaggedDomainValue(ARRAY_VALUE, boundedJsonText(values.map(externalSqlDomainValue), true, "ARRAY value"), "ARRAY value");
295
410
  }
296
411
  export function enumDomainValue(value, name, values) {
297
412
  if (value === null || value === undefined)
298
413
  return null;
299
414
  if (typeof value !== "string")
300
415
  throw new TypeError(`Enum ${name} accepts a string value`);
301
- const source = value.startsWith(ENUM_VALUE)
302
- ? JSON.parse(value.slice(ENUM_VALUE.length))[2]
303
- : value;
416
+ assertBoundedDomainString(value, `Enum ${name} value`);
417
+ let source = value;
418
+ // Enum labels are arbitrary strings. Prefer an exact declared label before recognizing the
419
+ // internal tag namespace, so a legal label can begin with that prefix just like ordinary TEXT.
420
+ if (!values.includes(value) && value.startsWith(ENUM_VALUE)) {
421
+ const encoded = value.slice(ENUM_VALUE.length);
422
+ let decoded;
423
+ try {
424
+ decoded = JSON.parse(encoded);
425
+ }
426
+ catch {
427
+ throw new TypeError(`Invalid tagged enum ${name} value`);
428
+ }
429
+ if (!Array.isArray(decoded) ||
430
+ decoded.length !== 3 ||
431
+ decoded[0] !== name ||
432
+ !Number.isSafeInteger(decoded[1]) ||
433
+ typeof decoded[2] !== "string" ||
434
+ JSON.stringify(decoded) !== encoded) {
435
+ throw new TypeError(`Invalid tagged enum ${name} value`);
436
+ }
437
+ source = decoded[2];
438
+ }
304
439
  const index = values.indexOf(source);
305
440
  if (index === -1)
306
441
  throw new TypeError(`${source} is not a value of enum ${name}`);
307
- return ENUM_VALUE + JSON.stringify([name, index, source]);
442
+ return boundedTaggedDomainValue(ENUM_VALUE, boundedJsonText([name, index, source], false, `Enum ${name} value`), `Enum ${name} value`);
308
443
  }
309
444
  export function enumDomainCompare(left, right) {
310
445
  if (typeof left !== "string" ||
@@ -340,6 +475,7 @@ export function normalizeSqlDomainValue(domain, value) {
340
475
  if (typeof value !== "string")
341
476
  throw new TypeError("ARRAY columns accept JSON array text");
342
477
  const source = value.startsWith(ARRAY_VALUE) ? value.slice(ARRAY_VALUE.length) : value;
478
+ assertBoundedDomainString(source, "ARRAY value");
343
479
  let parsed;
344
480
  try {
345
481
  parsed = JSON.parse(source);
@@ -349,9 +485,12 @@ export function normalizeSqlDomainValue(domain, value) {
349
485
  }
350
486
  if (!Array.isArray(parsed))
351
487
  throw new TypeError("ARRAY value must be an array");
352
- return arrayDomainValue(parsed);
488
+ // JSON array text contains ordinary JSON strings. Do not interpret a string that happens to
489
+ // begin with Minnow's internal domain prefix as an already-tagged SQL scalar.
490
+ return boundedTaggedDomainValue(ARRAY_VALUE, boundedJsonText(parsed, true, "ARRAY value"), "ARRAY value");
353
491
  }
354
492
  const collators = new Map();
493
+ const MAX_COLLATOR_CACHE_ENTRIES = 64;
355
494
  export function collatedDomainValue(value, collation) {
356
495
  if (value === null || value === undefined)
357
496
  return null;
@@ -359,16 +498,20 @@ export function collatedDomainValue(value, collation) {
359
498
  if (typeof value !== "string" || typeof collation !== "string") {
360
499
  throw new TypeError("COLLATE requires a string value and collation name");
361
500
  }
501
+ assertBoundedDomainString(value, "COLLATE value");
502
+ assertBoundedDomainString(collation, "COLLATE name");
362
503
  const locale = collation === "POSIX" ? "C" : collation;
363
- if (locale !== "C" && !collators.has(locale)) {
364
- try {
365
- collators.set(locale, new Intl.Collator(locale, { usage: "sort", sensitivity: "variant" }));
366
- }
367
- catch {
368
- throw new TypeError(`Unsupported collation: ${collation}`);
369
- }
504
+ if (locale !== "C")
505
+ collatorFor(locale, collation);
506
+ return boundedTaggedDomainValue(COLLATION_VALUE, boundedJsonText([locale, value], false, "COLLATE value"), "COLLATE value");
507
+ }
508
+ function boundedTaggedDomainValue(prefix, value, label) {
509
+ // The prefix is an internal physical tag and is removed at the JavaScript boundary. Limit the
510
+ // user-visible payload, while the fixed tag adds only a few bounded bytes in storage.
511
+ if (value.length > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
512
+ throw new RangeError(`${label} exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
370
513
  }
371
- return COLLATION_VALUE + JSON.stringify([locale, value]);
514
+ return prefix + value;
372
515
  }
373
516
  export function collatedDomainCompare(left, right) {
374
517
  const decode = (value) => typeof value === "string" && value.startsWith(COLLATION_VALUE)
@@ -390,7 +533,29 @@ export function collatedDomainCompare(left, right) {
390
533
  const rightValue = b?.[1] ?? right;
391
534
  if (locale === "C")
392
535
  return leftValue === rightValue ? 0 : leftValue < rightValue ? -1 : 1;
393
- return (collators.get(locale) ?? new Intl.Collator(locale)).compare(leftValue, rightValue);
536
+ return collatorFor(locale, locale).compare(leftValue, rightValue);
537
+ }
538
+ function collatorFor(locale, displayName) {
539
+ const cached = collators.get(locale);
540
+ if (cached !== undefined) {
541
+ collators.delete(locale);
542
+ collators.set(locale, cached);
543
+ return cached;
544
+ }
545
+ let created;
546
+ try {
547
+ created = new Intl.Collator(locale, { usage: "sort", sensitivity: "variant" });
548
+ }
549
+ catch {
550
+ throw new TypeError(`Unsupported collation: ${displayName}`);
551
+ }
552
+ if (collators.size >= MAX_COLLATOR_CACHE_ENTRIES) {
553
+ const oldest = collators.keys().next().value;
554
+ if (oldest !== undefined)
555
+ collators.delete(oldest);
556
+ }
557
+ collators.set(locale, created);
558
+ return created;
394
559
  }
395
560
  export function externalSqlDomainValue(value) {
396
561
  if (typeof value !== "string")
@@ -418,4 +583,3 @@ export function externalSqlDomainValue(value) {
418
583
  export function isSqlDomainValue(value) {
419
584
  return typeof value === "string" && value.startsWith(PREFIX);
420
585
  }
421
- //# sourceMappingURL=sql-domains.js.map
@@ -1,5 +1,5 @@
1
1
  import type { Catalog } from "./catalog.js";
2
- import type { ExecuteResult, QueryOptions } from "./database.js";
2
+ import type { ExecuteResult, QueryCursorOptions, QueryOptions } from "./database.js";
3
3
  import type { QueryResult, QueryValue } from "./query.js";
4
4
  /**
5
5
  * The stable SQL boundary used by clients and third-party adapters.
@@ -10,10 +10,10 @@ import type { QueryResult, QueryValue } from "./query.js";
10
10
  */
11
11
  export interface MinnowSqlExecutor {
12
12
  query(sql: string, options?: QueryOptions): Promise<QueryResult>;
13
+ queryCursor(sql: string, options?: QueryCursorOptions): AsyncIterableIterator<QueryResult>;
13
14
  execute(sql: string, params?: readonly QueryValue[]): Promise<ExecuteResult>;
14
15
  }
15
16
  /** SQL execution plus the catalog surface schema-aware adapters need. */
16
17
  export interface MinnowSqlDriver extends MinnowSqlExecutor {
17
18
  introspect(): Promise<Catalog>;
18
19
  }
19
- //# sourceMappingURL=sql-driver.d.ts.map
@@ -1,2 +1 @@
1
1
  export {};
2
- //# sourceMappingURL=sql-driver.js.map
@@ -26,4 +26,3 @@ export declare function jsonValueOf(value: unknown): unknown;
26
26
  */
27
27
  export declare function jsonConstructor(name: "JSON_ARRAY" | "JSON_OBJECT", values: readonly unknown[]): string;
28
28
  export {};
29
- //# sourceMappingURL=sql-json.d.ts.map
@@ -1,7 +1,11 @@
1
1
  import { stringArgument } from "./sql-semantics.js";
2
2
  import { externalSqlDomainValue } from "./sql-domains.js";
3
+ import { MAX_CACHEABLE_TEXT_CHARACTERS, MAX_SQL_SCALAR_RESULT_CHARACTERS } from "./cache-limits.js";
3
4
  export function parseJsonPath(path, caller) {
4
5
  const text = stringArgument(caller, path).trim();
6
+ if (text.length > MAX_CACHEABLE_TEXT_CHARACTERS) {
7
+ throw new RangeError(`${caller} path exceeds ${String(MAX_CACHEABLE_TEXT_CHARACTERS)} characters`);
8
+ }
5
9
  if (!text.startsWith("$"))
6
10
  throw new TypeError(`${caller} paths start at $`);
7
11
  const steps = [];
@@ -42,8 +46,9 @@ export function parseJsonPath(path, caller) {
42
46
  export function jsonAtPath(document, path, caller) {
43
47
  const steps = parseJsonPath(path, caller);
44
48
  let current;
49
+ const source = boundedJsonDocument(document, caller);
45
50
  try {
46
- current = JSON.parse(stringArgument(caller, externalSqlDomainValue(document)));
51
+ current = JSON.parse(source);
47
52
  }
48
53
  catch {
49
54
  // A document that is not JSON selects nothing rather than failing the whole statement,
@@ -75,6 +80,9 @@ export function jsonIsValid(document, kind) {
75
80
  document = externalSqlDomainValue(document);
76
81
  if (typeof document !== "string")
77
82
  return false;
83
+ if (document.length > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
84
+ throw new RangeError(`JSON document exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
85
+ }
78
86
  let parsed;
79
87
  try {
80
88
  parsed = JSON.parse(document);
@@ -97,7 +105,7 @@ export function jsonIsValid(document, kind) {
97
105
  /** A SQL value as its JSON counterpart: datetimes serialize as ISO text, like every cast. */
98
106
  export function jsonValueOf(value) {
99
107
  const external = externalSqlDomainValue(value);
100
- return external instanceof Date ? external.toISOString() : external;
108
+ return external instanceof Date ? dateIsoString(external) : external;
101
109
  }
102
110
  /**
103
111
  * JSON_ARRAY(v, ...) and JSON_OBJECT(k, v, ...) (T811/T812). The omitted null-handling clause is
@@ -106,7 +114,8 @@ export function jsonValueOf(value) {
106
114
  */
107
115
  export function jsonConstructor(name, values) {
108
116
  if (name === "JSON_ARRAY") {
109
- return JSON.stringify(values.map((value) => jsonValueOf(value ?? null)));
117
+ const members = values.map((value) => boundedJsonValue(value ?? null, "JSON_ARRAY value"));
118
+ return joinBoundedJson("[", members, "]", "JSON_ARRAY result");
110
119
  }
111
120
  const members = [];
112
121
  for (let index = 0; index + 1 < values.length; index += 2) {
@@ -116,7 +125,7 @@ export function jsonConstructor(name, values) {
116
125
  }
117
126
  let key;
118
127
  if (rawKey instanceof Date)
119
- key = rawKey.toISOString();
128
+ key = dateIsoString(rawKey);
120
129
  else if (typeof rawKey === "string")
121
130
  key = rawKey;
122
131
  else if (typeof rawKey === "number" || typeof rawKey === "boolean")
@@ -127,8 +136,45 @@ export function jsonConstructor(name, values) {
127
136
  // Build JSON text directly. WITHOUT UNIQUE KEYS is the default, so duplicate names must be
128
137
  // preserved; assigning through a JavaScript object would collapse them and mishandle
129
138
  // special names such as "__proto__".
130
- members.push(`${JSON.stringify(key)}:${JSON.stringify(jsonValueOf(member ?? null))}`);
139
+ const encodedKey = boundedJsonValue(key, "JSON_OBJECT key");
140
+ const encodedValue = boundedJsonValue(member ?? null, "JSON_OBJECT value");
141
+ members.push(`${encodedKey}:${encodedValue}`);
142
+ }
143
+ return joinBoundedJson("{", members, "}", "JSON_OBJECT result");
144
+ }
145
+ function boundedJsonDocument(value, caller) {
146
+ const document = stringArgument(caller, externalSqlDomainValue(value));
147
+ if (document.length > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
148
+ throw new RangeError(`${caller} document exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
149
+ }
150
+ return document;
151
+ }
152
+ function boundedJsonValue(value, label) {
153
+ const normalized = jsonValueOf(value);
154
+ if (typeof normalized === "string" && normalized.length > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
155
+ throw new RangeError(`${label} exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
156
+ }
157
+ const encoded = JSON.stringify(normalized);
158
+ if (typeof encoded !== "string")
159
+ throw new TypeError(`${label} is not JSON serializable`);
160
+ if (encoded.length > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
161
+ throw new RangeError(`${label} exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
162
+ }
163
+ return encoded;
164
+ }
165
+ function joinBoundedJson(prefix, members, suffix, label) {
166
+ let length = prefix.length + suffix.length;
167
+ for (const member of members) {
168
+ length += member.length;
169
+ if (!Number.isSafeInteger(length) || length > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
170
+ throw new RangeError(`${label} exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
171
+ }
172
+ }
173
+ if (members.length > 1)
174
+ length += members.length - 1;
175
+ if (!Number.isSafeInteger(length) || length > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
176
+ throw new RangeError(`${label} exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
131
177
  }
132
- return `{${members.join(",")}}`;
178
+ return `${prefix}${members.join(",")}${suffix}`;
133
179
  }
134
- //# sourceMappingURL=sql-json.js.map
180
+ import { dateIsoString } from "../date-value.js";
@@ -18,10 +18,14 @@ export declare function encodeSqlEqualityValue(value: unknown): readonly unknown
18
18
  * integer and clamps to 0..30, with ties rounded away from zero.
19
19
  */
20
20
  export declare function roundSqlNumber(value: number, precision?: number): number;
21
- /** Compiles SQL LIKE (% = any run, _ = one codepoint) into a bounded cached RegExp. */
22
- export declare function compileLikePattern(pattern: string, caseInsensitive?: boolean, escape?: string): RegExp;
23
- /** PostgreSQL SIMILAR TO: SQL wildcards plus the SQL regular-expression operators, whole-string. */
24
- export declare function compileSimilarPattern(pattern: string, escape?: string): RegExp;
21
+ /** A whole-string SQL pattern matcher. Unlike RegExp, test never coerces its input. */
22
+ export interface SqlPatternMatcher {
23
+ test(value: string): boolean;
24
+ }
25
+ /** Compiles SQL LIKE without exposing input to the host regular-expression engine. */
26
+ export declare function compileLikePattern(pattern: string, caseInsensitive?: boolean, escape?: string): SqlPatternMatcher;
27
+ /** PostgreSQL SIMILAR TO compiled to a Thompson NFA with bounded deterministic work. */
28
+ export declare function compileSimilarPattern(pattern: string, escape?: string): SqlPatternMatcher;
25
29
  /** Defines an own enumerable result-column property, including the special name `__proto__`. */
26
30
  export declare function defineSqlResultProperty(target: Record<string, unknown>, name: string, value: unknown): void;
27
31
  /**
@@ -30,4 +34,3 @@ export declare function defineSqlResultProperty(target: Record<string, unknown>,
30
34
  * a silent coercion here would make `LENGTH(42)` answer instead of failing.
31
35
  */
32
36
  export declare function stringArgument(name: string, value: unknown): string;
33
- //# sourceMappingURL=sql-semantics.d.ts.map