@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/ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# @jarenjs/linq — architecture
|
|
2
|
+
|
|
3
|
+
The package is three small machines in a row: a **recording proxy**
|
|
4
|
+
that turns a JavaScript callback into expression data, an **emitter**
|
|
5
|
+
that folds a stage list into one FLWOR query document, and a
|
|
6
|
+
**provider seam** that runs that document anywhere. Nothing here
|
|
7
|
+
evaluates anything — the query engine in `@jarenjs/json` stays the
|
|
8
|
+
only evaluator, which is what makes the same chain mean the same thing
|
|
9
|
+
in memory, over a cursor, or pushed into a database.
|
|
10
|
+
|
|
11
|
+
## The recording proxy (`src/expression.js`)
|
|
12
|
+
|
|
13
|
+
`captureExpression(fn, roots)` calls the user's callback once with
|
|
14
|
+
proxies. Every member access, comparison and operator call is recorded
|
|
15
|
+
as plain query-document JSON — never `Function.prototype.toString()`,
|
|
16
|
+
which breaks under minification and cannot see closures honestly. The
|
|
17
|
+
table of recordable methods (`METHODS`) is null-prototyped so
|
|
18
|
+
`constructor` and `toString` read as member access; methods shadow
|
|
19
|
+
members by design, with `get('name')` as the escape for collisions and
|
|
20
|
+
non-identifier keys. A proxy that escapes its callback (stored and
|
|
21
|
+
reused later) is detected by a stack of capture epochs and refused
|
|
22
|
+
(`JL0002`) — the emitted document would be nonsense, so the build fails
|
|
23
|
+
instead; captures nest, and an enclosing capture's proxy used inside a
|
|
24
|
+
nested one is refused by name for the same reason. A root whose items
|
|
25
|
+
are an entity's rows carries the entity's relation table (a provider's
|
|
26
|
+
`relations`), and a member naming a relation records a HOP: it is
|
|
27
|
+
lowered right there to the correlated phrase the engine runs —
|
|
28
|
+
`{ $for: { r1: '$.User[*]' }, $where: { $eq: [...] }, $return: … }`,
|
|
29
|
+
a to-many hop packed as an array until `all()` fans it — so the
|
|
30
|
+
document never carries a relation name; the hop bindings `r1`, `r2`, …
|
|
31
|
+
are numbered per capture and reserved, and `explain().hops` reports
|
|
32
|
+
them. The rows stop being rows at a projection, where the sequence
|
|
33
|
+
drops the table.
|
|
34
|
+
|
|
35
|
+
## The emitter (`src/document.js`)
|
|
36
|
+
|
|
37
|
+
A `Sequence` is an immutable stage list. Emission folds the stages
|
|
38
|
+
into FLWOR phrases with clause-order segmentation: a `where` after an
|
|
39
|
+
`orderBy` opens a new nested document, consecutive `where`s conjoin
|
|
40
|
+
into one `$and`, and the item binding is always `it` (nested documents
|
|
41
|
+
shadow it deliberately, so emitted documents stay hand-readable). Every
|
|
42
|
+
iterated source is bound through an array constructor so an
|
|
43
|
+
array-valued row stays one item under the engine's `$for` unpacking
|
|
44
|
+
(the format doc's §5); only a provider's own root is bound bare.
|
|
45
|
+
Element terminals emit `[window]` array wrappers because the engine's
|
|
46
|
+
result shape is `undefined | item | items` — the wrapper is what keeps
|
|
47
|
+
an array-VALUED item unambiguous. Aggregate terminals wrap the whole
|
|
48
|
+
document (`{ $count: … }`), and `groupBy` packs its default return as
|
|
49
|
+
`{ key: …, items: [ '$it' ] }` because an object member takes exactly
|
|
50
|
+
one item.
|
|
51
|
+
|
|
52
|
+
## The typed surface (`types/index.d.ts`)
|
|
53
|
+
|
|
54
|
+
Hand-authored declarations are the public type contract (the runtime
|
|
55
|
+
stays JSDoc'd JavaScript). The line: the common path is precisely
|
|
56
|
+
typed, the exotic path is honestly `unknown`, nothing is ever a WRONG
|
|
57
|
+
type. Every type-level claim has a runtime twin in the same test
|
|
58
|
+
fixtures plus captured compiler messages, because `checkJs` is off and
|
|
59
|
+
nothing else would notice drift.
|
|
60
|
+
|
|
61
|
+
## The async surface (`src/async.js`, `src/concurrency.js`)
|
|
62
|
+
|
|
63
|
+
Async is a boundary, not a colour (D5). `fromAsync` streams a
|
|
64
|
+
single-pass source through per-item compiled evaluators; a barrier
|
|
65
|
+
operator (`orderBy`, `groupBy`, `aggregate`, `reverse`) collects the
|
|
66
|
+
buffer and runs the MAXIMAL document slice through the sync engine in
|
|
67
|
+
one call — so async answers are sync answers by construction, proven by
|
|
68
|
+
a byte-identical-document test. An async `join` exists only over a
|
|
69
|
+
provider origin, pushed inside the one document — the inner side of a
|
|
70
|
+
join re-reads the source, and a single-pass stream cannot be read twice.
|
|
71
|
+
`mapAsync` is
|
|
72
|
+
the one place element-wise asynchronous work happens: `concurrency`
|
|
73
|
+
is required, the modes reuse the `createTaskEffect` vocabulary
|
|
74
|
+
(`parallel`/`concat`/`switch`/`exhaust`), and failure is fail-closed —
|
|
75
|
+
the first rejection aborts every in-flight signal and the source.
|
|
76
|
+
|
|
77
|
+
## The pens (`src/schema/`, `src/model/`, `src/jslt/`, `src/migration/`, `src/contract/`, `src/flow/`, `src/app/`, `src/forms/`)
|
|
78
|
+
|
|
79
|
+
A pen is a by-code front-end to one of the suite's document formats,
|
|
80
|
+
exported under its own subpath (`@jarenjs/linq/schema`, `/model`, `/jslt`,
|
|
81
|
+
`/migration`, `/contract`, `/flow`, `/app`, `/forms`; `.` stays the chain). The rule set is one paragraph: the document is the
|
|
82
|
+
deliverable (plain, deep-frozen JSON, memoized under `.schema`,
|
|
83
|
+
`toJSON()` returns it); the pen imports no engine and re-implements no
|
|
84
|
+
compile check — it refuses only what it cannot spell, with a `JL01xx`
|
|
85
|
+
code; types are phantoms (`Infer<>`/`Input<>`) proven by a three-way
|
|
86
|
+
agreement against emit's declarations and the validator's verdicts over
|
|
87
|
+
one corpus; objects are closed by default. The schema pen is four
|
|
88
|
+
modules: `builders.js` (one small immutable class per kind, state
|
|
89
|
+
replaced through `with()` — which is also how a later pen extends it, by
|
|
90
|
+
subclassing, never by patching a prototype), `emit.js` (assembly: `$defs`
|
|
91
|
+
hoisting in discovery order, `$ref` resolution, the two refusals a
|
|
92
|
+
document could not carry faithfully — a name spelled twice, a default in
|
|
93
|
+
a branch the normalizer never descends), `check.js` (the ONLY pen module
|
|
94
|
+
that imports the recording proxy: a `check()` rule is captured with the
|
|
95
|
+
value at `$` and exactly the two externals the validator binds) and
|
|
96
|
+
`brand.js` (a registry symbol every builder answers `true` under). The
|
|
97
|
+
chain recognises a builder handed to `ofType`/`cast` by that symbol —
|
|
98
|
+
looked up by key in `src/schema-of.js`, not imported from the pen — so a
|
|
99
|
+
chain-only bundle carries nothing from the pen's directory, and a
|
|
100
|
+
pen-only bundle carries no chain module and no engine (the tree-shaking
|
|
101
|
+
gate proves both). The one shared machine, the capture in
|
|
102
|
+
`expression.js`, rides in the pen's bundle whether or not `check()` is
|
|
103
|
+
called: a class method cannot be shaken. Two small modules beside it are
|
|
104
|
+
shared by every pen: `capture-root.js` (`captureQuery`: one capture over
|
|
105
|
+
a value at `$` with named externals — `check()`'s `root`/`path`, the
|
|
106
|
+
model pen's `compute()` with none, the JSLT pen's `body()` with the
|
|
107
|
+
declared parameters) and `json-boundary.js` (`requireJson`, the `JL0101`
|
|
108
|
+
door), and `effect.js` (the `{ run, with? }` descriptor two formats spell
|
|
109
|
+
identically — a machine's effects and an app transition's — with each pen
|
|
110
|
+
passing in how its props are captured). The model pen (`src/model/`) and
|
|
111
|
+
the forms pen (`src/forms/`) each subclass the schema pen's classes
|
|
112
|
+
through one mixin; the JSLT pen (`src/jslt/`) is `body.js` (the body
|
|
113
|
+
capture, `apply`/`op` lifted into it through `liftExpression`, the `[]`
|
|
114
|
+
refusal) and `rules.js` (the rule object and the envelope, in Appendix
|
|
115
|
+
A's member order) — it imports nothing of `src/schema/` but `brand.js`.
|
|
116
|
+
The contract pen (`src/contract/`) is `operation.js` (the three kinds,
|
|
117
|
+
the error declaration and the policy vocabulary), `http.js` (the binding
|
|
118
|
+
and the §4.2 path-template scan, mirrored from the compiler's parser)
|
|
119
|
+
and `define.js` (the document, in CONTRACT-FORMAT §12.1's member order,
|
|
120
|
+
and the three identity wrappers that type a client, a handler table and
|
|
121
|
+
an AI toolbox). It reaches back into `schema/emit.js` for one thing —
|
|
122
|
+
`createHoist`/`emitInto`/`hoistedDefs`, the same `$defs` walk `assemble`
|
|
123
|
+
runs, over several roots instead of one — so a contract's `$defs` are
|
|
124
|
+
hoisted to the CONTRACT's root by the one implementation. The flow pen
|
|
125
|
+
(`src/flow/`) is `fsm.js`, `dag.js` and `capture.js`, whose scope binds NO
|
|
126
|
+
externals because neither flow engine binds any — a guard written through
|
|
127
|
+
the JSLT pen's `body()` would read `$root` as false forever. The app pen
|
|
128
|
+
(`src/app/`) is `capture.js` (APP-FORMAT §3.1's three names, and §5.3's
|
|
129
|
+
narrower closed world for a subscription), `action.js`
|
|
130
|
+
(`action`/`transition`/`effect`/`bind`), `patch.js` (the six RFC 6902
|
|
131
|
+
operations, `append`, and the path lambda lowered to a JSON Pointer — as
|
|
132
|
+
text where every segment is literal, as a lifted `$concat` where one is
|
|
133
|
+
computed) and `define.js` (the document, the initial state derived from
|
|
134
|
+
the state schema's defaults, and the view scan that refuses a binding to
|
|
135
|
+
an undeclared action). The forms pen (`src/forms/`) is `rules.js` (the
|
|
136
|
+
`form()` mixin and the rule context) and `submit.js` (`assertOnSubmit`,
|
|
137
|
+
the layer-3 `$query` twin, pinned deep-equal to forms' own transform);
|
|
138
|
+
neither imports the package it writes for.
|
|
139
|
+
|
|
140
|
+
## The client (`src/db/`)
|
|
141
|
+
|
|
142
|
+
`@jarenjs/linq/db` is the one subpath with a runtime edge: `open.js`
|
|
143
|
+
imports `openStore` from `@jarenjs/db`, `JarenValidator` from
|
|
144
|
+
`@jarenjs/validate` and the string and date-time formats from
|
|
145
|
+
`@jarenjs/formats` — declared in `package.json` as OPTIONAL peer
|
|
146
|
+
dependencies, never dependencies, so a consumer of any other subpath
|
|
147
|
+
installs nothing new and the store never imports this package. Three
|
|
148
|
+
gates hold the edge: the tree-shaking probes (the `.` entry carries no
|
|
149
|
+
`src/db/` module and not one byte of the three; the `./db` bundle
|
|
150
|
+
carries all three, no other pen, and the size CONSUMING states), the
|
|
151
|
+
packed-consumer gate (every subpath is imported WITHOUT the peers first
|
|
152
|
+
— `./db` must fail by a peer's name and nothing else may fail — then
|
|
153
|
+
with their closures installed from the tarballs), and the edge suite
|
|
154
|
+
in `test/db/provider.test.js` (both manifests, and every source and
|
|
155
|
+
declaration file of both packages, for every import spelling).
|
|
156
|
+
`handle.js` builds one frozen handle per declared name at open — the
|
|
157
|
+
store's entity set spread in, the chain start generated from
|
|
158
|
+
`AsyncSequence.prototype` so nothing is duplicated (`explain()` is the
|
|
159
|
+
one overload: the empty chain's without a document, the store's with
|
|
160
|
+
one), and `include`, `link`/`unlink`, `live` beside them. `include.js`
|
|
161
|
+
is a builder that EMITS the store's `load` spec: the relation member
|
|
162
|
+
captured to its name, every callback captured over `$it` through the
|
|
163
|
+
same recording proxy with no parameters, the nested includes resolved
|
|
164
|
+
over the scope's relation tables, the spec deep-frozen in a fixed
|
|
165
|
+
member order. `membership.js` reads the relation table before the store
|
|
166
|
+
records a link (`JL0107` names the kind); `live.js` hands a chain's
|
|
167
|
+
document and `explain().bindings` to the store's own registration.
|
|
168
|
+
Nothing here runs a query, plans one or keeps state: the store stays
|
|
169
|
+
the engine, and every read is one its `explain()` can name.
|
|
170
|
+
|
|
171
|
+
## The provider seam (`src/provider.js`)
|
|
172
|
+
|
|
173
|
+
A provider is any object with `execute(queryDocument, { externals })` —
|
|
174
|
+
optionally carrying `root` (the path its items are bound through, bare),
|
|
175
|
+
`roots` (a store-level provider's entity roots; refused by name,
|
|
176
|
+
`JL0007`), `scope` (the identity two joinable providers share; with
|
|
177
|
+
`relations` keyed by root name, where a chained hop finds its target's
|
|
178
|
+
table) and `relations` (the relation table of its rows, what a hop
|
|
179
|
+
lowers from).
|
|
180
|
+
`@jarenjs/db` implements it; the chain imports no store, and the
|
|
181
|
+
package's one runtime edge — the client subpath below — runs the other
|
|
182
|
+
way, a test asserting the direction. On the async surface the provider is asked
|
|
183
|
+
for first, receives the whole chain up to a `mapAsync` as one document,
|
|
184
|
+
and may answer a promise. `mapAsync` splits a provider chain: the
|
|
185
|
+
translatable prefix is pushed to the provider in ONE call, the
|
|
186
|
+
residual runs locally, and `explain()` reports the split.
|
|
187
|
+
|
|
188
|
+
## The decisions that cost something
|
|
189
|
+
|
|
190
|
+
- **Emission re-runs per call.** A callback is captured ONCE, when
|
|
191
|
+
its operator is called; the document is re-emitted and the compiled
|
|
192
|
+
program looked up on every terminal by design (the `Sequence` is
|
|
193
|
+
immutable and cheap to walk; the benchmark publishes the price beside
|
|
194
|
+
the hand-written loop). Hold the compiled document when the same
|
|
195
|
+
query runs hot.
|
|
196
|
+
- **Same-source joins, or one provider scope.** One document has one
|
|
197
|
+
root, so `join`/`groupJoin` across different sources is refused
|
|
198
|
+
(`JL0005`) rather than silently materialised — except two providers
|
|
199
|
+
sharing a `scope` (one store's entity sets), whose roots are two
|
|
200
|
+
bindings of one multi-entity input.
|
|
201
|
+
- **The engine result shape leaks nowhere.** Every surface — sync,
|
|
202
|
+
async, provider — reproduces `undefined | item | items` exactly,
|
|
203
|
+
which is why the window-wrapper trick exists at all.
|
|
204
|
+
|
|
205
|
+
## What this is not
|
|
206
|
+
|
|
207
|
+
Not a storage engine (entities, identity, migrations — the store is
|
|
208
|
+
`@jarenjs/db`'s, and `src/db/` is its front door, never a second
|
|
209
|
+
engine), not IQueryable with expression trees over
|
|
210
|
+
arbitrary CLR-style methods (the operator set is the query engine's
|
|
211
|
+
104, closed and documented), not a runtime type inferrer for JSON
|
|
212
|
+
literals (`from(json)` is `unknown` until the caller asserts, and a pen
|
|
213
|
+
is the only inference route — `json-schema-to-ts`-style computation over
|
|
214
|
+
schema literals is deliberately absent), and not a lazy-collection
|
|
215
|
+
library for JavaScript iterables in general — the deliverable is always
|
|
216
|
+
a DOCUMENT, one of the suite's own, and everything else follows from
|
|
217
|
+
that.
|