@jarenjs/linq 0.73.0 → 0.83.2

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.
@@ -16,9 +16,9 @@ A **pen** is a by-code front-end to one of the suite's document formats:
16
16
  named functions that build a standard document — a JSON Schema, a
17
17
  `$model`, a `$jslt` stylesheet — the way the chain builds a query
18
18
  document. `@jarenjs/linq` exports each pen under its own subpath
19
- (<!--fact:coverage.subpaths-->`./ai`, `./app`, `./charts`, `./contract`, `./db`, `./flow`, `./forms`, `./jslt`, `./jtlt`, `./messages`, `./migration`, `./model`, `./project`, `./schema`<!--/fact-->); `.` stays the chain.
19
+ (<!--fact:coverage.subpaths-->`./ai`, `./app`, `./charts`, `./contract`, `./db`, `./flow`, `./forms`, `./formula`, `./jslt`, `./jtlt`, `./messages`, `./migration`, `./model`, `./project`, `./schema`<!--/fact-->); `.` stays the chain.
20
20
 
21
- Coverage: <!--fact:coverage.pens-->14 public pen/client subpaths beside the chain; 69/69 owned schema keywords have dedicated emission routes.<!--/fact-->
21
+ Coverage: <!--fact:coverage.pens-->15 public pen/client subpaths beside the chain; 69/69 owned schema keywords have dedicated emission routes.<!--/fact-->
22
22
 
23
23
  **This document is the family's normative reference**: §1 states the
24
24
  rules every pen keeps, §1.3 the error codes they share, and §4–§7 the
@@ -36,10 +36,10 @@ 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) | 948 | 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
- | [QUERY-PEN.md](QUERY-PEN.md) | 1,725 | 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 |
39
+ | [LINQ-FORMAT.md](LINQ-FORMAT.md) | 965 | 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
+ | [QUERY-PEN.md](QUERY-PEN.md) | 1,752 | 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
- | [MODEL-PEN.md](MODEL-PEN.md) | 1,092 | `./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 |
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
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 |
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 |
@@ -49,9 +49,10 @@ is the index of those guides, and it is how a reader reaches any of them.
49
49
  | [AI-PEN.md](AI-PEN.md) | 98 | `./ai` — the public action program over environment slots. **Read it when** you want typed fixtures or host-authored programs without a model client. |
50
50
  | [MESSAGES-PEN.md](MESSAGES-PEN.md) | 105 | `./messages` — JSON message catalogs and message references. **Read it when** you want checked translation keys, placeholders and explicit completeness. |
51
51
  | [JTLT-PEN.md](JTLT-PEN.md) | 83 | `./jtlt` — text templates with JSLT dispatch and query expressions. **Read it when** you want to author Markdown, XML or source text as portable JSON. |
52
- | [PROJECT-PEN.md](PROJECT-PEN.md) | 75 | `./project` — Studio projects with named, typed files. **Read it when** you want a portable editor workspace containing documents written by several pens. |
52
+ | [PROJECT-PEN.md](PROJECT-PEN.md) | 81 | `./project` — Studio projects with named, typed files. **Read it when** you want a portable editor workspace containing documents written by several pens. |
53
53
  | [CHARTS-PEN.md](CHARTS-PEN.md) | 94 | `./charts` — chart-definition documents for every chart kind. **Read it when** you want typed chart data and presentation options that `compileChart` consumes. |
54
- | [DB-CLIENT.md](DB-CLIENT.md) | 893 | `./db` — the client: the store's typed front door, not a pen, and the package's one runtime edge. **Read it when** you are reading or writing rows: `load`, `include`, `link`/`unlink`, `live` |
54
+ | [DB-CLIENT.md](DB-CLIENT.md) | 1,085 | `./db` — the client: the store's typed front door, not a pen, and the package's one runtime edge. **Read it when** you are reading or writing rows: `load`, `include`, `link`/`unlink`, `live` |
55
+ | [FORMULA-PEN.md](FORMULA-PEN.md) | 41 | `./formula` — author versioned saved JSON Query profiles without executing them. |
55
56
  <!--/fact-->
56
57
 
57
58
  Every row of that table is derived, and none of it is written here: the
@@ -204,23 +205,24 @@ and the bundle is the byte count the tree-shaking probe builds.
204
205
  <!--fact:pens.census-->
205
206
  | Document | Subpath | Lines | Mapping rows | Worked examples | Refusals | Bundle |
206
207
  |---|---|---:|---:|---:|---:|---:|
207
- | [LINQ-FORMAT.md](LINQ-FORMAT.md) | — | 948 | — | — | — | — |
208
- | [QUERY-PEN.md](QUERY-PEN.md) | `.` | 1,725 | 34 | 8 | 15 | 174,131 B |
209
- | [SCHEMA-PEN.md](SCHEMA-PEN.md) | `./schema` | 1,205 | 82 | 10 | 4 | 36,717 B |
210
- | [MODEL-PEN.md](MODEL-PEN.md) | `./model` | 1,092 | 28 | 6 | 3 | 45,143 B |
211
- | [JSLT-PEN.md](JSLT-PEN.md) | `./jslt` | 955 | 17 | 8 | 3 | 19,856 B |
212
- | [MIGRATION-PEN.md](MIGRATION-PEN.md) | `./migration` | 781 | 11 | 5 | 4 | 24,259 B |
213
- | [CONTRACT-PEN.md](CONTRACT-PEN.md) | `./contract` | 1,221 | 38 | 6 | 3 | 48,859 B |
214
- | [FLOW-PEN.md](FLOW-PEN.md) | `./flow` | 1,033 | 16 | 7 | 3 | 19,910 B |
215
- | [APP-PEN.md](APP-PEN.md) | `./app` | 1,143 | 22 | 7 | 3 | 51,006 B |
216
- | [FORMS-PEN.md](FORMS-PEN.md) | `./forms` | 940 | 18 | 6 | 3 | 40,873 B |
217
- | [AI-PEN.md](AI-PEN.md) | `./ai` | 98 | 12 | 1 | 3 | 16,904 B |
218
- | [MESSAGES-PEN.md](MESSAGES-PEN.md) | `./messages` | 105 | 9 | 2 | 1 | 18,337 B |
219
- | [JTLT-PEN.md](JTLT-PEN.md) | `./jtlt` | 83 | 13 | 1 | 2 | 16,725 B |
220
- | [PROJECT-PEN.md](PROJECT-PEN.md) | `./project` | 75 | 9 | 1 | 1 | 15,132 B |
221
- | [CHARTS-PEN.md](CHARTS-PEN.md) | `./charts` | 94 | 21 | 1 | 1 | 17,014 B |
222
- | [DB-CLIENT.md](DB-CLIENT.md) | `./db` | 893 | 41 | 4 | 2 | 627,769 B |
223
- | **16 documents** | | **12,391** | **371** | **73** | | |
208
+ | [LINQ-FORMAT.md](LINQ-FORMAT.md) | — | 965 | — | — | — | — |
209
+ | [QUERY-PEN.md](QUERY-PEN.md) | `.` | 1,752 | 34 | 8 | 15 | 175,221 B |
210
+ | [SCHEMA-PEN.md](SCHEMA-PEN.md) | `./schema` | 1,205 | 82 | 10 | 4 | 35,285 B |
211
+ | [MODEL-PEN.md](MODEL-PEN.md) | `./model` | 1,116 | 30 | 6 | 3 | 44,143 B |
212
+ | [JSLT-PEN.md](JSLT-PEN.md) | `./jslt` | 955 | 17 | 8 | 3 | 18,424 B |
213
+ | [MIGRATION-PEN.md](MIGRATION-PEN.md) | `./migration` | 781 | 11 | 5 | 4 | 22,827 B |
214
+ | [CONTRACT-PEN.md](CONTRACT-PEN.md) | `./contract` | 1,221 | 38 | 6 | 3 | 47,427 B |
215
+ | [FLOW-PEN.md](FLOW-PEN.md) | `./flow` | 1,033 | 16 | 7 | 3 | 18,478 B |
216
+ | [APP-PEN.md](APP-PEN.md) | `./app` | 1,143 | 22 | 7 | 3 | 49,574 B |
217
+ | [FORMS-PEN.md](FORMS-PEN.md) | `./forms` | 940 | 18 | 6 | 3 | 39,441 B |
218
+ | [AI-PEN.md](AI-PEN.md) | `./ai` | 98 | 12 | 1 | 3 | 15,472 B |
219
+ | [MESSAGES-PEN.md](MESSAGES-PEN.md) | `./messages` | 105 | 9 | 2 | 1 | 16,931 B |
220
+ | [JTLT-PEN.md](JTLT-PEN.md) | `./jtlt` | 83 | 13 | 1 | 2 | 15,293 B |
221
+ | [PROJECT-PEN.md](PROJECT-PEN.md) | `./project` | 81 | 9 | 1 | 1 | 13,793 B |
222
+ | [CHARTS-PEN.md](CHARTS-PEN.md) | `./charts` | 94 | 21 | 1 | 1 | 15,582 B |
223
+ | [DB-CLIENT.md](DB-CLIENT.md) | `./db` | 1,085 | 46 | 4 | 2 | 675,752 B |
224
+ | [FORMULA-PEN.md](FORMULA-PEN.md) | `./formula` | 41 | 2 | | | 14,850 B |
225
+ | **17 documents** | | **12,698** | **380** | **73** | | |
224
226
  <!--/fact-->
225
227
 
226
228
  A pen whose mapping rows are far below its worked examples is a pen
@@ -262,21 +264,22 @@ it and each document publishes it. The rounded column is what
262
264
  <!--fact:pens.cost-->
263
265
  | Subpath | Document | Bundle | Rounded |
264
266
  |---|---|---:|---:|
265
- | `@jarenjs/linq` | [QUERY-PEN.md](QUERY-PEN.md) | 174,131 B | 174 kB |
266
- | `@jarenjs/linq/schema` | [SCHEMA-PEN.md](SCHEMA-PEN.md) | 36,717 B | 37 kB |
267
- | `@jarenjs/linq/model` | [MODEL-PEN.md](MODEL-PEN.md) | 45,143 B | 45 kB |
268
- | `@jarenjs/linq/jslt` | [JSLT-PEN.md](JSLT-PEN.md) | 19,856 B | 20 kB |
269
- | `@jarenjs/linq/migration` | [MIGRATION-PEN.md](MIGRATION-PEN.md) | 24,259 B | 24 kB |
270
- | `@jarenjs/linq/contract` | [CONTRACT-PEN.md](CONTRACT-PEN.md) | 48,859 B | 49 kB |
271
- | `@jarenjs/linq/flow` | [FLOW-PEN.md](FLOW-PEN.md) | 19,910 B | 20 kB |
272
- | `@jarenjs/linq/app` | [APP-PEN.md](APP-PEN.md) | 51,006 B | 51 kB |
273
- | `@jarenjs/linq/forms` | [FORMS-PEN.md](FORMS-PEN.md) | 40,873 B | 41 kB |
274
- | `@jarenjs/linq/ai` | [AI-PEN.md](AI-PEN.md) | 16,904 B | 17 kB |
275
- | `@jarenjs/linq/messages` | [MESSAGES-PEN.md](MESSAGES-PEN.md) | 18,337 B | 18 kB |
276
- | `@jarenjs/linq/jtlt` | [JTLT-PEN.md](JTLT-PEN.md) | 16,725 B | 17 kB |
277
- | `@jarenjs/linq/project` | [PROJECT-PEN.md](PROJECT-PEN.md) | 15,132 B | 15 kB |
278
- | `@jarenjs/linq/charts` | [CHARTS-PEN.md](CHARTS-PEN.md) | 17,014 B | 17 kB |
279
- | `@jarenjs/linq/db` | [DB-CLIENT.md](DB-CLIENT.md) | 627,769 B | 628 kB |
267
+ | `@jarenjs/linq` | [QUERY-PEN.md](QUERY-PEN.md) | 175,221 B | 175 kB |
268
+ | `@jarenjs/linq/schema` | [SCHEMA-PEN.md](SCHEMA-PEN.md) | 35,285 B | 35 kB |
269
+ | `@jarenjs/linq/model` | [MODEL-PEN.md](MODEL-PEN.md) | 44,143 B | 44 kB |
270
+ | `@jarenjs/linq/jslt` | [JSLT-PEN.md](JSLT-PEN.md) | 18,424 B | 18 kB |
271
+ | `@jarenjs/linq/migration` | [MIGRATION-PEN.md](MIGRATION-PEN.md) | 22,827 B | 23 kB |
272
+ | `@jarenjs/linq/contract` | [CONTRACT-PEN.md](CONTRACT-PEN.md) | 47,427 B | 47 kB |
273
+ | `@jarenjs/linq/flow` | [FLOW-PEN.md](FLOW-PEN.md) | 18,478 B | 18 kB |
274
+ | `@jarenjs/linq/app` | [APP-PEN.md](APP-PEN.md) | 49,574 B | 50 kB |
275
+ | `@jarenjs/linq/forms` | [FORMS-PEN.md](FORMS-PEN.md) | 39,441 B | 39 kB |
276
+ | `@jarenjs/linq/ai` | [AI-PEN.md](AI-PEN.md) | 15,472 B | 15 kB |
277
+ | `@jarenjs/linq/messages` | [MESSAGES-PEN.md](MESSAGES-PEN.md) | 16,931 B | 17 kB |
278
+ | `@jarenjs/linq/jtlt` | [JTLT-PEN.md](JTLT-PEN.md) | 15,293 B | 15 kB |
279
+ | `@jarenjs/linq/project` | [PROJECT-PEN.md](PROJECT-PEN.md) | 13,793 B | 14 kB |
280
+ | `@jarenjs/linq/charts` | [CHARTS-PEN.md](CHARTS-PEN.md) | 15,582 B | 16 kB |
281
+ | `@jarenjs/linq/db` | [DB-CLIENT.md](DB-CLIENT.md) | 675,752 B | 676 kB |
282
+ | `@jarenjs/linq/formula` | [FORMULA-PEN.md](FORMULA-PEN.md) | 14,850 B | 15 kB |
280
283
  <!--/fact-->
281
284
 
282
285
  Read these as prices, not as scores. `./db` is the largest by an order of
@@ -524,6 +527,8 @@ it says.
524
527
  | `.updated()` | `default: 'updated'` — a stamp on insert AND on every update | marks it `generated` | native |
525
528
  | `.fill(value)` | `default: { value }` — a literal, filled when absent; the value crosses the JSON boundary (`requireJson`) | marks it `generated` | native; a value that is not JSON is `JL0101` |
526
529
  | `.compute(fn)`, `.compute(query)` | `default: { query }` — captured over the document being written (`$`), or a query document verbatim | marks it `generated` | native; a captured rule that binds ANY external is `JL0104` |
530
+ | `.physical(layout)` | `physical` on the entity declaration | preserves entity types; explicit column codecs | native on an object builder |
531
+ | `.invariants(rules)` | `invariants` on the entity declaration | explicit writer qualification | native on an object builder |
527
532
  | `.renamedFrom(name)` | `x-rename: name` on the ENTITY (or collection) declaration — a planning hint the migration planner reads, never part of the shape (MIGRATION-FORMAT §3) | — | native on the declaration's own builder; on a member, `JL0102` (the document has no place for one) |
528
533
  | `.meta(annotations)` | as the schema pen ([SCHEMA-PEN.md](SCHEMA-PEN.md#28-annotations-and-messages)), minus one key | — | refused (`JL0104`) for `x-entity`: the pen owns that keyword |
529
534
  | `.entity(patch)` | the patch, merged into `x-entity` — the primitive every row above is written in terms of, and the way to spell a member of the vocabulary that has no method of its own | — (it sets no flag; the named methods do — §5.2) | native; a member outside the closed vocabulary, `JL0102` |
@@ -844,8 +849,8 @@ it says.
844
849
 
845
850
  | Method | Emits | Type | Status |
846
851
  |---|---|---|---|
847
- | `file(name, kind, text)` | `{ name, kind, text }`, preserving text | literal name/kind | native |
848
- | `jsonFile(name, kind, document)` | same file, with serialized JSON text | literal name/kind | native |
852
+ | `file(name, kind, text, options?)` | `{ name, kind, text }` and optional routing/import members, preserving text | literal name/kind | native |
853
+ | `jsonFile(name, kind, document, options?)` | same file, with serialized JSON text | literal name/kind | native |
849
854
  | `defineProject(files?, options?)` | version, files, optional active/layout | names from files | native |
850
855
  | `.files(files)` | replacement file list | replaces known names | native |
851
856
  | `.file(file)` | appended file | adds its name | native |
@@ -903,17 +908,22 @@ it says.
903
908
  | `defineReplication(header)` | a logical replication document builder — §2.7 | `ReplicationPen` |
904
909
  | `defaultValidator()` | `new JarenValidator({ collectErrors: true })` with `stringFormats` and `dateTimeFormats` registered | `JarenValidator` |
905
910
  | `createDbLedger(client, options?)` | the contract idempotency ledger (`claim`/`commit`/`fail`/`lookup`/`sweep`) over a declared collection of the client's store — §2.6 | `DbLedger`; structurally `@jarenjs/contract`'s `Ledger` |
911
+ | `createDbReceipts(client, options)` | permanent receipts and independent fenced leases; see durable mapped records | structural receipt repository |
912
+ | `createDbEffectStore(client, options)` | reviewed external intent and reconciliation under job fences | structural effect store |
913
+ | `createDbRunStore(client, options)` | mapped workflow checkpoints and bounded revision event pages | structural run store |
914
+ | `createDbIngestionStore(client, options)` | atomic page/checkpoint staging and complete snapshot publication; see [complete ingestion store](DB-CLIENT.md#complete-ingestion-store) | structural ingestion store |
915
+ | `createDbRangeProvider(store, entity, spec, options)` | a bounded structural range source over keyset pages and committed capture; also `handle.range(spec, options)` — [COLLECTION-PROVIDER.md](../../app/docs/COLLECTION-PROVIDER.md) | `Promise<DbRangeProvider>` |
906
916
 
907
917
  **The entity handle**
908
918
 
909
919
  | Group | Members |
910
920
  |---|---|
911
- | the unit of work | `create` `get` `update` `delete` `add` `put` `remove` `discard` `asNoTracking` |
921
+ | the unit of work | `create` `get` `update` `delete` `add` `put` `remove` `discard` `asNoTracking`; `mutate` executes an untracked [native column mutation](../../db/docs/NATIVE-PLANS.md) |
912
922
  | the store's reads | `load` `explainLoad` `execute` |
913
923
  | the provider seam | `root` `scope` `relations` |
914
924
  | membership | `link` `unlink` — the store's, behind §4.2's check |
915
925
  | the chain | every `AsyncSequence` operator and terminal: `where` `select` `selectMany` `orderBy` `orderByDescending` `thenBy` `thenByDescending` `groupBy` `aggregate` `join` `groupJoin` `skip` `take` `distinct` `reverse` `concat` `defaultIfEmpty` `ofType` `cast` `zip` `mapAsync` `params` `toDocument` `toArray` `first` `firstOrDefault` `single` `singleOrDefault` `last` `lastOrDefault` `elementAt` `elementAtOrDefault` `count` `sum` `average` `min` `max` `any` `all`, and `Symbol.asyncIterator` |
916
- | the client's own | `include` (§2.4) and `live` |
926
+ | the client's own | `include` (§2.4), `live`, and `range(spec, options)` |
917
927
  | in both | `explain` |
918
928
 
919
929
  **The graph**
@@ -945,4 +955,11 @@ it says.
945
955
  | `maxRows`, `maxBytes` | `maxRows`, `maxBytes` | the per-root bounds (MODEL-FORMAT §10.4): rows of the relation per parent and serialised bytes per parent; crossing one is the store's `JD2073`, never a truncated graph. Defaults 1000 rows / 1 MiB (a `take` is the row bound of the include it windows); `Infinity` spells the unbounded case and emits as `null` |
946
956
  | `include: { comments: spec }` | `include: { comments: <lowered> }` | over the TARGET's relation table (the scope carries every root's) |
947
957
  | anything else | `JL0101` | the vocabulary is closed; `after` paginates the root, never an include |
958
+
959
+ ### Formula document authoring — [FORMULA-PEN.md §2](FORMULA-PEN.md)
960
+
961
+ | Call / option | Emitted member |
962
+ |---|---|
963
+ | `defineFormula(id, expression)` | `$formula`, `id`, `expression`, default `revision` |
964
+ | `revision`, `bindings`, schema/helper references, result mode | Same named profile members |
948
965
  <!--/fact-->
@@ -100,6 +100,6 @@ gate compares generated files and all locale parameter sets with their sources.
100
100
 
101
101
  ## 7. Cost
102
102
 
103
- The isolated messages pen costs **<!--fact:bundle.messages-->18,337<!--/fact--> bytes**.
103
+ The isolated messages pen costs **<!--fact:bundle.messages-->16,931<!--/fact--> bytes**.
104
104
  It carries the shared template compiler and generated vocabulary, with no locale,
105
105
  validator, forms or contract engine and no query chain.
@@ -745,10 +745,10 @@ from a drop plus a create, and guessing risks silent data loss.
745
745
 
746
746
  ## 7. Cost
747
747
 
748
- `@jarenjs/linq/migration` builds to **<!--fact:bundle.migration-->24,259<!--/fact--> bytes** as a minified,
748
+ `@jarenjs/linq/migration` builds to **<!--fact:bundle.migration-->22,827<!--/fact--> bytes** as a minified,
749
749
  tree-shaken ESM bundle — the figure `scripts/check-tree-shaking.js`
750
750
  measures and `npm run test:tree-shaking` reports, published rounded
751
- (<!--fact:bundle.migration.kb-->24<!--/fact--> kB) beside the other nine subpath prices in
751
+ (<!--fact:bundle.migration.kb-->23<!--/fact--> kB) beside the other nine subpath prices in
752
752
  [docs/CONSUMING.md](../../../docs/CONSUMING.md).
753
753
 
754
754
  The probe is a gate, not a report: building a two-step migration as a
@@ -778,4 +778,4 @@ proxy behind it, and the canonicalizer — and the two model documents
778
778
  themselves arrive as data, deep-frozen JSON that the consumer's own model
779
779
  module built. So a consumer who ships migrations to a browser does not
780
780
  ship the model pen with them; a consumer who OPENS a store does, and pays
781
- `./model`'s <!--fact:bundle.model-->45,143<!--/fact--> bytes for it.
781
+ `./model`'s <!--fact:bundle.model-->44,143<!--/fact--> bytes for it.
package/docs/MODEL-PEN.md CHANGED
@@ -135,6 +135,8 @@ leaves `key` where it was and replaces `default`.
135
135
  | `.updated()` | `default: 'updated'` — a stamp on insert AND on every update | marks it `generated` | native |
136
136
  | `.fill(value)` | `default: { value }` — a literal, filled when absent; the value crosses the JSON boundary (`requireJson`) | marks it `generated` | native; a value that is not JSON is `JL0101` |
137
137
  | `.compute(fn)`, `.compute(query)` | `default: { query }` — captured over the document being written (`$`), or a query document verbatim | marks it `generated` | native; a captured rule that binds ANY external is `JL0104` |
138
+ | `.physical(layout)` | `physical` on the entity declaration | preserves entity types; explicit column codecs | native on an object builder |
139
+ | `.invariants(rules)` | `invariants` on the entity declaration | explicit writer qualification | native on an object builder |
138
140
  | `.renamedFrom(name)` | `x-rename: name` on the ENTITY (or collection) declaration — a planning hint the migration planner reads, never part of the shape (MIGRATION-FORMAT §3) | — | native on the declaration's own builder; on a member, `JL0102` (the document has no place for one) |
139
141
  | `.meta(annotations)` | as the schema pen ([SCHEMA-PEN.md](SCHEMA-PEN.md#28-annotations-and-messages)), minus one key | — | refused (`JL0104`) for `x-entity`: the pen owns that keyword |
140
142
  | `.entity(patch)` | the patch, merged into `x-entity` — the primitive every row above is written in terms of, and the way to spell a member of the vocabulary that has no method of its own | — (it sets no flag; the named methods do — §5.2) | native; a member outside the closed vocabulary, `JL0102` |
@@ -1062,10 +1064,10 @@ catch them:
1062
1064
 
1063
1065
  ## 7. Cost
1064
1066
 
1065
- `@jarenjs/linq/model` builds to **<!--fact:bundle.model-->45,143<!--/fact--> bytes** as a minified,
1067
+ `@jarenjs/linq/model` builds to **<!--fact:bundle.model-->44,143<!--/fact--> bytes** as a minified,
1066
1068
  tree-shaken ESM bundle — the figure `scripts/check-tree-shaking.js`
1067
1069
  measures and `npm run test:tree-shaking` reports, published rounded
1068
- (<!--fact:bundle.model.kb-->45<!--/fact--> kB) beside the other nine subpath prices in
1070
+ (<!--fact:bundle.model.kb-->44<!--/fact--> kB) beside the other nine subpath prices in
1069
1071
  [docs/CONSUMING.md](../../../docs/CONSUMING.md).
1070
1072
 
1071
1073
  The probe is a gate, not a report: building a two-member model as a
@@ -1083,10 +1085,32 @@ them:
1083
1085
  byte of `packages/linq/src/model/`, because the subclasses are built by
1084
1086
  this subpath rather than patched onto the base classes.
1085
1087
 
1086
- The price above the schema pen's <!--fact:bundle.schema-->36,717<!--/fact--> is about 8 kB: the mixin, the
1088
+ The price above the schema pen's <!--fact:bundle.schema-->35,285<!--/fact--> is about 8 kB: the mixin, the
1087
1089
  three relation factories, `collection()`/`index()` with their capture,
1088
1090
  `defineModel()` — and the refusal MESSAGES, which are most of what §4
1089
1091
  costs. That is a deliberate trade: naming the rule and the spelling that
1090
1092
  works is why a mapping mistake is a `JL0102` at build rather than a
1091
1093
  `JD0005` at `openStore`, so the ceiling is raised with the reason and the
1092
1094
  text is not shaved.
1095
+
1096
+ ## Existing column layouts and persistence rules
1097
+
1098
+ An entity object builder's `.physical({ table, kind?, keys?, columns })` writes
1099
+ an explicit column-only layout onto the entity declaration. Each column names
1100
+ its physical identifier, codec, SQL NULL policy, and optional database default
1101
+ or generated ownership. `.invariants(rules)` writes application-declared predicates
1102
+ with explicit database/store enforcement. The pen emits declarations; the database
1103
+ package owns validation, codecs, SQL lowering and preservation planning. See
1104
+ [MODEL-FORMAT](../../db/docs/MODEL-FORMAT.md#12-existing-column-layouts).
1105
+
1106
+ ```js
1107
+ import * as m from '@jarenjs/linq/model';
1108
+ const model = m.defineModel({ entities: {
1109
+ Setting: m.object({ id: m.string().key(), value: m.string() }).physical({
1110
+ table: 'app_settings', columns: {
1111
+ id: { name: 'key', codec: 'text', null: 'reject' },
1112
+ value: { name: 'value', codec: 'text', null: 'reject' }
1113
+ }
1114
+ })
1115
+ } });
1116
+ ```
@@ -17,8 +17,8 @@ layout defaults. `validateFile()` checks each file through its own engine.
17
17
 
18
18
  | Method | Emits | Type | Status |
19
19
  |---|---|---|---|
20
- | `file(name, kind, text)` | `{ name, kind, text }`, preserving text | literal name/kind | native |
21
- | `jsonFile(name, kind, document)` | same file, with serialized JSON text | literal name/kind | native |
20
+ | `file(name, kind, text, options?)` | `{ name, kind, text }` and optional routing/import members, preserving text | literal name/kind | native |
21
+ | `jsonFile(name, kind, document, options?)` | same file, with serialized JSON text | literal name/kind | native |
22
22
  | `defineProject(files?, options?)` | version, files, optional active/layout | names from files | native |
23
23
  | `.files(files)` | replacement file list | replaces known names | native |
24
24
  | `.file(file)` | appended file | adds its name | native |
@@ -42,6 +42,12 @@ Call `parseProject(project.schema)` from `@jarenjs/studio`. A parsed nonempty
42
42
  project round-trips through JSON and the parser unchanged. Empty projects are
43
43
  valid; the parser represents their absent active file as `null` internally.
44
44
 
45
+ File options preserve `imports`, `input`, `model` and `collection`. For example,
46
+ `jsonFile('app', 'app', { view: [] }, { imports: { state: 'seed' } })` supplies
47
+ state from another file; `file('q', 'query', '"$"', { model: 'store', collection:
48
+ 'notes' })` routes a query to a store. The pen snapshots these values without
49
+ resolving them. Studio checks missing references, cycles and execution kinds.
50
+
45
51
  ## 4. Refusals
46
52
 
47
53
  | Code | Condition |
@@ -57,7 +63,7 @@ pen preserves these behaviors and does not carry a second project validator.
57
63
 
58
64
  `ProjectBuilder<Names>` tracks file names only in declarations. `FILE_KINDS`
59
65
  contains the schema's file-kind vocabulary, checked against Studio's own set.
60
- `ProjectFile<Name, Kind>`, `FileKind`, `ProjectLayout` and `ProjectDocument` are
66
+ `ProjectFile<Name, Kind>`, `ProjectFileOptions`, `FileKind`, `ProjectLayout` and `ProjectDocument` are
61
67
  types. `JsonInput` accepts readonly structural documents and leaves unknown
62
68
  schema extension values to the runtime JSON check. `.files()` replaces the name union and `.file()` widens it. `.active()`
63
69
  refuses an undeclared literal name in TypeScript. `from()` deliberately keeps
@@ -71,5 +77,5 @@ app rendering and contract checks remain the file engines' responsibilities.
71
77
 
72
78
  ## 7. Cost
73
79
 
74
- The isolated project pen costs **<!--fact:bundle.project-->15,132<!--/fact--> bytes**.
80
+ The isolated project pen costs **<!--fact:bundle.project-->13,793<!--/fact--> bytes**.
75
81
  Its tree probe excludes Studio, other target engines and the query chain.
package/docs/QUERY-PEN.md CHANGED
@@ -472,8 +472,9 @@ time:
472
472
  emits `{ "$for": { "it": "$.Post[*]", "it2": "$.User[*]" }, "$where":
473
473
  { "$eq": ["$it.authorId", "$it2.id"] }, "$return": "$it" }` — the shape
474
474
  the store's translator answers in ONE statement when the result is a
475
- bare binding (MODEL-FORMAT §10.2), and the declared residual over both
476
- fetched roots when it is a projection (§10.6). `concat` stays
475
+ bare binding or a projection tree of member paths and constants
476
+ (MODEL-FORMAT §10.2). An opaque projection remains a declared residual
477
+ over the fetched roots (§10.6). `concat` stays
477
478
  same-source even within a scope: one input per document. A scope MAY
478
479
  carry `relations` — the relation tables of every root of the scope,
479
480
  keyed by root name (a store's does) — which is where a chained hop
@@ -565,6 +566,7 @@ Runtime errors (`LinqRuntimeError`):
565
566
  | `JL2005` | a push queue was fed after it ended |
566
567
  | `JL2006` | a provider answered an element terminal with something other than one array |
567
568
  | `JL2007` | a ledger settlement named a ref that settles no started record — `createDbLedger`'s fence ([DB-CLIENT.md §2.6](DB-CLIENT.md#26-the-ledger)) |
569
+ | `JL2009` | a durable mapped record violated identity, retention or fencing |
568
570
  | `JL2008` | a federated fetch reached its row or byte budget (§12.1) |
569
571
 
570
572
  Engine errors (`JQ…`) from a hand-written `fromDocument` document pass
@@ -732,46 +734,55 @@ const rows = await fromAsync(fed.source('orders'))
732
734
  spaces or other characters), and all of a federation's sources share one scope — which
733
735
  is exactly what admits the join. The federation is what executes it:
734
736
 
735
- 1. each side's own document the filters and the projection the chain
736
- already packed per side runs against ITS source, over that
737
- source's own root;
738
- 2. the smaller side (by declared `estimatedRows`, else the first named)
739
- is streamed into a hash table keyed by the join key, counting rows
740
- and serialized bytes against the budget as it fills;
741
- 3. the other side is streamed and PROBED: a row whose key no build row
742
- carries cannot pair, so it is dropped before it costs anything;
743
- 4. the caller's own document runs in the engine over the two reduced
744
- sets the **resident join**, which is what decides.
745
-
746
- Step 4 is the contract. This boundary spells no join semantics of its
747
- own: the engine's `$eq` decides which rows pair, its ordering orders
748
- them, its projection shapes them. The hash table bounds the FETCH and
749
- nothing else, so a reduction that cannot key a value (a compound join
750
- key) keeps the row rather than guessing at it.
751
-
752
- `fed.source(name).explain(document)` answers the plan without running
753
- any of it: the `strategy`, the `budget`, the `build` and `probe` sides
754
- with their estimates, their own documents and whether each streams, and
755
- the `resident` document the engine answers.
756
-
757
- **A budget is a refusal, not a spill.** A side that reaches `maxRows` or
758
- `maxBytes` stops at the row that WOULD have broken it and raises
759
- `JL2008`; every cursor the call opened is closed exactly once, whether
760
- it answered, refused, failed or was aborted. A `signal` on the call is
761
- read at the ROW boundary where a cursor can be let go without
762
- abandoning a pull the source is still inside and a declared
763
- `estimatedRows` decides only which side BUILDS, never how much is held:
764
- a source that under-reports is refused by the budget all the same. A source offering a
765
- cursor (§12) is pulled row by row, so the bound is enforced before the
766
- memory is spent; one offering only `execute` answers whole, and
767
- `explain()` says `buffered` rather than pretending otherwise.
768
-
769
- Refused, by name: more than two sides in one federated document, a
770
- binding over a root the federation does not carry, and a join with no
771
- equality between one member of each side without one the fetch is the
772
- cross product of two sources, which is what the budget exists to
773
- refuse. Non-goals: no spill, no distributed transaction, no
774
- cross-source write.
737
+ 1. Each side's own filters and projection run at its source. Cursor sources
738
+ receive that document directly; buffered sources receive an array wrapper
739
+ so array-valued projected rows retain their item boundaries.
740
+ 2. Mandatory equality predicates form a connected binding graph. The smallest
741
+ declared `estimatedRows` starts the fetch. Each subsequent side is the
742
+ smallest estimated side connected to an already fetched side. Missing or
743
+ equal estimates preserve binding declaration order. Equality inside an OR
744
+ does not establish an edge; disconnected bindings refuse `JL0005` before
745
+ any source opens.
746
+ 3. Each next side is reduced against the retained keys of its already fetched
747
+ neighbours. Singular member/index paths are supported, including quoted
748
+ names and negative array indexes. Compound key values are conservatively
749
+ retained for the engine to compare.
750
+ 4. The original query engine decides the result over those sets, in the
751
+ original binding order. Fetch order never changes tuple or result order.
752
+ Packed joins emitted by successive `.join()` calls execute inside out;
753
+ their projected results become bounded intermediate sides. Aliases of one
754
+ source retain independent fetched sets.
755
+
756
+ `explain(document)` reports `strategy`, per-side `budget`, `combinedBudget`,
757
+ `order` (the ordered side descriptions), and `resident.document`. Each side
758
+ names its source, estimate, join key, child document and `row`/`buffered`
759
+ streaming mode. Nested sides additionally expose `children`. `build` and
760
+ `probe` remain the first two sides for compatibility.
761
+
762
+ **Budgets are admission credits.** `maxRows` and `maxBytes` apply separately
763
+ at each source or intermediate. `maxTotalRows` and `maxTotalBytes` cover all
764
+ retained source and intermediate admissions in the entire call, and default
765
+ to twice their corresponding per-side budget. All four are positive safe
766
+ integers. Credits are cumulative: freeing an intermediate's inputs does not
767
+ refund them. This conservative rule makes a nested plan's total explicit.
768
+
769
+ A cursor refuses with `JL2008` before retaining the row that would exceed a
770
+ credit. Buffered sources are checked after they produce their array. Nested
771
+ resident phrases additionally cap `sequenceItems` and `resultItems` at
772
+ `maxRows` (or a stricter caller limit); exceeding those intermediate limits
773
+ raises `JL2008`. Intermediate bytes are checked after projection. These are
774
+ bounds on admitted data and intermediate item counts, **not** a promise to
775
+ bound a buffered provider's heap, the size of one constructed value, or the
776
+ final resident result. A caller can also supply query-engine limits for that
777
+ final result. Estimates affect planning only, never admission.
778
+
779
+ Every opened cursor closes exactly once on success, budget refusal, child
780
+ failure or cancellation. `signal` is checked at row boundaries and passed to
781
+ providers. Cleanup failures preserve a primary execution failure and never
782
+ strand another opened cursor.
783
+
784
+ There is no spill, distributed transaction or cross-source write. A merge
785
+ strategy remains unavailable because providers declare no ordering guarantee.
775
786
 
776
787
  ## 13. Worked examples
777
788
 
@@ -1195,7 +1206,7 @@ proves that too, so the exclusion cannot hide a chain refusal).
1195
1206
  | `JL2004` | a provider's `execute()` answered a promise on the synchronous surface |
1196
1207
  | `JL2005` | a push queue was fed after `end()` |
1197
1208
  | `JL2006` | a provider answered an element terminal with something other than one array |
1198
- | `JL2008` | a federated fetch reached one side's row or byte budget (§12.1) |
1209
+ | `JL2008` | a federated fetch or intermediate reached a per-side or combined row/byte budget (§12.1) |
1199
1210
 
1200
1211
  `JL2007` is the client door's, not the chain's: `createDbLedger`'s stale
1201
1212
  settlement ([DB-CLIENT.md §2.6](DB-CLIENT.md#26-the-ledger)); it is
@@ -1314,7 +1325,7 @@ that preserves it, so there is no spelling that is allowed to.
1314
1325
  | `from(rows).join([], …)` | `join takes another sequence as its inner side` | `from(sameSource)` |
1315
1326
  | `from(a).join(from(b), …)` | `join's other side must derive from the same source, or from two providers sharing one scope (one store's entity sets) — a query document reads one input; load both collections under one root, or join two entity sets of one store` | one source, or one store's two entity sets (§13.2) |
1316
1327
  | `from(rows).concat(42)` | `concat takes a sequence or a constant array` | a sequence over the same source, or an array |
1317
- | `select((r) => r.v.all().rolling(spec))` where `spec` reads the row | `rolling() takes a plain literal spec object; it is read once when the query compiles, so it cannot be an expression or carry a captured value` | a literal spec |
1328
+ | `select((r) => r.v.all().rolling(spec))` where `spec` reads the row | `rolling() takes a plain literal spec object, not an expression or captured value` | a literal spec |
1318
1329
 
1319
1330
  **A provider or a document that is not shaped as the contract says.**
1320
1331
 
@@ -1672,14 +1683,14 @@ are shorter:
1672
1683
  ## 17. Cost
1673
1684
 
1674
1685
  A consumer importing `from` from `@jarenjs/linq` and calling one
1675
- terminal bundles **<!--fact:bundle.chain-->174,131<!--/fact--> bytes** (esbuild, ESM, minified, tree-shaken,
1686
+ terminal bundles **<!--fact:bundle.chain-->175,221<!--/fact--> bytes** (esbuild, ESM, minified, tree-shaken,
1676
1687
  `platform: 'neutral'`). The figure is measured by
1677
1688
  `scripts/check-tree-shaking.js`'s chain probe and compared with this
1678
1689
  section on every `npm run test:tree-shaking`: it is derived, never typed,
1679
1690
  and a stale one is red here rather than wrong in a document somebody
1680
1691
  reads.
1681
1692
 
1682
- Of that, **<!--fact:bundle.chain.own-->40,131<!--/fact--> bytes** are the chain's own modules — `sequence.js`,
1693
+ Of that, **<!--fact:bundle.chain.own-->38,701<!--/fact--> bytes** are the chain's own modules — `sequence.js`,
1683
1694
  `async.js`, `expression.js`, `document.js`, `provider.js`,
1684
1695
  `concurrency.js`, `errors.js` and `schema-of.js`. The remaining ~134 kB
1685
1696
  is the query ENGINE and the core it stands on: a chain's document has to
@@ -1700,13 +1711,13 @@ making:
1700
1711
  `@jarenjs/formats`** — the client's optional peers. A consumer of the
1701
1712
  chain alone installs nothing new;
1702
1713
  - **no pen bytes at all**, in either direction: the pens carry no chain
1703
- module either, which is what keeps a <!--fact:bundle.jslt.kb-->20<!--/fact--> kB JSLT
1704
- pen <!--fact:bundle.jslt.kb-->20<!--/fact--> kB.
1714
+ module either, which is what keeps a <!--fact:bundle.jslt.kb-->18<!--/fact--> kB JSLT
1715
+ pen <!--fact:bundle.jslt.kb-->18<!--/fact--> kB.
1705
1716
 
1706
1717
  `docs/CONSUMING.md` states the rounded price of all ten subpaths in one
1707
1718
  table, each figure held equal to the same measurements. Two of its rows
1708
- are the ones to read together: the chain at <!--fact:bundle.chain.kb-->174<!--/fact--> kB and
1709
- `./db` at <!--fact:bundle.db.kb-->628<!--/fact--> kB.
1719
+ are the ones to read together: the chain at <!--fact:bundle.chain.kb-->175<!--/fact--> kB and
1720
+ `./db` at <!--fact:bundle.db.kb-->676<!--/fact--> kB.
1710
1721
  The client costs what the store costs, by construction, and the chain
1711
1722
  costs what running a query costs.
1712
1723
 
@@ -1715,7 +1726,7 @@ the reason is worth knowing: a bundler counts a shared module once, and
1715
1726
  the chain and every pen share the expression capture (`expression.js`)
1716
1727
  and the coded errors under it (`errors.js`, and `@jarenjs/core`'s error
1717
1728
  and object helpers). A consumer importing the chain AND the schema pen
1718
- bundles **<!--fact:bundle.chain.withSchemaPen-->198,801<!--/fact--> bytes** — **<!--fact:bundle.chain.shared-->12,047<!--/fact--> bytes** less than the sum of the
1729
+ bundles **<!--fact:bundle.chain.withSchemaPen-->199,375<!--/fact--> bytes** — **<!--fact:bundle.chain.shared-->11,131<!--/fact--> bytes** less than the sum of the
1719
1730
  figure above and [SCHEMA-PEN.md](SCHEMA-PEN.md#7-cost) §7's, which is
1720
1731
  what those shared modules weigh. The probe measures that pair too, so
1721
1732
  the saving is derived like everything else here. What the chain does NOT
@@ -1723,3 +1734,19 @@ share with a pen is the pens' own two shared doors, `capture-root.js`
1723
1734
  and `json-boundary.js`: no chain callback reaches either, and neither is
1724
1735
  in the figure above. Every pen document's §7 carries its own
1725
1736
  subpath's figure; nothing here restates one.
1737
+
1738
+ ## Native column queries and ranges
1739
+
1740
+ Chains and JSON documents share the db planner. Adopted scalar projections, connected joins, restricted correlated counts and proved one-root grouped aggregates have [native plans](../../db/docs/NATIVE-PLANS.md); unsupported shapes retain explanations and strict-mode refusals. The structural [range provider](../../app/docs/COLLECTION-PROVIDER.md) is `handle.range(spec, options)` from the root client, composed over the existing pager and capture.
1741
+
1742
+
1743
+ ## Lexical provider authoring
1744
+
1745
+ A string expression's `lexical(provider, request)` method emits
1746
+ `{ $lexical: [provider, textExpression, request] }`. The provider name is a nonempty
1747
+ string; the request is a plain literal JSON object. It records a declaration,
1748
+ never builds an index or runs a second ranker. Compile the emitted document with
1749
+ `compileJsonQuery(document, {lexicalProviders})`, binding the same
1750
+ `createLexicalProvider` capability that a hand-written query uses. The ordinary
1751
+ sequence provider has no implicit search catalog. Filters/facets run before top-k,
1752
+ continuations bind the source snapshot, and `$search` remains regex-based.
@@ -1167,10 +1167,10 @@ hand, or generate it some other way, when:
1167
1167
 
1168
1168
  ## 7. Cost
1169
1169
 
1170
- `@jarenjs/linq/schema` builds to **<!--fact:bundle.schema-->36,717<!--/fact--> bytes** as a minified,
1170
+ `@jarenjs/linq/schema` builds to **<!--fact:bundle.schema-->35,285<!--/fact--> bytes** as a minified,
1171
1171
  tree-shaken ESM bundle — the figure `scripts/check-tree-shaking.js`
1172
1172
  measures and `npm run test:tree-shaking` reports, published rounded
1173
- (<!--fact:bundle.schema.kb-->37<!--/fact--> kB) beside the other nine subpath prices in
1173
+ (<!--fact:bundle.schema.kb-->35<!--/fact--> kB) beside the other nine subpath prices in
1174
1174
  [docs/CONSUMING.md](../../../docs/CONSUMING.md).
1175
1175
 
1176
1176
  The probe is a gate, not a report: building
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jarenjs/linq",
3
3
  "private": false,
4
- "version": "0.73.0",
4
+ "version": "0.83.2",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
7
7
  "types": "./types/index.d.ts",
@@ -67,6 +67,10 @@
67
67
  "./ai": {
68
68
  "types": "./types/ai.d.ts",
69
69
  "default": "./src/ai/index.js"
70
+ },
71
+ "./formula": {
72
+ "types": "./types/formula.d.ts",
73
+ "default": "./src/formula/index.js"
70
74
  }
71
75
  },
72
76
  "files": [
@@ -104,13 +108,13 @@
104
108
  "prepack": "npm run build:types"
105
109
  },
106
110
  "dependencies": {
107
- "@jarenjs/core": "^0.73.0",
108
- "@jarenjs/json": "^0.73.0"
111
+ "@jarenjs/core": "^0.83.2",
112
+ "@jarenjs/json": "^0.83.2"
109
113
  },
110
114
  "peerDependencies": {
111
- "@jarenjs/db": "^0.73.0",
112
- "@jarenjs/formats": "^0.73.0",
113
- "@jarenjs/validate": "^0.73.0"
115
+ "@jarenjs/db": "^0.83.2",
116
+ "@jarenjs/formats": "^0.83.2",
117
+ "@jarenjs/validate": "^0.83.2"
114
118
  },
115
119
  "peerDependenciesMeta": {
116
120
  "@jarenjs/db": {