@jarenjs/linq 0.49.2 → 0.66.1

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 (79) hide show
  1. package/ARCHITECTURE.md +227 -0
  2. package/README.md +650 -17
  3. package/docs/APP-PEN.md +1143 -0
  4. package/docs/CONTRACT-PEN.md +1221 -0
  5. package/docs/DB-CLIENT.md +882 -0
  6. package/docs/FLOW-PEN.md +1033 -0
  7. package/docs/FORMS-PEN.md +940 -0
  8. package/docs/JSLT-PEN.md +955 -0
  9. package/docs/LINQ-FORMAT.md +778 -383
  10. package/docs/MIGRATION-PEN.md +781 -0
  11. package/docs/MODEL-PEN.md +1092 -0
  12. package/docs/QUERY-PEN.md +1724 -0
  13. package/docs/SCHEMA-PEN.md +1218 -0
  14. package/package.json +57 -4
  15. package/src/app/action.js +251 -0
  16. package/src/app/capture.js +63 -0
  17. package/src/app/define.js +255 -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 +377 -75
  22. package/src/capture-root.js +82 -0
  23. package/src/concurrency.js +48 -11
  24. package/src/contract/define.js +282 -0
  25. package/src/contract/http.js +247 -0
  26. package/src/contract/index.js +23 -0
  27. package/src/contract/operation.js +338 -0
  28. package/src/db/handle.js +89 -0
  29. package/src/db/include.js +351 -0
  30. package/src/db/index.js +24 -0
  31. package/src/db/ledger.js +195 -0
  32. package/src/db/live.js +43 -0
  33. package/src/db/membership.js +37 -0
  34. package/src/db/open.js +130 -0
  35. package/src/document.js +143 -13
  36. package/src/effect.js +65 -0
  37. package/src/errors.js +78 -6
  38. package/src/expression.js +463 -36
  39. package/src/federate.js +531 -0
  40. package/src/flow/capture.js +33 -0
  41. package/src/flow/dag.js +316 -0
  42. package/src/flow/fsm.js +323 -0
  43. package/src/flow/index.js +22 -0
  44. package/src/forms/index.js +43 -0
  45. package/src/forms/rules.js +170 -0
  46. package/src/forms/submit.js +177 -0
  47. package/src/index.js +5 -2
  48. package/src/jslt/body.js +226 -0
  49. package/src/jslt/index.js +18 -0
  50. package/src/jslt/rules.js +202 -0
  51. package/src/json-boundary.js +90 -0
  52. package/src/migration/define.js +318 -0
  53. package/src/migration/index.js +15 -0
  54. package/src/migration/steps.js +244 -0
  55. package/src/model/collection.js +273 -0
  56. package/src/model/define.js +125 -0
  57. package/src/model/entity.js +307 -0
  58. package/src/model/index.js +47 -0
  59. package/src/model/relation.js +85 -0
  60. package/src/provider.js +137 -20
  61. package/src/schema/brand.js +31 -0
  62. package/src/schema/builders.js +526 -0
  63. package/src/schema/check.js +29 -0
  64. package/src/schema/emit.js +394 -0
  65. package/src/schema/factories.js +239 -0
  66. package/src/schema/index.js +37 -0
  67. package/src/schema-of.js +24 -0
  68. package/src/sequence.js +233 -103
  69. package/src/sources.js +10 -3
  70. package/types/app.d.ts +293 -0
  71. package/types/contract.d.ts +468 -0
  72. package/types/db.d.ts +359 -0
  73. package/types/flow.d.ts +285 -0
  74. package/types/forms.d.ts +253 -0
  75. package/types/index.d.ts +296 -26
  76. package/types/jslt.d.ts +193 -0
  77. package/types/migration.d.ts +201 -0
  78. package/types/model.d.ts +526 -0
  79. package/types/schema.d.ts +494 -0
@@ -0,0 +1,1221 @@
1
+ # The Jaren contract pen
2
+
3
+ > `./contract` — `$contract` 0.1 documents: the operations, their
4
+ > schemas, their declared behavior and their REST binding. **Read it
5
+ > when** you are declaring an API and want its client, its server and
6
+ > its tools typed from one document
7
+
8
+ Version 0.1. The key words MUST, MUST NOT, SHOULD and MAY are to be
9
+ interpreted as described in RFC 2119. This document is a **guide** — read
10
+ it in order and you can write the format — whose one normative section is
11
+ [§2 The mapping table](#2-the-mapping-table); the rules every pen keeps, the shared refusal table, the
12
+ index of the other pens and every pen's mapping table collected in one
13
+ place are the normative reference,
14
+ [LINQ-FORMAT.md](LINQ-FORMAT.md).
15
+
16
+ ## 1. What it writes
17
+
18
+ You have a service with an API, and three things need to agree about it:
19
+ the server that implements it, the client that calls it, and whatever
20
+ else reads it — a generated OpenAPI file, a tool set handed to a model, a
21
+ test. A contract is the one document all three read, and writing it by
22
+ hand means keeping JSON Schemas, HTTP verbs and error names in step by
23
+ eye. This pen writes that document from typed calls, and the same typed
24
+ calls then check the client, the handlers and the tools against it.
25
+
26
+ ```js
27
+ import { defineContract, read, command, subscribe, http, error } from '@jarenjs/linq/contract';
28
+ ```
29
+
30
+ writes `$contract` 0.1 documents
31
+ ([CONTRACT-FORMAT](../../contract/docs/CONTRACT-FORMAT.md)): the
32
+ operations, their schemas, their declared behavior and their REST
33
+ binding, as one document `compileContract` takes unchanged. The schemas
34
+ are the schema pen's ([SCHEMA-PEN.md](SCHEMA-PEN.md)), and every
35
+ `named()` builder any operation reaches is hoisted once into the
36
+ CONTRACT's own `$defs` and referenced `#/$defs/<name>` — the same
37
+ hoisting walk [the schema pen](SCHEMA-PEN.md#27-references-and-defs)
38
+ runs over one root, run over every operation's `input`, `output` and
39
+ error schemas instead.
40
+
41
+ The pen imports nothing of `@jarenjs/contract`: the compiler stays the
42
+ only judge of what the document means, and a tree-shaking probe holds it
43
+ (§7).
44
+
45
+ **The running example.** §3 is one shop's service surface, and it is six
46
+ contracts rather than one on purpose: a `$contract` document is the unit
47
+ a service publishes, so a product with a health endpoint, a catalog, an
48
+ order intake, a live picking board, a document store and an internal
49
+ notes service publishes six of them. Read in order they build the whole
50
+ surface up — the smallest possible contract, then a shared definition,
51
+ then declared failures and the whole policy, then a stream, then the
52
+ binding's harder cases, and last one contract driving a client, a handler
53
+ map and a tool set at once. §5 reads the types back off the last of
54
+ them. What the pen adds over writing the JSON by hand is three things —
55
+ the member order the revision hashes, the `$defs` hoisting, and the
56
+ phantom types the three consumers of §5 read.
57
+
58
+ ### 1.1 The two rules that make the document comparable
59
+
60
+ Both are gates, and together they are why a pen contract and its own
61
+ public projection can be compared member for member:
62
+
63
+ - **The member order is §12.1's** — the normative order the contract
64
+ revision hashes: root `$contract, id, version, compat, $defs,
65
+ operations`; operation `kind, input, output, errors, policy, http,
66
+ doc`; error `status, schema`; http `method, path, in, body, status,
67
+ media`; policy §12.1's public order with the two server-side knobs
68
+ (`limits`, `errors`) in their §3.1 places; `$defs` in first-reference
69
+ order. The pen writes members in that order whatever order they were
70
+ declared in — `test/linq/contract-pen.test.js` builds a deliberately
71
+ scrambled contract and asserts every `Object.keys` above.
72
+ - **No default is ever written.** §3.1's defaults are the compiler's to
73
+ materialize and `describe()` marks them inferred; a pen that wrote
74
+ them would turn every default into a declaration and move the revision
75
+ for nothing. The test asserts the exact difference: strip from
76
+ `publicProjection(compileContract(doc))` what `describe().inferred`
77
+ names, the locations §4.1 chose or the template forced, the policy
78
+ members the source never declared and an error's resolved status, undo
79
+ §4.2's path canonicalization — and what is left is the pen's own
80
+ document.
81
+
82
+ The second rule has a visible consequence a reader meets early:
83
+ `command({ output: true })` with no `http` emits an operation of two
84
+ members, and the operation the compiler describes has a task, an
85
+ idempotency mode, a cache mode, a media type and a `POST /<op-id>`
86
+ binding. None of that is missing from the document; all of it is
87
+ inferred, and `describe().inferred` says so member by member.
88
+
89
+ ## 2. The mapping table
90
+
91
+ Nine exported functions, one exported class and the two members that
92
+ class carries — eleven names a caller writes, and every one of them is
93
+ in a table below. The class itself is §5's, because a caller never
94
+ constructs one.
95
+
96
+ ### 2.1 The document
97
+
98
+ The two calls that make a contract: the envelope, and the identity
99
+ members the format compares two revisions by.
100
+
101
+ | Method | Emits | Type reading | Status |
102
+ |---|---|---|---|
103
+ | `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` |
104
+ | `.document` | the deep-frozen `$contract` document — the same object every time | `ContractDocument` | native |
105
+ | `toJSON()` | the same document, so `JSON.stringify(contract)` is the contract | `ContractDocument` | native |
106
+
107
+ `operations` is a name → value map read by its own keys (the binder's
108
+ §1.1 rule 5): an operation id is written with `setObjectMember`, so
109
+ `{ ['__proto__']: read({ … }) }` is an ordinary operation and
110
+ `{ __proto__: read({ … }) }` is `JL0101`. An operation id is not
111
+ otherwise checked here — `catalog.load` and `Not An Id` both emit —
112
+ because CONTRACT-FORMAT §2.2's pattern
113
+ (`^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)*$`) is `compileContract`'s to
114
+ enforce, at `JC0003`.
115
+
116
+ ### 2.2 The three operation kinds
117
+
118
+ An operation declares what it is by which of these three writes it, and
119
+ the kind decides what the format lets it do — whether it may repeat
120
+ safely, whether it may change state, whether it streams.
121
+
122
+ | Method | Emits | Type reading | Status |
123
+ |---|---|---|---|
124
+ | `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` |
125
+ | `command({ … })` | `{ kind: 'command', … }` | `OperationDeclaration<'command', S>` | native; the same three |
126
+ | `subscribe({ … })` | `{ kind: 'subscribe', … }` — `output` is the SNAPSHOT schema (§17) | `OperationDeclaration<'subscribe', S>`; never opaque | native; the same three |
127
+
128
+ The three are one function under three names: they differ only in the
129
+ `kind` they carry and in what the COMPILER then requires of them (§2.8).
130
+ An operation may also be written by hand as `{ kind, …members }`, which
131
+ `defineContract` accepts and lowers the same way. It is a second door
132
+ into one emitter and it runs the same checks: the closed member set, the
133
+ required `output` and the string `doc` are all applied, and the refusal
134
+ names the operation's own position (`/operations/note.get/extra`) rather
135
+ than the spec's. §4.4 has the pair side by side.
136
+
137
+ ### 2.3 The schema positions
138
+
139
+ Three positions take a schema: an operation's `input`, its `output`, and
140
+ an error's `schema`.
141
+
142
+ | Written as | Emits | Type reading | Status |
143
+ |---|---|---|---|
144
+ | 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 |
145
+ | 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` |
146
+
147
+ `output` is required and `input` is not: an operation with no `input`
148
+ takes none, and reads `input: null` on both sides of §5's agreement.
149
+ `true` is the honest spelling for "any value", and it is what an opaque
150
+ operation's `output` usually is, since the contract never decodes those
151
+ bytes.
152
+
153
+ The hoist is per CONTRACT, not per operation: one builder reached from
154
+ three operations is one `$defs` entry and three `$ref`s, and the entries
155
+ come out in first-reference order. Two DISTINCT builders under one name
156
+ is `JL0103` (§4.3), and identity is what "distinct" means — the same
157
+ builder reached from anywhere is one definition.
158
+
159
+ ### 2.4 The errors map
160
+
161
+ The failures an operation DECLARES, as opposed to the ones any operation
162
+ can raise: each a name the caller matches on, with a status and an
163
+ optional payload schema.
164
+
165
+ | Method | Emits | Type reading | Status |
166
+ |---|---|---|---|
167
+ | `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` |
168
+ | `error({ status?, schema? })` | `{ status?, schema? }` in that order — `error()` with nothing emits `{}` | `ErrorDeclaration<E>` | native; another member, or a status outside 100–599, `JL0101` |
169
+
170
+ `error()` is a checked declaration, and the same two members written by
171
+ hand are accepted and checked identically: `readErrors` runs the plain
172
+ object through `error()` itself, so `{ conflict: { status: 409 } }` and
173
+ `{ conflict: error({ status: 409 }) }` emit the same entry and refuse the
174
+ same way. The default status (`400`) is the compiler's, so an entry that
175
+ declares none emits `{}` and reads its status from `describe()`.
176
+
177
+ ### 2.5 The policy
178
+
179
+ `policy` takes the nine members CONTRACT-FORMAT §3.1 declares, and emits
180
+ the declared ones only, in this order:
181
+
182
+ | Member | Takes | Emits |
183
+ |---|---|---|
184
+ | `task` | `switch`, `exhaust`, `concat`, `parallel` | the token |
185
+ | `idempotency` | `none`, `optional`, `required` | the token |
186
+ | `revision` | `"input:<json-pointer>"` | the string, verbatim |
187
+ | `cache` | `none`, `revision` | the token |
188
+ | `limits` | `{ maxBodyBytes }`, a positive integer | `{ maxBodyBytes }` |
189
+ | `errors` | `{ details }` — `none`, `paths`, `full` | `{ details }` |
190
+ | `retry` | `{ max, on }` — an integer ≥ 0 and an array of code strings | `{ max, on }`, the array copied |
191
+ | `stream` | `{ resume?, heartbeatMs?, maxPatchBytes? }` — `snapshot`/`replay`, an integer ≥ 1000, a positive integer | the declared members only |
192
+ | `audience` | `public`, `server` | the token |
193
+
194
+ Every one is `native`, and every one refuses a value outside its set with
195
+ `JL0101` naming the set (§4.1). `limits` and `errors` are the two
196
+ server-side knobs the public projection drops, written in their §3.1
197
+ places (§1.1). The pen checks each member against its own table and stops
198
+ there: it does not check a member against the operation's KIND, so a
199
+ `read` carrying `idempotency: 'required'` is a well-formed policy the pen
200
+ writes and the compiler refuses (`JC0014`).
201
+
202
+ ### 2.6 The HTTP binding
203
+
204
+ Where an operation lands on a URL, and where each input member goes when
205
+ it gets there — the one part of a contract that is about transport.
206
+
207
+ | Method | Emits | Type reading | Status |
208
+ |---|---|---|---|
209
+ | `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` |
210
+
211
+ | Member | Takes | Note |
212
+ |---|---|---|
213
+ | `method` | one uppercase token of `GET HEAD POST PUT PATCH DELETE OPTIONS` | lowercase is refused; the format's table is uppercase |
214
+ | `path` | a path template (§4.2) | `{name}` and `:name` both accepted, and **written as declared** |
215
+ | `in` | input member → `path` \| `query` \| `header` \| `body` | only the members the §4.1 default does not already place |
216
+ | `body` | the input member whose value IS the request body | a non-empty string |
217
+ | `status` | 200–299 | the success status; `200` is the default and is never written |
218
+ | `media` | a media type | anything but `application/json` or a `+json` suffix makes the operation opaque (§4.5) |
219
+
220
+ Two things this row does not do, and both are deliberate. It does not
221
+ canonicalize: `path: '/docs/:id'` stays `/docs/:id` in the document, and
222
+ `/docs/{id}` is what `describe()` and every projection show — §4.2's
223
+ canonical form is the compiler's, not the pen's. And it does not require
224
+ a `path` variable to be an input member (`JC0009`) or check the route
225
+ shape against the contract's other operations (`JC0010`): both need the
226
+ whole document, and one binding is all `http()` can see.
227
+
228
+ The binding may also be written as a plain object in the operation's
229
+ `http:` position. `defineContract` runs it through `http()` — the same
230
+ checks, the same messages — so the two spellings differ only in when the
231
+ refusal arrives.
232
+
233
+ ### 2.7 The three consumers
234
+
235
+ Three identity wrappers that type a client, a handler map or a tool set
236
+ against the contract that describes it. None of them emits anything;
237
+ they exist so a mismatch is a compile error rather than a 404.
238
+
239
+ | Method | Emits | Type reading | Status |
240
+ |---|---|---|---|
241
+ | `typedClient(client, contract)` | — (identity) | `TypedClient<C>`: `invoke` over the invokable operations, `subscribe` over the subscribe ones, `url` over all of them | native |
242
+ | `typedHttpClient(client, contract)` | — (identity) | `TypedHttpClient<C>`: `TypedClient<C>` plus `bytes` over `OpaqueOf<C>` — the opaque operations, whose success is a `ByteResponse` (a live stream) rather than the output type; for an `openHttpClient` client only, a local or port client has no `bytes` | native |
243
+ | `typedHandlers(contract, handlers)` | — (identity) | `TypedHandlerTable<C, Host = null, Carrier = 'http'>`: one handler per invokable operation, `(input, ctx) => output \| Failure`; `ctx` is `HandlerContext<Host, Carrier>` — the HTTP context by default, `Host` the host lifecycle's `ctx.host`, a carrier union a discriminated union to narrow on `ctx.carrier` (CONTRACT-FORMAT §7.7) | native; a missing or misspelled operation does not compile, and an HTTP-only member on a port/local context does not either |
244
+ | `typedTools(tools, contract)` | — (identity) | `TypedTool<C>[]`: `name` is the id with `.` → `_`, `execute` takes the operation's ACCEPTED input | native |
245
+
246
+ All of them are `void contract; return x;` at run time — they add
247
+ nothing, wrap nothing and cost nothing. What they do is carry the phantom `Ops`
248
+ onto a value the engine produced, which is what makes one authored
249
+ document type a client, a server's handler table and an AI toolbox with
250
+ no generate step. §3.6 runs all three over one contract and §5 is what
251
+ holds their readings true.
252
+
253
+ ### 2.8 What the pen does not judge
254
+
255
+ The pen refuses its own surface — a member it does not know, a value
256
+ outside a declared set — and the one format rule it can see earlier and
257
+ exactly, the path template. Everything else is `compileContract`'s,
258
+ because everything else needs the document as a whole:
259
+
260
+ | The document the pen writes | The compiler's refusal |
261
+ |---|---|
262
+ | a member the document's own closed vocabulary does not carry | `JC0013` (§4.4) |
263
+ | an operation id outside `^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)*$` | `JC0003` |
264
+ | a `read` that declares `idempotency` | `JC0014` |
265
+ | a `GET` carrying a body-located member | `JC0016` |
266
+ | an opaque operation with a body-located member | `JC0017` |
267
+ | a `subscribe` bound to anything but `GET` | `JC0019` |
268
+ | a path variable that is not an input member | `JC0009` |
269
+ | two operations sharing a route shape | `JC0010` |
270
+
271
+ Each row is a document the pen EMITS: `test/linq/contract-pen.test.js`
272
+ builds several of them through the pen and asserts the compiler's code,
273
+ which is how the division stays a division rather than a gap. LINQ-FORMAT
274
+ §1.1 rule 1 is the reason — a pen refuses only what it cannot SPELL, or
275
+ what the engine's own rule would refuse and the pen can see earlier,
276
+ mirrored and never invented.
277
+
278
+ ## 3. Worked examples
279
+
280
+ Every `js` fence exports exactly one contract, and the `json` fence that
281
+ follows is what the pen emits — executed by
282
+ `test/linq/pen-docs.test.js`. CONTRACT-FORMAT's own three worked examples
283
+ are rebuilt the same way and held BYTE-equal to that document's fences by
284
+ `test/linq/contract-pen.test.js`, so the format spec and this pen cannot
285
+ drift apart either.
286
+
287
+ ### 3.1 The smallest complete contract
288
+
289
+ **The health endpoint** — the smallest complete contract the shop
290
+ publishes. One `read`, one binding, and a `doc` string. Nothing else is required:
291
+ no `version`, no `$defs`, no `policy`.
292
+
293
+ ```js
294
+ import * as s from '@jarenjs/linq/schema';
295
+ import { defineContract, http, read } from '@jarenjs/linq/contract';
296
+
297
+ export const health = defineContract({ id: 'health' }, {
298
+ 'health.check': read({
299
+ output: s.object({ ok: s.boolean(), uptimeMs: s.integer() }).open(),
300
+ http: http({ method: 'GET', path: '/api/health' }),
301
+ doc: 'Liveness, and how long the process has been up.',
302
+ }),
303
+ });
304
+ ```
305
+ ```json
306
+ {
307
+ "$contract": "0.1",
308
+ "id": "health",
309
+ "operations": {
310
+ "health.check": {
311
+ "kind": "read",
312
+ "output": {
313
+ "type": "object",
314
+ "properties": { "ok": { "type": "boolean" }, "uptimeMs": { "type": "integer" } },
315
+ "required": ["ok", "uptimeMs"]
316
+ },
317
+ "http": { "method": "GET", "path": "/api/health" },
318
+ "doc": "Liveness, and how long the process has been up."
319
+ }
320
+ }
321
+ }
322
+ ```
323
+
324
+ `.open()` is a schema-pen decision, not a contract one (the binder's §1.1
325
+ rule 4 closes objects by default), and an open response schema is what
326
+ lets a server add a member without breaking a client that validates.
327
+
328
+ ### 3.2 A shared definition, hoisted once
329
+
330
+ **The catalog.** `Product` is reached by four positions across two operations and is one
331
+ `$defs` entry with four `$ref`s. The hoist is the contract's, so a
332
+ definition never appears inside an operation.
333
+
334
+ ```js
335
+ import * as s from '@jarenjs/linq/schema';
336
+ import { command, defineContract, http, read } from '@jarenjs/linq/contract';
337
+
338
+ const Product = s.named('Product', s.object({
339
+ id: s.integer(),
340
+ name: s.string().min(1),
341
+ price: s.number().min(0),
342
+ }).open());
343
+
344
+ export const catalog = defineContract({ id: 'catalog', version: '2', compat: ['1'] }, {
345
+ 'product.get': read({
346
+ input: s.object({ id: s.integer() }).open(),
347
+ output: Product,
348
+ http: http({ method: 'GET', path: '/api/products/{id}' }),
349
+ }),
350
+ 'product.save': command({
351
+ input: s.object({ id: s.integer(), product: Product }).open(),
352
+ output: Product,
353
+ http: http({ method: 'PUT', path: '/api/products/{id}', in: { product: 'body' } }),
354
+ }),
355
+ });
356
+ ```
357
+ ```json
358
+ {
359
+ "$contract": "0.1",
360
+ "id": "catalog",
361
+ "version": "2",
362
+ "compat": ["1"],
363
+ "$defs": {
364
+ "Product": {
365
+ "type": "object",
366
+ "properties": {
367
+ "id": { "type": "integer" },
368
+ "name": { "type": "string", "minLength": 1 },
369
+ "price": { "type": "number", "minimum": 0 }
370
+ },
371
+ "required": ["id", "name", "price"]
372
+ }
373
+ },
374
+ "operations": {
375
+ "product.get": {
376
+ "kind": "read",
377
+ "input": {
378
+ "type": "object",
379
+ "properties": { "id": { "type": "integer" } },
380
+ "required": ["id"]
381
+ },
382
+ "output": { "$ref": "#/$defs/Product" },
383
+ "http": { "method": "GET", "path": "/api/products/{id}" }
384
+ },
385
+ "product.save": {
386
+ "kind": "command",
387
+ "input": {
388
+ "type": "object",
389
+ "properties": { "id": { "type": "integer" }, "product": { "$ref": "#/$defs/Product" } },
390
+ "required": ["id", "product"]
391
+ },
392
+ "output": { "$ref": "#/$defs/Product" },
393
+ "http": { "method": "PUT", "path": "/api/products/{id}", "in": { "product": "body" } }
394
+ }
395
+ }
396
+ }
397
+ ```
398
+
399
+ `id` is a path variable in both bindings and is placed by §4.1's rules,
400
+ so neither `in` map names it; `product` is named because a `PUT`'s
401
+ default would have put it in the body anyway and saying so is the
402
+ document being explicit. The `$defs` entry carries no `$defs` of its
403
+ own — the schema pen's own per-root hoist is superseded by the
404
+ contract's.
405
+
406
+ ### 3.3 Declared failures, and the whole policy
407
+
408
+ **Order intake**, where a failure is a thing you declare rather than a
409
+ status you hope for. `errors` and `policy` together — every policy member the format declares,
410
+ including the two the public projection drops.
411
+
412
+ ```js
413
+ import * as s from '@jarenjs/linq/schema';
414
+ import { command, defineContract, error, http, read } from '@jarenjs/linq/contract';
415
+
416
+ const Conflict = s.named('Conflict', s.object({ currentRevision: s.integer() }).open());
417
+
418
+ export const orders = defineContract({ id: 'orders' }, {
419
+ 'order.place': command({
420
+ input: s.object({ sku: s.string(), qty: s.integer().min(1), revision: s.integer() }).open(),
421
+ output: s.object({ orderId: s.string() }).open(),
422
+ errors: {
423
+ conflict: error({ status: 409, schema: Conflict }),
424
+ 'out-of-stock': error({ status: 422 }),
425
+ rejected: error(),
426
+ },
427
+ policy: {
428
+ task: 'exhaust',
429
+ idempotency: 'required',
430
+ revision: 'input:/revision',
431
+ limits: { maxBodyBytes: 16384 },
432
+ errors: { details: 'paths' },
433
+ retry: { max: 2, on: ['JC2002'] },
434
+ audience: 'public',
435
+ },
436
+ http: http({ method: 'POST', path: '/api/orders' }),
437
+ }),
438
+ 'order.list': read({
439
+ output: s.array(s.string()),
440
+ policy: { cache: 'revision', task: 'switch' },
441
+ http: http({ method: 'GET', path: '/api/orders' }),
442
+ }),
443
+ });
444
+ ```
445
+ ```json
446
+ {
447
+ "$contract": "0.1",
448
+ "id": "orders",
449
+ "$defs": {
450
+ "Conflict": {
451
+ "type": "object",
452
+ "properties": { "currentRevision": { "type": "integer" } },
453
+ "required": ["currentRevision"]
454
+ }
455
+ },
456
+ "operations": {
457
+ "order.place": {
458
+ "kind": "command",
459
+ "input": {
460
+ "type": "object",
461
+ "properties": {
462
+ "sku": { "type": "string" },
463
+ "qty": { "type": "integer", "minimum": 1 },
464
+ "revision": { "type": "integer" }
465
+ },
466
+ "required": ["sku", "qty", "revision"]
467
+ },
468
+ "output": {
469
+ "type": "object",
470
+ "properties": { "orderId": { "type": "string" } },
471
+ "required": ["orderId"]
472
+ },
473
+ "errors": {
474
+ "conflict": { "status": 409, "schema": { "$ref": "#/$defs/Conflict" } },
475
+ "out-of-stock": { "status": 422 },
476
+ "rejected": {}
477
+ },
478
+ "policy": {
479
+ "task": "exhaust",
480
+ "idempotency": "required",
481
+ "revision": "input:/revision",
482
+ "limits": { "maxBodyBytes": 16384 },
483
+ "errors": { "details": "paths" },
484
+ "retry": { "max": 2, "on": ["JC2002"] },
485
+ "audience": "public"
486
+ },
487
+ "http": { "method": "POST", "path": "/api/orders" }
488
+ },
489
+ "order.list": {
490
+ "kind": "read",
491
+ "output": { "type": "array", "items": { "type": "string" } },
492
+ "policy": { "task": "switch", "cache": "revision" },
493
+ "http": { "method": "GET", "path": "/api/orders" }
494
+ }
495
+ }
496
+ }
497
+ ```
498
+
499
+ Three things the fence shows that a sentence would not. `rejected:
500
+ error()` emits `{}` — a declared code with no status and no details
501
+ schema, which is legal and which reads its `400` from `describe()`.
502
+ `order.list` declared `cache` before `task` and the document carries
503
+ `task` first: §12.1's order is the pen's, not the author's. And
504
+ `retry.on` names a `JC2xxx` taxonomy code rather than one of this
505
+ operation's own — both spellings are accepted, because a retry policy
506
+ that could only name declared codes could not say "retry a timeout".
507
+
508
+ ### 3.4 A subscribe operation and its stream binding
509
+
510
+ **The picking board, live.** `subscribe`'s `output` is the SNAPSHOT schema (§17); the emissions that
511
+ follow travel the stream wire as patches against it.
512
+
513
+ ```js
514
+ import * as s from '@jarenjs/linq/schema';
515
+ import { command, defineContract, http, subscribe } from '@jarenjs/linq/contract';
516
+
517
+ const Board = s.named('Board', s.object({
518
+ seq: s.integer(),
519
+ rows: s.array(s.string()),
520
+ }).open());
521
+
522
+ export const board = defineContract({ id: 'board' }, {
523
+ 'board.watch': subscribe({
524
+ input: s.object({ id: s.string() }).open(),
525
+ output: Board,
526
+ policy: { task: 'switch', stream: { resume: 'replay', heartbeatMs: 2000, maxPatchBytes: 65536 } },
527
+ http: http({ method: 'GET', path: '/board/{id}' }),
528
+ doc: 'The board, live: a snapshot, then patches.',
529
+ }),
530
+ 'board.clear': command({ input: s.object({ id: s.string() }).open(), output: Board }),
531
+ });
532
+ ```
533
+ ```json
534
+ {
535
+ "$contract": "0.1",
536
+ "id": "board",
537
+ "$defs": {
538
+ "Board": {
539
+ "type": "object",
540
+ "properties": {
541
+ "seq": { "type": "integer" },
542
+ "rows": { "type": "array", "items": { "type": "string" } }
543
+ },
544
+ "required": ["seq", "rows"]
545
+ }
546
+ },
547
+ "operations": {
548
+ "board.watch": {
549
+ "kind": "subscribe",
550
+ "input": {
551
+ "type": "object",
552
+ "properties": { "id": { "type": "string" } },
553
+ "required": ["id"]
554
+ },
555
+ "output": { "$ref": "#/$defs/Board" },
556
+ "policy": {
557
+ "task": "switch",
558
+ "stream": { "resume": "replay", "heartbeatMs": 2000, "maxPatchBytes": 65536 }
559
+ },
560
+ "http": { "method": "GET", "path": "/board/{id}" },
561
+ "doc": "The board, live: a snapshot, then patches."
562
+ },
563
+ "board.clear": {
564
+ "kind": "command",
565
+ "input": {
566
+ "type": "object",
567
+ "properties": { "id": { "type": "string" } },
568
+ "required": ["id"]
569
+ },
570
+ "output": { "$ref": "#/$defs/Board" }
571
+ }
572
+ }
573
+ }
574
+ ```
575
+
576
+ `board.clear` declares no `http` and takes the canonical binding —
577
+ `POST /board.clear`, every input member in the body, status `200`,
578
+ `application/json` (§4.3). The pen writes no `http` member for it,
579
+ because the canonical binding is a default and §1.1's second rule
580
+ forbids writing defaults; `describe()` marks the whole binding inferred.
581
+
582
+ The three stream knobs and the `task` are what the pen writes. The media
583
+ type a subscribe travels on is NOT: `text/event-stream` is forced by the
584
+ compiler (§17), which is also why a subscribe is never opaque no matter
585
+ what its media says.
586
+
587
+ ### 3.5 Path templates, member locations and an opaque operation
588
+
589
+ **The document store**, which is where the HTTP binding gets interesting.
590
+ The two template spellings, the four locations, a whole-body member, a
591
+ declared success status, and a media type that makes an operation
592
+ opaque.
593
+
594
+ ```js
595
+ import * as s from '@jarenjs/linq/schema';
596
+ import { command, defineContract, http, read } from '@jarenjs/linq/contract';
597
+
598
+ export const docs = defineContract({ id: 'docs' }, {
599
+ 'doc.put': command({
600
+ input: s.object({
601
+ id: s.string(),
602
+ folder: s.string(),
603
+ body: s.array(s.object({}).open()),
604
+ dry: s.boolean().optional(),
605
+ }).open(),
606
+ output: true,
607
+ http: http({
608
+ method: 'PUT',
609
+ path: '/docs/{folder}/{id}',
610
+ in: { dry: 'query' },
611
+ body: 'body',
612
+ status: 204,
613
+ }),
614
+ }),
615
+ 'doc.thumbnail': read({
616
+ input: s.object({ id: s.string() }).open(),
617
+ output: true,
618
+ http: http({ method: 'GET', path: '/docs/:id/thumb', media: 'image/png' }),
619
+ }),
620
+ });
621
+ ```
622
+ ```json
623
+ {
624
+ "$contract": "0.1",
625
+ "id": "docs",
626
+ "operations": {
627
+ "doc.put": {
628
+ "kind": "command",
629
+ "input": {
630
+ "type": "object",
631
+ "properties": {
632
+ "id": { "type": "string" },
633
+ "folder": { "type": "string" },
634
+ "body": { "type": "array", "items": { "type": "object" } },
635
+ "dry": { "type": "boolean" }
636
+ },
637
+ "required": ["id", "folder", "body"]
638
+ },
639
+ "output": true,
640
+ "http": {
641
+ "method": "PUT",
642
+ "path": "/docs/{folder}/{id}",
643
+ "in": { "dry": "query" },
644
+ "body": "body",
645
+ "status": 204
646
+ }
647
+ },
648
+ "doc.thumbnail": {
649
+ "kind": "read",
650
+ "input": {
651
+ "type": "object",
652
+ "properties": { "id": { "type": "string" } },
653
+ "required": ["id"]
654
+ },
655
+ "output": true,
656
+ "http": { "method": "GET", "path": "/docs/:id/thumb", "media": "image/png" }
657
+ }
658
+ }
659
+ }
660
+ ```
661
+
662
+ `/docs/:id/thumb` is written as declared and canonicalized to
663
+ `/docs/{id}/thumb` by the compiler; a projection or a `describe()` shows
664
+ the canonical form, this document shows the author's. The reserved forms
665
+ `{+id}`, `{id*}`, `{a,b}`, `*` and `:id?` are all refused here rather
666
+ than at compile time, naming the same form `JC0008` would (§4.2).
667
+
668
+ Two members are placed and two are not. `id` and `folder` are path
669
+ variables, named by the template itself — mapping one to `path` in the
670
+ `in` map is redundant and mapping a member the template does NOT declare
671
+ is `JL0102`. `body` and `dry` are the two the author placed, and `body`
672
+ is the whole-body member: its value IS the request body, so no other
673
+ member may be body-located beside it.
674
+
675
+ `doc.thumbnail`'s `image/png` makes it **opaque** (§4.5). The
676
+ consequences are all on the type side and §5 pins them: it is out of
677
+ `invoke` and out of the tool set, and it is reachable through `url()`
678
+ only.
679
+
680
+ ### 3.6 One document, three consumers
681
+
682
+ **The internal notes service**, and the pen's whole value in one fence: one authored contract, and a client,
683
+ a handler table and an AI toolbox all typed off it with no generate step.
684
+ The three wrappers are identity at run time, so what this fence proves is
685
+ that a pen contract IS a contract — it compiles, it serves, it invokes,
686
+ and its declared failure comes back as an outcome rather than a throw.
687
+
688
+ ```js
689
+ import * as s from '@jarenjs/linq/schema';
690
+ import { compileContract } from '@jarenjs/contract';
691
+ import { openLocalClient } from '@jarenjs/contract/local';
692
+ import { contractTools } from '@jarenjs/contract/project';
693
+ import { command, defineContract, error, http, read, typedClient, typedHandlers, typedTools }
694
+ from '@jarenjs/linq/contract';
695
+
696
+ const Note = s.named('Note', s.object({ id: s.string(), text: s.string().min(1) }).open());
697
+
698
+ export const notes = defineContract({ id: 'notes', version: '1' }, {
699
+ 'note.get': read({
700
+ input: s.object({ id: s.string() }).open(),
701
+ output: Note,
702
+ errors: { 'not-found': error({ status: 404 }) },
703
+ http: http({ method: 'GET', path: '/notes/{id}' }),
704
+ }),
705
+ 'note.save': command({
706
+ input: s.object({ note: Note }).open(),
707
+ output: Note,
708
+ policy: { idempotency: 'required' },
709
+ http: http({ method: 'POST', path: '/notes' }),
710
+ }),
711
+ });
712
+
713
+ // the three consumers, all typed off the one document above
714
+ const store = new Map([['n1', { id: 'n1', text: 'first' }]]);
715
+ const compiled = compileContract(notes.document);
716
+ const handlers = typedHandlers(notes, {
717
+ 'note.get': (input, ctx) => store.get(input.id) ?? ctx.fail('not-found'),
718
+ 'note.save': (input) => { store.set(input.note.id, input.note); return input.note; },
719
+ });
720
+ const api = typedClient(openLocalClient(compiled, handlers), notes);
721
+ const got = await api.invoke('note.get', { id: 'n1' });
722
+ if (!got.ok || got.value.text !== 'first') throw new Error('the read did not round-trip');
723
+ const missing = await api.invoke('note.get', { id: 'n9' });
724
+ if (missing.ok || missing.error.code !== 'not-found') throw new Error('the declared failure is an outcome');
725
+ const tools = typedTools(contractTools(compiled, api), notes);
726
+ if (tools.map((t) => t.name).join(' ') !== 'note_get note_save') throw new Error('the tool names are the ids');
727
+ api.close();
728
+ ```
729
+ ```json
730
+ {
731
+ "$contract": "0.1",
732
+ "id": "notes",
733
+ "version": "1",
734
+ "$defs": {
735
+ "Note": {
736
+ "type": "object",
737
+ "properties": { "id": { "type": "string" }, "text": { "type": "string", "minLength": 1 } },
738
+ "required": ["id", "text"]
739
+ }
740
+ },
741
+ "operations": {
742
+ "note.get": {
743
+ "kind": "read",
744
+ "input": {
745
+ "type": "object",
746
+ "properties": { "id": { "type": "string" } },
747
+ "required": ["id"]
748
+ },
749
+ "output": { "$ref": "#/$defs/Note" },
750
+ "errors": { "not-found": { "status": 404 } },
751
+ "http": { "method": "GET", "path": "/notes/{id}" }
752
+ },
753
+ "note.save": {
754
+ "kind": "command",
755
+ "input": {
756
+ "type": "object",
757
+ "properties": { "note": { "$ref": "#/$defs/Note" } },
758
+ "required": ["note"]
759
+ },
760
+ "output": { "$ref": "#/$defs/Note" },
761
+ "policy": { "idempotency": "required" },
762
+ "http": { "method": "POST", "path": "/notes" }
763
+ }
764
+ }
765
+ }
766
+ ```
767
+
768
+ The `ctx.fail('not-found')` in the handler is the only spelling that
769
+ compiles for a code this operation declares, and `missing.error.code`
770
+ is that code — a declared failure is data, never an exception, on either
771
+ side of the wire. The tool names are the operation ids with `.` → `_`,
772
+ which is `ToolName<>` in §5 and `contractTools`' own rule at run time.
773
+
774
+ ## 4. Refusals
775
+
776
+ The contract pen raises these three `LinqBuildError` codes and no others
777
+ — `test/linq/pen-docs.test.js` holds this list equal, in both directions,
778
+ to the codes `packages/linq/src/contract/` names. The full condition each
779
+ code states across every pen is the binder's,
780
+ [LINQ-FORMAT.md](LINQ-FORMAT.md) §1.3.
781
+
782
+ | Code | What this pen raises it for |
783
+ |---|---|
784
+ | `JL0101` | a value this pen cannot spell, a member of its own surface it does not know, a value outside a declared set, or a name → value map it cannot read |
785
+ | `JL0102` | a construct the format cannot carry: a reserved path-template form, a member mapped to a `path` the template does not declare, or a hand-written operation `kind` outside the three |
786
+ | `JL0103` | a `$defs` name collision, a reference no definition answers, or a `lazy()` that does not return a named builder |
787
+
788
+ Every message below is the one the pen raised when the spelling beside it
789
+ was run, with the code prefix (`JL0101: `) removed. `docPath`, where the
790
+ refusal carries one, is the JSON pointer of the node being assembled and
791
+ is appended to the message text as well (`… at /operations/a/policy/task`).
792
+
793
+ **Where the three codes come from is not one directory.** `JL0101` and
794
+ `JL0102` are thrown by `packages/linq/src/contract/` — 59 sites across
795
+ `define.js`, `operation.js` and `http.js`, 40 of the first and 19 of the
796
+ second. The three tables below carry 73 rows over them, which is not a
797
+ contradiction: one site serves many spellings (`closedTo` is a single
798
+ throw reached from six functions, and one branch of `policyMember`
799
+ covers four policy members), so a row is a CONDITION a caller can hit
800
+ and a site is a line in the source. `JL0103` is thrown by the SHARED
801
+ hoisting walk (`packages/linq/src/schema/emit.js`), reached from
802
+ `defineContract` and named in `define.js`'s own `@throws`, which is why
803
+ the refusal gate finds it in this pen's directory and why its `docPath`
804
+ is rooted in the contract (`/operations/a/output/properties/p`) rather
805
+ than in a schema. `JL0104` — the schema pen's owned-keyword and external
806
+ rules — is NOT in the table, and the reason is worth a reader's time: it
807
+ fires when the schema builder's method is called, before the contract is
808
+ written, so a caller who sees it is looking at a schema-pen line.
809
+
810
+ ### 4.1 `JL0101` — the value, the member and the set
811
+
812
+ Raised at the door of whichever function received it.
813
+
814
+ | The spelling that trips it | The message | The spelling that works |
815
+ |---|---|---|
816
+ | `defineContract(42, { … })` | `defineContract() takes ({ id?, version?, compat? }, operations), got 42 as its first argument` | `defineContract({}, { … })` |
817
+ | `defineContract({ name: 'x' }, …)` | `defineContract() does not take 'name' — the head is id, version, compat; everything else is an operation` | move it into `operations` |
818
+ | `defineContract({ id: '9shop' }, …)` | `defineContract() id matches [A-Za-z_][A-Za-z0-9_-]*, got a string` | `{ id: 'shop9' }` |
819
+ | `defineContract({ version: 5 }, …)` | `defineContract() version is a string, got 5` | `{ version: '5' }` |
820
+ | `defineContract({ compat: '4' }, …)` | `defineContract() compat is an array of peer version strings` | `{ compat: ['4'] }` |
821
+ | `defineContract({}, [])` | `defineContract() operations is a plain object of id → operation, got a Array instance` | a plain object |
822
+ | `defineContract({}, {})` | `defineContract() needs at least one operation` | declare one |
823
+ | `defineContract({}, { __proto__: read({ … }) })` | `defineContract() operations received a map whose prototype was replaced: a '__proto__:' key in an object literal sets the prototype instead of adding a member, so that member is not there to emit — spell it { ['__proto__']: … }, which is an own key` | `{ ['__proto__']: read({ … }) }` |
824
+ | `defineContract({}, { a: 42 })` | `an operation is read(), command() or subscribe() — got 42` | `read({ output: true })` |
825
+ | `read(42)` | `read() takes { input?, output, errors?, policy?, http?, doc? }, got 42` | a plain object |
826
+ | `read({ output: true, extra: 1 })` | `read() does not take 'extra' — it takes input, output, errors, policy, http, doc` | drop it, or put it in `policy` |
827
+ | `read({})` | `read() needs an output — every operation declares one (true for "any value")` | `read({ output: true })` |
828
+ | `read({ output: true, doc: 42 })` | `read() doc is a string, got 42` | `doc: '…'` |
829
+ | `read({ output: { type: 'string', default: () => 1 } })` | `the schema at /operations/a/output received a Object instance, which is not JSON — a document carries null, booleans, finite numbers (never -0), strings, arrays and plain objects, and nothing else` | a JSON value for the default |
830
+ | `read({ output: 42 })` | `a schema is an object, true or false — got 42` | a builder, an object, or `true` |
831
+ | `errors: 42` | `errors is a plain object of code → error(), got 42` | a plain object |
832
+ | `errors: { Bad: error({}) }` | `an error code matches ^[a-z][a-z0-9-]*$, got 'Bad'` | `{ bad: error({}) }` |
833
+ | `errors: { bad: 42 }` | `errors.bad is error({ status?, schema? }), got 42` | `error({ status: 400 })` |
834
+ | `error({ code: 'x' })` | `error() does not take 'code' — it takes status, schema` | the code is the map's key |
835
+ | `error({ status: 99 })` | `error() status is an integer in 100–599, got 99` | `error({ status: 409 })` |
836
+ | `policy: 42` | `policy is a plain object of the members CONTRACT-FORMAT §3.1 declares, got 42` | a plain object |
837
+ | `policy: { mode: 1 }` | `policy does not take 'mode' — it takes task, idempotency, revision, cache, limits, errors, retry, stream, audience` | the member §3.1 names |
838
+ | `policy: { task: 'queue' }` | `policy.task is one of switch, exhaust, concat, parallel, got a string` | `task: 'exhaust'` |
839
+ | `policy: { idempotency: 'maybe' }` | `policy.idempotency is one of none, optional, required, got a string` | `idempotency: 'optional'` |
840
+ | `policy: { cache: 'always' }` | `policy.cache is one of none, revision, got a string` | `cache: 'revision'` |
841
+ | `policy: { audience: 'admin' }` | `policy.audience is one of public, server, got a string` | `audience: 'server'` |
842
+ | `policy: { revision: '/x' }` | `policy.revision is "input:<json-pointer>" — where in the input the revision a command asserts lives, got a string` | `revision: 'input:/x'` |
843
+ | `policy: { limits: 1 }` | `policy.limits is { maxBodyBytes }` | `limits: { maxBodyBytes: 4096 }` |
844
+ | `policy: { limits: { max: 1 } }` | `policy.limits does not take 'max' — it takes maxBodyBytes` | `maxBodyBytes` |
845
+ | `policy: { limits: { maxBodyBytes: 0 } }` | `policy.limits.maxBodyBytes is a positive integer, got 0` | a positive integer |
846
+ | `policy: { errors: 1 }` | `policy.errors is { details }` | `errors: { details: 'paths' }` |
847
+ | `policy: { errors: { detail: 1 } }` | `policy.errors does not take 'detail' — it takes details` | `details` |
848
+ | `policy: { errors: { details: 'some' } }` | `policy.errors.details is one of none, paths, full, got a string` | `details: 'full'` |
849
+ | `policy: { retry: 1 }` | `policy.retry is { max, on }` | `retry: { max: 2, on: [] }` |
850
+ | `policy: { retry: { max: 1, ms: 1 } }` | `policy.retry does not take 'ms' — it takes max, on` | the backoff is the client's |
851
+ | `policy: { retry: { max: -1, on: [] } }` | `policy.retry.max is an integer ≥ 0, got -1` | `max: 0` or more |
852
+ | `policy: { retry: { max: 1, on: 1 } }` | `policy.retry.on is an array of error codes (declared codes, or JC2xxx taxonomy codes)` | `on: ['JC2002']` |
853
+ | `policy: { stream: 1 }` | `policy.stream is { resume?, heartbeatMs?, maxPatchBytes? }` | a plain object |
854
+ | `policy: { stream: { keepAlive: 1 } }` | `policy.stream does not take 'keepAlive' — it takes resume, heartbeatMs, maxPatchBytes` | `heartbeatMs` |
855
+ | `policy: { stream: { resume: 'always' } }` | `policy.stream.resume is snapshot or replay, got a string` | `resume: 'replay'` |
856
+ | `policy: { stream: { heartbeatMs: 500 } }` | `policy.stream.heartbeatMs is an integer ≥ 1000, got 500` | `heartbeatMs: 2000` |
857
+ | `policy: { stream: { maxPatchBytes: 0 } }` | `policy.stream.maxPatchBytes is a positive integer, got 0` | a positive integer |
858
+ | `http(42)` | `http() takes { method, path, in?, body?, status?, media? }, got 42` | a plain object |
859
+ | `http({ method: 'GET', path: '/a', headers: {} })` | `http() does not take 'headers' — the binding is method, path, in, body, status, media (CONTRACT-FORMAT §4)` | `in: { … }` for a header-located member |
860
+ | `http({ method: 'get', path: '/a' })` | `http() method is one uppercase token of GET HEAD POST PUT PATCH DELETE OPTIONS, got a string` | `method: 'GET'` |
861
+ | `http({ …, in: 42 })` | `http() in is a plain object of input member → path \| query \| header \| body` | a plain object |
862
+ | `http({ …, in: { x: 'cookie' } })` | `http() in.x is one of path, query, header, body, got a string` | `{ x: 'header' }` |
863
+ | `http({ …, body: 42 })` | `http() body names the input member whose value IS the request body, got 42` | `body: 'doc'` |
864
+ | `http({ …, status: 404 })` | `http() status is an integer in 200–299, got 404` | `status: 204` |
865
+ | `http({ …, media: 42 })` | `http() media is a media type, got 42` | `media: 'image/png'` |
866
+
867
+ Two rows to read carefully. `{ __proto__: … }` is the binder's §1.1 rule
868
+ 5 at this pen's one map door: the literal form sets the object's
869
+ prototype instead of adding a member, so the operation never reaches the
870
+ pen at all and the prototype is the only trace left to refuse by. And
871
+ `http() in received a Object instance, which is not JSON` — the message
872
+ for `in: { x: NaN }` — is the JSON boundary, shared by every pen, and it
873
+ is the one refusal in this table that carries no `docPath`: its `what`
874
+ names the position instead.
875
+
876
+ ### 4.2 `JL0102` — the reserved path template, and the kind
877
+
878
+ Raised by `http()` scanning the template, which mirrors the compiler's
879
+ own parser: every form §4.2 reserves is refused by name at build time,
880
+ before `compileContract` would answer `JC0008` with the same meaning.
881
+ Every message carries `docPath: '/path'`.
882
+
883
+ | The spelling that trips it | The message | The spelling that works |
884
+ |---|---|---|
885
+ | `path: 42` | `http() path is a path template string, got 42` | a string |
886
+ | `path: 'a/b'` | `a path template must start with "/"` | `'/a/b'` |
887
+ | `path: '/a/'` | `a trailing "/" declares an empty segment; the root template "/" is the only empty path` | `'/a'` |
888
+ | `path: '/a//b'` | `an empty segment ("//")` | `'/a/b'` |
889
+ | `path: '/a/{id}.json'` | `a variable must be a whole segment ("{name}"), found "{id}.json" — the format reserves a variable that is only part of a segment` | `'/a/{id}'`, with the suffix in `media` |
890
+ | `path: '/a/{id'` | `a variable must be a whole segment ("{name}"), found "{id" — the format reserves a variable that is only part of a segment` | close the brace |
891
+ | `path: '/a/x:y'` | `":" is reserved for a variable segment (":name"), found "x:y"` | `'/a/x/y'` |
892
+ | `path: '/a/*'` | `"*" is a reserved wildcard form; $contract 0.1 has no wildcards, found "*"` | name the segments |
893
+ | `path: '/a?b'` | `"?" cannot appear in a path template (the query and fragment are not part of the path)` | `in: { b: 'query' }` |
894
+ | `path: '/a b'` | `whitespace or a control character in segment "a b"` | `'/a%20b'` |
895
+ | `path: '/a%zz'` | `a malformed percent-escape in segment "a%zz"` | a well-formed escape |
896
+ | `path: '/a/{+id}'` | `"{+id}" uses the reserved RFC 6570 operator "+"; $contract 0.1 supports only "{name}"` | `'/a/{id}'` |
897
+ | `path: '/a/{id*}'` | `"{id*}" uses the reserved "*" expansion modifier; $contract 0.1 has no wildcards` | `'/a/{id}'` |
898
+ | `path: '/a/{a,b}'`, `path: '/a/{id:3}'` | `"{a,b}" uses a reserved RFC 6570 list or prefix form; $contract 0.1 supports only "{name}"` | one variable per segment |
899
+ | `path: '/a/:'` | `":name" must be a whole segment with an identifier name, found ":"` | `'/a/:id'` |
900
+ | `path: '/a/:id?'` | `":id?" uses a reserved "?" modifier; $contract 0.1 has no wildcards or optional segments` | two operations, or `{id}` |
901
+ | `path: '/a/{1x}'` | `a variable name must match [A-Za-z_][A-Za-z0-9_]*, found "1x"` | `'/a/{x1}'` |
902
+ | `path: '/a/{id}/b/{id}'` | `the variable "id" is declared twice` | two names |
903
+ | `http({ method: 'GET', path: '/a', in: { id: 'path' } })` | `http() maps 'id' to path, but the template declares no {id} — a path member is named by the template itself` | `path: '/a/{id}'`, and drop the `in` entry |
904
+ | `defineContract({}, { a: { kind: 'stream', output: true } })` | `an operation kind is one of read, command, subscribe — got a string` | `subscribe({ output: true })` |
905
+
906
+ The last row is the only `JL0102` that is not a template: a HAND-WRITTEN
907
+ operation carries its own `kind`, and a kind outside the three is a
908
+ construct the format cannot carry. `read()`, `command()` and
909
+ `subscribe()` cannot trip it — they write the kind themselves.
910
+
911
+ The eight reserved forms are refused by the pen and by the compiler with
912
+ the same meaning and the same names; `test/linq/contract-pen.test.js`
913
+ runs `/a/{id}.json` through both and asserts `JL0102` from the pen and
914
+ `JC0008` from `compileContract`. Which one a caller sees depends only on
915
+ where the template was written — through `http()`, or into a JSON
916
+ document by hand.
917
+
918
+ ### 4.3 `JL0103` — the definition
919
+
920
+ Raised when the `$defs` block is closed and every name a `ref()` demanded
921
+ must be answered, or when a `lazy()` thunk is resolved. The walk is the
922
+ schema pen's, run over every operation's schemas at once, so a collision
923
+ between two operations is found here and nowhere earlier.
924
+
925
+ | The spelling that trips it | The message | The spelling that works |
926
+ |---|---|---|
927
+ | two operations, each `output: s.named('P', …)` over a DIFFERENT builder | `two distinct builders are named 'P' in one document — a $defs entry can hold one definition; rename one of them` | one shared `const P = s.named('P', …)` |
928
+ | `output: s.object({ p: s.ref('Nope') })` | `ref('Nope') names no definition in this document — a name is defined by named('Nope', …) somewhere the root can reach` | define it in a schema this contract reaches |
929
+ | `output: s.object({ n: s.lazy(() => s.string()) })` | `lazy() must return a NAMED builder — a recursion is spelled as a $ref, and a $ref needs a definition to point at: lazy(() => Node) where Node = named('Node', …)` | `s.lazy(() => Node)` |
930
+
931
+ "Distinct" is by identity, not by shape: the same builder under one name,
932
+ reached from any operation, is one definition. Two builders that emit the
933
+ same JSON are still two and still a collision. The `docPath` names the
934
+ operation the second reference came from, which is what tells a caller
935
+ which two to reconcile.
936
+
937
+ A `ref()` may reach across operations: a definition named in one
938
+ operation's `input` answers a `ref()` in another's `output`, because the
939
+ `$defs` block is the contract's. What it cannot do is reach a name
940
+ nothing defines — that is this row, and it is why a contract assembled
941
+ from schemas written in several files still has one namespace.
942
+
943
+ ### 4.4 Two closed vocabularies, and which one a caller meets
944
+
945
+ There are two closed member sets over the same document, and a reader who
946
+ does not know that will look up the wrong code.
947
+
948
+ - **The pen's surface** is what `read()`, `command()`, `subscribe()`,
949
+ `error()`, `http()` and `defineContract()` take. A member outside it is
950
+ `JL0101`, at the call, naming the members that function does take.
951
+ - **The document's own vocabulary** is CONTRACT-FORMAT's, and it is
952
+ `compileContract`'s to enforce: `JC0013`, at compile time, naming the
953
+ closed set. It is what a hand-written JSON contract meets, and what a
954
+ pen contract meets for anything the pen does not check.
955
+
956
+ The two are deliberately not one. The pen cannot own the document's
957
+ vocabulary — it writes only the members it was given, so a member the
958
+ format adds tomorrow must not be a member the pen refuses today — and the
959
+ compiler cannot own the pen's, because `read({ output: true, extra: 1 })`
960
+ never becomes a document at all.
961
+
962
+ ```js
963
+ read({ output: true, extra: 1 })
964
+ // JL0101: read() does not take 'extra' — it takes input, output, errors, policy, http, doc
965
+
966
+ compileContract({ $contract: '0.1', operations: { a: { kind: 'read', output: true, extra: 1 } } })
967
+ // JC0013: unknown operation member 'extra' — the operation vocabulary is closed
968
+ // (kind, input, output, errors, policy, http, doc) at /operations/a/extra
969
+ ```
970
+
971
+ **The pen's surface has two doors, and both run the same check.** An
972
+ operation written by hand inside `defineContract` —
973
+ `{ kind: 'read', output: true, extra: 1 }` — meets the pen's `JL0101`,
974
+ not the compiler's `JC0013`, because the member never becomes a document
975
+ member. The only difference from the declaration door is the `docPath`,
976
+ which names the operation's own position because there is one:
977
+
978
+ ```js
979
+ defineContract({}, { 'note.get': { kind: 'read', output: true, extra: 1 } })
980
+ // JL0101: read() does not take 'extra' — it takes input, output, errors,
981
+ // policy, http, doc at /operations/note.get/extra
982
+ ```
983
+
984
+ That symmetry is load-bearing rather than tidy. A pen emits only the
985
+ members it was given, so an unchecked door does not pass an unknown
986
+ member through to the compiler — it DROPS it, silently, where neither
987
+ refusal can reach it; and a member the pen does write without checking
988
+ (a non-string `doc`) reaches the document, which then fails the
989
+ published grammar. Both halves are pinned by
990
+ `test/linq/contract-pen.test.js`, which runs the same three spellings
991
+ through both doors and asserts the reason and the `docPath` of each.
992
+
993
+ ## 5. The types
994
+
995
+ The pen is the only inference route (the binder's §1.1 rule 2): a
996
+ `Contract<Ops>` carries a phantom that `ContractOf<>` reads, and every
997
+ consumer below narrows off that one reading. Nothing here exists at run
998
+ time. The `shop` contract below is a catalog like §3.2's with declared
999
+ failures like §3.3's on it — the two features a consumer's types show off
1000
+ at once.
1001
+
1002
+ ```ts
1003
+ import { compileContract } from '@jarenjs/contract';
1004
+ import { openLocalClient } from '@jarenjs/contract/local';
1005
+ import { contractTools } from '@jarenjs/contract/project';
1006
+ import { typedClient, typedHandlers, typedTools } from '@jarenjs/linq/contract';
1007
+ import type { Contract, ContractOf, InvokableOf, Outcome } from '@jarenjs/linq/contract';
1008
+
1009
+ type Ops = ContractOf<typeof shop>;
1010
+ // { 'product.save': { kind: 'command'; input: …; accepts: …; output: …;
1011
+ // errors: 'conflict' | 'not-found'; opaque: false }, … }
1012
+
1013
+ const compiled = compileContract(shop.document);
1014
+ const handlers = typedHandlers(shop, {
1015
+ 'catalog.load': () => catalog, // must answer the declared output
1016
+ 'product.save': (input, ctx) => saved ? input.product : ctx.fail('conflict'),
1017
+ });
1018
+ const served = openLocalClient(compiled, handlers); // the binding: any contract client
1019
+ const api = typedClient(served, shop); // the same object, narrowed by the phantom
1020
+ const outcome: Outcome<Product> = await api.invoke('product.save', { id: 1, revision: 4, product });
1021
+ api.url('image.bytes', { id: 3 }); // an opaque operation: a URL builder here
1022
+ api.invoke('image.bytes', { id: 3 }); // does not compile — it carries bytes
1023
+ const web = typedHttpClient(openHttpClient(compiled, { baseUrl }), shop);
1024
+ const image = await web.bytes('image.bytes', { id: 3 }); // Outcome<ByteResponse>: { status, headers, media, body: ReadableStream }
1025
+ web.bytes('product.save', { id: 1, revision: 4, product }); // does not compile — a JSON operation is invoked, not streamed
1026
+ for (const tool of typedTools(contractTools(compiled, api), shop)) toolbox.add(tool);
1027
+
1028
+ declare const anyContract: Contract<any>; // the class: an annotation, never a `new`
1029
+ type Invokable = keyof InvokableOf<typeof shop>; // 'catalog.load' | 'product.save'
1030
+ ```
1031
+
1032
+ `contractTools` takes either client — the binding (`served`) or the
1033
+ typed view of it (`api`). That is not free: its `ToolClient` declares
1034
+ `invoke` as a METHOD rather than as a function-valued property, and a
1035
+ method's parameters are bivariant, so a client that narrows `invoke`'s
1036
+ operation type to a literal union — which is exactly what `typedClient`
1037
+ exists to do — still satisfies it. Written as a property it would not,
1038
+ and the pen's whole claim, one document and three consumers, would fail
1039
+ at the third. `test/consumer/linq-contract.ts` pins both spellings and
1040
+ asserts they answer the same type.
1041
+
1042
+ ### 5.1 What `ContractOf<>` reads, member by member
1043
+
1044
+ One entry per declared operation, each an `OperationType`:
1045
+
1046
+ | Member | Read from | Value |
1047
+ |---|---|---|
1048
+ | `kind` | the declaration | `'read'`, `'command'` or `'subscribe'`, as a literal |
1049
+ | `input` | `Infer<>` of the input schema | the OUTPUT shape — post-normalization, defaults present — or `null` when none is declared |
1050
+ | `accepts` | `Input<>` of the input schema | the ACCEPTED shape: a defaulted member optional, a coerced member in its transport form |
1051
+ | `output` | `Infer<>` of the output schema | `unknown` for a hand-written JSON Schema or a boolean |
1052
+ | `errors` | the keys of `errors` | a literal union, `never` when none is declared |
1053
+ | `opaque` | the binding's `media` | `true` unless the media is `application/json` or a `+json` suffix; always `false` for a `subscribe` |
1054
+
1055
+ `opaque` is what splits the three consumers' views. `ContractOf<>` is
1056
+ every operation; `InvokableOf<>` drops the opaque ones, exactly as
1057
+ §12.3's `Operations` does, and it is what types `invoke`, the handler
1058
+ table and the tool set; `SubscribableOf<>` keeps the `subscribe` ones and
1059
+ types `client.subscribe`. `url()` is the one member declared over
1060
+ `ContractOf<>` itself, which is how an opaque operation stays reachable.
1061
+
1062
+ The media is read by PATTERN, never by indexing: `S extends { http:
1063
+ HttpBinding<infer H> }` and then `H extends { media: infer M }`. An
1064
+ absent optional member of a constraint is not the same as one the author
1065
+ declared, and indexing `H['media']` would make the two indistinguishable
1066
+ — so a binding with no `media` falls through to `'application/json'` by
1067
+ the pattern failing rather than by a lookup returning `undefined`.
1068
+
1069
+ ### 5.2 The class, and the two members it carries
1070
+
1071
+ `Contract` is the one exported class, and a caller never constructs one:
1072
+ its constructor is private in the declaration and `defineContract()` is
1073
+ the only route. A caller meets it as an annotation — `Contract<any>` for
1074
+ a function that takes any pen contract — and reaches its two members,
1075
+ `document` and `toJSON()`, both of which answer the same deep-frozen
1076
+ JSON. The phantom `__ops` is declared and never present at run time; it
1077
+ exists so `ContractOf<>` has something to infer from.
1078
+
1079
+ ### 5.3 What the pins hold
1080
+
1081
+ Two files, both compiled by `npm run test:types`:
1082
+
1083
+ | File | What it proves |
1084
+ |---|---|
1085
+ | `test/consumer/linq-contract-generated.ts` | `toTypeScript`'s own declarations for the three documents `test/linq/contract-corpus.js` emits, produced by `scripts/generate-contract-pen-fixture.js`. `test/linq/contract-pen.test.js` asserts the committed file is exactly what the generator produces today, so it cannot drift |
1086
+ | `test/consumer/linq-contract.ts` | for all three corpus contracts and a fourth built in the file, `ContractOf<>`'s `input`, `output` and error codes EQUAL (not merely assignable) to what the projection declares; `Meta`, `WireError`, `Outcome<T>`, `InvokeContext`, `Failure` and `HandlerContext` equal to the projection's rendering of §10.1's fixed D6 shapes; a local round trip; and six negatives |
1087
+
1088
+ The six negatives are the list of what the types forbid, each of which
1089
+ FAILS the build the day it starts compiling:
1090
+
1091
+ ```ts
1092
+ void api.invoke('product.saev', input); // a misspelled operation id
1093
+ void api.invoke('product.save', { id: 1, rev: 4, product }); // 'revision' is the member
1094
+ void api.invoke('image.bytes', { id: 3 }); // opaque: reach it through url()
1095
+ const short = typedHandlers(Shop, { 'catalog.load': () => catalog }); // a missing handler
1096
+ const notATool: 'image_bytes' = tools[0]!.name; // the opaque operation is not a tool
1097
+ void liveApi.subscribe('board.set', input, {}); // 'board.set' is a command
1098
+ ```
1099
+
1100
+ ### 5.4 Three readings a caller will otherwise meet at run time
1101
+
1102
+ - **A date-formatted string is the `DateTime` brand on both sides.** It
1103
+ is the suite's one reading of `format: "date-time"`, shared by
1104
+ `@jarenjs/db`'s generated entity types, the schema pen and
1105
+ `toTypeScript` — so a contract whose input carries a date types the
1106
+ same through the pen and through `jaren-contract types`. The branding
1107
+ MOVED the published projection: a consumer comparing a generated file
1108
+ from before it will see a diff on every date member, and that diff is
1109
+ not a regression. `test/consumer/linq-contract.ts` pins the equality
1110
+ from both directions.
1111
+ - **A hand-written JSON Schema is `unknown` on both sides.** `output:
1112
+ { type: 'string' }` reads `unknown`, not `string`, and so does the
1113
+ projection's rendering of the same document. That is the binder's rule
1114
+ 2 — a literal is never inferred — and the honest answer, since nothing
1115
+ proved the literal is a schema at all. `s.from<T>(json)` is the
1116
+ caller's assertion when they want one.
1117
+ - **Pattern-match an absent optional member; never index it.** A handler
1118
+ writing against `ContractOf<C>[K]` reaches `input` and `output` through
1119
+ `extends { input: infer I } ? I : never`, not through `['input']`. The
1120
+ declaration does the same everywhere, and the reason is §5.1's: an
1121
+ operation that declares no input has `input: null`, and an index would
1122
+ make "declared as null" and "never declared" the same type.
1123
+
1124
+ ## 6. What it cannot spell
1125
+
1126
+ The contract pen's limits are narrow, because a `$contract` document is
1127
+ mostly a map of schemas and the schema pen carries those limits
1128
+ ([SCHEMA-PEN.md](SCHEMA-PEN.md#6-what-it-cannot-spell) §6 is the list
1129
+ that matters most to a contract author). What is left is three groups,
1130
+ and then §6.1 — the cases where the answer is not to reach for this pen
1131
+ at all.
1132
+
1133
+ - **The reserved path-template forms.** RFC 6570's operators and
1134
+ modifiers, the `*` wildcard, the optional `:name?` segment, and a
1135
+ variable that is only part of a segment. `$contract` 0.1 has level-1
1136
+ templates and nothing else, because a template is also a MATCHER
1137
+ (§5) and a router that has to expand `{+path}` cannot answer
1138
+ `match(method, path)` in one pass. §4.2 lists every form with the
1139
+ spelling that works; the general answer is one variable per whole
1140
+ segment, and a query member for everything else.
1141
+ - **Anything the format decides across members.** A `read`'s
1142
+ idempotency, a `GET`'s body member, an opaque operation's body member,
1143
+ a `subscribe`'s method, a route-shape collision, a path variable that
1144
+ is not an input member. None of these is unspellable — the pen writes
1145
+ every one of them — and each is `compileContract`'s to refuse, by
1146
+ §2.8's table. They are in this section because a reader looking for
1147
+ "why can't I do X" will look here, and the answer is that they CAN
1148
+ write it and it will not compile.
1149
+ - **The wire, and the bindings.** `jaren-contract-port` frames (§16.1)
1150
+ are the one `@jarenjs/contract` document shape with no pen and there
1151
+ will not be one: they are exchanged on a wire, not authored, so a
1152
+ builder for them would type nothing a caller writes. The binder's
1153
+ decision table ([LINQ-FORMAT.md](LINQ-FORMAT.md) §1.0) carries the
1154
+ rule and the whole list of formats it excludes; message catalogs are on
1155
+ the other side of that table, an authored format whose pen is not
1156
+ written yet.
1157
+
1158
+ One absence is worth naming because it is not a limit at all. There is no
1159
+ `describe()`, no `publicProjection()` and no `toTypeScript()` here: those
1160
+ are the compiler's, they need the materialized defaults the pen refuses
1161
+ to write, and reaching them means one import of `@jarenjs/contract` over
1162
+ `contract.document`. §7 is what that separation buys.
1163
+
1164
+ ### 6.1 When not to reach for this pen
1165
+
1166
+ - **The contract is data.** A `$contract` read from a file, fetched from
1167
+ a running service's `describe()`, or produced by another tool is a
1168
+ value; `compileContract` takes it directly.
1169
+ - **You are consuming a contract you do not own.** The three wrappers of
1170
+ §2.7 type a client, a handler map or a tool set against a contract
1171
+ DOCUMENT — they do not need this pen to have written it. Import the
1172
+ document the service publishes and wrap that; authoring a second copy
1173
+ of somebody else's contract is how the two drift.
1174
+ - **The service is not one.** A contract is a published surface with a
1175
+ version and a compatibility list, and its whole value is that two
1176
+ parties can compare two revisions. One function called over a local
1177
+ import is not a service, and giving it a contract buys nothing but a
1178
+ build step.
1179
+ - **The API is not request/response.** Three operation kinds is the whole
1180
+ vocabulary — a read, a command and a subscription. A protocol that
1181
+ negotiates, that is bidirectional beyond a subscription, or that is
1182
+ really a stream of bytes has no spelling here, and §6's third bullet
1183
+ says why the wire's own frames deliberately have no pen.
1184
+ - **You want what the compiler produces, not what the pen writes.**
1185
+ Defaults materialized, a public projection, an OpenAPI file, a
1186
+ TypeScript declaration — all of those are `@jarenjs/contract`'s over
1187
+ `contract.document`, and none of them needs this subpath at run time.
1188
+
1189
+ ## 7. Cost
1190
+
1191
+ `@jarenjs/linq/contract` builds to **<!--fact:bundle.contract-->45,298<!--/fact--> bytes** as a minified,
1192
+ tree-shaken ESM bundle — the figure `scripts/check-tree-shaking.js`
1193
+ measures and `npm run test:tree-shaking` reports, published rounded
1194
+ (<!--fact:bundle.contract.kb-->45<!--/fact--> kB) beside the other nine subpath prices in
1195
+ [docs/CONSUMING.md](../../../docs/CONSUMING.md).
1196
+
1197
+ The probe is a gate, not a report: building a one-operation contract as a
1198
+ consumer would, it asserts four things and fails the build on any of
1199
+ them:
1200
+
1201
+ - **the schema pen is included, and that is the ceiling.** A contract's
1202
+ inputs and outputs are schemas, so the two are measured together and
1203
+ the bundle carries <!--fact:bundle.schema-->33,156<!--/fact--> of its <!--fact:bundle.contract-->45,298<!--/fact--> bytes as the schema pen's own.
1204
+ The contract pen's own share is the remaining ~12 kB, most of it the
1205
+ refusal messages §4 lists;
1206
+ - **no chain module** — none of `sequence.js`, `document.js`, `async.js`,
1207
+ `concurrency.js`, `provider.js` or `sources.js` contributes a byte, and
1208
+ the chain's own bundle carries no contract module either;
1209
+ - **no `@jarenjs/contract` byte** — not one, which is the tree-shaken
1210
+ proof of §1's claim that the compiler is the only judge of what the
1211
+ document means. Nor `@jarenjs/validate`, `@jarenjs/emit`,
1212
+ `@jarenjs/db`, `@jarenjs/formats`, `@jarenjs/refs` or `@jarenjs/json`;
1213
+ - **no other pen** — the schema pen is the only one, and the schema pen
1214
+ in turn carries no contract module.
1215
+
1216
+ A consumer who writes a contract and also compiles it pays both prices
1217
+ and they add rather than overlap. That is the shape the separation is
1218
+ for: a browser bundle that only needs the TYPES a contract implies —
1219
+ `typedClient` over an HTTP binding, say — ships the pen's <!--fact:bundle.contract.kb-->45<!--/fact--> kB and none
1220
+ of the compiler, while the server that serves the contract imports
1221
+ `@jarenjs/contract` and does not need the pen at all.