@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.
- package/ARCHITECTURE.md +412 -56
- package/README.md +600 -57
- package/docs/HOSTS.md +269 -0
- package/docs/JOBS-FORMAT.md +293 -45
- package/docs/LIVE-FORMAT.md +169 -20
- package/docs/MIGRATION-FORMAT.md +142 -17
- package/docs/MODEL-FORMAT.md +752 -64
- package/docs/REPLICATION-FORMAT.md +208 -0
- package/package.json +21 -7
- package/schemas/jaren-model.draft-07.schema.json +224 -162
- package/schemas/jaren-model.schema.json +224 -162
- package/schemas/jaren-replication-snapshot.draft-07.schema.json +83 -0
- package/schemas/jaren-replication-snapshot.schema.json +83 -0
- package/schemas/jaren-replication.draft-07.schema.json +82 -0
- package/schemas/jaren-replication.schema.json +82 -0
- package/src/algebra.js +227 -9
- package/src/backup.js +161 -0
- package/src/cancellation.js +48 -0
- package/src/capture.js +230 -47
- package/src/cli.js +165 -59
- package/src/cursor.js +417 -0
- package/src/dag-job.js +154 -21
- package/src/ddl.js +102 -8
- package/src/dialect.js +268 -113
- package/src/dialects/expression-read.js +158 -0
- package/src/dialects/postgres.js +618 -0
- package/src/dialects/rtree-ddl.js +129 -0
- package/src/dialects/sqlite.js +244 -11
- package/src/document-files.js +311 -0
- package/src/document-steps.js +422 -0
- package/src/documents.js +335 -0
- package/src/driver.js +448 -61
- package/src/drivers/bun.js +37 -1
- package/src/drivers/indexeddb-snapshot.js +149 -0
- package/src/drivers/node-pool.js +11 -0
- package/src/drivers/node-worker-endpoint.js +105 -0
- package/src/drivers/node-worker.js +204 -0
- package/src/drivers/node.js +41 -7
- package/src/drivers/postgres.js +331 -0
- package/src/drivers/wasm-oo1.js +97 -0
- package/src/drivers/wasm-session.js +67 -0
- package/src/drivers/wasm.js +17 -83
- package/src/drivers/worker-pool.js +183 -0
- package/src/drivers/worker-protocol.js +79 -0
- package/src/drivers/worker-queue.js +60 -0
- package/src/emit.js +339 -48
- package/src/entity.js +20 -22
- package/src/errors.js +430 -19
- package/src/expression.js +284 -0
- package/src/graph.js +64 -8
- package/src/index.js +48 -17
- package/src/introspect.js +583 -0
- package/src/jobs.js +843 -107
- package/src/json-bytes.js +58 -0
- package/src/live-join.js +250 -0
- package/src/live-nested.js +120 -0
- package/src/live.js +18 -4
- package/src/logical-rows.js +90 -0
- package/src/maintenance.js +175 -0
- package/src/migrate.js +248 -181
- package/src/model.js +68 -0
- package/src/plan.js +1119 -138
- package/src/pragmas.js +314 -0
- package/src/profile.js +151 -3
- package/src/query.js +1634 -323
- package/src/replication-format.js +115 -0
- package/src/replication.js +332 -0
- package/src/residual.js +17 -0
- package/src/series.js +12 -4
- package/src/store.js +1567 -273
- package/src/tracker.js +203 -29
- package/src/udf.js +88 -7
- package/types/index.d.ts +1158 -27
- package/types/node-pool.d.ts +28 -0
- package/types/node-worker.d.ts +54 -0
- package/types/node.d.ts +69 -2
- package/types/postgres.d.ts +46 -0
- package/types/typed.d.ts +27 -4
- 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
|
+
}
|