@jarenjs/linq 0.86.0 → 0.89.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/README.md CHANGED
@@ -387,6 +387,14 @@ model that moved without a plan (`jaren-db check`). The mapping table
387
387
  and the worked examples are
388
388
  [docs/MIGRATION-PEN.md](docs/MIGRATION-PEN.md).
389
389
 
390
+ For existing physical tables, `.step({ kind: 'table', plan })` preserves
391
+ the complete guarded `planTableMigration` artifact. `fromPlanned` keeps the
392
+ physical source/dispositions/assertions/target header. An optional `{ model }`
393
+ on `transform` or `assert` selects the layout at that point in the chain,
394
+ including an old name absent from the final model. Execution, complete target
395
+ acceptance, fixture replay and receipts remain in db; the pen imports no engine.
396
+ See the [runnable physical lifecycle](../db/docs/MIGRATION-FORMAT.md#runnable-physical-lifecycle).
397
+
390
398
  ## By code: the contract pen
391
399
 
392
400
  `@jarenjs/linq/contract` writes `$contract` 0.1 documents — the
package/docs/DB-CLIENT.md CHANGED
@@ -843,10 +843,10 @@ never builds one; the migration between two of them is
843
843
 
844
844
  ## 7. Cost
845
845
 
846
- `@jarenjs/linq/db` builds to **<!--fact:bundle.db-->724,181<!--/fact--> bytes** as a minified,
846
+ `@jarenjs/linq/db` builds to **<!--fact:bundle.db-->728,423<!--/fact--> bytes** as a minified,
847
847
  tree-shaken ESM bundle — the figure `scripts/check-tree-shaking.js`
848
848
  measures and `npm run test:tree-shaking` reports, published rounded
849
- (<!--fact:bundle.db.kb-->724<!--/fact--> kB) beside the other nine subpath prices in
849
+ (<!--fact:bundle.db.kb-->728<!--/fact--> kB) beside the other nine subpath prices in
850
850
  [docs/CONSUMING.md](../../../docs/CONSUMING.md).
851
851
 
852
852
  It is by far the largest of the ten, and the reason is §1.1's edge rather
package/docs/JSLT-PEN.md CHANGED
@@ -948,7 +948,7 @@ source. That makes it the SMALLEST of the nine pen bundles, and the
948
948
  reason is that a stylesheet is mostly bodies, and a body is the shared
949
949
  machine every pen already pays for.
950
950
 
951
- Two figures worth reading beside it: `./migration` (<!--fact:bundle.migration-->22,827<!--/fact--> bytes)
951
+ Two figures worth reading beside it: `./migration` (<!--fact:bundle.migration-->23,806<!--/fact--> bytes)
952
952
  carries this pen's `body()` and pays for it, which is why the two prices
953
953
  sit so close; and `./flow` (<!--fact:bundle.flow-->18,478<!--/fact--> bytes) is within 60 bytes of this one
954
954
  despite writing two formats, because it shares the same capture and adds
@@ -36,12 +36,12 @@ is the index of those guides, and it is how a reader reaches any of them.
36
36
  <!--fact:pens.index-->
37
37
  | Document | Lines | What it writes, and when to open it |
38
38
  |---|---:|---|
39
- | [LINQ-FORMAT.md](LINQ-FORMAT.md) | 944 | this file, the binder and the family's **normative reference**: what a pen is, the rules all of them keep, the shared `JL01xx` table, and the cross-pen views derived from the guides it indexes. **Read it when** you want a rule that is true of every pen, an index of the documents, or one place to look up a method without knowing which pen owns it |
39
+ | [LINQ-FORMAT.md](LINQ-FORMAT.md) | 945 | this file, the binder and the family's **normative reference**: what a pen is, the rules all of them keep, the shared `JL01xx` table, and the cross-pen views derived from the guides it indexes. **Read it when** you want a rule that is true of every pen, an index of the documents, or one place to look up a method without knowing which pen owns it |
40
40
  | [QUERY-PEN.md](QUERY-PEN.md) | 1,761 | the chain, `.` — query documents (`jaren-query`) and the provider seam. **Read it when** you are querying data, or implementing a provider that answers a query document |
41
41
  | [SCHEMA-PEN.md](SCHEMA-PEN.md) | 1,205 | `./schema` — JSON Schema 2020-12: the structural keywords, the constraints and the annotations, each with a method of its own, plus `$query`, `$defs`/`$ref` recursion and the normalizer's per-field predicates. **Read it when** you are describing the shape of data — for validation, for a form, or as the base of an entity |
42
42
  | [MODEL-PEN.md](MODEL-PEN.md) | 1,116 | `./model` — the `x-entity` vocabulary on JSON Schema, and the `$model` 0.1 document `openStore` accepts unchanged. **Read it when** you are declaring a store's entities, their keys and their relations |
43
43
  | [JSLT-PEN.md](JSLT-PEN.md) | 955 | `./jslt` — `$jslt` 0.1 stylesheets: the envelope and its rules, whose bodies are captured over the matched value. **Read it when** you are transforming one document into another |
44
- | [MIGRATION-PEN.md](MIGRATION-PEN.md) | 781 | `./migration` — `$migration` 0.1 documents: the two shape hashes and the ordered steps the runner takes. **Read it when** you are moving a store from one model to the next |
44
+ | [MIGRATION-PEN.md](MIGRATION-PEN.md) | 808 | `./migration` — `$migration` 0.1 documents: the two shape hashes and the ordered steps the runner takes. **Read it when** you are moving a store from one model to the next |
45
45
  | [CONTRACT-PEN.md](CONTRACT-PEN.md) | 1,221 | `./contract` — `$contract` 0.1 documents: the operations, their schemas, their declared behavior and their REST binding. **Read it when** you are declaring an API and want its client, its server and its tools typed from one document |
46
46
  | [FLOW-PEN.md](FLOW-PEN.md) | 1,033 | `./flow` — `jaren-fsm` 0.1 machines and `jaren-dag` 0.1 dataflows, every query-valued member captured. **Read it when** you are declaring a state machine or a dependency graph of tasks |
47
47
  | [APP-PEN.md](APP-PEN.md) | 1,143 | `./app` — the `jaren-app` 0.1 document `createApp` runs, and the JSON Schema of its state beside it. **Read it when** you are declaring a whole application: state, view, actions, effects |
@@ -203,12 +203,12 @@ and the bundle is the byte count the tree-shaking probe builds.
203
203
  <!--fact:pens.census-->
204
204
  | Document | Subpath | Lines | Mapping rows | Worked examples | Refusals | Bundle |
205
205
  |---|---|---:|---:|---:|---:|---:|
206
- | [LINQ-FORMAT.md](LINQ-FORMAT.md) | — | 944 | — | — | — | — |
206
+ | [LINQ-FORMAT.md](LINQ-FORMAT.md) | — | 945 | — | — | — | — |
207
207
  | [QUERY-PEN.md](QUERY-PEN.md) | `.` | 1,761 | 34 | 8 | 15 | 175,560 B |
208
208
  | [SCHEMA-PEN.md](SCHEMA-PEN.md) | `./schema` | 1,205 | 82 | 10 | 4 | 35,285 B |
209
209
  | [MODEL-PEN.md](MODEL-PEN.md) | `./model` | 1,116 | 30 | 6 | 3 | 44,143 B |
210
210
  | [JSLT-PEN.md](JSLT-PEN.md) | `./jslt` | 955 | 17 | 8 | 3 | 18,424 B |
211
- | [MIGRATION-PEN.md](MIGRATION-PEN.md) | `./migration` | 781 | 11 | 5 | 4 | 22,827 B |
211
+ | [MIGRATION-PEN.md](MIGRATION-PEN.md) | `./migration` | 808 | 12 | 5 | 4 | 23,806 B |
212
212
  | [CONTRACT-PEN.md](CONTRACT-PEN.md) | `./contract` | 1,221 | 38 | 6 | 3 | 47,427 B |
213
213
  | [FLOW-PEN.md](FLOW-PEN.md) | `./flow` | 1,033 | 16 | 7 | 3 | 18,478 B |
214
214
  | [APP-PEN.md](APP-PEN.md) | `./app` | 1,143 | 22 | 7 | 3 | 49,574 B |
@@ -217,9 +217,9 @@ and the bundle is the byte count the tree-shaking probe builds.
217
217
  | [JTLT-PEN.md](JTLT-PEN.md) | `./jtlt` | 83 | 13 | 1 | 2 | 15,293 B |
218
218
  | [PROJECT-PEN.md](PROJECT-PEN.md) | `./project` | 81 | 9 | 1 | 1 | 13,793 B |
219
219
  | [CHARTS-PEN.md](CHARTS-PEN.md) | `./charts` | 94 | 21 | 1 | 1 | 15,582 B |
220
- | [DB-CLIENT.md](DB-CLIENT.md) | `./db` | 1,085 | 46 | 4 | 2 | 724,181 B |
220
+ | [DB-CLIENT.md](DB-CLIENT.md) | `./db` | 1,085 | 46 | 4 | 2 | 728,423 B |
221
221
  | [FORMULA-PEN.md](FORMULA-PEN.md) | `./formula` | 41 | 2 | — | — | 14,850 B |
222
- | **16 documents** | | **12,588** | **368** | **72** | | |
222
+ | **16 documents** | | **12,616** | **369** | **72** | | |
223
223
  <!--/fact-->
224
224
 
225
225
  A pen whose mapping rows are far below its worked examples is a pen
@@ -265,7 +265,7 @@ it and each document publishes it. The rounded column is what
265
265
  | `@jarenjs/linq/schema` | [SCHEMA-PEN.md](SCHEMA-PEN.md) | 35,285 B | 35 kB |
266
266
  | `@jarenjs/linq/model` | [MODEL-PEN.md](MODEL-PEN.md) | 44,143 B | 44 kB |
267
267
  | `@jarenjs/linq/jslt` | [JSLT-PEN.md](JSLT-PEN.md) | 18,424 B | 18 kB |
268
- | `@jarenjs/linq/migration` | [MIGRATION-PEN.md](MIGRATION-PEN.md) | 22,827 B | 23 kB |
268
+ | `@jarenjs/linq/migration` | [MIGRATION-PEN.md](MIGRATION-PEN.md) | 23,806 B | 24 kB |
269
269
  | `@jarenjs/linq/contract` | [CONTRACT-PEN.md](CONTRACT-PEN.md) | 47,427 B | 47 kB |
270
270
  | `@jarenjs/linq/flow` | [FLOW-PEN.md](FLOW-PEN.md) | 18,478 B | 18 kB |
271
271
  | `@jarenjs/linq/app` | [APP-PEN.md](APP-PEN.md) | 49,574 B | 50 kB |
@@ -274,7 +274,7 @@ it and each document publishes it. The rounded column is what
274
274
  | `@jarenjs/linq/jtlt` | [JTLT-PEN.md](JTLT-PEN.md) | 15,293 B | 15 kB |
275
275
  | `@jarenjs/linq/project` | [PROJECT-PEN.md](PROJECT-PEN.md) | 13,793 B | 14 kB |
276
276
  | `@jarenjs/linq/charts` | [CHARTS-PEN.md](CHARTS-PEN.md) | 15,582 B | 16 kB |
277
- | `@jarenjs/linq/db` | [DB-CLIENT.md](DB-CLIENT.md) | 724,181 B | 724 kB |
277
+ | `@jarenjs/linq/db` | [DB-CLIENT.md](DB-CLIENT.md) | 728,423 B | 728 kB |
278
278
  | `@jarenjs/linq/formula` | [FORMULA-PEN.md](FORMULA-PEN.md) | 14,850 B | 15 kB |
279
279
  <!--/fact-->
280
280
 
@@ -597,10 +597,11 @@ it says.
597
597
  | `.transform(name, (row, x) => …)` | `{ kind: 'jslt', collection: name, stylesheet: [{ match: '$', body }] }` — one root rule, the body captured through the JSLT pen's `body()` over the WHOLE row, `x.root`/`x.path` the externals the engine binds (JSLT-FORMAT §8.2) | `row` is `Expr<Old>` (`DocOf<From, name>`); the result must spell `New` — a dropped, mistyped or foreign member does not compile; the honest top (`get()`) is admitted where a precise value is | native; a table the target model does not declare `JL0106`; an undeclared external `JL0104` |
598
598
  | `.transform(name, stylesheet(…))`, `.transform(name, rules)` | the rules ARRAY — a `jslt` step carries the array, so the envelope's `unmatched`/`modes` have no place in it | a typed stylesheet's or first rule's `Out` must be `New`; a hand-written rule is the honest top | native; a disposition or a mode table `JL0102`; not JSON `JL0101` |
599
599
  | `.assert(name, (row) => …, { expect? })` | `{ kind: 'query', collection: name, assert: { $for: { it: '$[*]' }, $where: <predicate>, $return: '$it' }, expect? }` — the format's own `$for` over the rows; the predicate names the VIOLATION (`expect: 'empty'`, the default, absent from the document) or the witness (`expect: 'ebv'`) | `row` is the members the two shapes share — a precondition sees old rows, a postcondition new ones, and what both agree on is what neither lies about; annotate (`(row: Expr<User>) => …`) when one shape is meant | native; another `expect` `JL0101`; an external `JL0104`; an undeclared table `JL0106` |
600
- | `.assert(name, query, { expect? })` | the query document verbatim | — | native |
600
+ | `.assert(name, query, { expect?, model? })` | the query document verbatim, plus an optional current model | — | native |
601
+ | `.transform(name, spelling, { model })`, `.assert(name, spelling, { model, expect? })` | the ordinary step plus an immutable current `$model` document | transform input/output and assertion rows use this model's layout; names may be absent from the final model | native; an invalid model `JL0101`; a name absent from the selected model `JL0106` |
601
602
  | `.derive(name, columns)` | `{ kind: 'derive', collection: name, columns }` — a backfill of stored derived columns (§2.1), the columns verbatim | `readonly DeriveColumn[]` | native; no columns `JL0101`; an undeclared table `JL0106` |
602
- | `.step(raw)` | any planner-emitted step, verbatim the escape that keeps `rebuild` (§10) authorable without the pen re-implementing it; a `draft` flag rides untouched | `MigrationStep` | native; an unrecognised kind or a missing member (the runner's `JD0023` rules, seen early) `JL0101` |
603
- | `fromPlanned(document, { from?, to? })` | the planner's document, taken up: `.transform(name, …)` replaces its draft for `name` in place; every other method appends | the models type the transforms and are checked against the document's hashes | native; a model that is not the planned one `JL0102`; two drafts for one name, or no draft and no target model `JL0106` |
603
+ | `.step(raw)` | any planner-emitted step, including a complete guarded `{ kind: 'table', plan }` or a hybrid `rebuild`; a `draft` flag rides untouched | `MigrationStep`, including `TableStep`/`ReviewedTablePlan` | native; an unrecognised kind or incomplete plan/step `JL0101` |
604
+ | `fromPlanned(document, { from?, to? })` | the planner's document, including its `physical` header and complete saved plans; `.transform(name, …)` replaces its draft in place, and other methods append | the models type transforms and are checked against the hashes | native; a model that is not the planned one `JL0102`; two drafts for one name, or no draft and neither target nor step model `JL0106` |
604
605
  | `.document`, `.toJSON()` | the deep-frozen `$migration` document — assembled once and memoized, so `a.document === a.document` | `MigrationDocument` | native |
605
606
 
606
607
  ### The Jaren contract pen — [CONTRACT-PEN.md §2](CONTRACT-PEN.md)
@@ -114,19 +114,16 @@ naming the reason).
114
114
  | `.transform(name, (row, x) => …)` | `{ kind: 'jslt', collection: name, stylesheet: [{ match: '$', body }] }` — one root rule, the body captured through the JSLT pen's `body()` over the WHOLE row, `x.root`/`x.path` the externals the engine binds (JSLT-FORMAT §8.2) | `row` is `Expr<Old>` (`DocOf<From, name>`); the result must spell `New` — a dropped, mistyped or foreign member does not compile; the honest top (`get()`) is admitted where a precise value is | native; a table the target model does not declare `JL0106`; an undeclared external `JL0104` |
115
115
  | `.transform(name, stylesheet(…))`, `.transform(name, rules)` | the rules ARRAY — a `jslt` step carries the array, so the envelope's `unmatched`/`modes` have no place in it | a typed stylesheet's or first rule's `Out` must be `New`; a hand-written rule is the honest top | native; a disposition or a mode table `JL0102`; not JSON `JL0101` |
116
116
  | `.assert(name, (row) => …, { expect? })` | `{ kind: 'query', collection: name, assert: { $for: { it: '$[*]' }, $where: <predicate>, $return: '$it' }, expect? }` — the format's own `$for` over the rows; the predicate names the VIOLATION (`expect: 'empty'`, the default, absent from the document) or the witness (`expect: 'ebv'`) | `row` is the members the two shapes share — a precondition sees old rows, a postcondition new ones, and what both agree on is what neither lies about; annotate (`(row: Expr<User>) => …`) when one shape is meant | native; another `expect` `JL0101`; an external `JL0104`; an undeclared table `JL0106` |
117
- | `.assert(name, query, { expect? })` | the query document verbatim | — | native |
117
+ | `.assert(name, query, { expect?, model? })` | the query document verbatim, plus an optional current model | — | native |
118
+ | `.transform(name, spelling, { model })`, `.assert(name, spelling, { model, expect? })` | the ordinary step plus an immutable current `$model` document | transform input/output and assertion rows use this model's layout; names may be absent from the final model | native; an invalid model `JL0101`; a name absent from the selected model `JL0106` |
118
119
  | `.derive(name, columns)` | `{ kind: 'derive', collection: name, columns }` — a backfill of stored derived columns (§2.1), the columns verbatim | `readonly DeriveColumn[]` | native; no columns `JL0101`; an undeclared table `JL0106` |
119
- | `.step(raw)` | any planner-emitted step, verbatim the escape that keeps `rebuild` (§10) authorable without the pen re-implementing it; a `draft` flag rides untouched | `MigrationStep` | native; an unrecognised kind or a missing member (the runner's `JD0023` rules, seen early) `JL0101` |
120
- | `fromPlanned(document, { from?, to? })` | the planner's document, taken up: `.transform(name, …)` replaces its draft for `name` in place; every other method appends | the models type the transforms and are checked against the document's hashes | native; a model that is not the planned one `JL0102`; two drafts for one name, or no draft and no target model `JL0106` |
120
+ | `.step(raw)` | any planner-emitted step, including a complete guarded `{ kind: 'table', plan }` or a hybrid `rebuild`; a `draft` flag rides untouched | `MigrationStep`, including `TableStep`/`ReviewedTablePlan` | native; an unrecognised kind or incomplete plan/step `JL0101` |
121
+ | `fromPlanned(document, { from?, to? })` | the planner's document, including its `physical` header and complete saved plans; `.transform(name, …)` replaces its draft in place, and other methods append | the models type transforms and are checked against the hashes | native; a model that is not the planned one `JL0102`; two drafts for one name, or no draft and neither target nor step model `JL0106` |
121
122
  | `.document`, `.toJSON()` | the deep-frozen `$migration` document — assembled once and memoized, so `a.document === a.document` | `MigrationDocument` | native |
122
123
 
123
- **This table is complete, and deliberately short.** Ten callable names
124
- against eleven rows: the step vocabulary of `$migration` 0.1 is six kinds
125
- (`ddl`, `jslt`, `query`, `derive`, `sql`, `rebuild`), five of them have a
126
- method here and the sixth is `step()`. What makes this pen worth reading
127
- is not the size of its surface but what each step is checked against —
128
- §4 is the richest refusal section in the family relative to the pen's
129
- size, and it is the reason.
124
+ The `$migration` 0.1 vocabulary has seven kinds: `ddl`, `jslt`, `query`,
125
+ `derive`, `sql`, `rebuild` and `table`. The two structural artifact kinds use
126
+ `step()`; the pen preserves their plans without implementing SQL execution.
130
127
 
131
128
  Three rules the table implies, spelled out:
132
129
 
@@ -137,13 +134,11 @@ Three rules the table implies, spelled out:
137
134
  instruction, not shape, so a model that keeps carrying a satisfied hint
138
135
  hashes the same as one without it and plans nothing (MIGRATION-FORMAT
139
136
  §3 — a rename is idempotent across `plan` runs).
140
- - **A step's table is one the target model declares.** The runner would
141
- fail the statement on a table that does not exist; the pen says so
142
- first (`JL0106`) for `transform`, `assert` and `derive` alike,
143
- whenever it knows the target. Entities and collections both count as
144
- declared. Over a planned document alone it knows only the drafts, so
145
- every other name is the runner's to judge and a transform for another
146
- table is spelled with `step()`.
137
+ - **A step's name belongs to its selected model.** `transform` and
138
+ `assert` use an explicit `{ model }` when supplied, or the known target
139
+ otherwise; `derive` uses the target. Entities and collections both count.
140
+ A planned transform with no draft and no known model refuses `JL0106`;
141
+ supply its current model or spell the raw step explicitly.
147
142
  - **Steps are appended in the order they are called**, and `transform`
148
143
  over a planned document is the one exception: it replaces the draft for
149
144
  that name IN PLACE, so the planner's ordering — DDL before the data
@@ -480,7 +475,7 @@ else.
480
475
  | `m().assert('User', p, { other: 1 })` | `assert() does not take 'other'` | `{ expect }` |
481
476
  | `m().assert('User', undefined)` | `assert() takes a predicate (row) => … or a query document over the rows` | a predicate |
482
477
  | `m().derive('User', [])` | `derive() takes a non-empty array of derived-column records ({ name, derive, segments }), got a Array instance` | the columns the planner emitted |
483
- | `m().step({ kind: 'nope' })`, `m().step(42)` | `step() takes a migration step with a recognised kind (ddl, jslt, query, derive, sql, rebuild), got kind a string` | one of the six kinds |
478
+ | `m().step({ kind: 'nope' })`, `m().step(42)` | `step() takes a migration step with a recognised kind (ddl, jslt, query, derive, sql, rebuild, table), got kind a string` | one of the seven kinds |
484
479
  | `m().step({ kind: 'rebuild', table: 'User' })` | `step() 'rebuild' needs 'create' (MIGRATION-FORMAT §2)` — `docPath` `/create` | the rendered parts |
485
480
  | `m().step({ kind: 'sql' })` | `step() 'sql' needs 'sql' (MIGRATION-FORMAT §2)` — `docPath` `/sql` | the statement |
486
481
  | `m().step({ kind: 'jslt', collection: 'User' })` | `step() 'jslt' needs 'stylesheet' (MIGRATION-FORMAT §2)` — `docPath` `/stylesheet` | a rules array |
@@ -532,8 +527,9 @@ exactly what it must not have.
532
527
 
533
528
  This pen's own code. Two conditions, and no other pen raises either.
534
529
 
535
- **A step naming a table the target model does not declare.** Checked for
536
- `transform`, `assert` and `derive`, whenever the target model is known.
530
+ **A step naming a table its selected model does not declare.** Checked for
531
+ `transform` and `assert` against an explicit current model when supplied,
532
+ or the known target otherwise. `derive` checks the known target.
537
533
 
538
534
  | The spelling that trips it | The message | The spelling that works |
539
535
  |---|---|---|
@@ -553,20 +549,20 @@ no steps, so there is never a draft.
553
549
 
554
550
  | The spelling that trips it | The message | The spelling that works |
555
551
  |---|---|---|
556
- | `fromPlanned(planned).transform('Nope', fn)` — no models given | `transform() over 'Nope': the planned migration drafts no transform for it and no target model was given — pass { to } to fromPlanned(), or spell the step with step()` | `fromPlanned(planned, { from: v1, to: v2 })`, or `.step({ kind: 'jslt', … })` |
552
+ | `fromPlanned(planned).transform('Nope', fn)` — no models given | `transform() over 'Nope': the planned migration drafts no transform for it and no target model or explicit step model was given — pass { model } to transform(), { to } to fromPlanned(), or spell the step with step()` | supply the current `{ model }`, give `fromPlanned` its `{ to }`, or use `.step({ kind: 'jslt', … })` |
557
553
  | `fromPlanned(twoDrafts).transform('User', fn)` | `transform() cannot tell which draft to replace: the planned migration carries 2 draft transforms for 'User'` | edit the planned document down to one draft per table, or replace them with `step()` calls |
558
554
 
559
555
  The first is not a mistake so much as a missing fact: without a target
560
556
  model the pen knows only which tables the planner drafted, so a name it
561
557
  has never seen could be a typo or could be perfectly good. It refuses and
562
- names both ways out rather than guessing. The second cannot arise from
558
+ names the available ways to supply that fact. The second cannot arise from
563
559
  `jaren-db plan`, which drafts at most one transform per table; it arises
564
560
  when a planned document is edited or two are concatenated, and a pen that
565
561
  picked one would be picking which of the author's two transforms to
566
562
  throw away.
567
563
 
568
- Note what is NOT checked: with no target model, `ddl`, `sql`, `assert`,
569
- `derive` and `step` take any identifier, and the runner judges. That is
564
+ With neither a target nor an explicit step model, `ddl`, `sql`, `assert`,
565
+ `derive` and `step` leave undeclared-name checks to the runner. That is
570
566
  the honest position — the pen refuses what it can see, and it cannot see
571
567
  a model it was not given.
572
568
 
@@ -609,6 +605,14 @@ over old rows and a postcondition over new ones, and the pen cannot know
609
605
  which this one is, so what both agree on is what neither lies about;
610
606
  annotate (`(u: Expr<NewUser>) => …`) when one shape is meant.
611
607
 
608
+ An explicit `{ model: current }` overrides these default type readings.
609
+ The transform row and result both use `DocOf<typeof current, name>`;
610
+ an assertion reads that current shape. `fromPlanned` retains the entire
611
+ physical header as immutable JSON. Db's generic `PhysicalMigrationDocument`
612
+ preserves supplied step types, so `fromPlanned(await planPhysicalMigration(...))`
613
+ composes directly when the steps are typed. Unknown saved input remains
614
+ unknown until validated; incomplete table artifacts do not gain missing guards.
615
+
612
616
  ### 5.2 The honest limits, and the two routes past them
613
617
 
614
618
  **From a JSON snapshot the old row is `unknown`.** A JSON literal is
@@ -674,6 +678,27 @@ be re-implementing the planner. The alternative for all three is the same
674
678
  one the format intends: let `jaren-db plan` render them, and take the
675
679
  document up with `fromPlanned`.
676
680
 
681
+ `table` also uses `step()`, carrying the entire reviewed
682
+ `planTableMigration` artifact. Its `source`, `after`, row/storage and identity
683
+ guards must not be replaced by a loop over `statements` and `finish`.
684
+ The pen checks the complete artifact's structure and preserves it; db checks
685
+ the source/checksum and executes its guarded transaction.
686
+
687
+ Historical models can precede and follow that structural step:
688
+
689
+ ```js
690
+ const steps = defineMigration({ id: 'upgrade', from: before, to: after })
691
+ .transform('Item', (row) => ({ id: row.id, value: row.value.upper() }), { model: before })
692
+ .step({ kind: 'table', plan: reviewedTablePlan })
693
+ .assert('Item', (row) => row.revision.lt(1), { model: after }).document.steps;
694
+ ```
695
+
696
+ The [runnable db example](../../db/docs/MIGRATION-FORMAT.md#runnable-physical-lifecycle)
697
+ supplies the complete model/table declarations, reviewed target fixture,
698
+ physical dispositions, populated replay and checked second startup. Synthetic
699
+ fixture rows stay outside the shared migration document. Physical model diffing
700
+ still refuses changed declarations; the author reviews structural plans.
701
+
677
702
  ### 6.2 `defineMigration` does not validate a migration against a database
678
703
 
679
704
  This is the sentence a reader most needs, because believing otherwise
@@ -687,8 +712,10 @@ below is `@jarenjs/db`'s `migrate()`, and none of it has happened when
687
712
  - **The shadow replay.** Before the real store is touched, the WHOLE
688
713
  chain — baseline, applied and pending — replays on a shadow database:
689
714
  every DDL statement runs, every stylesheet and assertion compiles and
690
- executes, and the end shape is verified against the target model
691
- (MIGRATION-FORMAT §4). A step the pen accepted and SQLite rejects fails
715
+ executes, and the selected model or complete physical target is checked.
716
+ `shadowFixture` may initialize an application-owned historical schema;
717
+ replay still uses the same migration/history executor (MIGRATION-FORMAT §4).
718
+ A step the pen accepted and SQLite rejects fails
692
719
  there, with the real store untouched.
693
720
  - **The widening/narrowing check against real data.** At the end of the
694
721
  run, inside its transaction, every stored document is validated against
@@ -745,10 +772,10 @@ from a drop plus a create, and guessing risks silent data loss.
745
772
 
746
773
  ## 7. Cost
747
774
 
748
- `@jarenjs/linq/migration` builds to **<!--fact:bundle.migration-->22,827<!--/fact--> bytes** as a minified,
775
+ `@jarenjs/linq/migration` builds to **<!--fact:bundle.migration-->23,806<!--/fact--> bytes** as a minified,
749
776
  tree-shaken ESM bundle — the figure `scripts/check-tree-shaking.js`
750
777
  measures and `npm run test:tree-shaking` reports, published rounded
751
- (<!--fact:bundle.migration.kb-->23<!--/fact--> kB) beside the other nine subpath prices in
778
+ (<!--fact:bundle.migration.kb-->24<!--/fact--> kB) beside the other nine subpath prices in
752
779
  [docs/CONSUMING.md](../../../docs/CONSUMING.md).
753
780
 
754
781
  The probe is a gate, not a report: building a two-step migration as a
package/docs/QUERY-PEN.md CHANGED
@@ -1717,7 +1717,7 @@ making:
1717
1717
  `docs/CONSUMING.md` states the rounded price of all ten subpaths in one
1718
1718
  table, each figure held equal to the same measurements. Two of its rows
1719
1719
  are the ones to read together: the chain at <!--fact:bundle.chain.kb-->176<!--/fact--> kB and
1720
- `./db` at <!--fact:bundle.db.kb-->724<!--/fact--> kB.
1720
+ `./db` at <!--fact:bundle.db.kb-->728<!--/fact--> kB.
1721
1721
  The client costs what the store costs, by construction, and the chain
1722
1722
  costs what running a query costs.
1723
1723
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jarenjs/linq",
3
3
  "private": false,
4
- "version": "0.86.0",
4
+ "version": "0.89.0",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
7
7
  "types": "./types/index.d.ts",
@@ -108,13 +108,13 @@
108
108
  "prepack": "npm run build:types"
109
109
  },
110
110
  "dependencies": {
111
- "@jarenjs/core": "^0.86.0",
112
- "@jarenjs/json": "^0.86.0"
111
+ "@jarenjs/core": "^0.89.0",
112
+ "@jarenjs/json": "^0.89.0"
113
113
  },
114
114
  "peerDependencies": {
115
- "@jarenjs/db": "^0.86.0",
116
- "@jarenjs/formats": "^0.86.0",
117
- "@jarenjs/validate": "^0.86.0"
115
+ "@jarenjs/db": "^0.89.0",
116
+ "@jarenjs/formats": "^0.89.0",
117
+ "@jarenjs/validate": "^0.89.0"
118
118
  },
119
119
  "peerDependenciesMeta": {
120
120
  "@jarenjs/db": {
@@ -10,7 +10,7 @@
10
10
  * and nothing here ever clears a `draft` flag: a draft left in place
11
11
  * still refuses to run (`JD0021`, the runner's rule). The pen refuses
12
12
  * what it cannot spell and what the runner would refuse later and the
13
- * pen can see now — a step over a table the target model does not
13
+ * pen can see now — a step over a table the selected model does not
14
14
  * declare (`JL0106`).
15
15
  */
16
16
 
@@ -25,7 +25,7 @@ import {
25
25
  } from './steps.js';
26
26
 
27
27
  const MIGRATION_VERSION = '0.1';
28
- const HEAD_MEMBERS = ['$migration', 'id', 'from', 'to', 'note', 'steps'];
28
+ const HEAD_MEMBERS = ['$migration', 'id', 'from', 'to', 'note', 'steps', 'physical'];
29
29
 
30
30
  /** A JSON value, copied: the document is a value of its own. @param {any} v */
31
31
  const copy = (v) => JSON.parse(JSON.stringify(v));
@@ -132,14 +132,14 @@ export class Migration {
132
132
  return this.#with([...this.#steps, step]);
133
133
  }
134
134
 
135
- /** A step's table must be one the target model declares when the
136
- * target is known; the runner would fail the statement on a table
137
- * that does not exist, and the pen can say so first. */
138
- #requireDeclared(name, what) {
139
- if (this.#names !== null && !this.#names.includes(name)) {
135
+ /** A step's table must be one its explicit current model or the known
136
+ * target model declares; the pen catches a missing name before execution. */
137
+ #requireDeclared(name, what, model = undefined) {
138
+ const names = model === undefined ? this.#names : declaredNames(model);
139
+ if (names !== null && !names.includes(name)) {
140
140
  throw new LinqBuildError('JL0106',
141
- `${what} names '${name}', which the target model does not declare — it declares `
142
- + (this.#names.length === 0 ? 'nothing' : this.#names.map((n) => `'${n}'`).join(', ')));
141
+ `${what} names '${name}', which the ${model === undefined ? 'target' : 'step'} model does not declare — it declares `
142
+ + (names.length === 0 ? 'nothing' : names.map((n) => `'${n}'`).join(', ')));
143
143
  }
144
144
  }
145
145
 
@@ -156,15 +156,17 @@ export class Migration {
156
156
  /**
157
157
  * The transform of one table's rows (a `jslt` step). Over a planned
158
158
  * document it REPLACES the draft the planner left for that table, in
159
- * place; otherwise it is appended, for a table the target model
160
- * declares. Two drafts for one name, a draft-less planned document with
161
- * no target model, or an undeclared name are `JL0106`.
159
+ * place; otherwise it is appended, for a table the explicit current or
160
+ * target model declares. Two drafts for one name, a draft-less planned
161
+ * document with neither model, or an undeclared name are `JL0106`.
162
162
  * @param {string} name
163
163
  * @param {any} spelling - a callback `(row, x) => …`, a `stylesheet(…)`
164
164
  * document, or a rules array
165
+ * @param {{ model?: any }} [options] - The layout at this step, before later DDL
165
166
  */
166
- transform(name, spelling) {
167
- const step = transformStep(name, spelling);
167
+ transform(name, spelling, options = undefined) {
168
+ const step = transformStep(name, spelling, options);
169
+ if (step.model !== undefined) this.#requireDeclared(name, 'transform()', step.model);
168
170
  const drafts = [];
169
171
  this.#steps.forEach((s, i) => {
170
172
  if (s.kind === 'jslt' && s.draft === true && s.collection === name) drafts.push(i);
@@ -179,12 +181,12 @@ export class Migration {
179
181
  next[drafts[0]] = step;
180
182
  return this.#with(next);
181
183
  }
182
- if (this.#names === null) {
184
+ if (this.#names === null && step.model === undefined) {
183
185
  throw new LinqBuildError('JL0106',
184
186
  `transform() over '${name}': the planned migration drafts no transform for it and no `
185
- + 'target model was given — pass { to } to fromPlanned(), or spell the step with step()');
187
+ + 'target model or explicit step model was given — pass { model } to transform(), { to } to fromPlanned(), or spell the step with step()');
186
188
  }
187
- this.#requireDeclared(name, 'transform()');
189
+ this.#requireDeclared(name, 'transform()', step.model);
188
190
  return this.#append(step);
189
191
  }
190
192
 
@@ -192,11 +194,11 @@ export class Migration {
192
194
  * An assertion over one table's rows (a `query` step).
193
195
  * @param {string} name
194
196
  * @param {any} spelling - a predicate `(row) => …`, or a query document
195
- * @param {{ expect?: 'empty' | 'ebv' }} [options]
197
+ * @param {{ expect?: 'empty' | 'ebv', model?: any }} [options]
196
198
  */
197
199
  assert(name, spelling, options = undefined) {
198
200
  const step = assertStep(name, spelling, options);
199
- this.#requireDeclared(name, 'assert()');
201
+ this.#requireDeclared(name, 'assert()', step.model);
200
202
  return this.#append(step);
201
203
  }
202
204
 
@@ -314,5 +316,13 @@ export function fromPlanned(document, options = undefined) {
314
316
  }
315
317
  head.note = doc.note;
316
318
  }
319
+ if (doc.physical !== undefined) {
320
+ if (!isJsonObject(doc.physical) || !Array.isArray(doc.physical.source)
321
+ || !isJsonObject(doc.physical.dispositions) || !Array.isArray(doc.physical.assertions)) {
322
+ throw new LinqBuildError('JL0101',
323
+ 'fromPlanned() physical header needs source, dispositions and assertions', '/physical');
324
+ }
325
+ head.physical = doc.physical;
326
+ }
317
327
  return new Migration(head, doc.steps.map((step) => rawStep(step)), names);
318
328
  }
@@ -20,7 +20,7 @@ import { describeValue, requireJson } from '../json-boundary.js';
20
20
  import { LinqBuildError } from '../errors.js';
21
21
 
22
22
  /** The kinds the runner accepts, in the artifact's order. */
23
- const STEP_KINDS = ['ddl', 'jslt', 'query', 'derive', 'sql', 'rebuild'];
23
+ const STEP_KINDS = ['ddl', 'jslt', 'query', 'derive', 'sql', 'rebuild', 'table'];
24
24
  const EXPECTS = ['empty', 'ebv'];
25
25
  const NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
26
26
  /** An assertion evaluates with no externals: `p.x` cannot appear. */
@@ -61,6 +61,15 @@ function readNote(note, what) {
61
61
  return note;
62
62
  }
63
63
 
64
+ /** A current layout travels as immutable JSON, without importing the model pen. */
65
+ function withModel(step, model) {
66
+ if (model === undefined) return step;
67
+ const doc = requireJson(model, 'step model');
68
+ if (!isJsonObject(doc) || doc.$model !== '0.1')
69
+ throw new LinqBuildError('JL0101', 'a step model must be a $model 0.1 document');
70
+ return { ...step, model: copy(doc) };
71
+ }
72
+
64
73
  /** The externals proxy an assertion's predicate sees: nothing. */
65
74
  const NO_EXTERNALS = new Proxy(Object.freeze({}), {
66
75
  get(_target, prop) {
@@ -104,8 +113,10 @@ export function sqlStep(sql, note = undefined) {
104
113
  * @param {string} name
105
114
  * @param {any} spelling
106
115
  */
107
- export function transformStep(name, spelling) {
116
+ export function transformStep(name, spelling, options = undefined) {
108
117
  requireName(name, 'transform()');
118
+ if (options !== undefined && (!isJsonObject(options) || Object.keys(options).some((key) => key !== 'model')))
119
+ throw new LinqBuildError('JL0101', 'transform() options are { model? }');
109
120
  let stylesheet;
110
121
  if (typeof spelling === 'function') {
111
122
  stylesheet = [{ match: '$', body: body(spelling) }];
@@ -133,7 +144,7 @@ export function transformStep(name, spelling) {
133
144
  'transform() takes a callback (row, x) => …, a stylesheet(…) document or a rules array, '
134
145
  + `got ${describeValue(spelling)}`);
135
146
  }
136
- return { kind: 'jslt', collection: name, stylesheet };
147
+ return withModel({ kind: 'jslt', collection: name, stylesheet }, options?.model);
137
148
  }
138
149
 
139
150
  /**
@@ -145,7 +156,7 @@ export function transformStep(name, spelling) {
145
156
  * are the witness. A document is taken verbatim.
146
157
  * @param {string} name
147
158
  * @param {any} spelling
148
- * @param {{ expect?: 'empty' | 'ebv' }} [options]
159
+ * @param {{ expect?: 'empty' | 'ebv', model?: any }} [options]
149
160
  */
150
161
  export function assertStep(name, spelling, options = undefined) {
151
162
  requireName(name, 'assert()');
@@ -155,7 +166,7 @@ export function assertStep(name, spelling, options = undefined) {
155
166
  throw new LinqBuildError('JL0101', `assert() options are { expect? }, got ${describeValue(options)}`);
156
167
  }
157
168
  for (const key of Object.keys(options)) {
158
- if (key !== 'expect') throw new LinqBuildError('JL0101', `assert() does not take '${key}'`);
169
+ if (key !== 'expect' && key !== 'model') throw new LinqBuildError('JL0101', `assert() does not take '${key}'`);
159
170
  }
160
171
  if (options.expect !== undefined) {
161
172
  if (!EXPECTS.includes(options.expect)) {
@@ -180,7 +191,7 @@ export function assertStep(name, spelling, options = undefined) {
180
191
  }
181
192
  const out = { kind: 'query', collection: name, assert: query };
182
193
  if (expect === 'ebv') out.expect = 'ebv';
183
- return out;
194
+ return withModel(out, options?.model);
184
195
  }
185
196
 
186
197
  /**
@@ -219,6 +230,14 @@ export function rawStep(step) {
219
230
  }
220
231
  };
221
232
  switch (raw.kind) {
233
+ case 'table':
234
+ need('plan', isJsonObject(raw.plan) && raw.plan.version === 1
235
+ && ['id', 'table', 'checksum', 'temporary'].every((key) => typeof raw.plan[key] === 'string' && raw.plan[key].length > 0)
236
+ && typeof raw.plan.rebuild === 'boolean'
237
+ && ['source', 'after'].every((key) => Array.isArray(raw.plan[key]) && raw.plan[key].every(isJsonObject))
238
+ && ['unchanged', 'statements', 'finish'].every((key) => Array.isArray(raw.plan[key])
239
+ && raw.plan[key].every((value) => typeof value === 'string' && value.length > 0)));
240
+ break;
222
241
  case 'ddl': case 'sql':
223
242
  need('sql', typeof raw.sql === 'string' && raw.sql !== '');
224
243
  break;
@@ -240,5 +259,5 @@ export function rawStep(step) {
240
259
  need('copy', typeof raw.copy === 'string');
241
260
  need('indexes', Array.isArray(raw.indexes));
242
261
  }
243
- return raw;
262
+ return ['jslt', 'query'].includes(raw.kind) ? withModel(raw, raw.model) : raw;
244
263
  }
@@ -18,6 +18,8 @@
18
18
  * assertion's row is typed by the members the two shapes SHARE (a
19
19
  * precondition sees old rows, a postcondition new ones; what both agree
20
20
  * on is what neither lies about), annotated when one shape is meant.
21
+ * With an explicit step `model`, transform input/output and assertion
22
+ * rows use that model's layout, including names absent from the final model.
21
23
  * Every claim here has a runtime twin in
22
24
  * `test/linq/migration-pen.test.js` and a compile-level pin in
23
25
  * `test/consumer/linq-migration.ts`.
@@ -39,6 +41,8 @@ export interface JsltStep {
39
41
  /** A planner placeholder; the runner refuses it (`JD0021`). The pen never sets or clears it. */
40
42
  readonly draft?: boolean;
41
43
  readonly note?: string;
44
+ /** The immutable model for this step's current layout. */
45
+ readonly model?: object;
42
46
  }
43
47
  export interface QueryStep {
44
48
  readonly kind: 'query';
@@ -46,6 +50,7 @@ export interface QueryStep {
46
50
  readonly assert: Json;
47
51
  readonly expect?: 'empty' | 'ebv';
48
52
  readonly note?: string;
53
+ readonly model?: object;
49
54
  }
50
55
  export interface DeriveColumn {
51
56
  readonly name: string;
@@ -69,7 +74,39 @@ export interface RebuildStep {
69
74
  readonly indexes: readonly string[];
70
75
  readonly note?: string;
71
76
  }
72
- export type MigrationStep = DdlStep | SqlStep | JsltStep | QueryStep | DeriveStep | RebuildStep;
77
+ /** The complete serializable artifact returned by planTableMigration. */
78
+ export interface ReviewedTablePlan {
79
+ readonly version: 1;
80
+ readonly id: string;
81
+ readonly table: string;
82
+ readonly checksum: string;
83
+ readonly source: readonly unknown[];
84
+ readonly after: readonly unknown[];
85
+ readonly rebuild: boolean;
86
+ readonly temporary: string;
87
+ readonly unchanged: readonly string[];
88
+ readonly statements: readonly string[];
89
+ readonly finish: readonly string[];
90
+ }
91
+ export interface TableStep {
92
+ readonly kind: 'table';
93
+ readonly plan: ReviewedTablePlan;
94
+ readonly note?: string;
95
+ }
96
+ export type MigrationStep = DdlStep | SqlStep | JsltStep | QueryStep | DeriveStep | RebuildStep | TableStep;
97
+
98
+ export interface PhysicalObject {
99
+ readonly type: 'table' | 'view' | 'index' | 'trigger';
100
+ readonly name: string;
101
+ readonly owner: string;
102
+ readonly sql: string | null;
103
+ }
104
+ export interface PhysicalHeader {
105
+ readonly source: readonly PhysicalObject[];
106
+ readonly dispositions: Readonly<Record<string, 'preserve' | 'replace' | 'drop'>>;
107
+ readonly assertions: readonly { readonly sql: string; readonly params?: readonly unknown[]; readonly expected: readonly unknown[] }[];
108
+ readonly target?: { readonly objects: readonly PhysicalObject[]; readonly tables?: readonly string[] };
109
+ }
73
110
 
74
111
  /** The `$migration` 0.1 document (MIGRATION-FORMAT §2). */
75
112
  export interface MigrationDocument {
@@ -79,6 +116,7 @@ export interface MigrationDocument {
79
116
  readonly to: string;
80
117
  readonly note?: string;
81
118
  readonly steps: readonly MigrationStep[];
119
+ readonly physical?: PhysicalHeader;
82
120
  }
83
121
 
84
122
  // ————— what a model document types —————
@@ -124,7 +162,11 @@ export type SheetFor<S, New> = S extends { readonly __out: infer O }
124
162
  ? (unknown extends O ? unknown : O extends New ? unknown : never)
125
163
  : unknown;
126
164
 
127
- export interface AssertOptions {
165
+ export interface TransformOptions<Current = never> {
166
+ /** Override the final model with the layout at this step. */
167
+ readonly model?: Current;
168
+ }
169
+ export interface AssertOptions<Current = never> extends TransformOptions<Current> {
128
170
  /** `'empty'` (the default, absent from the document): no row may
129
171
  * satisfy the predicate — it names the violation; `'ebv'`: the matching
130
172
  * rows are the witness. */
@@ -141,6 +183,22 @@ export class Migration<From = unknown, To = unknown> {
141
183
  ddl(sql: string, note?: string): Migration<From, To>;
142
184
  /** One data statement spelled directly (`sql`, MIGRATION-FORMAT §9.4). */
143
185
  sql(sql: string, note?: string): Migration<From, To>;
186
+ /** With an explicit current model, both the row and result use that layout. */
187
+ transform<Current extends object, N extends DeclaredNames<Current>, V extends ExprBase<unknown> = MemberExpr<DocOf<Current, N>>>(
188
+ name: N,
189
+ rule: (row: V, x: Externals<{}, ValueOf<V>>) => Spell<DocOf<Current, N>>,
190
+ options: TransformOptions<Current> & { readonly model: Current },
191
+ ): Migration<From, To>;
192
+ transform<Current extends object, N extends DeclaredNames<Current>, S extends StylesheetLike>(
193
+ name: N,
194
+ stylesheet: S & SheetFor<S, DocOf<Current, N>>,
195
+ options: TransformOptions<Current> & { readonly model: Current },
196
+ ): Migration<From, To>;
197
+ transform<Current extends object, N extends DeclaredNames<Current>, const R extends readonly RuleDocument[]>(
198
+ name: N,
199
+ rules: R & RulesFor<R, DocOf<Current, N>>,
200
+ options: TransformOptions<Current> & { readonly model: Current },
201
+ ): Migration<From, To>;
144
202
  /** A `jslt` step: one root rule captured over the OLD row (`root`/`path`
145
203
  * as externals), whose result spells the NEW row. Over a planned
146
204
  * document it replaces the draft for `name`; otherwise it is appended
@@ -148,6 +206,7 @@ export class Migration<From = unknown, To = unknown> {
148
206
  transform<N extends DeclaredNames<To>, V extends ExprBase<unknown> = MemberExpr<DocOf<From, N>>>(
149
207
  name: N,
150
208
  rule: (row: V, x: Externals<{}, ValueOf<V>>) => Spell<DocOf<To, N>>,
209
+ options?: TransformOptions,
151
210
  ): Migration<From, To>;
152
211
  /** A `stylesheet(…)` document — its output must be the new row when it
153
212
  * is typed; a hand-written envelope is the honest top. A disposition or
@@ -155,12 +214,23 @@ export class Migration<From = unknown, To = unknown> {
155
214
  transform<N extends DeclaredNames<To>, S extends StylesheetLike>(
156
215
  name: N,
157
216
  stylesheet: S & SheetFor<S, DocOf<To, N>>,
217
+ options?: TransformOptions,
158
218
  ): Migration<From, To>;
159
219
  /** A rules array: a typed first rule must produce the new row; a
160
220
  * hand-written rule is the honest top. */
161
221
  transform<N extends DeclaredNames<To>, const R extends readonly RuleDocument[]>(
162
222
  name: N,
163
223
  rules: R & RulesFor<R, DocOf<To, N>>,
224
+ options?: TransformOptions,
225
+ ): Migration<From, To>;
226
+ /** An explicit current model types a historical assertion's row. */
227
+ assert<Current extends object, N extends DeclaredNames<Current>, V extends ExprBase<unknown> = MemberExpr<DocOf<Current, N>>>(
228
+ name: N,
229
+ predicate: (row: V) => BoolExpr | boolean,
230
+ options: AssertOptions<Current> & { readonly model: Current },
231
+ ): Migration<From, To>;
232
+ assert<Current extends object, N extends DeclaredNames<Current>>(
233
+ name: N, query: Json, options: AssertOptions<Current> & { readonly model: Current },
164
234
  ): Migration<From, To>;
165
235
  /** A `query` step over `name`'s rows: the predicate names the VIOLATION
166
236
  * (`expect: 'empty'`, the default) or the witness (`'ebv'`); the row is