@jarenjs/db 0.56.0 → 0.66.1
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 +393 -56
- package/README.md +585 -53
- package/docs/HOSTS.md +269 -0
- package/docs/JOBS-FORMAT.md +293 -45
- package/docs/LIVE-FORMAT.md +122 -14
- package/docs/MIGRATION-FORMAT.md +142 -17
- package/docs/MODEL-FORMAT.md +744 -64
- package/package.json +21 -7
- package/schemas/jaren-model.draft-07.schema.json +224 -162
- package/schemas/jaren-model.schema.json +224 -162
- package/src/algebra.js +227 -9
- package/src/backup.js +161 -0
- package/src/cancellation.js +48 -0
- package/src/capture.js +218 -45
- package/src/cli.js +165 -59
- package/src/cursor.js +411 -0
- package/src/dag-job.js +154 -21
- package/src/ddl.js +102 -8
- package/src/dialect.js +267 -112
- 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 +243 -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 +422 -19
- package/src/expression.js +284 -0
- package/src/graph.js +64 -8
- package/src/index.js +46 -17
- package/src/introspect.js +583 -0
- package/src/jobs.js +843 -107
- package/src/json-bytes.js +58 -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/residual.js +17 -0
- package/src/series.js +12 -4
- package/src/store.js +1505 -264
- package/src/tracker.js +203 -29
- package/src/udf.js +88 -7
- package/types/index.d.ts +1097 -25
- 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 +25 -3
- package/types/wasm.d.ts +14 -0
package/docs/MIGRATION-FORMAT.md
CHANGED
|
@@ -166,8 +166,12 @@ store untouched.
|
|
|
166
166
|
|
|
167
167
|
The shadow runs over an empty data set; the real-data facts (the
|
|
168
168
|
widening check, key consistency, the assertions over real rows) run on
|
|
169
|
-
the real store inside its transaction.
|
|
170
|
-
|
|
169
|
+
the real store inside its transaction. Batched transforms and validation
|
|
170
|
+
include every row identity, including negative integer primary keys;
|
|
171
|
+
target-schema validation visits every declared entity, even after an
|
|
172
|
+
empty entity table.
|
|
173
|
+
The shadow registers the same functions as the real run:
|
|
174
|
+
`migrate(…, { registerFunctions })` runs on
|
|
171
175
|
the shadow, the real and the reference connections before any DDL
|
|
172
176
|
(§10), so a hand-created index over a registered deterministic
|
|
173
177
|
function neither fails the shadow nor is silently dropped by it.
|
|
@@ -204,26 +208,115 @@ hash of the `baseline` model when no migration has run.
|
|
|
204
208
|
an empty history, so a dry run may be pointed at a production
|
|
205
209
|
database and leave its file byte-identical. The API default is to
|
|
206
210
|
run; a CLI SHOULD default to the dry run.
|
|
207
|
-
- `migrationStatus` (and the CLI's `status`/`check`)
|
|
208
|
-
history table
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
211
|
+
- `migrationStatus` (and the CLI's `status`/`check`) write nothing
|
|
212
|
+
either: the history table is probed, never created, and an absent one
|
|
213
|
+
reads as an empty history, so a fresh file answers `applied: (none)`
|
|
214
|
+
and stays byte for byte what it was. The history table is created by
|
|
215
|
+
the real run alone, before its first migration is recorded.
|
|
212
216
|
- Each pending migration runs in ONE exclusive transaction
|
|
213
217
|
(`BEGIN IMMEDIATE` on SQLite — concurrent writers wait or time out
|
|
214
218
|
under the busy timeout) with a savepoint per step; any failure rolls
|
|
215
219
|
back the whole migration including its earlier steps. Where a driver
|
|
216
220
|
cannot open exclusively, the transaction still isolates; the busy
|
|
217
221
|
policy of MODEL-FORMAT §4 governs contention.
|
|
222
|
+
- A run is cancellable: `migrate(target, migrations, { signal,
|
|
223
|
+
deadline })` checks both BETWEEN migrations, between steps and
|
|
224
|
+
between the batches of a data step — never inside a statement, which
|
|
225
|
+
runs to its end — with the deadline read against `options.runtime`'s
|
|
226
|
+
clock. An abort is `JD2080` and a passed deadline `JD2075`; the
|
|
227
|
+
migration in flight rolls back whole (its savepoints, its history
|
|
228
|
+
row), the migrations already committed stand, and a rerun resumes
|
|
229
|
+
from the recorded position. The shadow replay is cancellable at the
|
|
230
|
+
same boundaries. `migrationStatus` refuses a call already cancelled
|
|
231
|
+
or past its deadline before it opens anything.
|
|
232
|
+
- **An assertion is classified before it runs, and the classification
|
|
233
|
+
decides what it costs.** One classifier answers for every host — a
|
|
234
|
+
Store, an array, a file — so they cannot disagree about the price:
|
|
235
|
+
|
|
236
|
+
| strategy | which assertions | what it costs |
|
|
237
|
+
|---|---|---|
|
|
238
|
+
| per-document | a FLWOR over `$[*]` whose `$where`/`$return` read only the binding | one keyset batch at a time; fails fast at the first batch that violates |
|
|
239
|
+
| fold | exactly one of `$count`, `$sum`, `$min`, `$max` over the root | one batch at a time; each batch is answered by the ENGINE and the partial answers combine |
|
|
240
|
+
| materialize | everything else (`$let`, `$distinct`, a nested `$for`, two aggregates) | every document at once, under `assertionBounds` |
|
|
241
|
+
|
|
242
|
+
A fold is sound because the operator is associative: the answer over a
|
|
243
|
+
collection is the combination of the answers over any partition of it.
|
|
244
|
+
Nothing reimplements an operator — each batch is evaluated by the same
|
|
245
|
+
compiled query the whole-collection path would use, and only the
|
|
246
|
+
COMBINE step is written here, so null handling, empty-sequence answers
|
|
247
|
+
and type coercions are the engine's. A suite runs every fold shape both
|
|
248
|
+
ways, over ten corpora and six partitions, and requires the value and
|
|
249
|
+
the verdict to be indistinguishable; a shape that cannot pass it is not
|
|
250
|
+
in the set.
|
|
251
|
+
|
|
252
|
+
**A materializing assertion is bounded.** `options.assertionBounds`
|
|
253
|
+
defaults to `{ maxRows: 100000, maxBytes: 67108864 }` and is crossed
|
|
254
|
+
BEFORE the excess is held — the walk stops at the row that would break
|
|
255
|
+
it, refusing `JD2007` (rows) or `JD2076` (bytes) and naming the two
|
|
256
|
+
assertion shapes that are answered in batches instead. `null` on either
|
|
257
|
+
member removes that bound, which a caller must ask for: an unbounded
|
|
258
|
+
read nobody declared is exactly what this classification removes. This
|
|
259
|
+
is a deliberate behavior change — a migration that used to read a very
|
|
260
|
+
large collection whole now refuses until its bound is raised or its
|
|
261
|
+
assertion is rewritten.
|
|
218
262
|
- JSLT steps walk the collection in bounded batches
|
|
219
263
|
(`options.batchSize`, default 500) ordered by row identity, report
|
|
220
|
-
progress through `options.onProgress
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
264
|
+
progress through `options.onProgress` (`{ migration, collection,
|
|
265
|
+
transformed | derived | asserted }`, one event per batch), and never
|
|
266
|
+
hold the whole collection in memory. A PER-DOCUMENT assertion — a
|
|
267
|
+
FLWOR over `$[*]` whose `$where` and `$return` read only the binding
|
|
268
|
+
— walks the same batches and fails fast at the first batch that
|
|
269
|
+
violates, because its answer over each batch is its answer over the
|
|
270
|
+
whole. A cross-document assertion (one that reads the root: `$count:
|
|
271
|
+
'$[*]'`, a `$let`, a `$distinct`, a nested `$for`) reads the whole
|
|
272
|
+
collection into one array — a stated cost; keep such assertions
|
|
273
|
+
early, before the data grows. A cross-document assertion that is one
|
|
274
|
+
associative aggregate no longer costs that read at all — see the
|
|
275
|
+
classification table above.
|
|
224
276
|
- A transform MUST NOT change a caller-keyed document's key member —
|
|
225
277
|
the key column would go stale; the run refuses (`JD0023`).
|
|
226
278
|
|
|
279
|
+
### 6.1 Running without a database
|
|
280
|
+
|
|
281
|
+
A migration's `jslt` and `query` steps act on DOCUMENTS, so they do not
|
|
282
|
+
need tables. Two surfaces run them against documents a caller already
|
|
283
|
+
holds, sharing one implementation of what a step means with the Store —
|
|
284
|
+
the same transform rule, the same key rule, the same classification of
|
|
285
|
+
an assertion, the same refusals in the same words.
|
|
286
|
+
|
|
287
|
+
- `migrateDocuments({ collection: [...] }, migrations, options)` answers
|
|
288
|
+
`{ documents, report }`. The source is REWINDABLE, so every step runs
|
|
289
|
+
over the whole collection before the next begins, exactly as a Store
|
|
290
|
+
runs it. This is what makes its answer — and its refusal, on the same
|
|
291
|
+
step — identical to the Store's for the same documents.
|
|
292
|
+
- `streamDocuments({ collection: iterable }, migrations, { write })`
|
|
293
|
+
walks a source that can be read only once, writing each document out
|
|
294
|
+
as it finishes. The input is consumed exactly once and nothing beyond
|
|
295
|
+
one batch is held, so a collection larger than memory still migrates.
|
|
296
|
+
|
|
297
|
+
Both refuse, BEFORE asking for the first document, any step this host
|
|
298
|
+
cannot honour (`JD0023`):
|
|
299
|
+
|
|
300
|
+
| Step kind | Without a database |
|
|
301
|
+
|---|---|
|
|
302
|
+
| `jslt`, `query` | runs |
|
|
303
|
+
| `ddl`, `sql`, `rebuild`, `derive` | refused by name — no tables to change |
|
|
304
|
+
|
|
305
|
+
A step naming a collection the caller did not supply is refused the same
|
|
306
|
+
way. Nothing is half-applied: a runner without a transaction cannot take
|
|
307
|
+
a partial write back, so the whole refusal happens before the first read.
|
|
308
|
+
|
|
309
|
+
Two limits are the single pass's, and are stated rather than hidden:
|
|
310
|
+
|
|
311
|
+
- A CROSS-DOCUMENT assertion needs every document at once, which one
|
|
312
|
+
pass does not hold. `streamDocuments` refuses it by name; run that
|
|
313
|
+
collection through `migrateDocuments`, whose source it can re-read.
|
|
314
|
+
- When two different steps would each refuse, `migrateDocuments` and the
|
|
315
|
+
Store name the EARLIER step, because each step finishes before the
|
|
316
|
+
next begins. `streamDocuments` carries a batch through every step, so
|
|
317
|
+
it can name the later one. Both refuse, with the same code and the
|
|
318
|
+
same words; only which step is named can differ.
|
|
319
|
+
|
|
227
320
|
## 7. Non-goals
|
|
228
321
|
|
|
229
322
|
- **Down migrations are not shipped in 0.1.** A JSLT transform is not
|
|
@@ -247,6 +340,7 @@ hash of the `baseline` model when no migration has run.
|
|
|
247
340
|
| `JD0021` | the migration is missing a required data transform |
|
|
248
341
|
| `JD0022` | an applied migration disagrees with the history record |
|
|
249
342
|
| `JD0023` | a migration step failed |
|
|
343
|
+
| `JD0024` | a document source or target could not be read or written |
|
|
250
344
|
|
|
251
345
|
These live in the same runtime `DB_CODES` table as the storage codes
|
|
252
346
|
(MODEL-FORMAT §7); the union of both documents is proven in sync with
|
|
@@ -351,13 +445,17 @@ jaren-db status --model <model> --store <db> --baseline <model> [--migrations
|
|
|
351
445
|
jaren-db apply --store <db> --baseline <model> --migrations <dir> [--model <m>] [--dry-run] [--yes]
|
|
352
446
|
jaren-db check --model <model> --store <db> --baseline <model> [--migrations <dir>] [--snapshot <file>]
|
|
353
447
|
jaren-db shape --model <model>
|
|
448
|
+
jaren-db documents --migrations <dir> --in <file|-> (--out <file|-> | --in-place --yes | --check)
|
|
449
|
+
[--format json|jsonl] [--out-format json|jsonl] [--collection <name>]
|
|
450
|
+
[--batch-size <n>]
|
|
354
451
|
```
|
|
355
452
|
|
|
356
453
|
- **A model or a migration is a `.json` file or a MODULE.** `--model`,
|
|
357
454
|
`--from`, `--to` and `--baseline` accept a `.json` file or a module
|
|
358
455
|
(`.js`, `.mjs`, `.cjs` — and `.ts` where the host strips types: Node
|
|
359
456
|
≥ 24 does by default, and `--no-strip-types` is refused by name)
|
|
360
|
-
loaded
|
|
457
|
+
loaded through `@jarenjs/json/node` — the suite's one document loader,
|
|
458
|
+
shared with `jaren-contract` — and read as its `default` export or its `model`
|
|
361
459
|
export — the model pen's document, or any object whose `toJSON()`
|
|
362
460
|
emits one; `--migrations <dir>` reads `.json` files and modules
|
|
363
461
|
(`default` or `migration` — the migration pen's builder), sorted by
|
|
@@ -400,13 +498,40 @@ jaren-db shape --model <model>
|
|
|
400
498
|
`apply` without `--yes` exits 1 after the printout with nothing
|
|
401
499
|
applied — a CI job passes `--yes` deliberately, never by default.
|
|
402
500
|
`apply --dry-run` is the CLI's printout, not §6's `dryRun: true`: it
|
|
403
|
-
reads the history the way `status` does —
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
shadow's verdict comes with the real `apply`.
|
|
501
|
+
reads the history the way `status` does — probed, never created — and
|
|
502
|
+
does NOT replay the chain on the shadow, so a draft step still prints
|
|
503
|
+
instead of refusing. The shadow's verdict comes with the real `apply`.
|
|
407
504
|
- `status` lists applied/pending and reports drift (§12); on a
|
|
408
|
-
database without a history table it creates
|
|
505
|
+
database without a history table it creates nothing (§6).
|
|
409
506
|
- `shape` prints the physical mapping a model produces.
|
|
507
|
+
- `documents` runs a migration's DOCUMENT steps over a file instead of a
|
|
508
|
+
database — §6.1's runners, given a path or stdio. `--in`/`--out` take
|
|
509
|
+
a file or `-`; the encoding follows the extension (`.jsonl`/`.ndjson`
|
|
510
|
+
line-delimited, everything else one JSON array) unless `--format` /
|
|
511
|
+
`--out-format` says otherwise, and stdio defaults to JSONL. Input and
|
|
512
|
+
output encodings are independent, so this is also the converter.
|
|
513
|
+
- **A file holds ONE collection.** The migrations name it; a chain
|
|
514
|
+
whose document steps touch more than one cannot be applied to a
|
|
515
|
+
file, and is refused rather than partly run. `--collection` asserts
|
|
516
|
+
which collection the file holds and refuses a mismatch.
|
|
517
|
+
- **`--out` writes elsewhere; `--in-place` replaces the input and
|
|
518
|
+
needs `--yes`.** Either way the documents land in a sibling
|
|
519
|
+
temporary that is renamed over the target only once every document
|
|
520
|
+
has survived every step. A failure — a step, a malformed source, a
|
|
521
|
+
cancelled run — removes the temporary and leaves the target byte for
|
|
522
|
+
byte as it was.
|
|
523
|
+
- **`--check` transforms and validates everything and writes nothing**,
|
|
524
|
+
which is the CI shape: it answers whether this chain still applies
|
|
525
|
+
to this data.
|
|
526
|
+
- **Three exit codes, three meanings:** `0` the chain applies and every
|
|
527
|
+
assertion holds; `1` the run failed (a step refused, the source was
|
|
528
|
+
malformed, the file was missing, a step needs a database); `2` the
|
|
529
|
+
command line itself was wrong (a missing or contradictory flag, an
|
|
530
|
+
unknown format). A script can tell "you asked for the wrong thing"
|
|
531
|
+
from "what you asked for does not hold".
|
|
532
|
+
- The report names the strategy §6.1 chose — `streamed`, or
|
|
533
|
+
`materialized` when a cross-document assertion needs the collection
|
|
534
|
+
at once.
|
|
410
535
|
|
|
411
536
|
## 12. Drift
|
|
412
537
|
|