@jarenjs/linq 0.49.2 → 0.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +217 -0
- package/README.md +559 -17
- package/docs/APP-PEN.md +1143 -0
- package/docs/CONTRACT-PEN.md +1217 -0
- package/docs/DB-CLIENT.md +814 -0
- package/docs/FLOW-PEN.md +1026 -0
- package/docs/FORMS-PEN.md +940 -0
- package/docs/JSLT-PEN.md +955 -0
- package/docs/LINQ-FORMAT.md +771 -383
- package/docs/MIGRATION-PEN.md +781 -0
- package/docs/MODEL-PEN.md +1083 -0
- package/docs/QUERY-PEN.md +1636 -0
- package/docs/SCHEMA-PEN.md +1218 -0
- package/package.json +57 -4
- package/src/app/action.js +255 -0
- package/src/app/capture.js +63 -0
- package/src/app/define.js +260 -0
- package/src/app/index.js +20 -0
- package/src/app/patch.js +277 -0
- package/src/app/sub.js +106 -0
- package/src/async.js +329 -75
- package/src/capture-root.js +82 -0
- package/src/concurrency.js +9 -4
- package/src/contract/define.js +269 -0
- package/src/contract/http.js +247 -0
- package/src/contract/index.js +23 -0
- package/src/contract/operation.js +342 -0
- package/src/db/handle.js +86 -0
- package/src/db/include.js +316 -0
- package/src/db/index.js +19 -0
- package/src/db/live.js +43 -0
- package/src/db/membership.js +37 -0
- package/src/db/open.js +82 -0
- package/src/document.js +143 -13
- package/src/effect.js +65 -0
- package/src/errors.js +69 -6
- package/src/expression.js +437 -36
- package/src/flow/capture.js +33 -0
- package/src/flow/dag.js +302 -0
- package/src/flow/fsm.js +328 -0
- package/src/flow/index.js +22 -0
- package/src/forms/index.js +43 -0
- package/src/forms/rules.js +170 -0
- package/src/forms/submit.js +177 -0
- package/src/index.js +4 -2
- package/src/jslt/body.js +226 -0
- package/src/jslt/index.js +18 -0
- package/src/jslt/rules.js +207 -0
- package/src/json-boundary.js +90 -0
- package/src/migration/define.js +323 -0
- package/src/migration/index.js +15 -0
- package/src/migration/steps.js +248 -0
- package/src/model/collection.js +171 -0
- package/src/model/define.js +125 -0
- package/src/model/entity.js +307 -0
- package/src/model/index.js +47 -0
- package/src/model/relation.js +85 -0
- package/src/provider.js +137 -20
- package/src/schema/brand.js +31 -0
- package/src/schema/builders.js +526 -0
- package/src/schema/check.js +29 -0
- package/src/schema/emit.js +394 -0
- package/src/schema/factories.js +239 -0
- package/src/schema/index.js +37 -0
- package/src/schema-of.js +24 -0
- package/src/sequence.js +233 -103
- package/src/sources.js +10 -3
- package/types/app.d.ts +293 -0
- package/types/contract.d.ts +371 -0
- package/types/db.d.ts +188 -0
- package/types/flow.d.ts +285 -0
- package/types/forms.d.ts +253 -0
- package/types/index.d.ts +231 -26
- package/types/jslt.d.ts +193 -0
- package/types/migration.d.ts +201 -0
- package/types/model.d.ts +493 -0
- package/types/schema.d.ts +494 -0
package/docs/LINQ-FORMAT.md
CHANGED
|
@@ -1,110 +1,336 @@
|
|
|
1
|
-
# The Jaren
|
|
1
|
+
# The Jaren pens (normative)
|
|
2
|
+
|
|
3
|
+
> this file, the binder and the family's **normative reference**: what a
|
|
4
|
+
> pen is, the rules all of them keep, the shared `JL01xx` table, and the
|
|
5
|
+
> cross-pen views derived from the ten guides it indexes. **Read it when**
|
|
6
|
+
> you want a rule that is true of every pen, an index of the eleven
|
|
7
|
+
> documents, or one place to look up a method without knowing which pen
|
|
8
|
+
> owns it
|
|
2
9
|
|
|
3
10
|
Version 0.1. The key words MUST, MUST NOT, SHOULD and MAY are to be
|
|
4
11
|
interpreted as described in RFC 2119.
|
|
5
12
|
|
|
6
|
-
## 1. Scope
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
13
|
+
## 1. Scope and the pen rules
|
|
14
|
+
|
|
15
|
+
A **pen** is a by-code front-end to one of the suite's document formats:
|
|
16
|
+
named functions that build a standard document — a JSON Schema, a
|
|
17
|
+
`$model`, a `$jslt` stylesheet — the way the chain builds a query
|
|
18
|
+
document. `@jarenjs/linq` exports each pen under its own subpath
|
|
19
|
+
(`@jarenjs/linq/schema`, `/model`, `/jslt`, `/migration`, `/contract`,
|
|
20
|
+
`/flow`, `/app` and `/forms`); `.` stays the chain.
|
|
21
|
+
|
|
22
|
+
**This document is the family's normative reference**: §1 states the
|
|
23
|
+
rules every pen keeps, §1.3 the error codes they share, and §4–§7 the
|
|
24
|
+
cross-pen views — the census, the refusal map, the measured price of each
|
|
25
|
+
subpath, and every pen's mapping table in one place — none of which is
|
|
26
|
+
written here, all of it derived from the ten documents beside it. Those
|
|
27
|
+
ten are **guides**: each opens with the problem its pen solves, builds one
|
|
28
|
+
document across its sections, and carries exactly one normative section
|
|
29
|
+
of its own, the mapping table its rows here come from. So the two
|
|
30
|
+
questions have two homes and neither is a copy of the other — "what is
|
|
31
|
+
true of every pen, and where do I look this method up" is answered here;
|
|
32
|
+
"how do I write one of these documents" is answered there. This section
|
|
33
|
+
is the index of the ten, and it is how a reader reaches any of them.
|
|
34
|
+
|
|
35
|
+
<!--fact:pens.index-->
|
|
36
|
+
| Document | Lines | What it writes, and when to open it |
|
|
37
|
+
|---|---:|---|
|
|
38
|
+
| [LINQ-FORMAT.md](LINQ-FORMAT.md) | 812 | 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 ten guides it indexes. **Read it when** you want a rule that is true of every pen, an index of the eleven documents, or one place to look up a method without knowing which pen owns it |
|
|
39
|
+
| [QUERY-PEN.md](QUERY-PEN.md) | 1,636 | 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 |
|
|
40
|
+
| [SCHEMA-PEN.md](SCHEMA-PEN.md) | 1,218 | `./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 |
|
|
41
|
+
| [MODEL-PEN.md](MODEL-PEN.md) | 1,083 | `./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
|
+
| [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 |
|
|
43
|
+
| [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
|
+
| [CONTRACT-PEN.md](CONTRACT-PEN.md) | 1,217 | `./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 |
|
|
45
|
+
| [FLOW-PEN.md](FLOW-PEN.md) | 1,026 | `./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 |
|
|
46
|
+
| [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 |
|
|
47
|
+
| [FORMS-PEN.md](FORMS-PEN.md) | 940 | `./forms` — the `x-form` vocabulary on JSON Schema, and `assertOnSubmit()`, the same rules' layer-3 `$query` twin. **Read it when** you are turning a schema into a form |
|
|
48
|
+
| [DB-CLIENT.md](DB-CLIENT.md) | 814 | `./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` |
|
|
49
|
+
<!--/fact-->
|
|
50
|
+
|
|
51
|
+
Every row of that table is derived, and none of it is written here: the
|
|
52
|
+
sentence is the document's own opening line, the length is the file's,
|
|
53
|
+
and `npm run docs:derive` writes the table out of the eleven documents
|
|
54
|
+
beside this one. The line counts are not decoration — they are what tells
|
|
55
|
+
a reader whether the document they are about to open is a ten-minute read
|
|
56
|
+
or an afternoon — and `test/docs/linq-citations.test.js` holds each one
|
|
57
|
+
equal to the file it names, independently of the derivation.
|
|
58
|
+
|
|
59
|
+
A document missing from that table is a document a reader cannot reach:
|
|
60
|
+
the website opens these files through the binder and only through the
|
|
61
|
+
binder, so the index IS the directory listing, and a pen added to
|
|
62
|
+
`packages/linq/docs/` appears here the next time the derivation runs.
|
|
63
|
+
|
|
64
|
+
§2 to §5 are derived the same way, from the same eleven documents. They
|
|
65
|
+
exist so that a reader with a cross-pen question — which pens raise
|
|
66
|
+
`JL0104`, what a subpath costs, which pen has a `named()` — has one
|
|
67
|
+
place to look, and so that the answer is never a second copy anybody has
|
|
68
|
+
to keep true: **a row in a derived block is edited in the document it
|
|
69
|
+
came from**, and `npm run docs:check` fails until this file
|
|
70
|
+
agrees with it again.
|
|
71
|
+
|
|
72
|
+
### 1.0 What is a pen, what is not, and why
|
|
73
|
+
|
|
74
|
+
A pen exists where a document is **authored by a person** and an engine
|
|
75
|
+
compiles it. That is the whole test, and it decides both lists below. A
|
|
76
|
+
format whose documents are produced by a parser, generated by a tool,
|
|
77
|
+
projected from another document, or exchanged on a wire is not authored,
|
|
78
|
+
so a builder for it would type nothing a caller writes; a schema for
|
|
79
|
+
data rather than for a program has no compile step to be faithful to.
|
|
80
|
+
|
|
81
|
+
| Document (grammar) | Home | Pen | Document |
|
|
82
|
+
|---|---|---|---|
|
|
83
|
+
| JSON Schema (+ `$query`, `errorMessage`/`$msgid`, `x-coerce`/`x-trim`) | `@jarenjs/validate` | **`./schema`** | [SCHEMA-PEN.md](SCHEMA-PEN.md) |
|
|
84
|
+
| `x-entity` on JSON Schema; `$model` 0.1 (`jaren-model`) | `@jarenjs/db` | **`./model`** | [MODEL-PEN.md](MODEL-PEN.md) |
|
|
85
|
+
| query documents (`jaren-query`) | `@jarenjs/json` | **the chain**, `.` | [QUERY-PEN.md](QUERY-PEN.md) |
|
|
86
|
+
| `$jslt` 0.1 (`jaren-jslt`) | `@jarenjs/json` | **`./jslt`** | [JSLT-PEN.md](JSLT-PEN.md) |
|
|
87
|
+
| `$migration` 0.1 (`jaren-migration`) | `@jarenjs/db` | **`./migration`** | [MIGRATION-PEN.md](MIGRATION-PEN.md) |
|
|
88
|
+
| `$contract` 0.1 (`jaren-contract`) | `@jarenjs/contract` | **`./contract`** | [CONTRACT-PEN.md](CONTRACT-PEN.md) |
|
|
89
|
+
| `$fsm` 0.1, `$dag` 0.1 (`jaren-fsm`, `jaren-dag`) | `@jarenjs/flow` | **`./flow`** | [FLOW-PEN.md](FLOW-PEN.md) |
|
|
90
|
+
| `jaren-app` 0.1 | `@jarenjs/app` | **`./app`** | [APP-PEN.md](APP-PEN.md) |
|
|
91
|
+
| `x-form` on JSON Schema | `@jarenjs/forms` | **`./forms`** | [FORMS-PEN.md](FORMS-PEN.md) |
|
|
92
|
+
|
|
93
|
+
Authored formats without a pen **yet** — the contract generalises to
|
|
94
|
+
each, and each is its own piece of work rather than a silent extension
|
|
95
|
+
of the set above: `chart-definition` (`@jarenjs/charts`),
|
|
96
|
+
`jaren-project` (`@jarenjs/studio` — low value alone, since the studio
|
|
97
|
+
authors projects; its worth is round-tripping pen output INTO the
|
|
98
|
+
studio), the JTLT template document (`@jarenjs/json` — blocked first on
|
|
99
|
+
a published `jaren-jtlt` grammar, which is a format decision), message
|
|
100
|
+
catalogs (`@jarenjs/contract` and `@jarenjs/locales`), and the AI action
|
|
101
|
+
language (`@jarenjs/ai` — a model authors those documents; a pen's worth
|
|
102
|
+
there is fixtures and tests).
|
|
103
|
+
|
|
104
|
+
Formats with **no pen, by decision**, each for the reason its row gives:
|
|
105
|
+
|
|
106
|
+
| Document | Why not |
|
|
107
|
+
|---|---|
|
|
108
|
+
| `$md`, `$mermaid`, the calc AST | parser OUTPUTS — nobody writes one by hand |
|
|
109
|
+
| `jaren-emit-model` | generated by `@jarenjs/emit`; the agreement suite consumes it |
|
|
110
|
+
| `jaren-workflow` | a PROJECTION of a machine; the fsm pen's documents are its executable superset |
|
|
111
|
+
| `jaren-vnode(-safe)` | an output vocabulary, produced by views |
|
|
112
|
+
| `jaren-contract-port` frames | wire frames, not authored documents |
|
|
113
|
+
| GeoJSON, JOSL data, calc state, financial inputs, site documents | data schemas — there is no engine compiling them as a program |
|
|
114
|
+
| LIVE options, JOBS payloads | option objects AROUND a query or dag document; `live` is a client terminal ([DB-CLIENT.md](DB-CLIENT.md)) |
|
|
115
|
+
| `ToolDef` (`{ name, description, inputSchema, execute }`) | its `inputSchema` is already the schema pen's document, and `execute` is typed by `Infer<>` over it |
|
|
116
|
+
|
|
117
|
+
### 1.1 The rules
|
|
118
|
+
|
|
119
|
+
1. **The document is the deliverable.** A pen emits exactly the published
|
|
120
|
+
document: plain, deep-frozen JSON (`JSON.parse(JSON.stringify(x))`
|
|
121
|
+
deep-equals `x`; no functions, no class instances), valid under the
|
|
122
|
+
published grammar. `.schema` is the document, `toJSON()` returns it,
|
|
123
|
+
so `JSON.stringify(builder)` and `structuredClone(builder.schema)` are
|
|
124
|
+
the document. A pen imports no engine; the engine's compiler is the
|
|
125
|
+
only judge of semantics. A pen refuses only what it cannot SPELL, or
|
|
126
|
+
what the engine's own rule would refuse and the pen can see earlier —
|
|
127
|
+
mirrored, never invented — with a coded `JL01xx` build error. There is
|
|
128
|
+
no pen-private dialect, no `.transform()`-style function member and
|
|
129
|
+
no re-implementation of a compile check.
|
|
130
|
+
2. **Types are phantoms; the pen is the only inference route.** `Infer<>`,
|
|
131
|
+
`Input<>` and their kin exist at compile time only. A JSON literal is
|
|
132
|
+
never inferred: `from(json)` types as `unknown` unless the caller
|
|
133
|
+
asserts (`from<T>(json)`), and a reference by name (`ref<T>(name)`)
|
|
134
|
+
likewise. Every type claim is pinned three ways over one corpus — the
|
|
135
|
+
pen's type equals emit's declaration for the emitted document, and both
|
|
136
|
+
correspond to the validator's verdicts — plus a runtime twin.
|
|
137
|
+
3. **Home and shape.** A pen lives at `packages/linq/src/<pen>/` and
|
|
138
|
+
exports NAMED functions (`import * as s`), never a namespace object; a
|
|
139
|
+
pen that extends another does so by subclassing through the base
|
|
140
|
+
class's `with()`, never by patching an imported prototype. The only
|
|
141
|
+
shared runtime machine is the chain's recording proxy — with the one
|
|
142
|
+
root capture over it (`captureQuery`: a value at `$`, named externals)
|
|
143
|
+
that every query-valued member is captured through, the JSON boundary
|
|
144
|
+
(`requireJson`) and the builder brand. Every subpath has a tree-shaking
|
|
145
|
+
probe: a pen-only bundle carries no chain module and no engine.
|
|
146
|
+
5. **A map is read by its own keys, and written by them.** Wherever a pen
|
|
147
|
+
takes a name → value map — members, entities, operations, nodes,
|
|
148
|
+
actions, modes — it reads the map's OWN enumerable keys and emits each
|
|
149
|
+
as an own member. Two JavaScript hazards sit on that path and both are
|
|
150
|
+
closed: a `__proto__:` key in an object LITERAL sets the prototype
|
|
151
|
+
instead of adding a member, so such a map is refused (`JL0101`, naming
|
|
152
|
+
the `{ ['__proto__']: … }` spelling that works); and writing a member
|
|
153
|
+
back with a plain assignment would reassign the EMITTED object's
|
|
154
|
+
prototype, so every pen writes through `setObjectMember`. A member
|
|
155
|
+
named `__proto__` is therefore ordinary data, in a schema's
|
|
156
|
+
`properties`, in a model's `entities`, in a contract's `operations`, in
|
|
157
|
+
a dag's `nodes` and in an app's `actions` alike.
|
|
158
|
+
4. **Objects are closed by default.** `object()` emits
|
|
159
|
+
`additionalProperties: false`; `.open()` removes it. The type follows
|
|
160
|
+
emit's reading of the EMITTED document in both cases: an index
|
|
161
|
+
signature only for an open object.
|
|
162
|
+
|
|
163
|
+
### 1.2 Immutability and identity
|
|
164
|
+
|
|
165
|
+
Every builder is immutable and frozen; every method answers a new
|
|
166
|
+
builder. `.schema` assembles once and memoizes; two builds of the same
|
|
167
|
+
spelling are one document (`deepStrictEqual`). A builder used in two
|
|
168
|
+
places emits twice, as JSON; a NAMED builder (`named(name, b)`) emits
|
|
169
|
+
once, into `$defs`, and is referenced by `$ref` from every place it is
|
|
170
|
+
reached — also when reached once, because a name is a statement of
|
|
171
|
+
intent and a stable `$defs` is what a contract's definitions and a
|
|
172
|
+
bundler read.
|
|
173
|
+
|
|
174
|
+
### 1.3 Error codes
|
|
175
|
+
|
|
176
|
+
Pen refusals are `LinqBuildError`s with these codes, in `LINQ_CODES` and
|
|
177
|
+
mirrored in QUERY-PEN §9 (one table, held equal by a test):
|
|
178
|
+
|
|
179
|
+
| Code | Condition |
|
|
180
|
+
|---|---|
|
|
181
|
+
| `JL0101` | a pen received a value it cannot spell, or a map it cannot read: a name → value map whose prototype a `__proto__:` literal replaced (§1.1 rule 5); a value that is not JSON (a function, symbol, bigint, `NaN`, `±Infinity`, `-0`, a class instance, a cycle — the constant rule of QUERY-PEN §5 applied to defaults, literals, examples and annotations), or not what the keyword takes (`min('x')`, a member that is not a builder); in the contract pen, also a member the pen's own surface does not know or a value outside a declared set (a `policy` member outside §3.1's table, a method outside §4's, a status outside its range) — the document's own closed vocabulary stays `compileContract`'s `JC0013`; in the app pen a member of its own surface it does not know, a `payload` that is not a builder, or a CALLBACK under a subscription's `with` (which is verbatim data and never evaluated); in the forms pen a member `x-form` does not define, or a `message` that is neither an inline string nor a MessageSpec |
|
|
182
|
+
| `JL0102` | a pen was asked for a construct the format cannot carry: a function `refine`/`transform` (cross-field rules are `check()`; transforms are application code), a coercion the normalizer would never run, closed objects under `allOf`, an annotation on `never()`, a draft the pen does not write; in the JSLT pen an `apply()` as a bare object member (the `[]` idiom, JSLT-FORMAT §6.3 — the engine would fail at run time on the second child), a `match` of `{}` (the compiler's `JT0003`, earlier), an `apply()` outside a body; in the contract pen a path template form CONTRACT-FORMAT §4.2 reserves (the compiler's `JC0008`, earlier, naming the same form), a member mapped to `path` the template does not declare, or a hand-written operation `kind` outside `read`/`command`/`subscribe`; in the flow pen a guard given as a plain STRING (FLOW-FORMAT §3 makes a non-`$` literal vacuously true, so a projected display annotation must not decide execution), or a state or node id no declaration carries (the compiler's `JF0004`/`JF0006`/`JF0013`, earlier, naming the id); in the app pen a patch path that is not a chain of member reads and subscripts (a JSON Pointer cannot be written for it), an `$event` field APP-FORMAT §3.1 excludes by construction (`target`, `files`, a touch list — `$event` must survive `JSON.stringify`), an initial state no `default()` describes, a subscription-member combination §5.3 calls `JA0008`, or a view binding an action `actions` does not declare (the loop's `JA2001`, earlier, naming the declared ones); in the forms pen `preview`, which the format registry DERIVES from the field's own `format` |
|
|
183
|
+
| `JL0103` | a `$defs` name collision (two distinct builders under one name), a `ref()` no definition answers, or a `lazy()` that does not return a named builder |
|
|
184
|
+
| `JL0104` | a pen-owned keyword written through `meta()`, or an external a captured rule did not declare: a `check()` external other than `root`/`path`, a `compute()` external at all, a `body()` external other than `root`/`path` and its declared parameters — or `root`/`path` declared as one, since the engine binds them; a flow guard, effect `with`, node query or edge `select` external at all, since both flow engines evaluate with one `$` and nothing else; an app action naming anything but `$event` and `$payload` (APP-FORMAT §3.1's whole ambient vocabulary), a subscription member naming anything but `$item`, and that only under `for` (§5.3's closed world); a form rule naming anything but the context's `root`, `value` and `pointer` |
|
|
185
|
+
| `JL0105` | a relation hop on the chain — the query pen (QUERY-PEN §4, relation navigation) — cannot lower: the member is a many-to-many relation, whose join table is not a queryable root in this version (`load({ include })` reads the memberships); the relation's key column or the key it references is composite or undeclared; or the provider's relation table holds something that is not a relation record |
|
|
186
|
+
| `JL0106` | a migration step names an entity or collection the target model does not declare (`transform`, `assert`, `derive`); or a `transform` over a planned document finds no draft to replace, or two drafts for one name |
|
|
187
|
+
| `JL0107` | the client (`@jarenjs/linq/db`, [DB-CLIENT.md](DB-CLIENT.md)) was handed a member that is not the relation kind the operation needs: `include()` picks a declared relation member — a scalar member, or a name the model does not declare, is refused naming the declared ones; `link()`/`unlink()` attach many-to-many memberships only — a to-one or to-many relation is refused naming its kind |
|
|
188
|
+
|
|
189
|
+
The message names the fix; `docPath` is the JSON pointer of the node
|
|
190
|
+
being assembled where one exists (`/properties/lines/items`).
|
|
191
|
+
|
|
192
|
+
## 2. The census
|
|
193
|
+
|
|
194
|
+
What each of the eleven documents covers, counted from the document
|
|
195
|
+
itself. Every column has a gate behind it in a different file: the
|
|
196
|
+
mapping rows are held equal to the subpath's callable names, the worked
|
|
197
|
+
examples are executed against the JSON beside them, the refusal count is
|
|
198
|
+
held equal to the codes that pen's source raises — in both directions —
|
|
199
|
+
and the bundle is the byte count the tree-shaking probe builds.
|
|
200
|
+
|
|
201
|
+
<!--fact:pens.census-->
|
|
202
|
+
| Document | Subpath | Lines | Mapping rows | Worked examples | Refusals | Bundle |
|
|
203
|
+
|---|---|---:|---:|---:|---:|---:|
|
|
204
|
+
| [LINQ-FORMAT.md](LINQ-FORMAT.md) | — | 812 | — | — | — | — |
|
|
205
|
+
| [QUERY-PEN.md](QUERY-PEN.md) | `.` | 1,636 | 34 | 8 | 14 | 173,080 B |
|
|
206
|
+
| [SCHEMA-PEN.md](SCHEMA-PEN.md) | `./schema` | 1,218 | 66 | 10 | 4 | 32,427 B |
|
|
207
|
+
| [MODEL-PEN.md](MODEL-PEN.md) | `./model` | 1,083 | 28 | 6 | 3 | 40,857 B |
|
|
208
|
+
| [JSLT-PEN.md](JSLT-PEN.md) | `./jslt` | 955 | 17 | 8 | 3 | 19,124 B |
|
|
209
|
+
| [MIGRATION-PEN.md](MIGRATION-PEN.md) | `./migration` | 781 | 11 | 5 | 4 | 23,599 B |
|
|
210
|
+
| [CONTRACT-PEN.md](CONTRACT-PEN.md) | `./contract` | 1,217 | 37 | 6 | 3 | 44,644 B |
|
|
211
|
+
| [FLOW-PEN.md](FLOW-PEN.md) | `./flow` | 1,026 | 16 | 7 | 3 | 19,181 B |
|
|
212
|
+
| [APP-PEN.md](APP-PEN.md) | `./app` | 1,143 | 22 | 7 | 3 | 46,862 B |
|
|
213
|
+
| [FORMS-PEN.md](FORMS-PEN.md) | `./forms` | 940 | 18 | 6 | 3 | 36,587 B |
|
|
214
|
+
| [DB-CLIENT.md](DB-CLIENT.md) | `./db` | 814 | 36 | 4 | 2 | 478,172 B |
|
|
215
|
+
| **eleven documents** | | **11,625** | **285** | **67** | | |
|
|
216
|
+
<!--/fact-->
|
|
217
|
+
|
|
218
|
+
A pen whose mapping rows are far below its worked examples is a pen
|
|
219
|
+
whose surface is being taught by example rather than named; the opposite
|
|
220
|
+
is a reference nobody has exercised. Both are visible here and nowhere
|
|
221
|
+
else.
|
|
222
|
+
|
|
223
|
+
## 3. Where each refusal is raised
|
|
224
|
+
|
|
225
|
+
§1.3 says what each shared code MEANS. This says who raises it — one row
|
|
226
|
+
per code, naming every document whose refusal section carries it. The
|
|
227
|
+
meanings are written by a person and the raisers are not: each document's
|
|
228
|
+
refusal section is held equal to the codes its own source directory
|
|
229
|
+
throws, so this table moves when a pen's source does.
|
|
230
|
+
|
|
231
|
+
<!--fact:pens.codes-->
|
|
232
|
+
| Code | Raised by |
|
|
233
|
+
|---|---|
|
|
234
|
+
| `JL0101` | [SCHEMA-PEN.md](SCHEMA-PEN.md), [MODEL-PEN.md](MODEL-PEN.md), [JSLT-PEN.md](JSLT-PEN.md), [MIGRATION-PEN.md](MIGRATION-PEN.md), [CONTRACT-PEN.md](CONTRACT-PEN.md), [FLOW-PEN.md](FLOW-PEN.md), [APP-PEN.md](APP-PEN.md), [FORMS-PEN.md](FORMS-PEN.md), [DB-CLIENT.md](DB-CLIENT.md) |
|
|
235
|
+
| `JL0102` | [SCHEMA-PEN.md](SCHEMA-PEN.md), [MODEL-PEN.md](MODEL-PEN.md), [JSLT-PEN.md](JSLT-PEN.md), [MIGRATION-PEN.md](MIGRATION-PEN.md), [CONTRACT-PEN.md](CONTRACT-PEN.md), [FLOW-PEN.md](FLOW-PEN.md), [APP-PEN.md](APP-PEN.md), [FORMS-PEN.md](FORMS-PEN.md) |
|
|
236
|
+
| `JL0103` | [SCHEMA-PEN.md](SCHEMA-PEN.md), [CONTRACT-PEN.md](CONTRACT-PEN.md) |
|
|
237
|
+
| `JL0104` | [SCHEMA-PEN.md](SCHEMA-PEN.md), [MODEL-PEN.md](MODEL-PEN.md), [JSLT-PEN.md](JSLT-PEN.md), [MIGRATION-PEN.md](MIGRATION-PEN.md), [FLOW-PEN.md](FLOW-PEN.md), [APP-PEN.md](APP-PEN.md), [FORMS-PEN.md](FORMS-PEN.md) |
|
|
238
|
+
| `JL0105` | [QUERY-PEN.md](QUERY-PEN.md) |
|
|
239
|
+
| `JL0106` | [MIGRATION-PEN.md](MIGRATION-PEN.md) |
|
|
240
|
+
| `JL0107` | [DB-CLIENT.md](DB-CLIENT.md) |
|
|
241
|
+
<!--/fact-->
|
|
242
|
+
|
|
243
|
+
A code in §1.3 that no document raises fails the derivation rather than
|
|
244
|
+
appearing with an empty cell: a shared refusal nobody can reach is either
|
|
245
|
+
a dead code or a document that forgot it, and both need a person.
|
|
246
|
+
|
|
247
|
+
## 4. What each subpath costs
|
|
248
|
+
|
|
249
|
+
The minified, tree-shaken ESM bundle a consumer takes when they import
|
|
250
|
+
one subpath and nothing else, as `scripts/check-tree-shaking.js` measures
|
|
251
|
+
it and each document publishes it. The rounded column is what
|
|
252
|
+
[docs/CONSUMING.md](../../../docs/CONSUMING.md) states.
|
|
253
|
+
|
|
254
|
+
<!--fact:pens.cost-->
|
|
255
|
+
| Subpath | Document | Bundle | Rounded |
|
|
256
|
+
|---|---|---:|---:|
|
|
257
|
+
| `@jarenjs/linq` | [QUERY-PEN.md](QUERY-PEN.md) | 173,080 B | 173 kB |
|
|
258
|
+
| `@jarenjs/linq/schema` | [SCHEMA-PEN.md](SCHEMA-PEN.md) | 32,427 B | 32 kB |
|
|
259
|
+
| `@jarenjs/linq/model` | [MODEL-PEN.md](MODEL-PEN.md) | 40,857 B | 41 kB |
|
|
260
|
+
| `@jarenjs/linq/jslt` | [JSLT-PEN.md](JSLT-PEN.md) | 19,124 B | 19 kB |
|
|
261
|
+
| `@jarenjs/linq/migration` | [MIGRATION-PEN.md](MIGRATION-PEN.md) | 23,599 B | 24 kB |
|
|
262
|
+
| `@jarenjs/linq/contract` | [CONTRACT-PEN.md](CONTRACT-PEN.md) | 44,644 B | 45 kB |
|
|
263
|
+
| `@jarenjs/linq/flow` | [FLOW-PEN.md](FLOW-PEN.md) | 19,181 B | 19 kB |
|
|
264
|
+
| `@jarenjs/linq/app` | [APP-PEN.md](APP-PEN.md) | 46,862 B | 47 kB |
|
|
265
|
+
| `@jarenjs/linq/forms` | [FORMS-PEN.md](FORMS-PEN.md) | 36,587 B | 37 kB |
|
|
266
|
+
| `@jarenjs/linq/db` | [DB-CLIENT.md](DB-CLIENT.md) | 478,172 B | 478 kB |
|
|
267
|
+
<!--/fact-->
|
|
268
|
+
|
|
269
|
+
Read these as prices, not as scores. `./db` is the largest by an order of
|
|
270
|
+
magnitude because it is the one subpath that opens a store — a SQL
|
|
271
|
+
planner, a unit of work and a validator ride with it by construction —
|
|
272
|
+
and the chain's figure is mostly the query engine under it rather than
|
|
273
|
+
the fluent surface over it. Each document's Cost section says what its
|
|
274
|
+
own bundle carries and what the probe proves it does not.
|
|
275
|
+
|
|
276
|
+
## 5. Every pen's vocabulary, in one place
|
|
277
|
+
|
|
278
|
+
Every mapping table in the suite, in reading order, as the rows their own
|
|
279
|
+
documents carry. This is the section for a question that spans pens —
|
|
280
|
+
which pens have a `named()`, what `.optional()` emits where, whether the
|
|
281
|
+
flow pen spells an effect the way the app pen does — asked without
|
|
282
|
+
knowing which document to open first.
|
|
283
|
+
|
|
284
|
+
**A row here is edited in the document it came from.** Nothing in this
|
|
285
|
+
section is written here: `npm run docs:derive` splices each document's
|
|
286
|
+
mapping table in, and `npm run docs:check` fails until this
|
|
287
|
+
file agrees with all ten again. That is what lets each pen's document be
|
|
288
|
+
a guide with a shape of its own while the normative rows stay collected
|
|
289
|
+
in one place — neither is a copy of the other, so neither can drift from
|
|
290
|
+
it.
|
|
291
|
+
|
|
292
|
+
**The grouped re-export row.** Two pens are the schema pen with a
|
|
293
|
+
vocabulary added — `./model` and `./forms` — and neither restates the
|
|
294
|
+
rows it inherits. Both use one row kind for them, defined here so
|
|
295
|
+
the two tables below can be read as the same shape: one row per FAMILY of
|
|
296
|
+
names, a link to the schema pen's row for that family, and a third column
|
|
297
|
+
carrying the one thing that IS different in this pen. That third column
|
|
298
|
+
is a status for the model pen, whose re-exported builders gain behaviour
|
|
299
|
+
the moment an `x-entity` method is called on one, and the class that
|
|
300
|
+
comes back for the forms pen, where nothing gains behaviour and the class
|
|
301
|
+
is the whole difference. A grouped row is never an abridgement: the
|
|
302
|
+
completeness gate holds every callable name named somewhere in the
|
|
303
|
+
section, families included.
|
|
304
|
+
|
|
305
|
+
Only the rows travel. A table's surrounding prose, its refusals, its
|
|
306
|
+
types and its examples stay in the document that owns them, and each
|
|
307
|
+
group's heading links there. So **an unqualified `§N` inside a group is
|
|
308
|
+
that group's document's section, not this one's** — a row that says
|
|
309
|
+
"§8" under the query pen's heading means QUERY-PEN.md §8; a row that
|
|
310
|
+
names its document (`QUERY-FORMAT §6`, `MODEL-FORMAT §10.6`) means what
|
|
311
|
+
it says.
|
|
312
|
+
|
|
313
|
+
<!--fact:pens.vocabulary-->
|
|
314
|
+
### The Jaren query pen — [QUERY-PEN.md §4](QUERY-PEN.md)
|
|
89
315
|
|
|
90
316
|
| C# / LINQ | Emission | Status | Typing (element `T`) |
|
|
91
317
|
|---|---|---|---|
|
|
92
318
|
| `Where` | FLWOR `$where` | native | `(e: Expr<T>) => Expr<boolean>` → `Seq<T>` |
|
|
93
319
|
| `Select` | `$return` constructor | native | `(e: Expr<T>) => Expr<R>` → `Seq<R>` |
|
|
94
|
-
| `SelectMany` | `$return`
|
|
320
|
+
| `SelectMany` | `$return` of a `$for` phrase over the projection — the projected value is iterated ONE level (an array member's elements, a constructed array's members; a scalar is itself), and the FLWOR `$return` concatenates per tuple. Emitted as `{ "$for": { "it": <projection> }, "$return": "$it" }` (the nested phrase rebinds `it` legally) | native | `(e: Expr<T>) => Expr<R[]>` → `Seq<R>` |
|
|
95
321
|
| `OrderBy` / `OrderByDescending` | `$orderby` key spec (`$dir`; `$empty`/`$collation` via `options`) | native | `(e: Expr<T>) => Expr<K>` → `Seq<T>` |
|
|
96
322
|
| `ThenBy` / `ThenByDescending` | appended `$orderby` spec; must directly follow `orderBy*` (`JL0005`) | native | as `OrderBy` |
|
|
97
323
|
| `GroupBy` | `$groupby`; downstream items are `{ key, items }` | native | `(e: Expr<T>) => Expr<K>` → `Seq<{key: K, items: T[]}>` |
|
|
98
|
-
| `Join` | nested `$for` + `$where` `$eq` — the engine rewrites this shape to a HASH JOIN (compile-time, QUERY-FORMAT §6), which is why it is fast. Both sides MUST derive from the same source
|
|
99
|
-
| `GroupJoin` |
|
|
324
|
+
| `Join` | nested `$for` + `$where` `$eq` — the engine rewrites this shape to a HASH JOIN (compile-time, QUERY-FORMAT §6), which is why it is fast **when both keys are plain member paths** (`o => o.pid`, `i => i.id`); a key with an operator in it (`o => o.name.lower()`, `o => o.p.add(0)`) is not a probe key and the join runs as a nested loop. Both sides MUST derive from the same source, or from two providers sharing one `scope` (§8 — two entity sets of one store are two roots of ONE multi-entity input, and the store answers the equijoin in one statement); anything else is `JL0005`: a query document reads one input. On the async surface the join exists only over a provider, pushed whole (§10). The inner side's declared parameters ride along (§7) | native | `(inner: Seq<U>, ok, ik, (o: Expr<T>, i: Expr<U>) => Expr<R>)` → `Seq<R>` |
|
|
325
|
+
| `GroupJoin` | the matching group bound as an ARRAY value — `$let: { g: [ <correlated inner phrase> ] }` — so the result selector can index it (`g.at(0)`), fan it (`g.all()`), place it in a member (`{ matches: g }`) and aggregate over its members (`(u, g) => ({ n: g.count() })` counts the matches, `g.exists()` is whether there are any); same-source rule and parameter merge as `Join` | emulated | `(inner: Seq<U>, ok, ik, (o: Expr<T>, g: ArrayExpr<U> & AggregatableExpr) => Expr<R>)` → `Seq<R>` |
|
|
100
326
|
| `Skip` / `Take` | `$subsequence` | native | `(n: number)` → `Seq<T>` |
|
|
101
327
|
| `Distinct` | `$distinct` (deep structural equality — the grouping relation) | native | `()` → `Seq<T>` |
|
|
102
328
|
| `Reverse` | `$reverse` | native | `()` → `Seq<T>` |
|
|
103
329
|
| `Count` / `Sum` / `Average` / `Min` / `Max` | §8.8 aggregates (`Average` → `$avg`) | native | `count(): number`; `sum(): number`; `average/min/max(): number` (throw `JL2001` on empty; `min`/`max` follow the operand family) |
|
|
104
330
|
| `Any()` | `$exists` | native | `(): boolean` |
|
|
105
331
|
| `Any(pred)` / `All(pred)` | `$some` / `$every` quantifier phrase | native | `(pred): boolean` (`all` vacuously true on empty) |
|
|
106
|
-
| `Aggregate(seed, fn)` | `$fold` — the accumulator clause | native | `(seed: A, (acc: Expr<A>, e: Expr<T>) => Expr<A>)
|
|
107
|
-
| `Aggregate(fn)` (unseeded) | — JSON cannot spell "the implicit first element" as a lambda seed | unsupported (`
|
|
332
|
+
| `Aggregate(seed, fn)` | `$fold` — the accumulator clause; the result is a sequence of exactly ONE accumulated value (`.first()` reads it) | native | `(seed: A, (acc: Expr<A>, e: Expr<T>) => Expr<A>)` → `Seq<A>` |
|
|
333
|
+
| `Aggregate(fn)` (unseeded) | — JSON cannot spell "the implicit first element" as a lambda seed | unsupported (`JL0006`) | — |
|
|
108
334
|
| `First` / `FirstOrDefault` | `[ $subsequence [expr, 0, 1] ]` window | native | `(): T` (`JL2001` on empty) / `(d?): T \| D` |
|
|
109
335
|
| `Single` / `SingleOrDefault` | `[ $subsequence [expr, 0, 2] ]` window | native | `(): T` (`JL2001`/`JL2002`) / `(d?): T \| D` (`JL2002` on 2+) |
|
|
110
336
|
| `Last` / `LastOrDefault` | `[ $subsequence [$reverse expr, 0, 1] ]` | native | as `First` |
|
|
@@ -119,306 +345,468 @@ is part of THIS design.
|
|
|
119
345
|
| series family (§8.16) | `overlaps(other)` → `$overlaps`; `timeBucket(every, origin?, context?)` → `$time-bucket`; `resample(spec)`, `rolling(spec)` and `asof(right, spec?)` → the three sequence operators. A **spec is a literal** and is embedded verbatim — it is read once when the query compiles, so a spec built from the row is `JL0005`, and every rule about what it may *say* stays in the compiler (`JQ0003`). Note that a member literally named `at` is read with `get('at')`: `at(index)` is path navigation on this surface | native | on `ArrayExpr`/fanned paths for the three sequence operators, on `Expr<…>` for the two scalar ones |
|
|
120
346
|
| spatial family (§8.14) | `bbox geoArea geoLength centroid` → `$bbox $area $length $centroid`; `distance within bboxIntersects` → `$distance $within $bbox-intersects`; `geohash(precision?)` → `$geohash` (optional arity, like `substring`); `geoParse geoText geohashBounds geohashNeighbours` → the conversion family; `geoSimplify(tolerance)` → `$geo-simplify`. A plain JSON polygon embeds as a literal (`p.at.within(poly)`); `.params({ region })` makes it an external instead | native | on `Expr<…>`, per the typed-surface order |
|
|
121
347
|
| vector family (§8.15) | `similarity(other)` → `$similarity`. The other operand is an array of numbers: a captured one embeds as a literal, `.params({ query })` binds it at call time. There is no `knn` method — k-nearest is `orderByDescending(...).take(k)`, which is the composition the emitted document already is | native | on `Expr<…>`, per the typed-surface order |
|
|
348
|
+
| relation navigation — to-one hop (`p.author`, `p.author.email`) | over a provider with a relation table (§3, §8): `{ "$for": { "r1": "$.User[*]" }, "$where": { "$eq": ["$r1.<targetKey>", "$it.<via>"] }, "$return": "$r1.email" }` — the target's key against the row's foreign key (`kind: "oneToOne"`, the key on the declaring entity). Zero or one item: an object member's one value (absent when there is none), an operand elsewhere (empty compares false; `exists()`/`isEmpty()` say which), and under `$orderby` a key that may be empty (`$empty` applies). The binding is `r1`, `r2`, … per capture | native by desugaring — the document is the phrase; a store runs it as a named residual (`explain()`, MODEL-FORMAT §10.6) | `Expr<Post>['author']` is `ObjectExpr<User>` — emit's optional relation member, nothing new |
|
|
349
|
+
| relation navigation — to-many hop (`u.posts`, `u.posts.all()`) | `{ "$for": { "r1": "$.Post[*]" }, "$where": { "$eq": ["$r1.<via>", "$it.<targetKey>"] }, "$return": "$r1" }` — the target's foreign key against the row's key (`kind: "oneToMany"`, the key on the target). As a VALUE the phrase is packed, `[ <phrase> ]`, the array of related rows a member holds (`{ posts: u.posts }`; `u.posts.at(0)` indexes it); fanned, `u.posts.all()` is the bare phrase, a sequence: `.all().count()` → `{ "$count": <phrase> }`, `.all().exists()` → `{ "$exists": <phrase> }`, `.all().title` returns `"$r1.title"` per row (`[u.posts.all().title]` packs the titles). `count()`/`exists()` on the value range over the rows too, as a group-join's group's do | native by desugaring, as above | `ArrayExpr<Post>`; `all()` is `FannedExpr<Post>` |
|
|
350
|
+
| relation navigation — chained, and from every row binding | hops nest: `p.author.posts.all().count()` is `{ "$count": { "$for": { "r1": "$.User[*]" }, "$where": …, "$return": { "$for": { "r2": "$.Post[*]" }, "$where": { "$eq": ["$r2.authorId", "$r1.id"] }, "$return": "$r2" } } }` — the inner phrase correlates with the outer binding; a hop off a fanned to-many (`u.posts.all().author`) is a sequence, one target per row; a join's `it2` hops from the inner row; a group-join's fanned group (`g.all().author`) binds each row first (`{ "$for": { "r1": "$g[*]" }, "$return": <hop over $r1> }`); the group itself is an array, not a row | native by desugaring, as above | as the target's `Expr<…>` |
|
|
351
|
+
| relation navigation — many-to-many (`u.labels`) | — the join table is not a queryable root in this version, so no phrase exists to lower to; `load({ include: { labels: true } })` reads the memberships | unsupported (`JL0105`, naming the join table) | — |
|
|
122
352
|
|
|
123
|
-
|
|
124
|
-
is the same one that made §8.14's `$length` and §8.7's `$string-length`
|
|
125
|
-
two operators: **`length` on this surface is already `$string-length`**,
|
|
126
|
-
and §8.14's `$length` is a geodesic line measurement. One method name
|
|
127
|
-
cannot carry both, and renaming the shipped string method for symmetry
|
|
128
|
-
would break a published surface for a cosmetic gain — so the spatial one
|
|
129
|
-
is **`geoLength`**, and **`geoArea`** joins it, because a bare `area()`
|
|
130
|
-
on an arbitrary expression reads as arithmetic to a C# eye. The prefix
|
|
131
|
-
names the family the way `geoParse`/`geoText` already do.
|
|
132
|
-
|
|
133
|
-
Every method name shadows a data member of the same name — that is what
|
|
134
|
-
the null prototype on the method table is for, and what `get(name)`
|
|
135
|
-
escapes. A position stored as `at` is the case that bites: `p.at` is the
|
|
136
|
-
index method, so it reads `p.get('at').within(region)`. A stored score
|
|
137
|
-
named `similarity` is the same bite with a worse error — `r.similarity`
|
|
138
|
-
is the *method*, so calling it as a member yields a `TypeError` about a
|
|
139
|
-
function rather than a coded build error, because the surface never sees
|
|
140
|
-
a member access at all. `r.get('similarity')` reads the data.
|
|
141
|
-
|
|
142
|
-
**k-nearest is a chain, not a method.** `similarity()` is one operator
|
|
143
|
-
and the ordering and the window are stages that already exist, so the
|
|
144
|
-
top k reads as what it is:
|
|
145
|
-
|
|
146
|
-
```js
|
|
147
|
-
from(memories)
|
|
148
|
-
.params({ query })
|
|
149
|
-
.orderByDescending((m, p) => m.embedding.similarity(p.query), { empty: 'least' })
|
|
150
|
-
.thenBy((m) => m.id)
|
|
151
|
-
.take(10)
|
|
152
|
-
.select((m) => m.text)
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
`{ empty: 'least' }` under a descending sort puts the rows whose key is
|
|
156
|
-
empty — no vector, or one of the wrong width — **last**, and `thenBy` on
|
|
157
|
-
the identity breaks ties, so the chain answers the same rows in the same
|
|
158
|
-
order every time it runs. `.params({ query })` rather than a captured
|
|
159
|
-
array is what makes the emitted document one query for every question,
|
|
160
|
-
which is the shape a provider can push down.
|
|
161
|
-
|
|
162
|
-
## 5. Deferred execution and re-enumeration
|
|
163
|
-
|
|
164
|
-
Every operator returns a new immutable `Sequence`; NOTHING runs until a
|
|
165
|
-
terminal operation. A sequence may be enumerated repeatedly and **each
|
|
166
|
-
enumeration re-reads the source** — the C# contract, and the one that
|
|
167
|
-
surprises people:
|
|
168
|
-
|
|
169
|
-
```js
|
|
170
|
-
const rows = [1, 2, 3];
|
|
171
|
-
const q = from(rows).where((n) => n.gt(1));
|
|
172
|
-
q.toArray(); // [2, 3]
|
|
173
|
-
rows.push(4);
|
|
174
|
-
q.toArray(); // [2, 3, 4] — the source was read AGAIN
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
The compiled query is shared through a bounded cache keyed by the
|
|
178
|
-
document's COMPLETE structural identity (`semanticKey`), so
|
|
179
|
-
re-enumeration is cheap without pretending the results are frozen.
|
|
180
|
-
A 32-bit fingerprint would not do here: it collides after tens of
|
|
181
|
-
thousands of documents, and a collision means one query runs another
|
|
182
|
-
query's compiled program — wrong rows, cache hit reported, nothing said.
|
|
183
|
-
`for…of` a sequence iterates `toArray()`'s result (one enumeration per
|
|
184
|
-
loop).
|
|
185
|
-
|
|
186
|
-
**`toDocument()` is a deep snapshot.** A sequence is immutable, so the
|
|
187
|
-
document it hands out is an independent tree: writing into a returned
|
|
188
|
-
document cannot change what a later enumeration answers.
|
|
189
|
-
|
|
190
|
-
**A `null` a callback returns is a VALUE, not an absent clause.**
|
|
191
|
-
`where(() => null)` filters everything out (null is not true),
|
|
192
|
-
`select(() => null)` projects nulls, `groupBy(() => null)` is one
|
|
193
|
-
null-keyed group, and a null seed still folds. The emitted document
|
|
194
|
-
carries the clause with its null in place.
|
|
195
|
-
|
|
196
|
-
**A captured constant crosses a real JSON boundary.** The query data
|
|
197
|
-
model is JSON, so a `Date`, `Map`, `Set`, `RegExp` or class instance is
|
|
198
|
-
refused (`JL0005`) rather than embedded — `Object.keys` reports nothing
|
|
199
|
-
for them, so they would embed as `{}` and the query would compare against
|
|
200
|
-
an empty object. `NaN` and `±Infinity` are refused for the same reason
|
|
201
|
-
(JSON has neither, and lenient serialization folds them into `null`), and
|
|
202
|
-
so is `-0`, which shares its JSON text with `0` while dividing to the
|
|
203
|
-
opposite infinity. Convert first — a `Date` to its ISO string or epoch
|
|
204
|
-
number — or bind through `params()`.
|
|
205
|
-
|
|
206
|
-
## 6. Terminal semantics
|
|
207
|
-
|
|
208
|
-
The real C# semantics, because getting these wrong is how a
|
|
209
|
-
"LINQ-like" library becomes lodash with different names:
|
|
210
|
-
|
|
211
|
-
- `first()` on empty throws `JL2001`; `firstOrDefault(d)` returns `d`
|
|
212
|
-
(or `undefined` when omitted).
|
|
213
|
-
- `single()` on empty throws `JL2001`; on two-or-more throws `JL2002`;
|
|
214
|
-
`singleOrDefault(d)` throws on two-or-more and returns `d` on empty.
|
|
215
|
-
- `last()`/`lastOrDefault(d)` mirror `first` over the reversed window.
|
|
216
|
-
- `elementAt(i)` out of range throws `JL2003`;
|
|
217
|
-
`elementAtOrDefault(i, d)` returns `d`.
|
|
218
|
-
- `average()`, `min()` and `max()` over an empty sequence throw
|
|
219
|
-
`JL2001` (C# `InvalidOperationException`); `sum()` of nothing is `0`;
|
|
220
|
-
`count()` of nothing is `0`.
|
|
221
|
-
- `any()` is existence; `all(pred)` is vacuously true over the empty
|
|
222
|
-
sequence.
|
|
223
|
-
|
|
224
|
-
Element terminals emit their window inside an ARRAY constructor
|
|
225
|
-
(`[ … ]`), so the engine's result mapping (`undefined | item | items`)
|
|
226
|
-
can never confuse "one array-valued item" with "several items" — the
|
|
227
|
-
window array is always the single result and its elements are read
|
|
228
|
-
positionally.
|
|
229
|
-
|
|
230
|
-
## 7. Parameters
|
|
231
|
-
|
|
232
|
-
`.params({ tenantId })` declares AND binds externals; a callback reads
|
|
233
|
-
them through its last argument:
|
|
234
|
-
|
|
235
|
-
```js
|
|
236
|
-
from(rows)
|
|
237
|
-
.params({ tenantId: 'a7' })
|
|
238
|
-
.where((r, p) => r.tenant.eq(p.tenantId))
|
|
239
|
-
.toDocument();
|
|
240
|
-
// { "$for": { "it": "$[*]" }, "$where": { "$eq": ["$it.tenant", "$tenantId"] }, "$return": "$it" }
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
The emitted document carries `$tenantId` as an external parameter
|
|
244
|
-
(QUERY-FORMAT §9) — the seam that later becomes a bound SQL parameter.
|
|
245
|
-
Undeclared use is `JL0004` at BUILD time with the fix in the message
|
|
246
|
-
(the engine would say JQ0005 at compile time; earlier and clearer
|
|
247
|
-
wins). The names `it`, `it2`, `acc` and `g` are RESERVED — they are the
|
|
248
|
-
emitted document's own binding names — and declaring them is `JL0004`.
|
|
249
|
-
|
|
250
|
-
## 8. The provider contract
|
|
251
|
-
|
|
252
|
-
A **provider** is any object exposing:
|
|
253
|
-
|
|
254
|
-
```
|
|
255
|
-
execute(queryDocument, options) -> undefined | item | items[]
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
- `queryDocument` arrives WHOLE — a terminal hands over the full
|
|
259
|
-
emitted document (including the terminal's own wrapper, §6);
|
|
260
|
-
nothing is enumerated locally, ever.
|
|
261
|
-
- `options.externals` is the `{ name: value }` record of bound
|
|
262
|
-
parameters (§7).
|
|
263
|
-
- The return value uses the ENGINE's result mapping
|
|
264
|
-
(`undefined` = empty, a single item as itself, several items as an
|
|
265
|
-
array) — the in-memory runner is the reference semantics every
|
|
266
|
-
provider MUST match, and it implements this same interface.
|
|
267
|
-
- **`execute` is SYNCHRONOUS.** A `Sequence` terminal is a value —
|
|
268
|
-
`toArray(): T[]`, `count(): number` — so a promise cannot be returned
|
|
269
|
-
under that type. A provider that answers one is refused with `JL2004`
|
|
270
|
-
at the seam, because the alternative is not a slow answer but a wrong
|
|
271
|
-
one: the promise came back typed as the value, `count()` handed a
|
|
272
|
-
`Promise` to arithmetic, and `first()` indexed the promise and returned
|
|
273
|
-
`undefined`. An asynchronous provider (a wasm/OPFS driver) is reached
|
|
274
|
-
by emitting `toDocument()` and awaiting the provider directly.
|
|
275
|
-
|
|
276
|
-
`@jarenjs/db` implements this contract without either package
|
|
277
|
-
importing the other; a test double proves the document arrives whole.
|
|
278
|
-
|
|
279
|
-
### 8.1 Compilation registries
|
|
280
|
-
|
|
281
|
-
`from(source, options)` and `fromDocument(source, doc, options)` take the
|
|
282
|
-
engine's own compile options, so a document that is expressible is also
|
|
283
|
-
executable in memory:
|
|
284
|
-
|
|
285
|
-
| option | what it enables |
|
|
286
|
-
|---|---|
|
|
287
|
-
| `compileTypeTest` | `ofType`/`cast` (the schema operators) |
|
|
288
|
-
| `collations` | `orderBy(…, { collation })` — a `nl` sort is `JQ0010` without it |
|
|
289
|
-
| `functions` | `$call` in a hand-written or saved document |
|
|
290
|
-
| `pathFunctions` | custom RFC 9535 path function extensions |
|
|
291
|
-
| `limits` | step, sequence and result bounds — the reason a SAVED document can be run at all |
|
|
292
|
-
| `registry` | an explicit cache-partition key, when the hooks above are rebuilt per call |
|
|
353
|
+
### The Jaren schema pen — [SCHEMA-PEN.md §2](SCHEMA-PEN.md)
|
|
293
354
|
|
|
294
|
-
|
|
295
|
-
alone: the same document compiles to different code with and without a
|
|
296
|
-
collation registry, so sharing one partition would answer a caller who
|
|
297
|
-
passed no collations with the compiled-with version.
|
|
355
|
+
**Primitives, literals and enums**
|
|
298
356
|
|
|
299
|
-
|
|
357
|
+
| Method | Emits | `Infer` / `Input` | Status |
|
|
358
|
+
|---|---|---|---|
|
|
359
|
+
| `string()` | `{ type: 'string' }` | `string` | native |
|
|
360
|
+
| `number()` | `{ type: 'number' }` | `number` | native |
|
|
361
|
+
| `integer()` | `{ type: 'integer' }` | `number` (integer-ness is a documented widening) | native |
|
|
362
|
+
| number `.int()` | `{ type: 'integer' }` — the same node, retyped; `number().int()` and `integer()` are one document | `number` | native |
|
|
363
|
+
| `boolean()` | `{ type: 'boolean' }` | `boolean` | native |
|
|
364
|
+
| `nil()` | `{ type: 'null' }` | `null` | native |
|
|
365
|
+
| `literal(v)` | `{ const: v }` | the literal | native |
|
|
366
|
+
| `enumOf(values)` | `{ enum: values }` — an UNTYPED enum, any mix of JSON values | the literal union | native; an empty or non-array argument is `JL0101` |
|
|
367
|
+
| string/number `.enumOf(values)` | `enum` beside the `type` — a typed enum (what a store maps to a column); values of another JSON type are `JL0101` | the literal union; with `.coerce()` the `Input` widens by the one source primitive that can reach a member (`1 \| 2 \| 3 \| string`) | native |
|
|
368
|
+
| `datetime()`, `date()` | `{ type: 'string', format: 'date-time' \| 'date' }` | `DateTime` | native |
|
|
369
|
+
| `time()`, `duration()` | `{ type: 'string', format: 'time' \| 'duration' }` | `string` | native |
|
|
370
|
+
| `any()` | `{}` | `unknown` | native |
|
|
371
|
+
| `never()` | `false` | `never` | native; no annotation and no check while `false` IS the document (`JL0102`); `nullable()` lifts both |
|
|
372
|
+
|
|
373
|
+
**Objects**
|
|
374
|
+
|
|
375
|
+
| Method | Emits | `Infer` / `Input` | Status |
|
|
376
|
+
|---|---|---|---|
|
|
377
|
+
| `object(props)` | `{ type: 'object', properties, required, additionalProperties: false }` — `required` lists every member not `optional()`, in declaration order, and is omitted when empty | a closed object: members required unless `optional()`; no index signature; `object({})` is `Record<string, never>` | native |
|
|
378
|
+
| `.open()` | drops `additionalProperties: false` | `& { [k: string]: unknown }` | native |
|
|
379
|
+
| `.optional()` | the member leaves `required` | `?:` (on both sides; a `default()`ed member is present on `Infer`) | native |
|
|
380
|
+
| `.nullable()` | `type: [t, 'null']` on a typed node; `enum: [..., null]` on `enumOf`/`literal`; `anyOf: [node, { type: 'null' }]` on the rest | `\| null` | native / emulated |
|
|
381
|
+
| `record(values)` | `{ type: 'object', additionalProperties: values }` | `{ [k: string]: V }` | native |
|
|
382
|
+
| `.minProperties(n)`, `.maxProperties(n)` | `minProperties`, `maxProperties` | — | native |
|
|
383
|
+
| `.dependentRequired(map)` | `dependentRequired`, cloned | — | native; anything but a name → array-of-names map is `JL0101` |
|
|
384
|
+
| `.propertyNames(b)` | `propertyNames` | — | native |
|
|
385
|
+
| `.patternProperties(map)` | `patternProperties` | on a closed object the index signature carries the pattern values widened over the members (`[k: string]: V \| members`); on an open one `unknown` | native |
|
|
386
|
+
| `.extend(props)` | the reshaped `properties`/`required` — a later spelling of a name REPLACES the earlier one and moves to the end | the reshaped members | emulated |
|
|
387
|
+
| `.pick(keys)`, `.omit(keys)` | the selected `properties`, in the original order | `Pick<>` / `Omit<>` | emulated; a name the object does not carry is `JL0101` |
|
|
388
|
+
| `.partial()` | every member `optional()`, so `required` disappears | every member `?:` | emulated |
|
|
389
|
+
| `.required(keys?)` | the named members required again; every member when no keys are given | the members no longer `?:` | emulated |
|
|
390
|
+
|
|
391
|
+
**Arrays and tuples**
|
|
392
|
+
|
|
393
|
+
| Method | Emits | `Infer` / `Input` | Status |
|
|
394
|
+
|---|---|---|---|
|
|
395
|
+
| `array(items)` | `{ type: 'array', items }` | `T[]` | native |
|
|
396
|
+
| array `.min(n)`, `.max(n)`, `.length(n)` | `minItems`, `maxItems`, both | — | native |
|
|
397
|
+
| array `.unique()` | `uniqueItems: true` | — | native |
|
|
398
|
+
| array `.contains(b)` | `contains` | — | native; a normalizer keyword inside it is `JL0102` |
|
|
399
|
+
| `tuple(items)` | `{ type: 'array', prefixItems, minItems: items.length }` | `[A, B, ...unknown[]]` — every position required, the rest open (emit's reading of an omitted `items`) | native |
|
|
400
|
+
| tuple `.rest(b)` | `items: b`; `rest(never())` is `items: false` | `[A, B, ...R[]]`; `[A, B]` | native |
|
|
300
401
|
|
|
301
|
-
|
|
302
|
-
exists):
|
|
402
|
+
**Strings**
|
|
303
403
|
|
|
304
|
-
|
|
|
305
|
-
|
|
306
|
-
|
|
|
307
|
-
| `
|
|
308
|
-
| `
|
|
309
|
-
| `
|
|
310
|
-
| `JL0005` | an operator was used invalidly at build time |
|
|
311
|
-
| `JL0006` | an unsupported operator was invoked |
|
|
404
|
+
| Method | Emits | `Infer` / `Input` | Status |
|
|
405
|
+
|---|---|---|---|
|
|
406
|
+
| string `.min(n)`, `.max(n)`, `.length(n)` | `minLength`, `maxLength`, both | — | native |
|
|
407
|
+
| string `.pattern(p)` | `pattern` (a string, or a flagless `RegExp` by its source) | — | native; flags are `JL0102` |
|
|
408
|
+
| string `.format(f)` | `format` | `DateTime` for `'date-time'`/`'date'`, `string` otherwise | native |
|
|
409
|
+
| string `.email()`, `.uuid()`, `.uri()` | `format: 'email' \| 'uuid' \| 'uri'` | `string` | native |
|
|
312
410
|
|
|
313
|
-
|
|
411
|
+
**Numbers**
|
|
314
412
|
|
|
315
|
-
|
|
|
413
|
+
| Method | Emits | `Infer` / `Input` | Status |
|
|
414
|
+
|---|---|---|---|
|
|
415
|
+
| number `.min(n)`, `.max(n)` | `minimum`, `maximum` | — | native |
|
|
416
|
+
| number `.gt(n)`, `.lt(n)` | `exclusiveMinimum`, `exclusiveMaximum` | — | native |
|
|
417
|
+
| number `.multipleOf(n)` | `multipleOf` | — | native; zero or a negative is `JL0101` |
|
|
418
|
+
|
|
419
|
+
**Composition**
|
|
420
|
+
|
|
421
|
+
| Method | Emits | `Infer` / `Input` | Status |
|
|
422
|
+
|---|---|---|---|
|
|
423
|
+
| `union(options)` | `{ anyOf }` — an option may be a builder or a hand-written JSON Schema (`true`/`false` included), wrapped as `from(json)` | `A \| B` | native |
|
|
424
|
+
| `discriminated(key, options)` | `{ oneOf }` — every option an object declaring `key` as a `literal()`/`enumOf()` member | `A \| B` | native; a missing tag is `JL0102` |
|
|
425
|
+
| `intersection(parts)` | `{ allOf }` | `A & B` | native; a closed object part is `JL0102` (the parts would reject each other's members — `open()` them, or `extend()`) |
|
|
426
|
+
| `when(cond)`, `.then(b)`, `.else(b)` | `{ if, then, else }` | `unknown` (emit records a conditional, never types it) | native |
|
|
427
|
+
|
|
428
|
+
**References and `$defs`**
|
|
429
|
+
|
|
430
|
+
| Method | Emits | `Infer` / `Input` | Status |
|
|
431
|
+
|---|---|---|---|
|
|
432
|
+
| `named(name, b)` | `$defs[name]` at the document root, `{ $ref: '#/$defs/name' }` where reached — once, however many places reach it | `Infer<b>` | native; a name outside `[A-Za-z_][A-Za-z0-9_.-]*` is `JL0101`; two distinct builders under one name are `JL0103` |
|
|
433
|
+
| `ref(name)` | `{ $ref: '#/$defs/name' }` | `T` as asserted (`ref<T>`) | native; a name no `named()` in the document answers is `JL0103` |
|
|
434
|
+
| `lazy(() => Named)` | as `named` — the recursion spelling | `T` as annotated on the recursive constant | native; a thunk that is not a function is `JL0101`; an unnamed or non-builder target is `JL0103` |
|
|
435
|
+
| `from(json)` | the JSON, verbatim (cloned, so the document is its own tree) | `T` as asserted (`from<T>`) | native; anything but an object or a boolean is `JL0101` |
|
|
436
|
+
|
|
437
|
+
**Annotations and messages**
|
|
438
|
+
|
|
439
|
+
| Method | Emits | `Infer` / `Input` | Status |
|
|
440
|
+
|---|---|---|---|
|
|
441
|
+
| `.describe(text)`, `.title(text)` | `description`, `title` | — | native |
|
|
442
|
+
| `.example(v)` | one more entry of `examples`, in call order | — | native |
|
|
443
|
+
| `.meta(annotations)` | the keys verbatim, in the order first set | — | native; a pen-owned keyword is `JL0104` |
|
|
444
|
+
| `.message(spec)` | `errorMessage: spec` (the validator's string, map or `$msgid` forms) | — | native |
|
|
445
|
+
| `.annotate(key, value)` | one annotation keyword — the primitive the four above are written in terms of, and the one a subclass overrides | `this` | native; `never()` overrides it to refuse until `nullable()` widens it (`JL0102`) |
|
|
446
|
+
| `.annotation(key)` | nothing: it READS the annotation a builder already carries, or `undefined` — what a subclass consults before it folds one into a keyword it owns | the value as stored | native |
|
|
447
|
+
|
|
448
|
+
**Validation extensions**
|
|
449
|
+
|
|
450
|
+
| Method | Emits | `Infer` / `Input` | Status |
|
|
451
|
+
|---|---|---|---|
|
|
452
|
+
| `.check(fn)` | `$query`: the callback captured through the chain's proxy — `fn(value, { root, path })`, the value at `$`, the two externals the validator binds; two checks conjoin with `$and` | — (a dropped constraint) | native; another external is `JL0104` |
|
|
453
|
+
| `.check(query)` | `$query`: a query document embedded verbatim | — | native; a value that is not JSON is `JL0101` |
|
|
454
|
+
| `.coerce()` | `'x-coerce': true` on a scalar — the normalizer's per-field predicate | `Input` widens to the transport forms: string `\| number \| boolean`, number/integer `\| string`, boolean `\| string`, null `\| string` | native; on a non-scalar or a nullable, `JL0102` |
|
|
455
|
+
| `.trim()` | `'x-trim': true` on a string | — | native; off a string, `JL0102` |
|
|
456
|
+
|
|
457
|
+
**The document, and the builder itself**
|
|
458
|
+
|
|
459
|
+
| Method | Emits | `Infer` / `Input` | Status |
|
|
460
|
+
|---|---|---|---|
|
|
461
|
+
| `document(root, { draft })` | the document, with `$schema` first for `'2020-12'`; without a draft, the root's document unchanged | — | native; another draft, or a draft on a boolean schema, is `JL0102` |
|
|
462
|
+
| `.schema` | the assembled document — a deep-frozen value, computed once and memoized | `JsonSchema \| boolean` | native |
|
|
463
|
+
| `.toJSON()` | the same document, so `JSON.stringify(builder)` is the document | `JsonSchema \| boolean` | native |
|
|
464
|
+
| `.state` | the frozen builder state (kind, children, keywords, annotations) — what a subclass reads, never a document | the state object | native |
|
|
465
|
+
| `.with(patch)` | nothing: a NEW builder of the same class with part of the state replaced. Every method above is written in terms of it, and a subclass keeps its own class through all of them | `this` | native |
|
|
466
|
+
| `.keyword(key, value)` | one constraint keyword, in the order first set | `this` | native |
|
|
467
|
+
| `schemaOf(value)` | nothing: the document of a builder, or the value as given — the one call a consumer needs to accept "a schema, by hand or by pen" | `unknown` | native |
|
|
468
|
+
| `requireJson(value, what)` | nothing: the JSON boundary every value entering a document crosses, exported so a pen built over this one uses the same door | `T` | native; a non-JSON value is `JL0101` |
|
|
469
|
+
| `createFactories(classes)` | nothing: the named factories above, built for one SET of builder classes. `@jarenjs/linq/model` and `@jarenjs/linq/forms` call it with their subclasses, which is why the wiring exists exactly once and no subpath patches another's prototype | the factory record | native |
|
|
470
|
+
|
|
471
|
+
### The Jaren model pen — [MODEL-PEN.md §2](MODEL-PEN.md)
|
|
472
|
+
|
|
473
|
+
**The `x-entity` vocabulary**
|
|
474
|
+
|
|
475
|
+
| Method | Emits (an `x-entity` member) | `InferMeta` reading | Status |
|
|
476
|
+
|---|---|---|---|
|
|
477
|
+
| `.key()` | `key: true` — (part of) the primary key; several members make a composite one (MODEL-FORMAT §9.5) | marks the member `key`: `EntityKey` is its primitive, or the composite object over all of them | native; on a kind that can hold no column, `JL0102` |
|
|
478
|
+
| `.identity('uuid')` on a string, `.identity('auto')` on an integer | `key: true` **and** `default: 'uuid' \| 'auto'` — a store-allocated single key (§9.5) | marks it `key` **and** `generated`: optional on `input`, required on `doc` | native; off its kind, or beside a second `key()`, `JL0102` |
|
|
479
|
+
| `.unique()` | `unique: true` — a unique index over the member's column. On an ARRAY builder the schema pen already owns the name, and the base wins: it is `uniqueItems` there, unchanged ([SCHEMA-PEN.md §2.3](SCHEMA-PEN.md#23-arrays-and-tuples)) | — | native; on a kind that can hold no column, `JL0102` |
|
|
480
|
+
| `.index()` | `index: true` — a non-unique index over the member's column | — | native; on a kind that can hold no column, `JL0102` |
|
|
481
|
+
| `.version()` | `version: true` — the optimistic-concurrency token (§11.5), engine-owned: one plain integer column per entity | — | native; off an integer, `JL0102` |
|
|
482
|
+
| `.column('integer')` | `column: 'integer'` — an epoch-milliseconds column beside the RFC 3339 string, on a `datetime()`/`date()` member only (§9.3) | — | native; off a date-formatted string, `JL0102` |
|
|
483
|
+
| `.column('json')` | `column: 'json'` — the scalar stays in the JSONB document, which is the opt-out that preserves present-`null` (§9.3) | — | native |
|
|
484
|
+
| `.now()` | `default: 'now'` — an RFC 3339 stamp on insert, when the member is absent | marks it `generated`: optional on `input` | native |
|
|
485
|
+
| `.updated()` | `default: 'updated'` — a stamp on insert AND on every update | marks it `generated` | native |
|
|
486
|
+
| `.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` |
|
|
487
|
+
| `.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` |
|
|
488
|
+
| `.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) |
|
|
489
|
+
| `.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 |
|
|
490
|
+
| `.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` |
|
|
491
|
+
|
|
492
|
+
**The relation members**
|
|
493
|
+
|
|
494
|
+
| Method | Emits | `InferMeta` reading | Status |
|
|
495
|
+
|---|---|---|---|
|
|
496
|
+
| `rel.hasMany(to, { via, onDelete })` | `relation: { to, many: true, via, onDelete }` — one-to-many; `via` names the foreign key on the TARGET entity | `doc`: `to[]`, optional; dropped from `input`; `relations[name] = { entity: to, doc, many: true }` | native |
|
|
497
|
+
| `rel.hasOne(to, { via, onDelete })` | `relation: { to, via, onDelete }` — one-to-one, and the many-to-one side; `via` names the foreign key on the DECLARING entity | `doc`: `to`, optional; dropped from `input`; `many: false` | native |
|
|
498
|
+
| `rel.belongsToMany(to, { through? })` | `relation: { to, many: true, through? }` — many-to-many through a join table | `doc`: `to[]`, optional; `input`: `Array<key \| doc>`, optional; `many: true` | native |
|
|
499
|
+
|
|
500
|
+
**Collections and their indexes**
|
|
501
|
+
|
|
502
|
+
| Method | Emits | Type reading | Status |
|
|
503
|
+
|---|---|---|---|
|
|
504
|
+
| `collection(schema, { key?, identity?, indexes?, renamedFrom? })` | `{ schema, key?, identity?, indexes?, 'x-rename'? }` — `key` is an RFC 6901 pointer, a captured member path (`(d) => d.id` → `/id`) or `null` (the store allocates, `identity` says how); the other options ride verbatim | `CollectionSpec<Infer<B>>`, carrying the document shape its paths are checked against | native; an option outside the four, a key that is neither pointer nor lambda nor `null`, an `indexes` that is not an array of `index()` entries, all `JL0101` |
|
|
505
|
+
| `index(path, options?)` | `{ name, path, unique?, derive?, precision?, dims?, physical? }` — `path` is a captured lambda (`(p) => p.cell` → `$.cell`), a non-empty array of them (a composite), or a JSONPath string; `name` defaults to `by_<segments>`; the rest ride verbatim for the store's model walk to judge (§2.1) | `IndexPath<D>` over the collection's shape: a member the shape lacks does not compile | native; an option outside the six is `JL0101`; a lambda that answers an operator result or a surface method is `JL0102` |
|
|
506
|
+
|
|
507
|
+
**The model document**
|
|
508
|
+
|
|
509
|
+
| Method | Emits | Type reading | Status |
|
|
510
|
+
|---|---|---|---|
|
|
511
|
+
| `defineModel({ entities?, collections? })` | `{ $model: '0.1', collections?, entities? }`, deep-frozen; each entity is `{ schema, 'x-rename'? }` | `ModelDocument<E, C>`, whose phantoms `InferMeta<>` and the migration pen read | native; neither member given, a member outside the two, a name that is not an identifier, or a declaration of the wrong kind, all `JL0101`; an undeclared relation target or a `collection()` under `entities`, `JL0102` |
|
|
512
|
+
| `document(root, { draft? })` | as [SCHEMA-PEN.md](SCHEMA-PEN.md#210-the-document-and-the-builder-itself) — a standalone JSON Schema, with `x-entity` blocks riding as annotations. It writes a schema, never a `$model` | — | native |
|
|
513
|
+
| `schemaOf(value)` | as [SCHEMA-PEN.md](SCHEMA-PEN.md#210-the-document-and-the-builder-itself) — the document of a builder, or the value as given | `unknown` | native |
|
|
514
|
+
| `withEntity(Base)` | nothing: a NEW class, `Base` plus §2.1's vocabulary. The eight exported classes are made with it at module scope, and a consumer subclassing one takes the same route | `B` — the base class's own type | native |
|
|
515
|
+
|
|
516
|
+
**The schema pen's vocabulary, re-exported**
|
|
517
|
+
|
|
518
|
+
| Method | Row | Status |
|
|
519
|
+
|---|---|---|
|
|
520
|
+
| `string()`, `number()`, `integer()`, `boolean()`, `nil()`, `literal(v)`, `enumOf(values)`, `datetime()`, `date()`, `time()`, `duration()`, `any()`, `never()` | [SCHEMA-PEN.md §2.1](SCHEMA-PEN.md#21-primitives-literals-and-enums) | native, plus `x-entity` when a §2.1 method is called on it |
|
|
521
|
+
| `object(props)`, `record(values)` | [SCHEMA-PEN.md §2.2](SCHEMA-PEN.md#22-objects) | native; an entity's own builder is an `object()` (or an `intersection()` of them), and it is where `.renamedFrom()` lands |
|
|
522
|
+
| `array(items)`, `tuple(items)` | [SCHEMA-PEN.md §2.3](SCHEMA-PEN.md#23-arrays-and-tuples) | native; an array or tuple member is JSONB, so it takes no column of its own (§6) |
|
|
523
|
+
| `union(options)`, `discriminated(key, options)`, `intersection(parts)`, `when(cond)` | [SCHEMA-PEN.md §2.6](SCHEMA-PEN.md#26-composition) | native; a union of several scalar types stays in the document (MODEL-FORMAT §9.3) |
|
|
524
|
+
| `named(name, b)`, `ref(name)`, `lazy(thunk)`, `from(json)` | [SCHEMA-PEN.md §2.7](SCHEMA-PEN.md#27-references-and-defs) | native; `x-entity` is read through an entity's `allOf`, `$defs` and `definitions` blocks and nowhere deeper (MODEL-FORMAT §9.2) |
|
|
525
|
+
|
|
526
|
+
### The Jaren JSLT pen — [JSLT-PEN.md §2](JSLT-PEN.md)
|
|
527
|
+
|
|
528
|
+
| Method | Emits | Type reading | Status |
|
|
529
|
+
|---|---|---|---|
|
|
530
|
+
| `stylesheet(rules, { unmatched?, modes? })` | `{ $jslt: '0.1', unmatched?, modes?, rules }` — the envelope (§2.1), in that member order; the bare-array form is the rules array itself | `Stylesheet<In, Out>`: the FIRST rule's phantoms, or the author's (`stylesheet<In, Out>(…)`) | native; a non-array, another option, a rule that is not an object, a rule without `body`, a rule that is not JSON `JL0101` |
|
|
531
|
+
| `unmatched` | `unmatched: 'share' \| 'fresh' \| 'error'` (§5) | `Disposition` | native; another value `JL0101` |
|
|
532
|
+
| `modes` | `modes: { name: { unmatched } }` (§2.1) | — | native; another member, a mode that is not `{ unmatched }`, a map whose prototype a `__proto__:` literal replaced `JL0101` |
|
|
533
|
+
| `rule(match, body, { mode?, priority? })` | `{ mode?, match?, priority?, body }` — the rule object (§2.2), in that member order | `Rule<In, Out>` | native; a non-object options, another option `JL0101` |
|
|
534
|
+
| `match` as a JSONPath string | `match: '$..price'` (§3.1) | the honest top | native |
|
|
535
|
+
| `match` as `{ path?, schema? }` | the object; `schema` a schema-pen builder's document, or a schema verbatim | a builder types the body's value (`Infer<>`) | native; `{}` is `JL0102`; another member, a non-string `path`, a `schema` that is not JSON `JL0101` |
|
|
536
|
+
| `match` `null` or absent | no `match` member — the unconditional rule (default priority −1, §4) | the honest top | native |
|
|
537
|
+
| `mode` | `mode: 'toc'` (§7) | `string` | native; a non-string `JL0101` |
|
|
538
|
+
| `priority` | `priority: 2` (§4) | `number` | native; a non-finite number, or `-0`, `JL0101` |
|
|
539
|
+
| `body(fn, { externals? })` | the captured query document — `fn(v, x)` with `v` at `$`, `x.root`/`x.path` (§8.2) and the declared parameters as `$name` externals (§8.1); a returned literal is a constructor, a string starting `$` is escaped `$$` | `BodyDocument<In, Out>`: `In` from the annotated `v` (`(v: Expr<Book>) => …`), `Out` the unwrapped return; a declared parameter is `UnknownExpr` until `x` is annotated (`x: Externals<{ rate: number }>`) | native; a non-callback, a non-object options, another option, a non-array or non-identifier external `JL0101`; an undeclared external `JL0104`; `root`/`path` declared `JL0104` |
|
|
540
|
+
| a callback where a body is taken | `body(fn)` with no parameters | as above; the match's builder types `v` | native |
|
|
541
|
+
| a query document where a body is taken | the document, verbatim (a `body()` result, or by hand) | a `body()` document carries its phantoms; a hand-written one is `unknown` | native; not JSON `JL0101` |
|
|
542
|
+
| `apply(selector)` | `{ $apply: selector }` — the rule's own mode (§6.2); the selector an expression (`v.chapters.all()`), a path string verbatim (`'$.chapters[*]'`), or data (`[1, 2]` embeds as `$const`) | `UnknownExpr` — a dispatch to other rules | native; no selector `JL0101`; outside `body()` `JL0102` |
|
|
543
|
+
| `apply(selector, mode)` | `{ $apply: [selector, mode] }` — the argument-list form (§6.2) | `UnknownExpr` | native; a non-string mode `JL0101` |
|
|
544
|
+
| `[apply(…)]` as a member value | `[{ $apply: … }]` — the `[]` idiom (§6.3) | `unknown[]` | native |
|
|
545
|
+
| `apply(…)` as a bare member value | — | — | refused (`JL0102`): the engine fails at run time on the second child (`JQ2001`) |
|
|
546
|
+
| `op(name, operands)` | `{ [name]: operands }` — a registered operator (§13), spelled without judging it; one operand or a list | `UnknownExpr` | native; the engine's `JQ0002` decides; a name without `$` `JL0101`; outside any capture `JL0005` |
|
|
547
|
+
|
|
548
|
+
### The Jaren migration pen — [MIGRATION-PEN.md §2](MIGRATION-PEN.md)
|
|
549
|
+
|
|
550
|
+
| Method | Emits | Type reading | Status |
|
|
551
|
+
|---|---|---|---|
|
|
552
|
+
| `defineMigration({ id, from, to, note? })` | `{ $migration: '0.1', id, from, to, note?, steps }` — `from`/`to` the two models' shape hashes, exactly `shapeHash` (pinned) | `Migration<From, To>`, the two model documents' phantoms | native; not a `$model` document, an empty `id`, another member `JL0101` |
|
|
553
|
+
| `.ddl(sql, note?)` | `{ kind: 'ddl', sql, note? }` — one rendered statement (§2) | — | native; an empty statement `JL0101` |
|
|
554
|
+
| `.sql(sql, note?)` | `{ kind: 'sql', sql, note? }` — one data statement spelled directly (§9.4); a dry run always prints it with its note | — | native; an empty statement `JL0101` |
|
|
555
|
+
| `.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` |
|
|
556
|
+
| `.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` |
|
|
557
|
+
| `.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` |
|
|
558
|
+
| `.assert(name, query, { expect? })` | the query document verbatim | — | native |
|
|
559
|
+
| `.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` |
|
|
560
|
+
| `.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` |
|
|
561
|
+
| `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` |
|
|
562
|
+
| `.document`, `.toJSON()` | the deep-frozen `$migration` document — assembled once and memoized, so `a.document === a.document` | `MigrationDocument` | native |
|
|
563
|
+
|
|
564
|
+
### The Jaren contract pen — [CONTRACT-PEN.md §2](CONTRACT-PEN.md)
|
|
565
|
+
|
|
566
|
+
**The document**
|
|
567
|
+
|
|
568
|
+
| Method | Emits | Type reading | Status |
|
|
569
|
+
|---|---|---|---|
|
|
570
|
+
| `defineContract({ id?, version?, compat? }, operations)` | `{ $contract: '0.1', id?, version?, compat?, $defs?, operations }` in §12.1's root order, deep-frozen | `Contract<Ops>`; `ContractOf<typeof c>` is the operation map §5 reads | native; a head member the pen does not know, an `id` outside `[A-Za-z_][A-Za-z0-9_-]*`, a non-string `version`, a `compat` that is not an array of strings, or no operation at all, `JL0101` |
|
|
571
|
+
| `.document` | the deep-frozen `$contract` document — the same object every time | `ContractDocument` | native |
|
|
572
|
+
| `toJSON()` | the same document, so `JSON.stringify(contract)` is the contract | `ContractDocument` | native |
|
|
573
|
+
|
|
574
|
+
**The three operation kinds**
|
|
575
|
+
|
|
576
|
+
| Method | Emits | Type reading | Status |
|
|
577
|
+
|---|---|---|---|
|
|
578
|
+
| `read({ input?, output, errors?, policy?, http?, doc? })` | `{ kind: 'read', … }` in §12.1's operation order, declared members only | `OperationDeclaration<'read', S>`; `kind` is the literal | native; a member the spec does not take, a missing `output`, or a non-string `doc`, `JL0101` |
|
|
579
|
+
| `command({ … })` | `{ kind: 'command', … }` | `OperationDeclaration<'command', S>` | native; the same three |
|
|
580
|
+
| `subscribe({ … })` | `{ kind: 'subscribe', … }` — `output` is the SNAPSHOT schema (§17) | `OperationDeclaration<'subscribe', S>`; never opaque | native; the same three |
|
|
581
|
+
|
|
582
|
+
**The schema positions**
|
|
583
|
+
|
|
584
|
+
| Written as | Emits | Type reading | Status |
|
|
585
|
+
|---|---|---|---|
|
|
586
|
+
| a schema-pen builder | the builder's schema, its `named()` definitions hoisted to the contract's `$defs` and referenced `#/$defs/<name>` | `Infer<>` of the builder; `Input<>` for the accepted shape | native |
|
|
587
|
+
| a JSON Schema object, or `true` / `false` | copied verbatim, deep-cloned | `unknown` — a literal is never inferred (the binder's §1.1 rule 2) | native; a value that is neither an object nor a boolean, or one that is not JSON, `JL0101` |
|
|
588
|
+
|
|
589
|
+
**The errors map**
|
|
590
|
+
|
|
591
|
+
| Method | Emits | Type reading | Status |
|
|
592
|
+
|---|---|---|---|
|
|
593
|
+
| `errors: { <code>: … }` | `{ <code>: { status?, schema? } }`, in declaration order | the declared codes are the operation's `errors` union — `'conflict' \| 'not-found'` | native; a map that is not a plain object, a code outside `^[a-z][a-z0-9-]*$`, or an entry that is not a plain object, `JL0101` |
|
|
594
|
+
| `error({ status?, schema? })` | `{ status?, schema? }` in that order — `error()` with nothing emits `{}` | `ErrorDeclaration<E>` | native; another member, or a status outside 100–599, `JL0101` |
|
|
595
|
+
|
|
596
|
+
**The policy**
|
|
597
|
+
|
|
598
|
+
| Member | Takes | Emits |
|
|
599
|
+
|---|---|---|
|
|
600
|
+
| `task` | `switch`, `exhaust`, `concat`, `parallel` | the token |
|
|
601
|
+
| `idempotency` | `none`, `optional`, `required` | the token |
|
|
602
|
+
| `revision` | `"input:<json-pointer>"` | the string, verbatim |
|
|
603
|
+
| `cache` | `none`, `revision` | the token |
|
|
604
|
+
| `limits` | `{ maxBodyBytes }`, a positive integer | `{ maxBodyBytes }` |
|
|
605
|
+
| `errors` | `{ details }` — `none`, `paths`, `full` | `{ details }` |
|
|
606
|
+
| `retry` | `{ max, on }` — an integer ≥ 0 and an array of code strings | `{ max, on }`, the array copied |
|
|
607
|
+
| `stream` | `{ resume?, heartbeatMs?, maxPatchBytes? }` — `snapshot`/`replay`, an integer ≥ 1000, a positive integer | the declared members only |
|
|
608
|
+
| `audience` | `public`, `server` | the token |
|
|
609
|
+
|
|
610
|
+
**The HTTP binding**
|
|
611
|
+
|
|
612
|
+
| Method | Emits | Type reading | Status |
|
|
613
|
+
|---|---|---|---|
|
|
614
|
+
| `http({ method, path, in?, body?, status?, media? })` | the binding in §12.1's http order, declared members only | `HttpBinding<H>`; a non-JSON `media` makes the operation `opaque: true` | native; a member the binding does not take, a method outside the seven tokens, a non-string `body`, a status outside 200–299 or a non-string `media`, `JL0101`; a reserved path-template form or a member mapped to `path` the template does not declare, `JL0102` |
|
|
615
|
+
|
|
616
|
+
| Member | Takes | Note |
|
|
617
|
+
|---|---|---|
|
|
618
|
+
| `method` | one uppercase token of `GET HEAD POST PUT PATCH DELETE OPTIONS` | lowercase is refused; the format's table is uppercase |
|
|
619
|
+
| `path` | a path template (§4.2) | `{name}` and `:name` both accepted, and **written as declared** |
|
|
620
|
+
| `in` | input member → `path` \| `query` \| `header` \| `body` | only the members the §4.1 default does not already place |
|
|
621
|
+
| `body` | the input member whose value IS the request body | a non-empty string |
|
|
622
|
+
| `status` | 200–299 | the success status; `200` is the default and is never written |
|
|
623
|
+
| `media` | a media type | anything but `application/json` or a `+json` suffix makes the operation opaque (§4.5) |
|
|
624
|
+
|
|
625
|
+
**The three consumers**
|
|
626
|
+
|
|
627
|
+
| Method | Emits | Type reading | Status |
|
|
628
|
+
|---|---|---|---|
|
|
629
|
+
| `typedClient(client, contract)` | — (identity) | `TypedClient<C>`: `invoke` over the invokable operations, `subscribe` over the subscribe ones, `url` over all of them | native |
|
|
630
|
+
| `typedHandlers(contract, handlers)` | — (identity) | `TypedHandlerTable<C>`: one handler per invokable operation, `(input, ctx) => output \| Failure` | native; a missing or misspelled operation does not compile |
|
|
631
|
+
| `typedTools(tools, contract)` | — (identity) | `TypedTool<C>[]`: `name` is the id with `.` → `_`, `execute` takes the operation's ACCEPTED input | native |
|
|
632
|
+
|
|
633
|
+
**What the pen does not judge**
|
|
634
|
+
|
|
635
|
+
| The document the pen writes | The compiler's refusal |
|
|
316
636
|
|---|---|
|
|
317
|
-
|
|
|
318
|
-
|
|
|
319
|
-
| `
|
|
320
|
-
| `
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
|
637
|
+
| a member the document's own closed vocabulary does not carry | `JC0013` (§4.4) |
|
|
638
|
+
| an operation id outside `^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)*$` | `JC0003` |
|
|
639
|
+
| a `read` that declares `idempotency` | `JC0014` |
|
|
640
|
+
| a `GET` carrying a body-located member | `JC0016` |
|
|
641
|
+
| an opaque operation with a body-located member | `JC0017` |
|
|
642
|
+
| a `subscribe` bound to anything but `GET` | `JC0019` |
|
|
643
|
+
| a path variable that is not an input member | `JC0009` |
|
|
644
|
+
| two operations sharing a route shape | `JC0010` |
|
|
645
|
+
|
|
646
|
+
### The Jaren flow pen — [FLOW-PEN.md §2](FLOW-PEN.md)
|
|
647
|
+
|
|
648
|
+
| Method | Emits | Type reading | Status |
|
|
649
|
+
|---|---|---|---|
|
|
650
|
+
| `defineFsm({ initial, states, transitions, context? })` | `{ $fsm: '0.1', initial, states, transitions }` | `Fsm<States, Events, Context>`; `StatesOf<>`, `EventsOf<>`, `ContextOf<>` read it | native; a member the pen does not know, a missing `initial` (pass `null`), a non-array `states`/`transitions`, an entry that is not a declaration, a transition that never named its target, a `context` that is not a builder `JL0101`; an undeclared state id `JL0102` |
|
|
651
|
+
| `state(id, { entry?, exit?, final? })` | `{ id, entry?, exit?, final? }`; a bare string in `states` stays §2's shorthand | `StateDeclaration<Id>` — `Id` is a literal | native; an empty id, another member, a non-boolean `final`, a non-array or non-`effect()` entry/exit `JL0101` |
|
|
652
|
+
| `on(from, event?, { payload? })` | one entry of `transitions`, `{ from, event?, guard?, to, effects? }` in §2's order; a wildcard writes no `event` | `Transition<From, To, Event, Payload>` — a wildcard names no event, so it adds nothing to `EventsOf<>` | native; an empty `from`, a non-string event, another option, a `payload` that is not a builder `JL0101`; an undeclared `from`/`to` `JL0102` |
|
|
653
|
+
| `.when(fn)` / `.when(document)` | the transition's `guard` (§3) | the scope is `Scope<unknown, Payload>`; annotate for `context` | native; a plain STRING `JL0102` (§3's vacuous-guard rule); a non-JSON document `JL0101`; any external `JL0104` |
|
|
654
|
+
| `.to(state)` | the transition's `to` | `Transition<From, To, …>` — `To` is a literal | native; an empty id `JL0101`; an undeclared id `JL0102` at `defineFsm()` |
|
|
655
|
+
| `.effects([...])`, `state(…, { entry, exit })` | the effects lists §4 fires in exit → transition → entry order | `EffectDeclaration[]` | native; a non-array, or an entry that is not `effect()`, `JL0101` |
|
|
656
|
+
| `effect(run, with?)` | `{ run, with? }` | `EffectDeclaration<Run>`; the scope is the honest top until annotated | native; an empty `run`, a non-JSON `with` `JL0101`; any external `JL0104` |
|
|
657
|
+
| `defineDag({ nodes, edges })` | `{ $dag: '0.1', nodes, edges }` | `Dag<Ids, Tasks>`; `NodesOf<>`, `TasksOf<>` read it | native; a member the pen does not know, no node, a `nodes` map whose prototype a `__proto__:` literal replaced, a value that is not a node or edge declaration `JL0101`; an edge on an undeclared id `JL0102` |
|
|
658
|
+
| `input()` / `output()` | `{ kind: 'input' }` / `{ kind: 'output' }` | `NodeDeclaration<'input'>` / `<'output'>` | native |
|
|
659
|
+
| `constant(value)` | `{ kind: 'const', value }` | `NodeDeclaration<'const'>` | native; `undefined`, or a value that is not JSON, `JL0101` |
|
|
660
|
+
| `query(fn \| document)` | `{ kind: 'query', query }` | `NodeDeclaration<'query'>` | native; nothing passed, or a document that is not JSON, `JL0101`; any external `JL0104` |
|
|
661
|
+
| `jslt(stylesheet)` | `{ kind: 'jslt', stylesheet }` — the JSLT pen's document ([JSLT-PEN.md](JSLT-PEN.md)), or one by hand | `NodeDeclaration<'jslt'>` | native; nothing passed, or a value that is not JSON, `JL0101` |
|
|
662
|
+
| `task(run, with?)` | `{ kind: 'task', run, with? }` | `NodeDeclaration<'task', Run>` — `Run` is a literal | native; an empty `run` `JL0101`; any external in `with` `JL0104` |
|
|
663
|
+
| `.checkpoint()` | `checkpoint: true`, written last (§7.6) | a new declaration; the one it came from is unchanged | native |
|
|
664
|
+
| `edge(from, to, { port?, select? })` | `{ from, to, port?, select? }` | `EdgeDeclaration<From, To>` | native; an empty end, an empty `port`, another member `JL0101`; any external in `select` `JL0104` |
|
|
665
|
+
| `typedTasks(graph, tasks)` | — (identity) | the registry `compileDag` resolves must carry one handler per declared task name | native; a missing or misspelled name does not compile |
|
|
666
|
+
|
|
667
|
+
### The Jaren app pen — [APP-PEN.md §2](APP-PEN.md)
|
|
668
|
+
|
|
669
|
+
**The document**
|
|
670
|
+
|
|
671
|
+
| Method | Emits | Type reading | Status |
|
|
672
|
+
|---|---|---|---|
|
|
673
|
+
| `defineApp({ state, initial?, schema?, view, actions?, subs? })` | `{ document: { $app: '0.1', state?, view, actions?, subs? }, stateSchema }` — only the members the author declared, deep-frozen | `AppResult<State, Actions>`; `StateOf<>` and `ActionsOf<>` read it back | native; a member the pen does not know, a missing `view`, a `schema` beside a builder state `JL0101`; an underivable initial state, or a view binding an undeclared action, `JL0102` |
|
|
674
|
+
| `state` | the initial value, from the builder's `default()`s (§1.2); the builder itself becomes `stateSchema` | `Infer<B>` | native |
|
|
675
|
+
| `initial` | the initial value verbatim, in place of the derivation | `Infer<B>` | native; a non-JSON value `JL0101` |
|
|
676
|
+
| `schema` | nothing — it TYPES a `state` given as a plain JSON value, and becomes `stateSchema` | `Infer<B>` | native; beside a builder state, `JL0101` (a builder state IS its schema) |
|
|
677
|
+
| `view` | the JSLT stylesheet verbatim — the pen's `stylesheet([rule(…)])` envelope ([JSLT-PEN.md](JSLT-PEN.md)) or APP-FORMAT §2's bare rule array | `unknown`: a view is a document the grammar judges | native |
|
|
678
|
+
| `actions` | the `actions` map, one captured action document per name | `keyof A & string` — the literal names, which is what `bind<>()` is checked against | native; a value that is not an `action()` `JL0101` |
|
|
679
|
+
| `subs` | the `subs` array, one `sub()` entry per element | `readonly SubDeclaration[]` | native; a value that is not a `sub()` `JL0101` |
|
|
680
|
+
|
|
681
|
+
**The transition**
|
|
682
|
+
|
|
683
|
+
| Method | Emits | Type reading | Status |
|
|
684
|
+
|---|---|---|---|
|
|
685
|
+
| `action(fn, { payload?, event? })` | one action document, captured over `$`, `$event`, `$payload` | `ActionDeclaration<Payload>`; `payload` and `event` are TYPES — the format carries no schema for either, and nothing is emitted for them | native; a non-builder `payload` `JL0101`; an excluded `event` field `JL0102`; a name §3.1 does not bind `JL0104` |
|
|
686
|
+
| `transition({ state?, patch?, effects? })` | the transition object of APP-FORMAT §3.2, in the order the runtime applies it | `Transition` | native; another member `JL0101` |
|
|
687
|
+
| `effect(run, with?)` | `{ run, with? }` (§5.1); `with` is a value in the ACTION's scope, not a callback | `EffectDeclaration<Run>` — `Run` is a literal | native; an empty `run` `JL0101` |
|
|
688
|
+
|
|
689
|
+
**The seven patch operations**
|
|
690
|
+
|
|
691
|
+
| Method | Emits | Type reading | Status |
|
|
692
|
+
|---|---|---|---|
|
|
693
|
+
| `add(path, value)` | `{ op: 'add', path, value }` — **sets a member, and REPLACES an array when the path names one** | `PatchOp` | native |
|
|
694
|
+
| `append(path, value)` | `{ op: 'add', path: '<path>/-', value }` — RFC 6902's array APPEND, the same op at the array's `-` position | `PatchOp` | native |
|
|
695
|
+
| `replace(path, value)` | `{ op: 'replace', path, value }` — the op a transition writes most, and the one an array ELEMENT needs | `PatchOp` | native |
|
|
696
|
+
| `remove(path)` | `{ op: 'remove', path }` | `PatchOp` | native |
|
|
697
|
+
| `move(from, path)` | `{ op: 'move', from, path }` — both lowered as pointers | `PatchOp` | native |
|
|
698
|
+
| `copy(from, path)` | `{ op: 'copy', from, path }` | `PatchOp` | native |
|
|
699
|
+
| `test(path, value)` | `{ op: 'test', path, value }` — a failing test aborts the WHOLE transition (`JA2004`), which is the format's own way to write a precondition | `PatchOp` | native |
|
|
700
|
+
| a path lambda `(st, x) => …` | the JSON Pointer the state shape describes: `st.todos` → `/todos`, `st.todos.at(2).done` → `/todos/2/done`, `st.get('a/b')` → `/a~1b` (RFC 6901 escaping) | `PatchPath<State, Payload>` — annotate to type it | native |
|
|
701
|
+
| a path lambda with a COMPUTED index | the pointer as a string EXPRESSION, `{ "$concat": ["/todos/", <index>, "/done"] }` — APP-FORMAT §3.2's "op members like `value` and `path` are themselves query expressions" | the same | native; anything that is not a chain of member reads and subscripts `JL0102` |
|
|
702
|
+
| a path as a string | the pointer verbatim, `+ '/-'` under `append()` | `string` | native; a string that does not start with `/` `JL0102` |
|
|
703
|
+
|
|
704
|
+
**The two doors in**
|
|
705
|
+
|
|
706
|
+
| Method | Emits | Type reading | Status |
|
|
707
|
+
|---|---|---|---|
|
|
708
|
+
| `bind(name, { payload?, event?, preventDefault?, stopPropagation? })` | APP-FORMAT §4's object binding — `{ action, with?, event?, preventDefault?, stopPropagation? }`, in the format's member order | `Binding<Names>` — annotate the call (`bind<Action>('todo/add')`) and an undeclared name stops compiling | native; an empty name, an unknown member or a non-boolean control `JL0101`; a field §3.1 excludes `JL0102` |
|
|
709
|
+
| `sub(run, { with?, when?, withQuery?, key?, for? })` | one APP-FORMAT §5.3 entry, in the format's member order | `SubDeclaration<Run>` | native; an empty `run`, an unknown member or a callback under `with` `JL0101`; a combination §5.3 calls `JA0008` `JL0102`; `$item` outside a `for` `JL0104` |
|
|
710
|
+
|
|
711
|
+
### The Jaren forms pen — [FORMS-PEN.md §2](FORMS-PEN.md)
|
|
712
|
+
|
|
713
|
+
| Kind | Count | What the row does |
|
|
714
|
+
|---|---:|---|
|
|
715
|
+
| **re-exported unchanged** | 27 | links `SCHEMA-PEN.md` §2's row and states nothing of its own — the emission, the `Infer`/`Input` reading and every refusal are the schema pen's, and the only difference is the CLASS that comes back |
|
|
716
|
+
| **re-exported and extended** | 1 | the schema pen's behaviour plus what this pen adds, stated here |
|
|
717
|
+
| **forms-only** | 3 | this pen's own, stated here in full |
|
|
718
|
+
|
|
719
|
+
**Re-exported unchanged — 27 names**
|
|
720
|
+
|
|
721
|
+
| Names | Documented at | The class that comes back |
|
|
722
|
+
|---|---|---|
|
|
723
|
+
| `string()`, `number()`, `integer()`, `boolean()`, `nil()`, `literal(v)`, `enumOf(values)`, `datetime()`, `date()`, `time()`, `duration()`, `any()`, `never()` | [SCHEMA-PEN.md §2.1](SCHEMA-PEN.md#21-primitives-literals-and-enums) | `FormStringBuilder`, `FormNumberBuilder`, `FormBuilder`, `FormNeverBuilder` |
|
|
724
|
+
| `object(props)`, `record(values)` | [SCHEMA-PEN.md §2.2](SCHEMA-PEN.md#22-objects) | `FormObjectBuilder`, `FormBuilder` |
|
|
725
|
+
| `array(items)`, `tuple(items)` | [SCHEMA-PEN.md §2.3](SCHEMA-PEN.md#23-arrays-and-tuples) | `FormArrayBuilder`, `FormTupleBuilder` |
|
|
726
|
+
| `union(options)`, `discriminated(key, options)`, `intersection(parts)`, `when(cond)` | [SCHEMA-PEN.md §2.6](SCHEMA-PEN.md#26-composition) | `FormBuilder`, `FormWhenBuilder` |
|
|
727
|
+
| `named(name, b)`, `ref(name)`, `lazy(thunk)`, `from(json)` | [SCHEMA-PEN.md §2.7](SCHEMA-PEN.md#27-references-and-defs) | `FormBuilder` (a `named()` builder is one of its own) |
|
|
728
|
+
| `document(root, { draft })`, `schemaOf(value)` | [SCHEMA-PEN.md §2.10](SCHEMA-PEN.md#210-the-document-and-the-builder-itself) | — (both answer a document, not a builder) |
|
|
729
|
+
|
|
730
|
+
**Re-exported and extended — `meta()`**
|
|
731
|
+
|
|
732
|
+
| Method | Emits | `Infer` / `Input` | Status |
|
|
733
|
+
|---|---|---|---|
|
|
734
|
+
| `.meta(annotations)` | the keys verbatim, in the order first set — exactly [SCHEMA-PEN.md §2.8](SCHEMA-PEN.md#28-annotations-and-messages)'s behaviour | — | native; a schema-pen-owned keyword is `JL0104` there, and `'x-form'` is `JL0104` HERE: the forms pen owns that keyword, and the fix is to spell it through `form()` |
|
|
735
|
+
|
|
736
|
+
**Forms-only — three names**
|
|
737
|
+
|
|
738
|
+
| Method | Emits | `Infer` / `Input` | Status |
|
|
739
|
+
|---|---|---|---|
|
|
740
|
+
| `.form({ visible?, enabled?, assert?, computed?, message? })` | one `x-form` annotation, its members in the README's own order whatever order the author wrote; a second call MERGES into the same annotation rather than replacing it | `this` — the builder's phantoms are untouched, because a rule is an annotation | native; a member `x-form` does not define, or a `message` that is neither a string nor a MessageSpec, `JL0101`; `preview` `JL0102`; a name the context does not bind `JL0104` |
|
|
741
|
+
| `withForm(Base)` | nothing: a NEW class, `Base` plus `form()` and the overriding `meta()`. The route by which a third pen — or a project's own vocabulary — carries `x-form` (§3.5) | `B` (the base class's own type) | native; a non-constructor argument is JavaScript's own `TypeError` from the `extends` clause, not a `LinqBuildError` |
|
|
742
|
+
| `assertOnSubmit(root)` | the root document with one `allOf` branch `{ $query, errorMessage }` per `x-form.assert` in it; a document with no assert answers ITSELF, because a needless `allOf` would be a second spelling of the same schema | `JsonSchema` | native; a value that is neither a builder nor an object schema is `JL0101` |
|
|
743
|
+
|
|
744
|
+
| Member | Kind | Emits | The reader's question |
|
|
745
|
+
|---|---|---|---|
|
|
746
|
+
| `visible` | EBV query | `x-form.visible` | should the field be shown? A broken rule fails **open** — it must never hide data |
|
|
747
|
+
| `enabled` | EBV query | `x-form.enabled` | should the field accept input? Fails **open**, same reason |
|
|
748
|
+
| `assert` | EBV query | `x-form.assert` | a cross-field preemptive assertion. Fails **closed**: an assertion that cannot be computed has not been satisfied |
|
|
749
|
+
| `computed` | query | `x-form.computed` | the field's derived value, mapped to plain JSON. A failure leaves the value absent |
|
|
750
|
+
| `message` | string or MessageSpec | `x-form.message`, verbatim | what an `assert` failure renders — an inline template, or `{ $msgid, message?, params? }` for the catalog |
|
|
751
|
+
|
|
752
|
+
### The Jaren linq client — [DB-CLIENT.md §2](DB-CLIENT.md)
|
|
753
|
+
|
|
754
|
+
**What is the store's and what is the client's**
|
|
755
|
+
|
|
756
|
+
| Member | Whose | What the client does |
|
|
757
|
+
|---|---|---|
|
|
758
|
+
| `open(model, { driver, …, validator? })` | the store's `openStore`, every option forwarded verbatim (`capture`, `live`, `jobs`, `profile`, … included) | wires `validator` as `compileSchema` — the default, `defaultValidator()`, is `new JarenValidator({ collectErrors: true })` with the string and date-time formats registered (the configuration MIGRATING-FROM-ZOD's recipe reproduces, so `s.string().email()` asserts out of the box); an explicit `compileSchema` wins; `validator: null` opens unvalidated, by name (`capabilities.validated === false`) |
|
|
759
|
+
| `client.entities.<Name>` | one frozen handle per declared entity, built at open (no Proxy; an unknown name is `undefined`, and for a pen model a compile error) | the store's typed entity set, every member — `create get update delete load explainLoad add put remove discard link unlink asNoTracking execute explain root scope relations` — plus §2.3's additions |
|
|
760
|
+
| `where`, `select`, `orderBy`, …, `toArray`, `first`, `count`, … | the chain: `fromAsync(handle)` ([QUERY-PEN.md](QUERY-PEN.md) §8, §10) | every `AsyncSequence` operator and terminal, delegated — nothing is duplicated, every read is the chain's document and pushes down; the handle is iterable (`for await`); two handles of one client share a `scope`, so a join's inner may be `fromAsync(otherHandle)` |
|
|
761
|
+
| `include(pick, spec?)` | the store's `load(spec)` (MODEL-FORMAT §10.4, §10.5) | opens a graph that EMITS the spec (§2.4, §3), typed `Loaded<>` by what it included |
|
|
762
|
+
| `link(own, member, target)`, `unlink(…)` | the store's membership API (MODEL-FORMAT §11.7) | reads the relation table first — the member must be a many-to-many relation (`JL0107`, naming the kind it is, or the members that are) — then records through the store; `saveChanges()` writes the join rows |
|
|
763
|
+
| `live(chain \| document, options?)` | the store's registration — `store.live` for an entity root, `collection.live` for a collection (LIVE-FORMAT §7) | hands over the chain's document and its `explain().bindings` as the externals (`options.externals` merge over them); the strategy, the reason and the maintenance are the store's |
|
|
764
|
+
| `client.collections.<name>` | the store's collection | the same chain start and `live`, typed from the pen's collection schema (§2.5) |
|
|
765
|
+
| `saveChanges()`, `transaction(fn)`, `close()`, `capabilities`, `store` | the store's | pass-throughs; `saveChanges` and `live` exist exactly when the model declares entities, as on the store; `store` is the escape hatch, typed `TypedStore` |
|
|
766
|
+
|
|
767
|
+
**The two exported names**
|
|
768
|
+
|
|
769
|
+
| Name | Answers | Type reading |
|
|
770
|
+
|---|---|---|
|
|
771
|
+
| `open(model, options)` | a promise of the frozen client — `store`, `capabilities`, `entities`, `collections`, `transaction`, `close`, and `saveChanges`/`live` when the model declares entities | `Client<InferMeta<typeof model>>` for a pen model; `Client<E>` for `open<E>(json, …)`; the wide map for a bare JSON model |
|
|
772
|
+
| `defaultValidator()` | `new JarenValidator({ collectErrors: true })` with `stringFormats` and `dateTimeFormats` registered | `JarenValidator` |
|
|
773
|
+
|
|
774
|
+
**The entity handle**
|
|
775
|
+
|
|
776
|
+
| Group | Members |
|
|
341
777
|
|---|---|
|
|
342
|
-
| `
|
|
343
|
-
|
|
|
344
|
-
|
|
|
345
|
-
| `
|
|
346
|
-
| `
|
|
347
|
-
|
|
|
348
|
-
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
`
|
|
358
|
-
(`
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
host work. There is no parallel universe of `selectAwait`-shaped
|
|
378
|
-
operators; a per-element async *predicate* is `mapAsync` then `where`.
|
|
379
|
-
|
|
380
|
-
- `concurrency` is REQUIRED and MUST be a positive integer (`JL0005`)
|
|
381
|
-
— the unbounded default is how libraries like this take down a
|
|
382
|
-
downstream service.
|
|
383
|
-
- `mode` reuses the `createTaskEffect` vocabulary (`@jarenjs/app` §9),
|
|
384
|
-
deliberately, so a reader who knows one knows the other:
|
|
385
|
-
`parallel` (a sliding window of N), `concat` (strictly sequential),
|
|
386
|
-
`switch` (a newer item supersedes and ABORTS the in-flight task),
|
|
387
|
-
`exhaust` (items arriving while busy are dropped). The source is
|
|
388
|
-
pulled eagerly under `switch`/`exhaust` — that race IS the mode.
|
|
389
|
-
- `ordered: true` (default) preserves source order and buffers at most
|
|
390
|
-
`concurrency` results — the stated cost; `ordered: false` yields on
|
|
391
|
-
completion.
|
|
392
|
-
- An `AbortSignal` is threaded to every callback and aborted on early
|
|
393
|
-
termination and on failure. A rejected callback FAILS CLOSED: the
|
|
394
|
-
first failure wins, every in-flight sibling aborts, the source
|
|
395
|
-
closes (the `compileDag` discipline).
|
|
396
|
-
- `mapAsync` is NOT translatable to a provider. A provider-backed
|
|
397
|
-
chain that reaches it SPLITS: everything before is pushed to the
|
|
398
|
-
provider whole, everything after runs locally, and `explain()`
|
|
399
|
-
reports `{ split: { pushed, residual } }` — the same residual
|
|
400
|
-
honesty the SQL pushdown owes (D8), applied to the async boundary.
|
|
401
|
-
|
|
402
|
-
## 12. The cursor contract and the source adapters
|
|
403
|
-
|
|
404
|
-
`fromAsync` accepts, in order of preference:
|
|
405
|
-
|
|
406
|
-
- any **`AsyncIterable`** (async generators, `ReadableStream` — every
|
|
407
|
-
target exposes `Symbol.asyncIterator` on it, josl's
|
|
408
|
-
`iterateCsvStream` output);
|
|
409
|
-
- any sync iterable (wrapped);
|
|
410
|
-
- a **cursor**: `{ next(): Promise<{done, value}>, return?() }` — the
|
|
411
|
-
shape the SQL provider's row iterator implements later, adopted
|
|
412
|
-
as-is;
|
|
413
|
-
- a **push queue** (`createPushQueue({ highWaterMark = 1024 })`) for
|
|
414
|
-
feed/end-style readers with no pull protocol of their own (josl's
|
|
415
|
-
push parsers deliberately have no backpressure protocol; the queue
|
|
416
|
-
is where one appears): `feed(value)` returns `false` once the queue
|
|
417
|
-
exceeds the mark — a pause HINT, never a hard stop — and
|
|
418
|
-
`end(error?)` closes (or fails) the stream. Anything else is
|
|
419
|
-
`JL0001` at `fromAsync()` time.
|
|
420
|
-
|
|
421
|
-
What this surface does NOT do, by design: it does not make the query
|
|
422
|
-
engine async (`packages/json` is untouched and strictly synchronous),
|
|
423
|
-
it does not add a second operator table, and it does not add
|
|
424
|
-
`selectAwait`/`whereAwait` variants.
|
|
778
|
+
| the unit of work | `create` `get` `update` `delete` `add` `put` `remove` `discard` `asNoTracking` |
|
|
779
|
+
| the store's reads | `load` `explainLoad` `execute` |
|
|
780
|
+
| the provider seam | `root` `scope` `relations` |
|
|
781
|
+
| membership | `link` `unlink` — the store's, behind §4.2's check |
|
|
782
|
+
| 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` |
|
|
783
|
+
| the client's own | `include` (§2.4) and `live` |
|
|
784
|
+
| in both | `explain` |
|
|
785
|
+
|
|
786
|
+
**The graph**
|
|
787
|
+
|
|
788
|
+
| Member | Emits | Note |
|
|
789
|
+
|---|---|---|
|
|
790
|
+
| `include(pick, spec?)` | one entry of `include` | `pick` is `(u) => u.posts`, or `u.get('posts')` for a name that collides with a proxy method |
|
|
791
|
+
| `where(predicate)` | `where` | consecutive calls conjoin under one `$and` |
|
|
792
|
+
| `orderBy(key, options?)`, `orderByDescending(key, options?)` | `orderBy` | replaces; `options` is `{ empty?, collation? }` |
|
|
793
|
+
| `thenBy(key, options?)`, `thenByDescending(key, options?)` | appends to `orderBy` | `JL0005` when no `orderBy` precedes it |
|
|
794
|
+
| `take(n)`, `skip(n)` | `take`, `skip` | the offset window |
|
|
795
|
+
| `after(cursor)` | `after` | the keyset cursor (§10.5); the ROOT only |
|
|
796
|
+
| `maxDepth(n)` | `maxDepth` | the include depth bound (§10.4) |
|
|
797
|
+
| `asNoTracking()` | — | changes the load, never the document |
|
|
798
|
+
| `toSpec()`, `toJSON()` | the spec | plain deep-frozen JSON, a snapshot: mutating it changes nothing, and two builds are one document |
|
|
799
|
+
| `toArray()` | — | `load(spec)`: the store's one statement |
|
|
800
|
+
| `explain()` | — | `explainLoad(spec)`: the SQL, the includes, the pagination strategy |
|
|
801
|
+
|
|
802
|
+
| Spec member | Emitted | Note |
|
|
803
|
+
|---|---|---|
|
|
804
|
+
| absent, or `true` | `true` | the rows |
|
|
805
|
+
| `{ count: true }` | `{ count: true }` | the number; any other member beside it is the store's `JD0032` |
|
|
806
|
+
| `where: (p) => p.stars.ge(3)` | `where: { $ge: ["$it.stars", 3] }` | the target row is `it`; translatability is the store's verdict (`JD0032`), and a relation hop is a plain path here and refused there |
|
|
807
|
+
| `orderBy: (p) => p.pid` | `orderBy: "$it.pid"` | a bare key, ascending |
|
|
808
|
+
| `orderBy: { key, desc?, empty?, collation? }` | `orderBy: { $key, $dir, $empty, $collation }` | as the chain spells `$orderby`; an array of either is an array |
|
|
809
|
+
| `take`, `skip` | `take`, `skip` | the window inside the subquery (a non-integer is the store's `JD0032`) |
|
|
810
|
+
| `include: { comments: spec }` | `include: { comments: <lowered> }` | over the TARGET's relation table (the scope carries every root's) |
|
|
811
|
+
| anything else | `JL0101` | the vocabulary is closed; `after` paginates the root, never an include |
|
|
812
|
+
<!--/fact-->
|