@jarenjs/contract 0.43.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 (84) hide show
  1. package/README.md +508 -0
  2. package/dist/types/adapters/fetch.d.ts +27 -0
  3. package/dist/types/adapters/node.d.ts +47 -0
  4. package/dist/types/app/binding.d.ts +122 -0
  5. package/dist/types/app/effect.d.ts +77 -0
  6. package/dist/types/app/index.d.ts +31 -0
  7. package/dist/types/app/subscription.d.ts +82 -0
  8. package/dist/types/bundle.d.ts +43 -0
  9. package/dist/types/cli.d.ts +15 -0
  10. package/dist/types/client/http.d.ts +242 -0
  11. package/dist/types/client/outcome.d.ts +289 -0
  12. package/dist/types/compat.d.ts +36 -0
  13. package/dist/types/compile.d.ts +196 -0
  14. package/dist/types/describe.d.ts +115 -0
  15. package/dist/types/diff.d.ts +91 -0
  16. package/dist/types/errors.d.ts +205 -0
  17. package/dist/types/http/dispatch.d.ts +148 -0
  18. package/dist/types/http/serve.d.ts +154 -0
  19. package/dist/types/http/wire.d.ts +334 -0
  20. package/dist/types/index.d.ts +39 -0
  21. package/dist/types/ledger.d.ts +207 -0
  22. package/dist/types/local/index.d.ts +127 -0
  23. package/dist/types/messages.d.ts +63 -0
  24. package/dist/types/path.d.ts +119 -0
  25. package/dist/types/pipeline.d.ts +157 -0
  26. package/dist/types/port/client.d.ts +142 -0
  27. package/dist/types/port/frame.d.ts +195 -0
  28. package/dist/types/port/serve.d.ts +102 -0
  29. package/dist/types/project/index.d.ts +34 -0
  30. package/dist/types/project/markdown.d.ts +28 -0
  31. package/dist/types/project/openapi.d.ts +102 -0
  32. package/dist/types/project/tools.d.ts +57 -0
  33. package/dist/types/project/typescript.d.ts +59 -0
  34. package/dist/types/public.d.ts +73 -0
  35. package/dist/types/revision.d.ts +36 -0
  36. package/dist/types/stream/client.d.ts +104 -0
  37. package/dist/types/stream/server.d.ts +106 -0
  38. package/dist/types/stream/sse.d.ts +62 -0
  39. package/docs/APP-INTEGRATION.md +301 -0
  40. package/docs/CONTRACT-FORMAT.md +1923 -0
  41. package/package.json +110 -0
  42. package/schemas/jaren-contract-port.draft-07.schema.json +241 -0
  43. package/schemas/jaren-contract-port.schema.json +241 -0
  44. package/schemas/jaren-contract.draft-07.schema.json +287 -0
  45. package/schemas/jaren-contract.schema.json +287 -0
  46. package/src/adapters/fetch.js +109 -0
  47. package/src/adapters/node.js +238 -0
  48. package/src/app/binding.js +426 -0
  49. package/src/app/effect.js +190 -0
  50. package/src/app/index.js +26 -0
  51. package/src/app/subscription.js +130 -0
  52. package/src/bundle.js +168 -0
  53. package/src/cli.js +264 -0
  54. package/src/client/http.js +1150 -0
  55. package/src/client/outcome.js +364 -0
  56. package/src/compat.js +62 -0
  57. package/src/compile.js +1162 -0
  58. package/src/describe.js +109 -0
  59. package/src/diff.js +610 -0
  60. package/src/errors.js +236 -0
  61. package/src/http/dispatch.js +1054 -0
  62. package/src/http/serve.js +301 -0
  63. package/src/http/wire.js +469 -0
  64. package/src/index.js +33 -0
  65. package/src/ledger.js +225 -0
  66. package/src/local/index.js +363 -0
  67. package/src/messages.js +68 -0
  68. package/src/path.js +471 -0
  69. package/src/pipeline.js +241 -0
  70. package/src/port/client.js +518 -0
  71. package/src/port/frame.js +196 -0
  72. package/src/port/serve.js +442 -0
  73. package/src/project/index.js +29 -0
  74. package/src/project/markdown.js +244 -0
  75. package/src/project/openapi.js +564 -0
  76. package/src/project/openapi.jslt.json +149 -0
  77. package/src/project/tools.js +139 -0
  78. package/src/project/typescript.js +152 -0
  79. package/src/project/typescript.jtlt.json +72 -0
  80. package/src/public.js +206 -0
  81. package/src/revision.js +90 -0
  82. package/src/stream/client.js +212 -0
  83. package/src/stream/server.js +306 -0
  84. package/src/stream/sse.js +67 -0
@@ -0,0 +1,289 @@
1
+ /**
2
+ * @file The client-side outcome (docs/CONTRACT-FORMAT.md §10): the JSON
3
+ * value every `invoke` resolves to — `{ ok: true, value, meta }` or
4
+ * `{ ok: false, kind, error, meta }` with `kind` one of `failure`
5
+ * (a declared operation error, or a taxonomy error the server answered),
6
+ * `network` (the transport failed), `contract` (the peer violated the
7
+ * contract: an invalid response, an undeclared code, a malformed frame —
8
+ * or the client refused pre-send) and `cancelled` (a local abort). Never
9
+ * an `Error`, a `Response` or a `Headers`: JSON only, so the value can
10
+ * land in app state unchanged.
11
+ *
12
+ * `assembleOutcome` is binding-neutral: it takes what a wire answered as
13
+ * `{ status | null, headers, text | value | error }` and the operation's
14
+ * prepared route, and classifies. The HTTP client feeds it a status, the
15
+ * response headers and the body text; an in-process or message-port
16
+ * binding (no statuses) feeds `status: null` with a parsed `value` or a
17
+ * parsed error envelope. Nothing here performs I/O.
18
+ *
19
+ * Outcome, error and meta objects are built with a fixed member order so
20
+ * each shape is one hidden class — and the shapes `makeMeta` and
21
+ * `outcomeError` build ARE the D6 shapes of every binding (03A): `error`
22
+ * is always `{ code, message, status, details, retryable }`, `meta` is
23
+ * always `{ op, attempt, trace, revision, etag, notModified }`, and no
24
+ * member is ever `undefined` (`isJsonValue` — the predicate the app's
25
+ * task effect and state honor — rejects it, and the outcome would fall
26
+ * back to a string). A binding that cannot carry a member carries `null`
27
+ * (`status`, `etag`, `trace`) or `false` (`notModified`) and says so in
28
+ * its `capabilities`; it never omits the member. The member lists are
29
+ * exported (`OUTCOME_ERROR_MEMBERS`, `OUTCOME_META_MEMBERS`) so a later
30
+ * binding asserts against them instead of restating them.
31
+ */
32
+ export type CompiledOperation = import('../compile.js').CompiledOperation;
33
+ export type Catalog = import('../http/wire.js').Catalog;
34
+ export type OutcomeMeta = {
35
+ op: string;
36
+ attempt: unknown;
37
+ trace: string | null;
38
+ revision: string | null;
39
+ etag: string | null;
40
+ notModified: boolean;
41
+ };
42
+ export type OutcomeError = {
43
+ code: string;
44
+ message: string;
45
+ status: number | null;
46
+ details: unknown;
47
+ retryable: boolean;
48
+ };
49
+ /**
50
+ * @typedef {import('../compile.js').CompiledOperation} CompiledOperation
51
+ * @typedef {import('../http/wire.js').Catalog} Catalog
52
+ */
53
+ /**
54
+ * The correlation members of every outcome. `op` is the operation id;
55
+ * `attempt` is the CALLER's attempt id (`ctx.attempt`, `null` when the
56
+ * caller gave none) and is never read from a response; `trace` is the
57
+ * SERVER's request id (`x-jaren-trace`), `null` when the wire carried
58
+ * none; `revision` is reserved for the contract revision; `etag` is the
59
+ * entity tag a success carried (`null` otherwise); `notModified` is true
60
+ * exactly for a 304.
61
+ * @typedef {Object} OutcomeMeta
62
+ * @property {string} op
63
+ * @property {unknown} attempt
64
+ * @property {string | null} trace
65
+ * @property {string | null} revision
66
+ * @property {string | null} etag
67
+ * @property {boolean} notModified
68
+ */
69
+ /**
70
+ * The error member of a failed outcome: a stable `code` (a declared
71
+ * error code, a `JC2xxx` taxonomy code, or a `JC205x` client code), a
72
+ * rendered `message`, the HTTP `status` when the binding carries one
73
+ * (`null` otherwise), `details` (`null` when none — an outcome is JSON,
74
+ * so no member is ever `undefined`) and whether the caller may retry.
75
+ * @typedef {Object} OutcomeError
76
+ * @property {string} code
77
+ * @property {string} message
78
+ * @property {number | null} status
79
+ * @property {unknown} details
80
+ * @property {boolean} retryable
81
+ */
82
+ /**
83
+ * The members of every outcome `error`, in order (D6). Frozen.
84
+ * @type {readonly ['code', 'message', 'status', 'details', 'retryable']}
85
+ */
86
+ export declare const OUTCOME_ERROR_MEMBERS: readonly ['code', 'message', 'status', 'details', 'retryable'];
87
+ /**
88
+ * The members of every outcome `meta`, in order (D6). Frozen.
89
+ * @type {readonly ['op', 'attempt', 'trace', 'revision', 'etag', 'notModified']}
90
+ */
91
+ export declare const OUTCOME_META_MEMBERS: readonly ['op', 'attempt', 'trace', 'revision', 'etag', 'notModified'];
92
+ export type OkOutcome = {
93
+ ok: true;
94
+ value: unknown;
95
+ meta: OutcomeMeta;
96
+ };
97
+ export type FailedOutcome = {
98
+ ok: false;
99
+ kind: 'failure' | 'network' | 'contract' | 'cancelled';
100
+ error: OutcomeError;
101
+ meta: OutcomeMeta;
102
+ };
103
+ export type Outcome = OkOutcome | FailedOutcome;
104
+ /**
105
+ * @typedef {{ ok: true, value: unknown, meta: OutcomeMeta }} OkOutcome
106
+ * @typedef {{ ok: false, kind: 'failure' | 'network' | 'contract' | 'cancelled', error: OutcomeError, meta: OutcomeMeta }} FailedOutcome
107
+ * @typedef {OkOutcome | FailedOutcome} Outcome
108
+ */
109
+ /**
110
+ * The client-side taxonomy as data: code → `{ msgid, retryable }`. The
111
+ * normative table is docs/CONTRACT-FORMAT.md §10; a test holds the two
112
+ * equal, and equal to `CONTRACT_CODES` and the English catalog.
113
+ * `retryable` of `JC2055` is decided per response (5xx and 429 are
114
+ * retryable); the row carries the default.
115
+ */
116
+ export declare const CLIENT_ERRORS: Readonly<{
117
+ JC2050: Readonly<{
118
+ msgid: "contract/client-invalid-input";
119
+ retryable: false;
120
+ }>;
121
+ JC2051: Readonly<{
122
+ msgid: "contract/network";
123
+ retryable: true;
124
+ }>;
125
+ JC2052: Readonly<{
126
+ msgid: "contract/cancelled";
127
+ retryable: false;
128
+ }>;
129
+ JC2053: Readonly<{
130
+ msgid: "contract/invalid-response";
131
+ retryable: false;
132
+ }>;
133
+ JC2054: Readonly<{
134
+ msgid: "contract/key-storage-failed";
135
+ retryable: false;
136
+ }>;
137
+ JC2055: Readonly<{
138
+ msgid: "contract/undeclared-response";
139
+ retryable: false;
140
+ }>;
141
+ JC2056: Readonly<{
142
+ msgid: "contract/not-a-contract";
143
+ retryable: false;
144
+ }>;
145
+ JC2057: Readonly<{
146
+ msgid: "contract/incompatible";
147
+ retryable: false;
148
+ }>;
149
+ JC2058: Readonly<{
150
+ msgid: "contract/host-failed";
151
+ retryable: false;
152
+ }>;
153
+ }>;
154
+ export type OutcomeRoute = {
155
+ id: string;
156
+ validateOutput: (value: unknown) => any;
157
+ errors: Readonly<Record<string, import('../compile.js').CompiledErrorDecl>>;
158
+ retryOn: ReadonlySet<string>;
159
+ details: 'none' | 'paths' | 'full';
160
+ };
161
+ /**
162
+ * What the outcome assembler reads per operation, decided once.
163
+ * @typedef {Object} OutcomeRoute
164
+ * @property {string} id
165
+ * @property {(value: unknown) => any} validateOutput
166
+ * @property {Readonly<Record<string, import('../compile.js').CompiledErrorDecl>>} errors
167
+ * @property {ReadonlySet<string>} retryOn
168
+ * @property {'none' | 'paths' | 'full'} details
169
+ */
170
+ /**
171
+ * Prepare an operation for the assembler.
172
+ * @param {CompiledOperation} op
173
+ * @returns {OutcomeRoute}
174
+ */
175
+ export declare function prepareOutcomeRoute(op: CompiledOperation): OutcomeRoute;
176
+ /**
177
+ * A meta object in its fixed member order.
178
+ * @param {string} op
179
+ * @param {unknown} attempt
180
+ * @param {string | null} trace
181
+ * @returns {OutcomeMeta}
182
+ */
183
+ export declare function makeMeta(op: string, attempt: unknown, trace: string | null): OutcomeMeta;
184
+ /**
185
+ * @param {unknown} value
186
+ * @param {OutcomeMeta} meta
187
+ * @returns {OkOutcome}
188
+ */
189
+ export declare function okOutcome(value: unknown, meta: OutcomeMeta): OkOutcome;
190
+ /**
191
+ * @param {'failure' | 'network' | 'contract' | 'cancelled'} kind
192
+ * @param {OutcomeError} error
193
+ * @param {OutcomeMeta} meta
194
+ * @returns {FailedOutcome}
195
+ */
196
+ export declare function failedOutcome(kind: 'failure' | 'network' | 'contract' | 'cancelled', error: OutcomeError, meta: OutcomeMeta): FailedOutcome;
197
+ /**
198
+ * An error object in its fixed member order.
199
+ * @param {string} code
200
+ * @param {string} message
201
+ * @param {number | null} status
202
+ * @param {unknown} details
203
+ * @param {boolean} retryable
204
+ * @returns {OutcomeError}
205
+ */
206
+ export declare function outcomeError(code: string, message: string, status: number | null, details: unknown, retryable: boolean): OutcomeError;
207
+ /**
208
+ * A client-originated error (`JC205x`): message from the catalog, the
209
+ * row's `retryable` unless overridden.
210
+ * @param {Catalog | null} catalog
211
+ * @param {keyof typeof CLIENT_ERRORS} code
212
+ * @param {Record<string, unknown>} params
213
+ * @param {number | null} status
214
+ * @param {unknown} details
215
+ * @param {boolean} [retryable]
216
+ * @returns {OutcomeError}
217
+ */
218
+ export declare function clientError(catalog: Catalog | null, code: keyof typeof CLIENT_ERRORS, params: Record<string, unknown>, status: number | null, details: unknown, retryable?: boolean): OutcomeError;
219
+ /**
220
+ * True for a value shaped like an outcome: a plain object with a
221
+ * boolean `ok`, a `meta` carrying every D6 meta member, and, when
222
+ * failed, a `kind` and an `error` carrying every D6 error member with a
223
+ * string `code` — no member `undefined`. Reads guardedly, so a hostile
224
+ * value classifies as "not an outcome".
225
+ * @param {unknown} value
226
+ * @returns {value is Outcome}
227
+ */
228
+ export declare function isOutcome(value: unknown): value is Outcome;
229
+ export type WireMessage = {
230
+ status: number | null;
231
+ headers: Readonly<Record<string, string>> | null;
232
+ text?: string | null;
233
+ value?: unknown;
234
+ error?: unknown;
235
+ };
236
+ /**
237
+ * What a wire answered, as the assembler reads it. Exactly one of `text`,
238
+ * `value` or `error` carries the body: `text` is the raw body of an HTTP
239
+ * response (parsed here; `''`/`null` is an empty body), `value` a
240
+ * success value already decoded by a JSON-framed binding, `error` an
241
+ * error envelope (`{ code, message?, details?, retryable? }`) already
242
+ * decoded by such a binding. `status` is the HTTP status or `null` on a
243
+ * binding that carries none; `headers` holds `etag` when the binding
244
+ * carries entity tags.
245
+ * @typedef {Object} WireMessage
246
+ * @property {number | null} status
247
+ * @property {Readonly<Record<string, string>> | null} headers
248
+ * @property {string | null} [text]
249
+ * @property {unknown} [value]
250
+ * @property {unknown} [error]
251
+ */
252
+ /**
253
+ * Assemble the outcome of one response. TOTAL: every shape the peer can
254
+ * answer classifies; nothing throws.
255
+ *
256
+ * - a 2xx (or `status: null` with a `value`): an empty body is `null`;
257
+ * otherwise the text is parsed (`JC2053` when not JSON) and the value
258
+ * validated against the output schema (`JC2053` with details by
259
+ * `policy.errors.details`) → `{ ok: true, value, meta }` with `meta.etag`
260
+ * from the header;
261
+ * - a 304 → `{ ok: true, value: null, meta }` with `notModified: true`
262
+ * and the `etag`;
263
+ * - any other status: the body is parsed as JSON; a string `code` that
264
+ * the operation declares, or a `JC2xxx` taxonomy code, is `kind:
265
+ * "failure"` with `{ code, message, status, details?, retryable }` —
266
+ * the body's `message` when it is a string, else rendered; `retryable`
267
+ * the body's boolean, else whether `policy.retry.on` names the code;
268
+ * anything else (a non-JSON body, no string code, an unknown code) is
269
+ * `kind: "contract"` `JC2055` with the status kept and `retryable`
270
+ * for 5xx/429.
271
+ *
272
+ * @param {OutcomeRoute} route
273
+ * @param {WireMessage} message
274
+ * @param {OutcomeMeta} meta - mutated: `etag`/`notModified` are set here
275
+ * @param {Catalog | null} catalog
276
+ * @returns {Outcome}
277
+ */
278
+ export declare function assembleOutcome(route: OutcomeRoute, message: WireMessage, meta: OutcomeMeta, catalog: Catalog | null): Outcome;
279
+ /**
280
+ * The outcome a handler projects a THROWN host value into when it must
281
+ * settle with an outcome (the app effect): `kind: "contract"` `JC2058`,
282
+ * the message from the catalog, nothing of the thrown value (its text
283
+ * may carry anything).
284
+ * @param {string} op
285
+ * @param {unknown} attempt
286
+ * @param {Catalog | null} catalog
287
+ * @returns {Outcome}
288
+ */
289
+ export declare function hostFailureOutcome(op: string, attempt: unknown, catalog: Catalog | null): Outcome;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * @file Version compatibility — the one implementation of the
3
+ * negotiation rule (docs/CONTRACT-FORMAT.md §10.4, §13.1): two ends
4
+ * speak when they declare the same `version`, or when either end's
5
+ * `compat` list names the other's `version`. The client's `negotiate()`
6
+ * and any server that wants to refuse an incompatible peer both call
7
+ * this; `diffContracts` is the complementary question (WHAT changed),
8
+ * this is the declared answer (do the authors CLAIM the ends speak).
9
+ */
10
+ export type VersionedContract = {
11
+ version?: string | null;
12
+ compat?: readonly string[] | null;
13
+ };
14
+ /**
15
+ * Why two ends are compatible, or `null` when they are not:
16
+ * `'same-version'` (equal `version`s — two unversioned contracts included),
17
+ * `'server-accepts'` (the server's `compat` names the client's version),
18
+ * `'client-accepts'` (the client's `compat` names the server's version).
19
+ * Checked in that order, so the strongest claim wins the reason.
20
+ * @param {VersionedContract} client
21
+ * @param {VersionedContract} server
22
+ * @returns {'same-version' | 'server-accepts' | 'client-accepts' | null}
23
+ */
24
+ export declare function compatReason(client: VersionedContract, server: VersionedContract): 'same-version' | 'server-accepts' | 'client-accepts' | null;
25
+ /**
26
+ * Whether a client contract and a server contract declare themselves
27
+ * compatible — the negotiation rule as a predicate. Takes compiled
28
+ * contracts, raw documents or well-known descriptions alike (it reads
29
+ * only `version` and `compat`).
30
+ * @param {VersionedContract} clientContract
31
+ * @param {VersionedContract} serverContract
32
+ * @returns {boolean}
33
+ * @example
34
+ * isCompatible({ version: '5', compat: ['4'] }, { version: '4' }); // true — the client accepts 4
35
+ */
36
+ export declare function isCompatible(clientContract: VersionedContract, serverContract: VersionedContract): boolean;
@@ -0,0 +1,196 @@
1
+ /**
2
+ * @file `compileContract`: a `$contract` document (docs/CONTRACT-FORMAT.md
3
+ * §2) becomes a frozen `Contract` — per-operation validators, transport
4
+ * normalizers, the materialized HTTP binding with every default resolved,
5
+ * and one path matcher over the operation table.
6
+ *
7
+ * Two stages, like every compiler in the suite: everything is decided
8
+ * here, once; nothing that runs per request allocates or re-reads the
9
+ * document. The document is TRUSTED input but is still read totally: it
10
+ * is first snapshotted through guarded access into plain JSON (a
11
+ * throwing accessor, a non-JSON member or a cycle is `JC0001` at the
12
+ * member), then validated rule by rule against a CLOSED vocabulary
13
+ * (`JC0013` — a silently ignored `policy` member is a behavior bug), and
14
+ * only then compiled. Every refusal is a `ContractCompileError` with the
15
+ * `docPath` of the member at fault.
16
+ *
17
+ * `$ref` resolution rides the validator: the document is registered under
18
+ * a synthetic id and each operation schema is compiled as a `$ref` into
19
+ * it, so `#/$defs/Product` means the contract's own `$defs` and an
20
+ * absolute `$id` means one of `options.schemas`. Unresolved is `JC0007`
21
+ * at compile — never at request time.
22
+ */
23
+ import { JarenValidator } from '@jarenjs/validate';
24
+ export type RefScope = {
25
+ /**
26
+ * - the snapshotted document
27
+ */
28
+ src: any;
29
+ anchors: Map<string, object>;
30
+ validator: JarenValidator<any>;
31
+ };
32
+ export type CompiledValidate = (value: unknown) => any;
33
+ export type InputTransport = {
34
+ normalize: (value: any) => any;
35
+ members: {
36
+ path: readonly string[];
37
+ query: readonly string[];
38
+ header: readonly string[];
39
+ repeated: readonly string[];
40
+ };
41
+ schemas: Readonly<Record<string, any>>;
42
+ required: readonly string[];
43
+ };
44
+ export type CompiledInput = {
45
+ /**
46
+ * - the declared input schema (frozen source)
47
+ */
48
+ schema: any;
49
+ /**
50
+ * - the object schema `schema` resolves to: itself,
51
+ * or the end of its `$ref` chain (inside the document or a registered
52
+ * schema) — whose `properties` are the operation's members; frozen
53
+ */
54
+ effective: any;
55
+ validate: CompiledValidate;
56
+ /**
57
+ * - `null` when no member travels as a string
58
+ */
59
+ transport: InputTransport | null;
60
+ };
61
+ export type CompiledOutput = {
62
+ schema: any;
63
+ validate: CompiledValidate;
64
+ };
65
+ export type CompiledErrorDecl = {
66
+ status: number;
67
+ /**
68
+ * - `null` when undeclared
69
+ */
70
+ schema: any;
71
+ validate: CompiledValidate | null;
72
+ };
73
+ export type CompiledPolicy = {
74
+ task: 'switch' | 'exhaust' | 'concat' | 'parallel';
75
+ idempotency: 'none' | 'optional' | 'required';
76
+ /**
77
+ * - `input:<json-pointer>` or null
78
+ */
79
+ revision: string | null;
80
+ cache: 'none' | 'revision';
81
+ limits: {
82
+ maxBodyBytes: number;
83
+ };
84
+ errors: {
85
+ details: 'none' | 'paths' | 'full';
86
+ };
87
+ retry: {
88
+ max: number;
89
+ on: readonly string[];
90
+ } | null;
91
+ /**
92
+ * - the stream policy of a subscribe operation, defaults materialized; `null` on every other kind
93
+ */
94
+ stream: {
95
+ resume: 'snapshot' | 'replay';
96
+ heartbeatMs: number;
97
+ maxPatchBytes: number | null;
98
+ } | null;
99
+ /**
100
+ * - who may see the operation: `server`
101
+ * keeps it out of the public projection and every projection built on it
102
+ */
103
+ audience: 'public' | 'server';
104
+ };
105
+ export type CompiledHttp = {
106
+ method: string;
107
+ path: string;
108
+ template: import('./path.js').ParsedPathTemplate;
109
+ variables: readonly string[];
110
+ in: Readonly<Record<string, 'path' | 'query' | 'header' | 'body'>>;
111
+ body: string | null;
112
+ status: number;
113
+ media: string;
114
+ opaque: boolean;
115
+ };
116
+ export type CompiledOperation = {
117
+ id: string;
118
+ kind: 'read' | 'command' | 'subscribe';
119
+ doc: string | null;
120
+ input: CompiledInput | null;
121
+ output: CompiledOutput;
122
+ errors: Readonly<Record<string, CompiledErrorDecl>>;
123
+ policy: CompiledPolicy;
124
+ http: CompiledHttp;
125
+ };
126
+ export type Contract = {
127
+ /**
128
+ * - the source document, deep-frozen
129
+ */
130
+ doc: any;
131
+ id: string | null;
132
+ version: string | null;
133
+ compat: readonly string[];
134
+ operations: Readonly<Record<string, CompiledOperation>>;
135
+ /**
136
+ * - operation ids in document order
137
+ */
138
+ ids: readonly string[];
139
+ match: (method: string, path: string) => {
140
+ op: CompiledOperation;
141
+ params: Readonly<Record<string, string>>;
142
+ } | null;
143
+ /**
144
+ * - the methods under which
145
+ * this path shape reaches an operation, sorted (`[]` for none) — what a
146
+ * 405 answers in `Allow`; the path only, query split off, like `match`
147
+ */
148
+ allowed: (path: string) => string[];
149
+ /**
150
+ * - a pure-JSON summary (docs/CONTRACT-FORMAT.md §3)
151
+ */
152
+ describe: () => any;
153
+ /**
154
+ * - the SHA-256 (lowercase hex)
155
+ * over the RFC 8785 canonical bytes of the public projection, memoized —
156
+ * computed at most once per compiled contract (docs/CONTRACT-FORMAT.md §14)
157
+ */
158
+ revision: () => Promise<string>;
159
+ /**
160
+ * - frozen view of the document's `$defs` (`{}` when absent)
161
+ */
162
+ $defs: any;
163
+ };
164
+ export type CompileContractOptions = {
165
+ /**
166
+ * - the validator every schema
167
+ * compiles through; default `new JarenValidator({ collectErrors: true, skipErrors: false })`
168
+ */
169
+ validator?: JarenValidator<any>;
170
+ /**
171
+ * - schemas registered by `$id` before compile, so
172
+ * an absolute `$ref` resolves
173
+ */
174
+ schemas?: Record<string, any>[];
175
+ };
176
+ /**
177
+ * Compile a `$contract` document (docs/CONTRACT-FORMAT.md) into a frozen
178
+ * `Contract`: every operation's `input`/`output`/error validators, its
179
+ * transport normalizer, its resolved policy and materialized HTTP
180
+ * binding, and one `match(method, path)` over the whole table.
181
+ * Synchronous; total for a hostile document; no I/O.
182
+ *
183
+ * @param {unknown} doc - the contract document
184
+ * @param {CompileContractOptions} [options]
185
+ * @returns {Contract}
186
+ * @throws {ContractCompileError} when the document violates the format (`JC0001–JC0017`)
187
+ * @example
188
+ * const contract = compileContract({
189
+ * $contract: '0.1',
190
+ * operations: {
191
+ * 'catalog.load': { kind: 'read', output: true, http: { method: 'GET', path: '/api/catalog' } },
192
+ * },
193
+ * });
194
+ * contract.match('GET', '/api/catalog').op.id; // 'catalog.load'
195
+ */
196
+ export declare function compileContract(doc: unknown, options?: CompileContractOptions): Contract;
@@ -0,0 +1,115 @@
1
+ /**
2
+ * @file `describe()`: a compiled contract as pure JSON — the resolved
3
+ * binding and policy of every operation, with each defaulted value
4
+ * marked in `inferred` so a projection can tell what the author
5
+ * declared from what the compiler filled in. Stable member order, no
6
+ * functions, no schemas (those stay on the compiled operations); this is
7
+ * what a CLI prints and what a golden test compares. `revision` is read
8
+ * synchronously from the memo (`peekRevision`), so it is `null` until
9
+ * someone awaited `contract.revision()` — the well-known responder does
10
+ * exactly that before it renders this document.
11
+ */
12
+ export type OperationDescription = {
13
+ id: string;
14
+ kind: 'read' | 'command';
15
+ method: string;
16
+ /**
17
+ * - the canonical `{name}` template
18
+ */
19
+ path: string;
20
+ status: number;
21
+ media: string;
22
+ opaque: boolean;
23
+ /**
24
+ * - member → location
25
+ */
26
+ in: Readonly<Record<string, string>>;
27
+ /**
28
+ * - the whole-body member, or null
29
+ */
30
+ body: string | null;
31
+ task: string;
32
+ idempotency: string;
33
+ cache: string;
34
+ /**
35
+ * - the resolved stream policy; present exactly on subscribe operations
36
+ */
37
+ stream?: {
38
+ resume: string;
39
+ heartbeatMs: number;
40
+ maxPatchBytes: number | null;
41
+ };
42
+ /**
43
+ * which of the above the compiler defaulted: `http` when the whole
44
+ * binding is the canonical `POST /<id>`, `in` listing the members whose
45
+ * location was not declared
46
+ */
47
+ inferred: {
48
+ http: boolean;
49
+ status: boolean;
50
+ media: boolean;
51
+ in: readonly string[];
52
+ task: boolean;
53
+ idempotency: boolean;
54
+ cache: boolean;
55
+ };
56
+ };
57
+ export type ContractDescription = {
58
+ $contract: '0.1';
59
+ id: string | null;
60
+ version: string | null;
61
+ compat: readonly string[];
62
+ /**
63
+ * - the contract revision (64 lowercase
64
+ * hex; docs/CONTRACT-FORMAT.md §14) when `contract.revision()` has
65
+ * settled, `null` before — `describe()` stays synchronous and never
66
+ * computes it
67
+ */
68
+ revision: string | null;
69
+ /**
70
+ * - document order
71
+ */
72
+ operations: OperationDescription[];
73
+ };
74
+ /**
75
+ * The description of one operation.
76
+ * @typedef {Object} OperationDescription
77
+ * @property {string} id
78
+ * @property {'read' | 'command'} kind
79
+ * @property {string} method
80
+ * @property {string} path - the canonical `{name}` template
81
+ * @property {number} status
82
+ * @property {string} media
83
+ * @property {boolean} opaque
84
+ * @property {Readonly<Record<string, string>>} in - member → location
85
+ * @property {string | null} body - the whole-body member, or null
86
+ * @property {string} task
87
+ * @property {string} idempotency
88
+ * @property {string} cache
89
+ * @property {{ resume: string, heartbeatMs: number, maxPatchBytes: number | null }} [stream]
90
+ * - the resolved stream policy; present exactly on subscribe operations
91
+ * @property {{ http: boolean, status: boolean, media: boolean, in: readonly string[], task: boolean, idempotency: boolean, cache: boolean }} inferred
92
+ * which of the above the compiler defaulted: `http` when the whole
93
+ * binding is the canonical `POST /<id>`, `in` listing the members whose
94
+ * location was not declared
95
+ */
96
+ /**
97
+ * The description of a contract.
98
+ * @typedef {Object} ContractDescription
99
+ * @property {'0.1'} $contract
100
+ * @property {string | null} id
101
+ * @property {string | null} version
102
+ * @property {readonly string[]} compat
103
+ * @property {string | null} revision - the contract revision (64 lowercase
104
+ * hex; docs/CONTRACT-FORMAT.md §14) when `contract.revision()` has
105
+ * settled, `null` before — `describe()` stays synchronous and never
106
+ * computes it
107
+ * @property {OperationDescription[]} operations - document order
108
+ */
109
+ /**
110
+ * Describe a compiled contract. Reads the compiled operations for the
111
+ * resolved values and the frozen source document for what was declared.
112
+ * @param {import('./compile.js').Contract} contract
113
+ * @returns {ContractDescription}
114
+ */
115
+ export declare function describeContract(contract: import('./compile.js').Contract): ContractDescription;