@delali/sirannon-db 0.1.7 → 0.2.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 (72) hide show
  1. package/README.md +117 -969
  2. package/dist/backup-scheduler/index.d.ts +1 -1
  3. package/dist/backup-scheduler/index.mjs +2 -2
  4. package/dist/baseline-Br77Fnhb.d.ts +6 -0
  5. package/dist/{change-tracker-CbmaMO-N.d.ts → change-tracker-DKRVUC3l.d.ts} +5 -8
  6. package/dist/{chunk-DJLX6CAE.mjs → chunk-4ISB7XMA.mjs} +2 -1
  7. package/dist/chunk-5NOIGN5Y.mjs +18 -0
  8. package/dist/chunk-67M7KAH6.mjs +90 -0
  9. package/dist/{chunk-GEZUUIKV.mjs → chunk-D7LAYTKN.mjs} +5 -2
  10. package/dist/{chunk-4IGMIJQK.mjs → chunk-EBJXPQQO.mjs} +59 -20
  11. package/dist/{chunk-BNUTBHHH.mjs → chunk-FHWTZFI4.mjs} +1 -1
  12. package/dist/chunk-GVCNMPOS.mjs +130 -0
  13. package/dist/chunk-H237TXZW.mjs +1407 -0
  14. package/dist/{chunk-HHRMRFFR.mjs → chunk-H6PIVVDN.mjs} +353 -142
  15. package/dist/chunk-HR5CWTLC.mjs +661 -0
  16. package/dist/chunk-JU64Y7HM.mjs +299 -0
  17. package/dist/{chunk-CW6S3WL5.mjs → chunk-JZGINXTN.mjs} +7 -5
  18. package/dist/{chunk-CJLYFDP5.mjs → chunk-LFZ37BSX.mjs} +1 -1
  19. package/dist/chunk-LNY2VVHE.mjs +18 -0
  20. package/dist/chunk-NVQS53NT.mjs +665 -0
  21. package/dist/{chunk-H5AB6NIR.mjs → chunk-O7SLN3GI.mjs} +2 -15
  22. package/dist/chunk-OQVZBEBY.mjs +283 -0
  23. package/dist/{chunk-VLTICJOD.mjs → chunk-PIKHN33N.mjs} +1 -1
  24. package/dist/chunk-TJF5GZSV.mjs +187 -0
  25. package/dist/chunk-TUD5CJ76.mjs +126 -0
  26. package/dist/{chunk-YPYVQJ4C.mjs → chunk-UC3SCMIN.mjs} +9 -1
  27. package/dist/client/index.d.ts +216 -321
  28. package/dist/client/index.mjs +1192 -1342
  29. package/dist/client/topology.d.ts +61 -0
  30. package/dist/client/topology.mjs +682 -0
  31. package/dist/client-base-CLWmH5Ln.d.ts +234 -0
  32. package/dist/codegen/cli.d.ts +2 -0
  33. package/dist/codegen/cli.mjs +11 -0
  34. package/dist/codegen/index.d.ts +30 -0
  35. package/dist/codegen/index.mjs +5 -0
  36. package/dist/core/index.d.ts +37 -16
  37. package/dist/core/index.mjs +2322 -298
  38. package/dist/core/writer-worker.mjs +28 -6
  39. package/dist/{database-DuGp0Rtr.d.ts → database-BY0L5Q2n.d.ts} +59 -14
  40. package/dist/driver/better-sqlite3.d.ts +1 -1
  41. package/dist/driver/better-sqlite3.mjs +6 -6
  42. package/dist/driver/node.d.ts +1 -1
  43. package/dist/driver/node.mjs +6 -6
  44. package/dist/driver/wa-sqlite.d.ts +1 -136
  45. package/dist/{errors-5Nf5ZAEC.d.ts → errors-Bw5MdNCu.d.ts} +5 -1
  46. package/dist/file-migrations/index.d.ts +7 -5
  47. package/dist/file-migrations/index.mjs +42 -37
  48. package/dist/operation-registry-9DcvxcE5.d.ts +45 -0
  49. package/dist/primary-wins-DPAm2AKG.d.ts +61 -0
  50. package/dist/protocol-6KrSq2Hy.d.ts +66 -0
  51. package/dist/react/index.d.ts +20 -0
  52. package/dist/react/index.mjs +137 -0
  53. package/dist/replication/coordinator/etcd.d.ts +5 -3
  54. package/dist/replication/coordinator/etcd.mjs +554 -462
  55. package/dist/replication/index.d.ts +51 -105
  56. package/dist/replication/index.mjs +1415 -2061
  57. package/dist/server/index.d.ts +111 -87
  58. package/dist/server/index.mjs +1917 -489
  59. package/dist/server-options-1JHu8pid.d.ts +191 -0
  60. package/dist/{sirannon-4SspRvP5.d.ts → sirannon-DaQSyhbJ.d.ts} +7 -2
  61. package/dist/transport/grpc.d.ts +7 -4
  62. package/dist/transport/grpc.mjs +191 -97
  63. package/dist/transport/memory.d.ts +5 -134
  64. package/dist/transport/memory.mjs +3 -1
  65. package/dist/{types-BsjobKbl.d.ts → types-B7gmEsZW.d.ts} +8 -61
  66. package/dist/{types-BEu1I_9_.d.ts → types-BsVabqSI.d.ts} +1 -1
  67. package/dist/types-CL6piSnD.d.ts +43 -0
  68. package/dist/types-C_D8IhpO.d.ts +60 -0
  69. package/dist/{types-D_hQW1hr.d.ts → types-zhnRXrsb.d.ts} +55 -165
  70. package/package.json +44 -6
  71. package/dist/chunk-TKGHYWQ6.mjs +0 -35
  72. package/dist/types-D4p4UyDK.d.ts +0 -26
@@ -0,0 +1,299 @@
1
+ import { CDCError } from './chunk-UC3SCMIN.mjs';
2
+
3
+ // src/core/live/sql-tokens.ts
4
+ var WHITESPACE = /* @__PURE__ */ new Set([" ", " ", "\n", "\r", "\f", "\v"]);
5
+ function isWordStart(c) {
6
+ return c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c === "_";
7
+ }
8
+ function isWordPart(c) {
9
+ return isWordStart(c) || c >= "0" && c <= "9" || c === "$";
10
+ }
11
+ function isDigit(c) {
12
+ return c >= "0" && c <= "9";
13
+ }
14
+ function tokenizeSql(sql) {
15
+ const tokens = [];
16
+ const n = sql.length;
17
+ let i = 0;
18
+ let depth = 0;
19
+ while (i < n) {
20
+ const c = sql[i];
21
+ if (WHITESPACE.has(c)) {
22
+ i++;
23
+ continue;
24
+ }
25
+ if (c === "-" && sql[i + 1] === "-") {
26
+ i += 2;
27
+ while (i < n && sql[i] !== "\n") i++;
28
+ continue;
29
+ }
30
+ if (c === "/" && sql[i + 1] === "*") {
31
+ i += 2;
32
+ while (i < n && !(sql[i] === "*" && sql[i + 1] === "/")) i++;
33
+ i = i < n ? i + 2 : n;
34
+ continue;
35
+ }
36
+ if (c === "'") {
37
+ const end = skipQuoted(sql, i, "'");
38
+ tokens.push(token("string", sql.slice(i, end), i, end, depth, false));
39
+ i = end;
40
+ continue;
41
+ }
42
+ if (c === '"' || c === "`") {
43
+ const delimited = readDelimited(sql, i, c);
44
+ tokens.push(token("word", delimited.value, i, delimited.next, depth, true));
45
+ i = delimited.next;
46
+ continue;
47
+ }
48
+ if (c === "[") {
49
+ const close = sql.indexOf("]", i + 1);
50
+ const end = close === -1 ? n : close + 1;
51
+ tokens.push(token("word", sql.slice(i + 1, end - 1), i, end, depth, true));
52
+ i = end;
53
+ continue;
54
+ }
55
+ if (c === "(" || c === ")") {
56
+ const at = depth;
57
+ if (c === "(") depth++;
58
+ else if (depth > 0) depth--;
59
+ tokens.push(token("punct", c, i, i + 1, c === "(" ? at : depth, false));
60
+ i++;
61
+ continue;
62
+ }
63
+ if (c === "?" || c === ":" || c === "@" || c === "$") {
64
+ const param = readParam(sql, i);
65
+ if (param !== null) {
66
+ tokens.push(token("param", param.name, i, param.next, depth, false));
67
+ i = param.next;
68
+ continue;
69
+ }
70
+ }
71
+ if (isDigit(c) || c === "." && isDigit(sql[i + 1] ?? "")) {
72
+ let j = i;
73
+ while (j < n && (isDigit(sql[j]) || sql[j] === "." || sql[j] === "e" || sql[j] === "E" || sql[j] === "x")) {
74
+ if ((sql[j] === "e" || sql[j] === "E") && (sql[j + 1] === "+" || sql[j + 1] === "-")) j++;
75
+ j++;
76
+ }
77
+ tokens.push(token("number", sql.slice(i, j), i, j, depth, false));
78
+ i = j;
79
+ continue;
80
+ }
81
+ if (isWordStart(c)) {
82
+ let j = i + 1;
83
+ while (j < n && isWordPart(sql[j])) j++;
84
+ const word = sql.slice(i, j);
85
+ if ((word === "x" || word === "X") && sql[j] === "'") {
86
+ const end = skipQuoted(sql, j, "'");
87
+ tokens.push(token("string", sql.slice(i, end), i, end, depth, false));
88
+ i = end;
89
+ continue;
90
+ }
91
+ tokens.push(token("word", word, i, j, depth, false));
92
+ i = j;
93
+ continue;
94
+ }
95
+ tokens.push(token("punct", c, i, i + 1, depth, false));
96
+ i++;
97
+ }
98
+ return tokens;
99
+ }
100
+ function token(kind, value, start, end, depth, quoted) {
101
+ return { kind, value, lower: value.toLowerCase(), start, end, depth, quoted };
102
+ }
103
+ function readParam(sql, start) {
104
+ const marker = sql[start];
105
+ let i = start + 1;
106
+ const n = sql.length;
107
+ if (marker === "?") {
108
+ while (i < n && isDigit(sql[i])) i++;
109
+ return { name: sql.slice(start, i), next: i };
110
+ }
111
+ if (marker === ":" && sql[start + 1] === ":") return null;
112
+ while (i < n && isWordPart(sql[i])) i++;
113
+ if (i === start + 1) return null;
114
+ return { name: sql.slice(start, i), next: i };
115
+ }
116
+ function skipQuoted(sql, start, quote) {
117
+ const n = sql.length;
118
+ let i = start + 1;
119
+ while (i < n) {
120
+ if (sql[i] === quote) {
121
+ if (sql[i + 1] === quote) {
122
+ i += 2;
123
+ continue;
124
+ }
125
+ return i + 1;
126
+ }
127
+ i++;
128
+ }
129
+ return n;
130
+ }
131
+ function readDelimited(sql, start, quote) {
132
+ const n = sql.length;
133
+ let i = start + 1;
134
+ let value = "";
135
+ while (i < n) {
136
+ if (sql[i] === quote) {
137
+ if (sql[i + 1] === quote) {
138
+ value += quote;
139
+ i += 2;
140
+ continue;
141
+ }
142
+ return { value, next: i + 1 };
143
+ }
144
+ value += sql[i];
145
+ i++;
146
+ }
147
+ return { value, next: n };
148
+ }
149
+ function parameterName(token2) {
150
+ if (token2.kind !== "param") return null;
151
+ if (token2.value.startsWith("?")) return null;
152
+ return token2.value.slice(1);
153
+ }
154
+
155
+ // src/core/live/statement-clauses.ts
156
+ function unsupported(sql, reason) {
157
+ return new CDCError(`Cannot open a live query for '${sql}': ${reason}`);
158
+ }
159
+ function findClauses(sql, body) {
160
+ let fromIndex = -1;
161
+ let whereIndex = -1;
162
+ let orderIndex = -1;
163
+ let limitIndex = -1;
164
+ for (let i = 1; i < body.length; i++) {
165
+ const token2 = body[i];
166
+ if (token2.depth !== 0 || token2.kind !== "word" || token2.quoted) continue;
167
+ if (token2.lower === "from" && fromIndex === -1) fromIndex = i;
168
+ else if (token2.lower === "where" && whereIndex === -1) whereIndex = i;
169
+ else if (token2.lower === "order" && body[i + 1]?.lower === "by" && orderIndex === -1) orderIndex = i;
170
+ else if (token2.lower === "limit" && limitIndex === -1) limitIndex = i;
171
+ }
172
+ if (fromIndex === -1) throw unsupported(sql, "a live query reads from a table");
173
+ const boundaryAfter = (index) => {
174
+ for (const candidate of [whereIndex, orderIndex, limitIndex]) {
175
+ if (candidate > index) return body[candidate].start;
176
+ }
177
+ return body[body.length - 1].end;
178
+ };
179
+ const statementEnd = body[body.length - 1].end;
180
+ const tailIndex = orderIndex !== -1 ? orderIndex : limitIndex;
181
+ return {
182
+ tailStart: tailIndex === -1 ? statementEnd : body[tailIndex].start,
183
+ statementEnd,
184
+ selectStart: body[1] === void 0 ? body[0].end : body[1].start,
185
+ fromKeyword: body[fromIndex].start,
186
+ fromStart: body[fromIndex].end,
187
+ fromEnd: boundaryAfter(fromIndex),
188
+ whereStart: whereIndex === -1 ? null : body[whereIndex].end,
189
+ whereEnd: whereIndex === -1 ? null : boundaryAfter(whereIndex),
190
+ orderStart: orderIndex === -1 ? null : body[orderIndex + 1].end,
191
+ orderEnd: orderIndex === -1 ? null : boundaryAfter(orderIndex),
192
+ limitStart: limitIndex === -1 ? null : limitIndex
193
+ };
194
+ }
195
+ function readSource(sql, body, clauses) {
196
+ const source = body.filter((token2) => token2.start >= clauses.fromStart && token2.end <= clauses.fromEnd);
197
+ if (source.length === 0 || source[0].kind !== "word") {
198
+ throw unsupported(sql, "a live query names one table after FROM");
199
+ }
200
+ if (source.some((token2) => token2.kind === "punct" && (token2.value === "," || token2.value === "("))) {
201
+ throw unsupported(sql, "a live query reads one table, so a join or a subquery in FROM is not maintainable");
202
+ }
203
+ const table = source[0].value;
204
+ const rest = source.slice(1);
205
+ if (rest.length === 0) return { table, alias: null };
206
+ const labelled = rest[0].lower === "as" && !rest[0].quoted;
207
+ const aliasToken = labelled ? rest[1] : rest[0];
208
+ if (rest.length > (labelled ? 2 : 1) || aliasToken === void 0 || aliasToken.kind !== "word") {
209
+ throw unsupported(sql, "a live query names one table after FROM, with an optional alias");
210
+ }
211
+ return { table, alias: aliasToken.value };
212
+ }
213
+ function readSelectItems(sql, body, clauses) {
214
+ const spans = splitTopLevel(body, clauses.selectStart, clauses.fromKeyword);
215
+ return spans.map((span) => readSelectItem(sql, span));
216
+ }
217
+ function readSelectItem(sql, span) {
218
+ const last = span[span.length - 1];
219
+ const star = span.some((token2) => token2.kind === "punct" && token2.value === "*");
220
+ if (span.length >= 3 && span[span.length - 2].lower === "as" && last.kind === "word") {
221
+ return { expression: sql.slice(span[0].start, span[span.length - 2].start).trim(), alias: last.value, star };
222
+ }
223
+ if (span.length === 1 && last.kind === "word") {
224
+ return { expression: sql.slice(span[0].start, last.end), alias: last.value, star: false };
225
+ }
226
+ if (span.length === 3 && span[1].value === "." && last.kind === "word" && !star) {
227
+ return { expression: sql.slice(span[0].start, last.end), alias: last.value, star: false };
228
+ }
229
+ return { expression: sql.slice(span[0].start, last.end), alias: null, star };
230
+ }
231
+ function readSortTerms(sql, body, clauses) {
232
+ const start = clauses.orderStart;
233
+ const end = clauses.orderEnd;
234
+ if (start === null || end === null) return [];
235
+ return splitTopLevel(body, start, end).map((span) => readSortTerm(sql, span));
236
+ }
237
+ function readSortTerm(sql, span) {
238
+ let end = span.length;
239
+ let nulls = null;
240
+ let direction = "asc";
241
+ if (end >= 2 && span[end - 2].lower === "nulls") {
242
+ const placement = span[end - 1].lower;
243
+ if (placement !== "first" && placement !== "last") {
244
+ throw unsupported(sql, "NULLS in an ORDER BY term is followed by FIRST or LAST");
245
+ }
246
+ nulls = placement;
247
+ end -= 2;
248
+ }
249
+ if (end >= 1 && (span[end - 1].lower === "asc" || span[end - 1].lower === "desc")) {
250
+ direction = span[end - 1].lower === "desc" ? "desc" : "asc";
251
+ end -= 1;
252
+ }
253
+ if (end === 0) throw unsupported(sql, "an ORDER BY term needs an expression");
254
+ const collation = end >= 2 && span[end - 2].lower === "collate" ? span[end - 1].value : null;
255
+ return {
256
+ expression: sql.slice(span[0].start, span[end - 1].end),
257
+ direction,
258
+ nulls: nulls ?? (direction === "asc" ? "first" : "last"),
259
+ collation
260
+ };
261
+ }
262
+ function readWindow(sql, body, clauses) {
263
+ if (clauses.limitStart === null) return { limit: null, offset: null };
264
+ const tail = body.slice(clauses.limitStart + 1);
265
+ const separator = tail.findIndex((token2) => token2.depth === 0 && (token2.value === "," || token2.lower === "offset"));
266
+ if (separator === -1) return { limit: readBound(sql, tail), offset: null };
267
+ const first = readBound(sql, tail.slice(0, separator));
268
+ const second = readBound(sql, tail.slice(separator + 1));
269
+ if (tail[separator].value === ",") return { limit: second, offset: first };
270
+ return { limit: first, offset: second };
271
+ }
272
+ function readBound(sql, span) {
273
+ const token2 = span.length === 1 ? span[0] : void 0;
274
+ if (token2?.kind === "number") {
275
+ const literal = Number(token2.value);
276
+ if (!Number.isInteger(literal) || literal < 0) {
277
+ throw unsupported(sql, "LIMIT and OFFSET in a live query take a non-negative whole number");
278
+ }
279
+ return { kind: "literal", literal, parameter: "", position: token2.start };
280
+ }
281
+ if (token2?.kind === "param") {
282
+ return { kind: "parameter", literal: 0, parameter: token2.value, position: token2.start };
283
+ }
284
+ throw unsupported(sql, "LIMIT and OFFSET in a live query take a number or a bound parameter");
285
+ }
286
+ function splitTopLevel(body, start, end) {
287
+ const spans = [[]];
288
+ for (const token2 of body) {
289
+ if (token2.start < start || token2.end > end) continue;
290
+ if (token2.depth === 0 && token2.kind === "punct" && token2.value === ",") {
291
+ spans.push([]);
292
+ continue;
293
+ }
294
+ spans[spans.length - 1].push(token2);
295
+ }
296
+ return spans.filter((span) => span.length > 0);
297
+ }
298
+
299
+ export { findClauses, parameterName, readSelectItems, readSortTerms, readSource, readWindow, tokenizeSql, unsupported };
@@ -1,5 +1,5 @@
1
- import { assertSqlAllowed, encodeWireRowsInPlace } from './chunk-GEZUUIKV.mjs';
2
- import { SirannonError, QueryError } from './chunk-YPYVQJ4C.mjs';
1
+ import { assertSqlAllowed, encodeWireRowsInPlace } from './chunk-D7LAYTKN.mjs';
2
+ import { SirannonError, QueryError } from './chunk-UC3SCMIN.mjs';
3
3
 
4
4
  // src/core/query-executor.ts
5
5
  var STATEMENT_CACHE_CAPACITY = 128;
@@ -69,8 +69,10 @@ async function queryOne(conn, sql, params) {
69
69
  throw asQueryError(err, sql);
70
70
  }
71
71
  }
72
- async function execute(conn, sql, params) {
73
- assertSqlAllowed(sql);
72
+ async function execute(conn, sql, params, trusted = false) {
73
+ if (!trusted) {
74
+ assertSqlAllowed(sql);
75
+ }
74
76
  try {
75
77
  const stmt = await getStatement(conn, sql);
76
78
  const result = await stmt.run(...bindParams(params));
@@ -102,7 +104,7 @@ async function runUnit(conn, unit) {
102
104
  const values = new Array(unit.statements.length);
103
105
  for (let i = 0; i < unit.statements.length; i++) {
104
106
  const statement = unit.statements[i];
105
- values[i] = await execute(conn, statement.sql, statement.params);
107
+ values[i] = await execute(conn, statement.sql, statement.params, statement.trusted === true);
106
108
  }
107
109
  return values;
108
110
  }
@@ -1,4 +1,4 @@
1
- import { SirannonError } from './chunk-YPYVQJ4C.mjs';
1
+ import { SirannonError } from './chunk-UC3SCMIN.mjs';
2
2
 
3
3
  // src/core/driver/synchronous.ts
4
4
  var DEFAULT_SYNCHRONOUS = "normal";
@@ -0,0 +1,18 @@
1
+ import { MigrationError } from './chunk-UC3SCMIN.mjs';
2
+
3
+ // src/core/migrations/lazy-down.ts
4
+ var LAZY_DOWN_SQL = /* @__PURE__ */ Symbol("sirannon.file-migration.lazy-down");
5
+
6
+ // src/core/migrations/filename.ts
7
+ var MIGRATION_FILENAME_PATTERN = /^(\d+)_(\w+)\.(up|down)\.sql$/;
8
+ function parseMigrationFilename(filename) {
9
+ const match = MIGRATION_FILENAME_PATTERN.exec(filename);
10
+ if (!match) return null;
11
+ const version = parseInt(match[1], 10);
12
+ if (!Number.isFinite(version) || version <= 0 || !Number.isSafeInteger(version)) {
13
+ throw new MigrationError(`Invalid migration version: ${match[1]}`, version, "MIGRATION_VALIDATION_ERROR");
14
+ }
15
+ return { version, name: match[2], direction: match[3] };
16
+ }
17
+
18
+ export { LAZY_DOWN_SQL, MIGRATION_FILENAME_PATTERN, parseMigrationFilename };