@jarenjs/linq 0.46.5 → 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.
Files changed (77) hide show
  1. package/ARCHITECTURE.md +217 -0
  2. package/README.md +566 -17
  3. package/docs/APP-PEN.md +1143 -0
  4. package/docs/CONTRACT-PEN.md +1217 -0
  5. package/docs/DB-CLIENT.md +814 -0
  6. package/docs/FLOW-PEN.md +1026 -0
  7. package/docs/FORMS-PEN.md +940 -0
  8. package/docs/JSLT-PEN.md +955 -0
  9. package/docs/LINQ-FORMAT.md +774 -384
  10. package/docs/MIGRATION-PEN.md +781 -0
  11. package/docs/MODEL-PEN.md +1083 -0
  12. package/docs/QUERY-PEN.md +1636 -0
  13. package/docs/SCHEMA-PEN.md +1218 -0
  14. package/package.json +57 -4
  15. package/src/app/action.js +255 -0
  16. package/src/app/capture.js +63 -0
  17. package/src/app/define.js +260 -0
  18. package/src/app/index.js +20 -0
  19. package/src/app/patch.js +277 -0
  20. package/src/app/sub.js +106 -0
  21. package/src/async.js +329 -75
  22. package/src/capture-root.js +82 -0
  23. package/src/concurrency.js +9 -4
  24. package/src/contract/define.js +269 -0
  25. package/src/contract/http.js +247 -0
  26. package/src/contract/index.js +23 -0
  27. package/src/contract/operation.js +342 -0
  28. package/src/db/handle.js +86 -0
  29. package/src/db/include.js +316 -0
  30. package/src/db/index.js +19 -0
  31. package/src/db/live.js +43 -0
  32. package/src/db/membership.js +37 -0
  33. package/src/db/open.js +82 -0
  34. package/src/document.js +143 -13
  35. package/src/effect.js +65 -0
  36. package/src/errors.js +69 -6
  37. package/src/expression.js +532 -39
  38. package/src/flow/capture.js +33 -0
  39. package/src/flow/dag.js +302 -0
  40. package/src/flow/fsm.js +328 -0
  41. package/src/flow/index.js +22 -0
  42. package/src/forms/index.js +43 -0
  43. package/src/forms/rules.js +170 -0
  44. package/src/forms/submit.js +177 -0
  45. package/src/index.js +4 -2
  46. package/src/jslt/body.js +226 -0
  47. package/src/jslt/index.js +18 -0
  48. package/src/jslt/rules.js +207 -0
  49. package/src/json-boundary.js +90 -0
  50. package/src/migration/define.js +323 -0
  51. package/src/migration/index.js +15 -0
  52. package/src/migration/steps.js +248 -0
  53. package/src/model/collection.js +171 -0
  54. package/src/model/define.js +125 -0
  55. package/src/model/entity.js +307 -0
  56. package/src/model/index.js +47 -0
  57. package/src/model/relation.js +85 -0
  58. package/src/provider.js +137 -20
  59. package/src/schema/brand.js +31 -0
  60. package/src/schema/builders.js +526 -0
  61. package/src/schema/check.js +29 -0
  62. package/src/schema/emit.js +394 -0
  63. package/src/schema/factories.js +239 -0
  64. package/src/schema/index.js +37 -0
  65. package/src/schema-of.js +24 -0
  66. package/src/sequence.js +233 -103
  67. package/src/sources.js +10 -3
  68. package/types/app.d.ts +293 -0
  69. package/types/contract.d.ts +371 -0
  70. package/types/db.d.ts +188 -0
  71. package/types/flow.d.ts +285 -0
  72. package/types/forms.d.ts +253 -0
  73. package/types/index.d.ts +389 -41
  74. package/types/jslt.d.ts +193 -0
  75. package/types/migration.d.ts +201 -0
  76. package/types/model.d.ts +493 -0
  77. package/types/schema.d.ts +494 -0
@@ -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.