@jarenjs/db 0.56.0 → 0.67.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 (79) hide show
  1. package/ARCHITECTURE.md +412 -56
  2. package/README.md +600 -57
  3. package/docs/HOSTS.md +269 -0
  4. package/docs/JOBS-FORMAT.md +293 -45
  5. package/docs/LIVE-FORMAT.md +169 -20
  6. package/docs/MIGRATION-FORMAT.md +142 -17
  7. package/docs/MODEL-FORMAT.md +752 -64
  8. package/docs/REPLICATION-FORMAT.md +208 -0
  9. package/package.json +21 -7
  10. package/schemas/jaren-model.draft-07.schema.json +224 -162
  11. package/schemas/jaren-model.schema.json +224 -162
  12. package/schemas/jaren-replication-snapshot.draft-07.schema.json +83 -0
  13. package/schemas/jaren-replication-snapshot.schema.json +83 -0
  14. package/schemas/jaren-replication.draft-07.schema.json +82 -0
  15. package/schemas/jaren-replication.schema.json +82 -0
  16. package/src/algebra.js +227 -9
  17. package/src/backup.js +161 -0
  18. package/src/cancellation.js +48 -0
  19. package/src/capture.js +230 -47
  20. package/src/cli.js +165 -59
  21. package/src/cursor.js +417 -0
  22. package/src/dag-job.js +154 -21
  23. package/src/ddl.js +102 -8
  24. package/src/dialect.js +268 -113
  25. package/src/dialects/expression-read.js +158 -0
  26. package/src/dialects/postgres.js +618 -0
  27. package/src/dialects/rtree-ddl.js +129 -0
  28. package/src/dialects/sqlite.js +244 -11
  29. package/src/document-files.js +311 -0
  30. package/src/document-steps.js +422 -0
  31. package/src/documents.js +335 -0
  32. package/src/driver.js +448 -61
  33. package/src/drivers/bun.js +37 -1
  34. package/src/drivers/indexeddb-snapshot.js +149 -0
  35. package/src/drivers/node-pool.js +11 -0
  36. package/src/drivers/node-worker-endpoint.js +105 -0
  37. package/src/drivers/node-worker.js +204 -0
  38. package/src/drivers/node.js +41 -7
  39. package/src/drivers/postgres.js +331 -0
  40. package/src/drivers/wasm-oo1.js +97 -0
  41. package/src/drivers/wasm-session.js +67 -0
  42. package/src/drivers/wasm.js +17 -83
  43. package/src/drivers/worker-pool.js +183 -0
  44. package/src/drivers/worker-protocol.js +79 -0
  45. package/src/drivers/worker-queue.js +60 -0
  46. package/src/emit.js +339 -48
  47. package/src/entity.js +20 -22
  48. package/src/errors.js +430 -19
  49. package/src/expression.js +284 -0
  50. package/src/graph.js +64 -8
  51. package/src/index.js +48 -17
  52. package/src/introspect.js +583 -0
  53. package/src/jobs.js +843 -107
  54. package/src/json-bytes.js +58 -0
  55. package/src/live-join.js +250 -0
  56. package/src/live-nested.js +120 -0
  57. package/src/live.js +18 -4
  58. package/src/logical-rows.js +90 -0
  59. package/src/maintenance.js +175 -0
  60. package/src/migrate.js +248 -181
  61. package/src/model.js +68 -0
  62. package/src/plan.js +1119 -138
  63. package/src/pragmas.js +314 -0
  64. package/src/profile.js +151 -3
  65. package/src/query.js +1634 -323
  66. package/src/replication-format.js +115 -0
  67. package/src/replication.js +332 -0
  68. package/src/residual.js +17 -0
  69. package/src/series.js +12 -4
  70. package/src/store.js +1567 -273
  71. package/src/tracker.js +203 -29
  72. package/src/udf.js +88 -7
  73. package/types/index.d.ts +1158 -27
  74. package/types/node-pool.d.ts +28 -0
  75. package/types/node-worker.d.ts +54 -0
  76. package/types/node.d.ts +69 -2
  77. package/types/postgres.d.ts +46 -0
  78. package/types/typed.d.ts +27 -4
  79. package/types/wasm.d.ts +14 -0
@@ -0,0 +1,175 @@
1
+ //@ts-check
2
+ /**
3
+ * @file The maintenance surface: the four operations an operator runs
4
+ * on a production SQLite database — a WAL checkpoint, an integrity
5
+ * check, a foreign-key check and `PRAGMA optimize` — as typed store
6
+ * operations. Each runs under the store gate (so it never interleaves
7
+ * an in-flight write), returns SQLite's own answer as typed data, and
8
+ * is refused by code (`JD2077`) exactly where the capability report
9
+ * says it is unavailable: a driver whose binding does not declare it,
10
+ * or — for the two that write — a read-only store, on which the engine
11
+ * would otherwise answer a checkpoint with a silent no-op.
12
+ *
13
+ * Nothing here interprets the engine's numbers. A checkpoint answers
14
+ * the row `PRAGMA wal_checkpoint` returns, so a store that is not in
15
+ * WAL mode reports `-1` frames as the engine does; a second passive
16
+ * checkpoint reports the same frame counts as the first (the frames
17
+ * stay in the log until a writer restarts it) and a second `truncate`
18
+ * reports zeros; an integrity check answers `ok: true` for the single
19
+ * `ok` row and the engine's problem rows verbatim otherwise — corruption
20
+ * is the RESULT, never a throw. Only a driver failure throws (`JD2078`).
21
+ */
22
+
23
+ import { DbRuntimeError, wrapDriverError } from './errors.js';
24
+ import { chain, attempt } from './driver.js';
25
+ import { refuseCancelled } from './cancellation.js';
26
+
27
+ /** The checkpoint modes `PRAGMA wal_checkpoint` accepts, closed. */
28
+ export const CHECKPOINT_MODES = Object.freeze(['passive', 'full', 'restart', 'truncate']);
29
+
30
+ /** The operations, in the order the capability report lists them. */
31
+ export const MAINTENANCE_OPERATIONS = Object.freeze(
32
+ ['checkpoint', 'integrityCheck', 'foreignKeyCheck', 'optimize']);
33
+
34
+ /** The two operations that write, refused on a read-only store. */
35
+ const WRITES = new Set(['checkpoint', 'optimize']);
36
+
37
+ /**
38
+ * The driver failure wrap: an error that already carries a code is the
39
+ * error; the driver's own failure becomes `JD2078` with the original as
40
+ * `cause`.
41
+ * @param {string} operation
42
+ * @returns {(error: any) => Error}
43
+ */
44
+ const failed = (operation) => (error) => wrapDriverError(error,
45
+ { code: 'JD2078', reason: `the maintenance operation '${operation}' failed`, always: true });
46
+
47
+ /**
48
+ * The per-operation capability booleans of one store: the binding's
49
+ * declaration, and for the writing operations the store's read-only
50
+ * flag as well — the report says `false` exactly where a call is
51
+ * refused.
52
+ * @param {Readonly<Record<string, boolean>> | undefined} declared - the
53
+ * connection's `capabilities.maintenance`
54
+ * @param {boolean} readOnly
55
+ * @returns {Readonly<Record<string, boolean>>}
56
+ */
57
+ export function maintenanceCapabilities(declared, readOnly) {
58
+ /** @type {Record<string, boolean>} */
59
+ const out = {};
60
+ for (const operation of MAINTENANCE_OPERATIONS) {
61
+ out[operation] = declared?.[operation] === true && !(readOnly && WRITES.has(operation));
62
+ }
63
+ return Object.freeze(out);
64
+ }
65
+
66
+ /**
67
+ * Build the maintenance operations over a connection. The caller owns
68
+ * the gate: every operation here is value-or-promise and issues its
69
+ * statements wherever the connection routes them.
70
+ * Every operation takes `{ signal, deadline }` and checks them once,
71
+ * before the one statement it issues — the granularity the driver has
72
+ * (`JD2081` for an abort, `JD2075` for a passed deadline, on the clock
73
+ * the store was opened with).
74
+ * @param {{ connection: any, readOnly: boolean, now: () => number }} context
75
+ * @returns {{ capabilities: Readonly<Record<string, boolean>>,
76
+ * checkpoint: (options?: { mode?: string, signal?: AbortSignal, deadline?: number }) => any,
77
+ * integrityCheck: (options?: { limit?: number, signal?: AbortSignal, deadline?: number }) => any,
78
+ * foreignKeyCheck: (options?: { signal?: AbortSignal, deadline?: number }) => any,
79
+ * optimize: (options?: { signal?: AbortSignal, deadline?: number }) => any }}
80
+ */
81
+ export function createMaintenance({ connection, readOnly, now }) {
82
+ const dialect = connection.dialect;
83
+ const capabilities = maintenanceCapabilities(connection.capabilities.maintenance, readOnly);
84
+
85
+ /** The cancellation check before an operation's statement.
86
+ * @param {any} options @param {string} operation */
87
+ const callable = (options, operation) => refuseCancelled(options, now,
88
+ { abortCode: 'JD2081', aborted: `'${operation}' ran`, passed: `'${operation}' ran` });
89
+
90
+ /** @param {string} operation */
91
+ const require = (operation) => {
92
+ if (capabilities[operation] === true) return;
93
+ throw new DbRuntimeError('JD2077',
94
+ readOnly && WRITES.has(operation) && connection.capabilities.maintenance?.[operation] === true
95
+ ? `the maintenance operation '${operation}' is unavailable on a read-only store: it writes`
96
+ : `the maintenance operation '${operation}' is unavailable on this store: the driver's `
97
+ + 'binding does not declare it');
98
+ };
99
+
100
+ /** Run one pragma statement and answer its rows, driver failures wrapped.
101
+ * @param {string} operation @param {string} sql */
102
+ const rows = (operation, sql) => attempt(
103
+ () => chain(connection.prepare(sql), (statement) => statement.all([])),
104
+ failed(operation));
105
+
106
+ return {
107
+ capabilities,
108
+ /**
109
+ * `PRAGMA wal_checkpoint(<mode>)`: the engine's row, typed. `busy`
110
+ * is whether the checkpoint could not complete because a reader or
111
+ * writer held it; the frame counts are the engine's (`-1` when the
112
+ * database is not in WAL mode).
113
+ * @param {{ mode?: string, signal?: AbortSignal, deadline?: number }} [options]
114
+ */
115
+ checkpoint(options = undefined) {
116
+ const mode = options?.mode ?? 'passive';
117
+ if (typeof mode !== 'string' || !CHECKPOINT_MODES.includes(mode)) {
118
+ throw new TypeError(`checkpoint: mode is one of ${
119
+ CHECKPOINT_MODES.map((m) => `'${m}'`).join(', ')}, got ${JSON.stringify(mode)}`);
120
+ }
121
+ callable(options, 'checkpoint');
122
+ require('checkpoint');
123
+ return chain(rows('checkpoint', dialect.pragma.walCheckpoint(mode)), (answer) => {
124
+ const row = answer[0] ?? {};
125
+ return {
126
+ busy: Number(row.busy) === 1,
127
+ logFrames: Number(row.log ?? -1),
128
+ checkpointedFrames: Number(row.checkpointed ?? -1),
129
+ };
130
+ });
131
+ },
132
+ /**
133
+ * `PRAGMA integrity_check(<limit>)`: `ok` for the single `ok` row,
134
+ * otherwise the engine's problem rows verbatim. Corruption is the
135
+ * result, not a throw.
136
+ * @param {{ limit?: number, signal?: AbortSignal, deadline?: number }} [options]
137
+ */
138
+ integrityCheck(options = undefined) {
139
+ const limit = options?.limit;
140
+ if (limit !== undefined && (!Number.isSafeInteger(limit) || limit < 1)) {
141
+ throw new TypeError(`integrityCheck: limit is a positive integer, got ${JSON.stringify(limit)}`);
142
+ }
143
+ callable(options, 'integrityCheck');
144
+ require('integrityCheck');
145
+ return chain(rows('integrityCheck', dialect.pragma.integrityCheck(limit)), (answer) => {
146
+ const problems = answer.map((row) => String(Object.values(row)[0]));
147
+ const ok = problems.length === 1 && problems[0] === 'ok';
148
+ return { ok, problems: ok ? [] : problems };
149
+ });
150
+ },
151
+ /** `PRAGMA foreign_key_check`: every violating row, typed.
152
+ * @param {{ signal?: AbortSignal, deadline?: number }} [options] */
153
+ foreignKeyCheck(options = undefined) {
154
+ callable(options, 'foreignKeyCheck');
155
+ require('foreignKeyCheck');
156
+ return chain(rows('foreignKeyCheck', dialect.pragma.foreignKeyCheck()), (answer) => ({
157
+ ok: answer.length === 0,
158
+ violations: answer.map((row) => ({
159
+ table: String(row.table),
160
+ rowId: row.rowid === null || row.rowid === undefined ? null : Number(row.rowid),
161
+ parent: String(row.parent),
162
+ fkid: Number(row.fkid),
163
+ })),
164
+ }));
165
+ },
166
+ /** `PRAGMA optimize`: the engine answers no rows, so the honest
167
+ * result is that it ran — no invented statistics.
168
+ * @param {{ signal?: AbortSignal, deadline?: number }} [options] */
169
+ optimize(options = undefined) {
170
+ callable(options, 'optimize');
171
+ require('optimize');
172
+ return chain(rows('optimize', dialect.pragma.optimize()), () => ({ ran: true }));
173
+ },
174
+ };
175
+ }