@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
@@ -0,0 +1,434 @@
1
+ export class SqlLogicParseError extends Error {
2
+ location;
3
+ constructor(message, location) {
4
+ super(`${location.file}:${String(location.line)}: ${message}`);
5
+ this.location = location;
6
+ this.name = "SqlLogicParseError";
7
+ }
8
+ }
9
+ export class SqlLogicFailure extends Error {
10
+ location;
11
+ sql;
12
+ constructor(message, location, sql, options) {
13
+ super(`${location.file}:${String(location.line)}: ${message}${sql === undefined ? "" : `\n${sql}`}`, options);
14
+ this.location = location;
15
+ this.sql = sql;
16
+ this.name = "SqlLogicFailure";
17
+ }
18
+ }
19
+ /** Parses the original SQLite SQLLogicTest format, rejecting unknown extensions. */
20
+ export function parseSqlLogicTest(source, file = "<memory>") {
21
+ const groups = splitRecordGroups(source);
22
+ return groups.map((group) => parseRecord(group, file));
23
+ }
24
+ /**
25
+ * Parses a line source one record at a time. The full upstream corpus contains very large files;
26
+ * this form bounds parser memory to the largest individual record instead of the largest file.
27
+ */
28
+ export async function* parseSqlLogicTestLines(lines, file = "<stream>") {
29
+ let current = [];
30
+ let lineNumber = 0;
31
+ const finish = () => {
32
+ if (current.length === 0)
33
+ return undefined;
34
+ const record = parseRecord({ lines: current }, file);
35
+ current = [];
36
+ return record;
37
+ };
38
+ for await (const original of lines) {
39
+ lineNumber++;
40
+ const text = original.endsWith("\r") ? original.slice(0, -1) : original;
41
+ if (text.startsWith("#"))
42
+ continue;
43
+ if (text.trim().length === 0) {
44
+ const record = finish();
45
+ if (record !== undefined)
46
+ yield record;
47
+ continue;
48
+ }
49
+ current.push({ text, number: lineNumber });
50
+ }
51
+ const record = finish();
52
+ if (record !== undefined)
53
+ yield record;
54
+ }
55
+ function splitRecordGroups(source) {
56
+ const lines = source.replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n");
57
+ const groups = [];
58
+ let current = [];
59
+ const finish = () => {
60
+ if (current.length > 0)
61
+ groups.push({ lines: current });
62
+ current = [];
63
+ };
64
+ for (const [index, text] of lines.entries()) {
65
+ if (text.startsWith("#"))
66
+ continue;
67
+ if (text.trim().length === 0) {
68
+ finish();
69
+ continue;
70
+ }
71
+ current.push({ text, number: index + 1 });
72
+ }
73
+ finish();
74
+ return groups;
75
+ }
76
+ function parseRecord(group, file) {
77
+ let index = 0;
78
+ const conditions = [];
79
+ while (index < group.lines.length) {
80
+ const line = requireLine(group, index, file);
81
+ const tokens = tokenizeDirective(line.text);
82
+ if (tokens[0] !== "skipif" && tokens[0] !== "onlyif")
83
+ break;
84
+ if (tokens.length !== 2)
85
+ fail("conditional must name exactly one engine", file, line.number);
86
+ conditions.push({
87
+ kind: tokens[0],
88
+ engine: tokens[1] ?? "",
89
+ location: { file, line: line.number },
90
+ });
91
+ index++;
92
+ }
93
+ const header = requireLine(group, index, file);
94
+ const tokens = tokenizeDirective(header.text);
95
+ const location = { file, line: header.number };
96
+ if (tokens[0] === "statement") {
97
+ if (tokens.length !== 2 || (tokens[1] !== "ok" && tokens[1] !== "error")) {
98
+ fail("statement must be 'statement ok' or 'statement error'", file, header.number);
99
+ }
100
+ const sql = sqlLines(group.lines.slice(index + 1));
101
+ if (sql.length === 0)
102
+ fail("statement has no SQL", file, header.number);
103
+ return {
104
+ kind: "statement",
105
+ expectation: tokens[1],
106
+ sql,
107
+ conditions,
108
+ location,
109
+ };
110
+ }
111
+ if (tokens[0] === "query") {
112
+ if (tokens.length < 2 || tokens.length > 4) {
113
+ fail("query must be 'query <types> [sort-mode] [label]'", file, header.number);
114
+ }
115
+ const typeString = tokens[1] ?? "";
116
+ const types = [];
117
+ for (const type of typeString)
118
+ types.push(type);
119
+ if (types.length === 0 || types.some((type) => type !== "I" && type !== "R" && type !== "T")) {
120
+ fail("query type string must contain only I, R, and T", file, header.number);
121
+ }
122
+ const sortMode = tokens[2] ?? "nosort";
123
+ if (sortMode !== "nosort" && sortMode !== "rowsort" && sortMode !== "valuesort") {
124
+ fail(`unknown query sort mode '${sortMode}'`, file, header.number);
125
+ }
126
+ const body = group.lines.slice(index + 1);
127
+ const separator = body.findIndex((line) => line.text === "----");
128
+ const queryLines = separator < 0 ? body : body.slice(0, separator);
129
+ const sql = sqlLines(queryLines);
130
+ if (sql.length === 0)
131
+ fail("query has no SQL", file, header.number);
132
+ const expectedLines = separator < 0 ? [] : body.slice(separator + 1).map((line) => line.text);
133
+ return {
134
+ kind: "query",
135
+ types: types,
136
+ sortMode,
137
+ ...(tokens[3] === undefined ? {} : { label: tokens[3] }),
138
+ sql,
139
+ expected: parseExpected(expectedLines, file, header.number),
140
+ conditions,
141
+ location,
142
+ };
143
+ }
144
+ if (conditions.length > 0) {
145
+ fail("conditionals may prefix only a statement or query", file, header.number);
146
+ }
147
+ if (tokens[0] === "hash-threshold") {
148
+ if (tokens.length !== 2 || !isNonNegativeInteger(tokens[1])) {
149
+ fail("hash-threshold must be a non-negative whole number", file, header.number);
150
+ }
151
+ return { kind: "hash-threshold", valueCount: Number(tokens[1]), location };
152
+ }
153
+ if (tokens[0] === "halt") {
154
+ if (tokens.length !== 1 || group.lines.length !== 1) {
155
+ fail("halt does not accept arguments", file, header.number);
156
+ }
157
+ return { kind: "halt", location };
158
+ }
159
+ fail(`unknown SQLLogicTest directive '${tokens[0] ?? ""}'`, file, header.number);
160
+ }
161
+ function parseExpected(lines, file, line) {
162
+ if (lines.length !== 1)
163
+ return { kind: "values", values: lines };
164
+ const match = /^(\d+) values hashing to ([0-9a-fA-F]{32})$/.exec(lines[0] ?? "");
165
+ if (match === null)
166
+ return { kind: "values", values: lines };
167
+ const valueCount = Number(match[1]);
168
+ if (!Number.isSafeInteger(valueCount))
169
+ fail("hashed value count is too large", file, line);
170
+ return { kind: "hash", valueCount, hash: (match[2] ?? "").toLowerCase() };
171
+ }
172
+ function tokenizeDirective(line) {
173
+ return line.trim().split(/\s+/u);
174
+ }
175
+ function sqlLines(lines) {
176
+ return lines.map((line) => line.text).join("\n");
177
+ }
178
+ function requireLine(group, index, file) {
179
+ const line = group.lines[index];
180
+ if (line === undefined) {
181
+ const last = group.lines.at(-1)?.number ?? 1;
182
+ fail("conditional is missing its statement or query", file, last);
183
+ }
184
+ return line;
185
+ }
186
+ function isNonNegativeInteger(value) {
187
+ return value !== undefined && /^\d+$/u.test(value) && Number.isSafeInteger(Number(value));
188
+ }
189
+ function fail(message, file, line) {
190
+ throw new SqlLogicParseError(message, { file, line });
191
+ }
192
+ /** Runs already-parsed records against one empty database connection. */
193
+ export async function runSqlLogicTest(records, database, options = {}) {
194
+ let statements = 0;
195
+ let queries = 0;
196
+ let values = 0;
197
+ let skipped = 0;
198
+ let hashThreshold = 8;
199
+ let halted = false;
200
+ const labels = new Map();
201
+ try {
202
+ for await (const record of records) {
203
+ if (record.kind === "hash-threshold") {
204
+ hashThreshold = record.valueCount;
205
+ continue;
206
+ }
207
+ if (record.kind === "halt") {
208
+ halted = true;
209
+ break;
210
+ }
211
+ if (shouldSkip(record.conditions, database.engineName)) {
212
+ skipped++;
213
+ if (record.kind === "query" && record.label !== undefined) {
214
+ rememberExpectedLabel(record, labels);
215
+ }
216
+ continue;
217
+ }
218
+ try {
219
+ if (record.kind === "statement") {
220
+ statements++;
221
+ await runStatement(record, database);
222
+ continue;
223
+ }
224
+ queries++;
225
+ const actual = await runQuery(record, database);
226
+ values += actual.length;
227
+ compareExpected(record, actual, hashThreshold);
228
+ rememberActualLabel(record, actual, labels);
229
+ }
230
+ catch (error) {
231
+ if (error instanceof SqlLogicFailure && options.onFailure?.(error, record) === "continue") {
232
+ continue;
233
+ }
234
+ throw error;
235
+ }
236
+ }
237
+ }
238
+ finally {
239
+ await database.close();
240
+ }
241
+ return { files: 1, statements, queries, values, skipped, halted, hashThreshold };
242
+ }
243
+ function shouldSkip(conditions, engineName) {
244
+ const engine = engineName.toLowerCase();
245
+ return conditions.some((condition) => {
246
+ const matches = condition.engine.toLowerCase() === engine;
247
+ return condition.kind === "skipif" ? matches : !matches;
248
+ });
249
+ }
250
+ async function runStatement(record, database) {
251
+ let failure;
252
+ try {
253
+ await database.execute(record.sql);
254
+ }
255
+ catch (error) {
256
+ failure = error;
257
+ }
258
+ if (record.expectation === "error") {
259
+ if (failure !== undefined)
260
+ return;
261
+ throw new SqlLogicFailure("statement succeeded; expected an error", record.location, record.sql);
262
+ }
263
+ if (failure !== undefined) {
264
+ throw new SqlLogicFailure("statement failed; expected success", record.location, record.sql, {
265
+ cause: failure,
266
+ });
267
+ }
268
+ }
269
+ async function runQuery(record, database) {
270
+ let result;
271
+ try {
272
+ result = await database.query(record.sql);
273
+ }
274
+ catch (cause) {
275
+ throw new SqlLogicFailure("query failed", record.location, record.sql, { cause });
276
+ }
277
+ if (result.columns.length !== record.types.length) {
278
+ throw new SqlLogicFailure(`wrong column count: expected ${String(record.types.length)}, received ${String(result.columns.length)}`, record.location, record.sql);
279
+ }
280
+ const rows = result.rows.map((row) => result.columns.map((column, index) => renderSqlLogicValue(row[column] ?? null, record.types[index])));
281
+ if (record.sortMode === "rowsort")
282
+ rows.sort(compareRows);
283
+ const flattened = rows.flat();
284
+ if (record.sortMode === "valuesort")
285
+ flattened.sort(compareText);
286
+ return flattened;
287
+ }
288
+ function compareRows(left, right) {
289
+ for (let index = 0; index < left.length; index++) {
290
+ const comparison = compareText(left[index] ?? "", right[index] ?? "");
291
+ if (comparison !== 0)
292
+ return comparison;
293
+ }
294
+ return 0;
295
+ }
296
+ function compareText(left, right) {
297
+ return left < right ? -1 : left > right ? 1 : 0;
298
+ }
299
+ /** Renders one value with the original SQLite SQLLogicTest adapter rules. */
300
+ export function renderSqlLogicValue(value, type) {
301
+ if (value === null)
302
+ return "NULL";
303
+ if (type === "I") {
304
+ const numeric = typeof value === "boolean" ? Number(value) : Number(value);
305
+ if (!Number.isFinite(numeric))
306
+ return "0";
307
+ return String(Math.trunc(numeric));
308
+ }
309
+ if (type === "R") {
310
+ const numeric = typeof value === "boolean" ? Number(value) : Number(value);
311
+ return Number.isFinite(numeric) ? numeric.toFixed(3) : "0.000";
312
+ }
313
+ const rendered = value instanceof Date ? value.toISOString() : String(value);
314
+ if (rendered.length === 0)
315
+ return "(empty)";
316
+ return rendered.replaceAll(/[^ -~]/gu, "@");
317
+ }
318
+ function compareExpected(record, actual, hashThreshold) {
319
+ const hashed = hashThreshold > 0 && actual.length > hashThreshold;
320
+ if (hashed) {
321
+ if (record.expected.kind !== "hash") {
322
+ throw new SqlLogicFailure(`result exceeds hash-threshold ${String(hashThreshold)} but the expected result is not hashed`, record.location, record.sql);
323
+ }
324
+ const hash = md5Hex(withTrailingNewlines(actual));
325
+ if (record.expected.valueCount !== actual.length || record.expected.hash !== hash) {
326
+ throw new SqlLogicFailure(`wrong result hash: expected ${String(record.expected.valueCount)} values hashing to ${record.expected.hash}, received ${String(actual.length)} values hashing to ${hash}`, record.location, record.sql);
327
+ }
328
+ return;
329
+ }
330
+ if (record.expected.kind === "hash") {
331
+ throw new SqlLogicFailure(`expected result is hashed but ${String(actual.length)} values do not exceed hash-threshold ${String(hashThreshold)}`, record.location, record.sql);
332
+ }
333
+ if (!equalValues(record.expected.values, actual)) {
334
+ const expected = describeValues(record.expected.values, hashThreshold);
335
+ const received = describeValues(actual, hashThreshold);
336
+ throw new SqlLogicFailure(`wrong result:\nexpected ${expected}\nreceived ${received}`, record.location, record.sql);
337
+ }
338
+ }
339
+ function equalValues(expected, actual) {
340
+ return (expected.length === actual.length && expected.every((value, index) => value === actual[index]));
341
+ }
342
+ function describeValues(values, hashThreshold) {
343
+ if (hashThreshold > 0 && values.length > hashThreshold) {
344
+ return `${String(values.length)} values hashing to ${md5Hex(withTrailingNewlines(values))}`;
345
+ }
346
+ return JSON.stringify(values);
347
+ }
348
+ function rememberExpectedLabel(record, labels) {
349
+ if (record.label === undefined)
350
+ return;
351
+ const hash = record.expected.kind === "hash"
352
+ ? record.expected.hash
353
+ : md5Hex(withTrailingNewlines(record.expected.values));
354
+ rememberLabel(record.label, hash, record.location, labels);
355
+ }
356
+ function rememberActualLabel(record, values, labels) {
357
+ if (record.label === undefined)
358
+ return;
359
+ rememberLabel(record.label, md5Hex(withTrailingNewlines(values)), record.location, labels);
360
+ }
361
+ function rememberLabel(label, hash, location, labels) {
362
+ const previous = labels.get(label);
363
+ if (previous !== undefined && previous.hash !== hash) {
364
+ throw new SqlLogicFailure(`labeled result '${label}' differs from ${previous.location.file}:${String(previous.location.line)}`, location);
365
+ }
366
+ labels.set(label, { hash, location });
367
+ }
368
+ function withTrailingNewlines(values) {
369
+ return values.map((value) => `${value}\n`).join("");
370
+ }
371
+ /** Small browser-safe MD5 implementation for compatibility with the original corpus hashes. */
372
+ export function md5Hex(input) {
373
+ const bytes = new TextEncoder().encode(input);
374
+ const paddedLength = Math.ceil((bytes.length + 9) / 64) * 64;
375
+ const padded = new Uint8Array(paddedLength);
376
+ padded.set(bytes);
377
+ padded[bytes.length] = 0x80;
378
+ const bitLength = BigInt(bytes.length) * 8n;
379
+ const view = new DataView(padded.buffer);
380
+ view.setUint32(paddedLength - 8, Number(bitLength & 0xffffffffn), true);
381
+ view.setUint32(paddedLength - 4, Number((bitLength >> 32n) & 0xffffffffn), true);
382
+ let a0 = 0x67452301;
383
+ let b0 = 0xefcdab89;
384
+ let c0 = 0x98badcfe;
385
+ let d0 = 0x10325476;
386
+ const words = new Uint32Array(16);
387
+ for (let offset = 0; offset < paddedLength; offset += 64) {
388
+ for (let index = 0; index < 16; index++)
389
+ words[index] = view.getUint32(offset + index * 4, true);
390
+ let a = a0;
391
+ let b = b0;
392
+ let c = c0;
393
+ let d = d0;
394
+ for (let index = 0; index < 64; index++) {
395
+ const { mix, word } = md5Round(index, b, c, d);
396
+ const rotated = rotateLeft((a + mix + (MD5_CONSTANTS[index] ?? 0) + (words[word] ?? 0)) >>> 0, MD5_SHIFTS[index]);
397
+ a = d;
398
+ d = c;
399
+ c = b;
400
+ b = (b + rotated) >>> 0;
401
+ }
402
+ a0 = (a0 + a) >>> 0;
403
+ b0 = (b0 + b) >>> 0;
404
+ c0 = (c0 + c) >>> 0;
405
+ d0 = (d0 + d) >>> 0;
406
+ }
407
+ return [a0, b0, c0, d0].map(littleEndianHex).join("");
408
+ }
409
+ function md5Round(index, b, c, d) {
410
+ if (index < 16)
411
+ return { mix: (b & c) | (~b & d), word: index };
412
+ if (index < 32)
413
+ return { mix: (d & b) | (~d & c), word: (5 * index + 1) % 16 };
414
+ if (index < 48)
415
+ return { mix: b ^ c ^ d, word: (3 * index + 5) % 16 };
416
+ return { mix: c ^ (b | ~d), word: (7 * index) % 16 };
417
+ }
418
+ function rotateLeft(value, shift) {
419
+ const bits = shift ?? 0;
420
+ return ((value << bits) | (value >>> (32 - bits))) >>> 0;
421
+ }
422
+ function littleEndianHex(value) {
423
+ let output = "";
424
+ for (let index = 0; index < 4; index++) {
425
+ output += ((value >>> (index * 8)) & 0xff).toString(16).padStart(2, "0");
426
+ }
427
+ return output;
428
+ }
429
+ const MD5_SHIFTS = [
430
+ 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14,
431
+ 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6,
432
+ 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
433
+ ];
434
+ const MD5_CONSTANTS = Array.from({ length: 64 }, (_, index) => Math.floor(Math.abs(Math.sin(index + 1)) * 2 ** 32));
@@ -1,8 +1,16 @@
1
- import { type BlockStore, type BlockWrite, type LeaseKind, type LeaseRecord, type ManifestSummary, type RowIdRange, type SegmentRecord, type TransactionRecord, type FtsChanges, type UniqueKeyChanges } from "../storage/index.js";
1
+ import { type BlockStore, type BlockWrite, type LeaseKind, type LeaseRecord, MAX_LEASE_TTL_MS, type ManifestSummary, type RowIdRange, type SegmentRecord, type TableRecord, type TransactionRecord, type FtsChanges, type UniqueKeyChanges } from "../storage/types.js";
2
2
  export interface TransactionManagerOptions {
3
3
  now?: () => Date;
4
4
  createId?: () => string;
5
+ /** Durable writer deadline; live transactions renew every third of this interval. */
6
+ transactionTtlMs?: number;
7
+ /** Test seam for deterministic collection of an abandoned transaction reference. */
8
+ createWeakRef?: (transaction: DatabaseTransaction) => {
9
+ deref(): DatabaseTransaction | undefined;
10
+ };
5
11
  }
12
+ export declare const DEFAULT_TRANSACTION_TTL_MS = 30000;
13
+ export { MAX_LEASE_TTL_MS };
6
14
  export interface RecoveryOptions {
7
15
  staleBefore: Date;
8
16
  removePendingBlocks?: boolean;
@@ -23,24 +31,47 @@ export interface OpenLeasedSnapshotOptions {
23
31
  kind?: LeaseKind;
24
32
  version?: number | null;
25
33
  }
34
+ export interface BeginDeferredOptions {
35
+ /**
36
+ * Pins the captured snapshot with a renewable durable reader lease until this transaction
37
+ * either persists its own durable owner or finishes. Enabled by default; tightly bounded
38
+ * internal statements that cannot idle may opt out to retain the one-write fast path.
39
+ */
40
+ durableSnapshot?: boolean;
41
+ }
26
42
  export declare class TransactionClosedError extends Error {
27
43
  readonly transactionId: string;
28
44
  readonly name = "TransactionClosedError";
29
45
  constructor(transactionId: string);
30
46
  }
47
+ /** Opaque staged-state marker used by SQL SAVEPOINT. */
48
+ export interface TransactionCheckpoint {
49
+ readonly transactionId: string;
50
+ readonly pendingBlockIds: readonly string[];
51
+ readonly pendingSegmentIds: readonly string[];
52
+ readonly uniqueKeyChanges: readonly UniqueKeyChanges[];
53
+ readonly ftsChanges: readonly FtsChanges[];
54
+ readonly compactionJobId: string | null;
55
+ readonly compactionSourceBlockIds: readonly string[];
56
+ readonly levelZeroSegmentLimits: ReadonlyArray<{
57
+ tableId: string;
58
+ limit: number;
59
+ }>;
60
+ readonly changedTableIds: readonly string[];
61
+ readonly logicallyUnchanged: boolean;
62
+ }
31
63
  export declare class Snapshot {
32
- #private;
33
64
  private readonly store;
34
65
  readonly version: number | null;
35
- constructor(store: BlockStore, version: number | null, blockIds: readonly string[]);
36
- listBlockIds(): string[];
37
- hasBlock(id: string): boolean;
66
+ constructor(store: BlockStore, version: number | null);
67
+ hasBlock(id: string): Promise<boolean>;
68
+ hasBlocks(ids: readonly string[]): Promise<boolean[]>;
38
69
  getBlock(id: string): Promise<Uint8Array | undefined>;
39
70
  }
40
71
  export declare class LeasedSnapshot extends Snapshot {
41
72
  #private;
42
73
  private readonly now;
43
- constructor(store: BlockStore, version: number | null, blockIds: readonly string[], record: LeaseRecord, now: () => Date);
74
+ constructor(store: BlockStore, version: number | null, record: LeaseRecord, now: () => Date);
44
75
  get leaseId(): string;
45
76
  get expiresAt(): Date;
46
77
  getBlock(id: string): Promise<Uint8Array | undefined>;
@@ -61,15 +92,44 @@ export declare class DatabaseTransaction {
61
92
  #private;
62
93
  private readonly store;
63
94
  private readonly now;
64
- constructor(store: BlockStore, record: TransactionRecord, now: () => Date, options?: {
95
+ private readonly ttlMs;
96
+ constructor(store: BlockStore, record: TransactionRecord, now: () => Date, ttlMs: number, createWeakRef: (transaction: DatabaseTransaction) => {
97
+ deref(): DatabaseTransaction | undefined;
98
+ }, options?: {
65
99
  persisted?: boolean;
100
+ initialCatalogProbe?: {
101
+ catalogEpoch: number;
102
+ manifestVersion: number | null;
103
+ schemaEpoch: number;
104
+ };
105
+ snapshotLease?: LeasedSnapshot;
66
106
  });
67
107
  get id(): string;
68
108
  get status(): TransactionRecord["status"];
69
109
  get snapshotVersion(): number | null;
70
110
  get pendingBlockIds(): string[];
71
111
  get pendingSegmentIds(): string[];
72
- get supersededBlockIds(): string[];
112
+ /** Process-local segments retained for a bounded single-shot commit. */
113
+ get deferredSegments(): SegmentRecord[];
114
+ get pendingTableNextRowId(): bigint | undefined;
115
+ get initialCatalogProbe(): {
116
+ catalogEpoch: number;
117
+ manifestVersion: number | null;
118
+ schemaEpoch: number;
119
+ } | undefined;
120
+ /** Exact local commit contribution; undefined until this transaction commits. */
121
+ get committedCatalogContribution(): {
122
+ transaction: TransactionRecord;
123
+ segments: SegmentRecord[];
124
+ initialCatalogProbe?: {
125
+ catalogEpoch: number;
126
+ manifestVersion: number | null;
127
+ schemaEpoch: number;
128
+ };
129
+ } | undefined;
130
+ get compactionSourceBlockIds(): string[];
131
+ /** Extends durable ownership; scope owners call this while user code is legitimately waiting. */
132
+ renew(): Promise<void>;
73
133
  /**
74
134
  * Counts every registration this transaction carries: staged blocks and segments, unique-key
75
135
  * entries, full-text entries, and supersessions. A caller that fails part-way through a
@@ -78,6 +138,12 @@ export declare class DatabaseTransaction {
78
138
  * since there is no statement-level rollback inside a transaction.
79
139
  */
80
140
  get stagedWorkCount(): number;
141
+ /** Captures all publish-relevant state; row-id/autoincrement reservations deliberately burn. */
142
+ checkpoint(): TransactionCheckpoint;
143
+ /** Conservative retained-size estimate used to refuse savepoint clones before allocating. */
144
+ checkpointRetainedBytes(): number;
145
+ /** Rewinds staged artifacts and every in-memory commit delta to one earlier checkpoint. */
146
+ rollbackTo(checkpoint: TransactionCheckpoint): Promise<void>;
81
147
  snapshot(): Promise<Snapshot>;
82
148
  getBlock(id: string): Promise<Uint8Array | undefined>;
83
149
  stageBlock(id: string, bytes: Uint8Array): Promise<void>;
@@ -110,6 +176,8 @@ export declare class DatabaseTransaction {
110
176
  stageSegment(record: SegmentRecord): Promise<void>;
111
177
  /** Reconciles a persisted segment whose journal update was interrupted. */
112
178
  stageExistingSegment(segmentId: string): Promise<void>;
179
+ /** Atomically adopts an exact unpublished output left by an aborted attempt of this job. */
180
+ adoptAbortedCompactionSegment(record: SegmentRecord, abortedOwner: TransactionRecord): Promise<void>;
113
181
  /**
114
182
  * Records that this commit changes a table's logical content without staging a segment for
115
183
  * it — dropping the table, for instance, whose only publish is the retirement of its blocks.
@@ -132,7 +200,14 @@ export declare class DatabaseTransaction {
132
200
  * and token totals summed — so a scope can insert into one FTS table any number of times.
133
201
  */
134
202
  setFtsChanges(changes: FtsChanges): void;
135
- supersedeBlocks(blockIds: readonly string[]): void;
203
+ /**
204
+ * Binds a physical retirement to one persisted compaction job. Stores revalidate the job,
205
+ * source segments, current manifest, and aliases atomically with publication; this in-memory
206
+ * declaration is intent, never authority to hide arbitrary blocks.
207
+ */
208
+ setCompactionIntent(compactionJobId: string, sourceBlockIds: readonly string[]): void;
209
+ /** Adds an adapter-enforced post-commit level-zero ceiling for one table. */
210
+ limitLevelZeroSegments(tableId: string, limit: number): void;
136
211
  commit(): Promise<ManifestSummary>;
137
212
  rebase(): Promise<Snapshot>;
138
213
  abort(): Promise<void>;
@@ -142,14 +217,16 @@ export declare class TransactionManager {
142
217
  private readonly store;
143
218
  constructor(store: BlockStore, options?: TransactionManagerOptions);
144
219
  begin(): Promise<DatabaseTransaction>;
220
+ /** Atomically reserves an invisible catalog table for a create-and-populate transaction. */
221
+ beginWithPendingTable(record: TableRecord, expectedCatalogEpoch: number): Promise<DatabaseTransaction>;
145
222
  /**
146
- * Begins a transaction without a storage write: the record is pinned at the current manifest
147
- * version (one read) and written only when something is staged through the two-step methods
148
- * or never, when the whole write goes through `stageArtifactsAndCommit` on a store with
149
- * the single-shot `writeTransaction`, or the transaction aborts untouched. For writes whose
150
- * artifacts need no prior reservation; `beginWithReservation` is the shape for the rest.
223
+ * Begins without a transaction record. By default a renewable reader lease durably pins the
224
+ * probed manifest until the first two-step stage hands ownership to a transaction record. The
225
+ * record and lease are both omitted only for internal, uninterrupted single-shot writes that
226
+ * opt out and publish through `writeTransaction`. For writes whose artifacts need no prior
227
+ * reservation; `beginWithReservation` is the shape for the rest.
151
228
  */
152
- beginDeferred(): Promise<DatabaseTransaction>;
229
+ beginDeferred(options?: BeginDeferredOptions): Promise<DatabaseTransaction>;
153
230
  /**
154
231
  * Begins a transaction at the current manifest version and optionally reserves row ids and
155
232
  * auto-increment values in the same step. Stores implementing the atomic `beginTransaction`
@@ -186,4 +263,3 @@ export declare class TransactionManager {
186
263
  removeExpiredLeases(at?: Date): Promise<string[]>;
187
264
  recover(options: RecoveryOptions): Promise<RecoveryReport>;
188
265
  }
189
- //# sourceMappingURL=index.d.ts.map