@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.
- package/README.md +508 -0
- package/dist/types/adapters/fetch.d.ts +27 -0
- package/dist/types/adapters/node.d.ts +47 -0
- package/dist/types/app/binding.d.ts +122 -0
- package/dist/types/app/effect.d.ts +77 -0
- package/dist/types/app/index.d.ts +31 -0
- package/dist/types/app/subscription.d.ts +82 -0
- package/dist/types/bundle.d.ts +43 -0
- package/dist/types/cli.d.ts +15 -0
- package/dist/types/client/http.d.ts +242 -0
- package/dist/types/client/outcome.d.ts +289 -0
- package/dist/types/compat.d.ts +36 -0
- package/dist/types/compile.d.ts +196 -0
- package/dist/types/describe.d.ts +115 -0
- package/dist/types/diff.d.ts +91 -0
- package/dist/types/errors.d.ts +205 -0
- package/dist/types/http/dispatch.d.ts +148 -0
- package/dist/types/http/serve.d.ts +154 -0
- package/dist/types/http/wire.d.ts +334 -0
- package/dist/types/index.d.ts +39 -0
- package/dist/types/ledger.d.ts +207 -0
- package/dist/types/local/index.d.ts +127 -0
- package/dist/types/messages.d.ts +63 -0
- package/dist/types/path.d.ts +119 -0
- package/dist/types/pipeline.d.ts +157 -0
- package/dist/types/port/client.d.ts +142 -0
- package/dist/types/port/frame.d.ts +195 -0
- package/dist/types/port/serve.d.ts +102 -0
- package/dist/types/project/index.d.ts +34 -0
- package/dist/types/project/markdown.d.ts +28 -0
- package/dist/types/project/openapi.d.ts +102 -0
- package/dist/types/project/tools.d.ts +57 -0
- package/dist/types/project/typescript.d.ts +59 -0
- package/dist/types/public.d.ts +73 -0
- package/dist/types/revision.d.ts +36 -0
- package/dist/types/stream/client.d.ts +104 -0
- package/dist/types/stream/server.d.ts +106 -0
- package/dist/types/stream/sse.d.ts +62 -0
- package/docs/APP-INTEGRATION.md +301 -0
- package/docs/CONTRACT-FORMAT.md +1923 -0
- package/package.json +110 -0
- package/schemas/jaren-contract-port.draft-07.schema.json +241 -0
- package/schemas/jaren-contract-port.schema.json +241 -0
- package/schemas/jaren-contract.draft-07.schema.json +287 -0
- package/schemas/jaren-contract.schema.json +287 -0
- package/src/adapters/fetch.js +109 -0
- package/src/adapters/node.js +238 -0
- package/src/app/binding.js +426 -0
- package/src/app/effect.js +190 -0
- package/src/app/index.js +26 -0
- package/src/app/subscription.js +130 -0
- package/src/bundle.js +168 -0
- package/src/cli.js +264 -0
- package/src/client/http.js +1150 -0
- package/src/client/outcome.js +364 -0
- package/src/compat.js +62 -0
- package/src/compile.js +1162 -0
- package/src/describe.js +109 -0
- package/src/diff.js +610 -0
- package/src/errors.js +236 -0
- package/src/http/dispatch.js +1054 -0
- package/src/http/serve.js +301 -0
- package/src/http/wire.js +469 -0
- package/src/index.js +33 -0
- package/src/ledger.js +225 -0
- package/src/local/index.js +363 -0
- package/src/messages.js +68 -0
- package/src/path.js +471 -0
- package/src/pipeline.js +241 -0
- package/src/port/client.js +518 -0
- package/src/port/frame.js +196 -0
- package/src/port/serve.js +442 -0
- package/src/project/index.js +29 -0
- package/src/project/markdown.js +244 -0
- package/src/project/openapi.js +564 -0
- package/src/project/openapi.jslt.json +149 -0
- package/src/project/tools.js +139 -0
- package/src/project/typescript.js +152 -0
- package/src/project/typescript.jtlt.json +72 -0
- package/src/public.js +206 -0
- package/src/revision.js +90 -0
- package/src/stream/client.js +212 -0
- package/src/stream/server.js +306 -0
- package/src/stream/sse.js +67 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The frame grammar of the port binding (docs/CONTRACT-FORMAT.md
|
|
3
|
+
* §16), shared by `servePort` and `openPortClient`: JSON-safe plain
|
|
4
|
+
* objects marked `jaren: "contract/0.1"`, so contract traffic is
|
|
5
|
+
* distinguishable BY SHAPE from anything else on a shared channel — a
|
|
6
|
+
* frame without the marker is never touched, and never answered.
|
|
7
|
+
*
|
|
8
|
+
* Request ids are `"<clientId>:<seq>"` — the client id a UUID per
|
|
9
|
+
* client instance, the sequence a per-client counter — so two clients
|
|
10
|
+
* on one broadcast channel can never collide, and a client recognizes
|
|
11
|
+
* its own responses with one cheap prefix test before any map lookup.
|
|
12
|
+
* The request members `attempt` and `key` are reserved by the grammar
|
|
13
|
+
* (`schemas/jaren-contract-port.schema.json`) and ignored by this
|
|
14
|
+
* order's server: the attempt id stays caller-side in `meta` (D6 — the
|
|
15
|
+
* identities live in state, never in the transport) and idempotency is
|
|
16
|
+
* not carried on this binding (`capabilities` says so).
|
|
17
|
+
*/
|
|
18
|
+
/** The frame marker: the protocol and its version. */
|
|
19
|
+
export declare const FRAME_MARKER = "contract/0.1";
|
|
20
|
+
export type RequestFrame = {
|
|
21
|
+
jaren: string;
|
|
22
|
+
id: string;
|
|
23
|
+
op: string;
|
|
24
|
+
input: unknown;
|
|
25
|
+
};
|
|
26
|
+
export type ResponseFrame = {
|
|
27
|
+
jaren: string;
|
|
28
|
+
id: string;
|
|
29
|
+
ok: true;
|
|
30
|
+
value: unknown;
|
|
31
|
+
trace: string;
|
|
32
|
+
} | {
|
|
33
|
+
jaren: string;
|
|
34
|
+
id: string;
|
|
35
|
+
ok: false;
|
|
36
|
+
error: {
|
|
37
|
+
code: string;
|
|
38
|
+
message: string;
|
|
39
|
+
details?: unknown;
|
|
40
|
+
retryable?: boolean;
|
|
41
|
+
};
|
|
42
|
+
trace: string;
|
|
43
|
+
};
|
|
44
|
+
export type CancelFrame = {
|
|
45
|
+
jaren: string;
|
|
46
|
+
cancel: string;
|
|
47
|
+
};
|
|
48
|
+
export type SubscribeFrame = {
|
|
49
|
+
jaren: string;
|
|
50
|
+
subscribe: string;
|
|
51
|
+
op: string;
|
|
52
|
+
input: unknown;
|
|
53
|
+
lastSeq?: number;
|
|
54
|
+
};
|
|
55
|
+
export type UnsubscribeFrame = {
|
|
56
|
+
jaren: string;
|
|
57
|
+
unsubscribe: string;
|
|
58
|
+
};
|
|
59
|
+
export type PushFrame = {
|
|
60
|
+
jaren: string;
|
|
61
|
+
id: string;
|
|
62
|
+
event: string;
|
|
63
|
+
seq: number;
|
|
64
|
+
data: unknown;
|
|
65
|
+
};
|
|
66
|
+
export type ChannelLike = {
|
|
67
|
+
postMessage: (message: any) => void;
|
|
68
|
+
addEventListener?: (type: string, listener: (event: any) => void) => void;
|
|
69
|
+
removeEventListener?: (type: string, listener: (event: any) => void) => void;
|
|
70
|
+
onmessage?: ((event: any) => void) | null;
|
|
71
|
+
start?: () => void;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* A request frame. `input` is the whole (validated) input object, or
|
|
75
|
+
* `null` for an input-less operation.
|
|
76
|
+
* @typedef {{ jaren: string, id: string, op: string, input: unknown }} RequestFrame
|
|
77
|
+
*/
|
|
78
|
+
/**
|
|
79
|
+
* A response frame: success with the value, or an error envelope —
|
|
80
|
+
* `code` a declared error code or a `JC2xxx` taxonomy/binding code,
|
|
81
|
+
* `details` present only when the failure carries some. Every response
|
|
82
|
+
* carries the server's `trace`.
|
|
83
|
+
* @typedef {{ jaren: string, id: string, ok: true, value: unknown, trace: string }
|
|
84
|
+
* | { jaren: string, id: string, ok: false, error: { code: string, message: string, details?: unknown, retryable?: boolean }, trace: string }} ResponseFrame
|
|
85
|
+
*/
|
|
86
|
+
/**
|
|
87
|
+
* A cancel frame: aborts the server-side signal of the named request —
|
|
88
|
+
* an optimization; the client's id scoping is the guarantee. Never
|
|
89
|
+
* answered.
|
|
90
|
+
* @typedef {{ jaren: string, cancel: string }} CancelFrame
|
|
91
|
+
*/
|
|
92
|
+
/**
|
|
93
|
+
* A subscribe frame: opens one stream (docs/CONTRACT-FORMAT.md §18.2).
|
|
94
|
+
* `input` is the whole (validated) input object or `null`; `lastSeq`,
|
|
95
|
+
* when present, asks to resume after that seq.
|
|
96
|
+
* @typedef {{ jaren: string, subscribe: string, op: string, input: unknown, lastSeq?: number }} SubscribeFrame
|
|
97
|
+
*/
|
|
98
|
+
/**
|
|
99
|
+
* An unsubscribe frame: closes the named stream. Never answered.
|
|
100
|
+
* @typedef {{ jaren: string, unsubscribe: string }} UnsubscribeFrame
|
|
101
|
+
*/
|
|
102
|
+
/**
|
|
103
|
+
* A push frame: one stream event, server → client. `event` is
|
|
104
|
+
* `snapshot | patch | error | end` with the §18 data shapes; `seq` is
|
|
105
|
+
* the event's seq (`error`/`end` carry the last delivered one).
|
|
106
|
+
* @typedef {{ jaren: string, id: string, event: string, seq: number, data: unknown }} PushFrame
|
|
107
|
+
*/
|
|
108
|
+
/**
|
|
109
|
+
* The channel shape both halves accept: a `MessagePort`, a `Worker`, a
|
|
110
|
+
* `BroadcastChannel`, a worker's own `self`, or any object with
|
|
111
|
+
* `postMessage` and a message-listener surface. `start` is called when
|
|
112
|
+
* present (a `MessagePort` queues until it is).
|
|
113
|
+
* @typedef {{ postMessage: (message: any) => void,
|
|
114
|
+
* addEventListener?: (type: string, listener: (event: any) => void) => void,
|
|
115
|
+
* removeEventListener?: (type: string, listener: (event: any) => void) => void,
|
|
116
|
+
* onmessage?: ((event: any) => void) | null,
|
|
117
|
+
* start?: () => void }} ChannelLike
|
|
118
|
+
*/
|
|
119
|
+
/**
|
|
120
|
+
* Whether a value is a contract frame at all — the marker test every
|
|
121
|
+
* listener applies first, so foreign traffic on a shared channel is
|
|
122
|
+
* ignored by shape. Reads one member of a plain object; total.
|
|
123
|
+
* @param {unknown} value
|
|
124
|
+
* @returns {value is { jaren: string }}
|
|
125
|
+
*/
|
|
126
|
+
export declare function isContractFrame(value: unknown): value is {
|
|
127
|
+
jaren: string;
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* @param {string} id
|
|
131
|
+
* @param {string} op
|
|
132
|
+
* @param {unknown} input
|
|
133
|
+
* @returns {RequestFrame}
|
|
134
|
+
*/
|
|
135
|
+
export declare function requestFrame(id: string, op: string, input: unknown): RequestFrame;
|
|
136
|
+
/**
|
|
137
|
+
* @param {string} id
|
|
138
|
+
* @param {unknown} value - `undefined` is carried as `null` (frames are JSON)
|
|
139
|
+
* @param {string} trace
|
|
140
|
+
* @returns {ResponseFrame}
|
|
141
|
+
*/
|
|
142
|
+
export declare function valueFrame(id: string, value: unknown, trace: string): ResponseFrame;
|
|
143
|
+
/**
|
|
144
|
+
* @param {string} id
|
|
145
|
+
* @param {string} code
|
|
146
|
+
* @param {string} message
|
|
147
|
+
* @param {unknown} details - omitted from the frame when `undefined`
|
|
148
|
+
* @param {boolean} retryable
|
|
149
|
+
* @param {string} trace
|
|
150
|
+
* @returns {ResponseFrame}
|
|
151
|
+
*/
|
|
152
|
+
export declare function errorFrame(id: string, code: string, message: string, details: unknown, retryable: boolean, trace: string): ResponseFrame;
|
|
153
|
+
/**
|
|
154
|
+
* @param {string} id
|
|
155
|
+
* @returns {CancelFrame}
|
|
156
|
+
*/
|
|
157
|
+
export declare function cancelFrame(id: string): CancelFrame;
|
|
158
|
+
/**
|
|
159
|
+
* @param {string} id
|
|
160
|
+
* @param {string} op
|
|
161
|
+
* @param {unknown} input
|
|
162
|
+
* @param {number | null} lastSeq - omitted from the frame when `null`
|
|
163
|
+
* @returns {SubscribeFrame}
|
|
164
|
+
*/
|
|
165
|
+
export declare function subscribeFrame(id: string, op: string, input: unknown, lastSeq: number | null): SubscribeFrame;
|
|
166
|
+
/**
|
|
167
|
+
* @param {string} id
|
|
168
|
+
* @returns {UnsubscribeFrame}
|
|
169
|
+
*/
|
|
170
|
+
export declare function unsubscribeFrame(id: string): UnsubscribeFrame;
|
|
171
|
+
/**
|
|
172
|
+
* @param {string} id
|
|
173
|
+
* @param {string} event - `snapshot | patch | error | end`
|
|
174
|
+
* @param {number} seq
|
|
175
|
+
* @param {unknown} data
|
|
176
|
+
* @returns {PushFrame}
|
|
177
|
+
*/
|
|
178
|
+
export declare function pushFrame(id: string, event: string, seq: number, data: unknown): PushFrame;
|
|
179
|
+
/**
|
|
180
|
+
* Attach a message listener to a channel: `addEventListener` where the
|
|
181
|
+
* channel has one (plus `start()`, which a `MessagePort` needs to begin
|
|
182
|
+
* delivery), the `onmessage` slot otherwise. Returns the detacher.
|
|
183
|
+
* @param {ChannelLike} channel
|
|
184
|
+
* @param {(event: any) => void} listener
|
|
185
|
+
* @returns {() => void}
|
|
186
|
+
*/
|
|
187
|
+
export declare function attach(channel: ChannelLike, listener: (event: any) => void): () => void;
|
|
188
|
+
/**
|
|
189
|
+
* Whether a value is a usable channel. The listener surface is either
|
|
190
|
+
* `addEventListener` or an assignable `onmessage` slot — `in` sees the
|
|
191
|
+
* slot on platform channels and plain fakes alike.
|
|
192
|
+
* @param {unknown} value
|
|
193
|
+
* @returns {value is ChannelLike}
|
|
194
|
+
*/
|
|
195
|
+
export declare function isChannel(value: unknown): value is ChannelLike;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file `servePort(contract, handlers, options)`: the server half of the
|
|
3
|
+
* port binding (docs/CONTRACT-FORMAT.md §16) — request/response over a
|
|
4
|
+
* `MessagePort`, a `Worker`, a `BroadcastChannel` or a worker's own
|
|
5
|
+
* `self`. Requests run through the same neutral pipeline as every other
|
|
6
|
+
* server binding, under one `AbortController` per request id; a cancel
|
|
7
|
+
* frame aborts it (an optimization — the client's id scoping is the
|
|
8
|
+
* guarantee), and the response is posted back on the same channel.
|
|
9
|
+
*
|
|
10
|
+
* The listener touches nothing without the frame marker — other traffic
|
|
11
|
+
* may share the channel and is never answered — and never answers a
|
|
12
|
+
* RESPONSE frame either, so two ends on one broadcast channel cannot
|
|
13
|
+
* echo at each other. A request naming an operation this channel does
|
|
14
|
+
* not serve (unknown, or opaque — a port carries JSON only) is answered
|
|
15
|
+
* `JC2071` without echoing what it asked for; an invalid input is
|
|
16
|
+
* `JC2006` with details by the operation's policy; a handler fault of
|
|
17
|
+
* any class is `JC2070` with the cause reported to `onError`, never
|
|
18
|
+
* onto the wire. Exactly ONE server should serve a shared channel — two
|
|
19
|
+
* would both answer every request.
|
|
20
|
+
*/
|
|
21
|
+
import { PORT_LOCAL_ERRORS } from '../pipeline.js';
|
|
22
|
+
export { openPortClient } from './client.js';
|
|
23
|
+
export { FRAME_MARKER, isContractFrame } from './frame.js';
|
|
24
|
+
export { PORT_LOCAL_ERRORS };
|
|
25
|
+
export type Contract = import('../compile.js').Contract;
|
|
26
|
+
export type CompiledOperation = import('../compile.js').CompiledOperation;
|
|
27
|
+
export type Catalog = import('../http/wire.js').Catalog;
|
|
28
|
+
export type Handler = import('../http/dispatch.js').Handler;
|
|
29
|
+
export type PipelineRoute = import('../pipeline.js').PipelineRoute;
|
|
30
|
+
export type ChannelLike = import('./frame.js').ChannelLike;
|
|
31
|
+
export type ServePortOptions = {
|
|
32
|
+
/**
|
|
33
|
+
* - the channel to serve (required)
|
|
34
|
+
*/
|
|
35
|
+
channel: ChannelLike;
|
|
36
|
+
/**
|
|
37
|
+
* - the server trace generator; default `crypto.randomUUID`
|
|
38
|
+
*/
|
|
39
|
+
trace?: () => string;
|
|
40
|
+
/**
|
|
41
|
+
* - `'never'` is a declared
|
|
42
|
+
* downgrade, reported in `capabilities.validatedOutput`
|
|
43
|
+
*/
|
|
44
|
+
validateOutput?: 'always' | 'never';
|
|
45
|
+
/**
|
|
46
|
+
* - a message catalog consulted before the English one
|
|
47
|
+
*/
|
|
48
|
+
catalog?: Record<string, string | ((params: object) => string)>;
|
|
49
|
+
/**
|
|
50
|
+
* - observes the cause behind every `JC2070` frame, validator throws
|
|
51
|
+
* and a channel whose `postMessage` throws
|
|
52
|
+
*/
|
|
53
|
+
onError?: (error: unknown, ctx: {
|
|
54
|
+
op: string;
|
|
55
|
+
trace: string;
|
|
56
|
+
} | null) => void;
|
|
57
|
+
};
|
|
58
|
+
export type PortServerCapabilities = {
|
|
59
|
+
name: 'port';
|
|
60
|
+
status: false;
|
|
61
|
+
headers: false;
|
|
62
|
+
media: false;
|
|
63
|
+
etag: false;
|
|
64
|
+
idempotency: false;
|
|
65
|
+
validatedOutput: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* - subscribe operations stream as push frames (docs/CONTRACT-FORMAT.md §18.2)
|
|
68
|
+
*/
|
|
69
|
+
stream: true;
|
|
70
|
+
cancel: 'message';
|
|
71
|
+
};
|
|
72
|
+
export type PortServer = {
|
|
73
|
+
capabilities: PortServerCapabilities;
|
|
74
|
+
contract: Contract;
|
|
75
|
+
describe: () => any;
|
|
76
|
+
/**
|
|
77
|
+
* - ends every live stream with `end`
|
|
78
|
+
* (`server-shutdown`), detaches the listener and aborts every in-flight request
|
|
79
|
+
*/
|
|
80
|
+
close: () => void;
|
|
81
|
+
};
|
|
82
|
+
export type PortRoute = PipelineRoute & {
|
|
83
|
+
stream: boolean;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Serve a compiled contract over a message channel. Construction refuses
|
|
87
|
+
* host mistakes (`JC1001` handler table, channel or option, `JC1002`
|
|
88
|
+
* missing handler — an opaque operation is exempt, it is answered
|
|
89
|
+
* `JC2071` instead of served).
|
|
90
|
+
*
|
|
91
|
+
* @param {Contract} contract
|
|
92
|
+
* @param {Record<string, Handler>} handlers - operation id → handler
|
|
93
|
+
* @param {ServePortOptions} options
|
|
94
|
+
* @returns {PortServer}
|
|
95
|
+
* @throws {ContractHostError}
|
|
96
|
+
* @example
|
|
97
|
+
* // inside a worker: serve the worker's own channel
|
|
98
|
+
* servePort(contract, handlers, { channel: self });
|
|
99
|
+
* // and the same handlers to every tab on a BroadcastChannel
|
|
100
|
+
* servePort(contract, handlers, { channel: new BroadcastChannel('app') });
|
|
101
|
+
*/
|
|
102
|
+
export declare function servePort(contract: Contract, handlers: Record<string, Handler>, options: ServePortOptions): PortServer;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The projections of a compiled contract (`@jarenjs/contract/project`):
|
|
3
|
+
* `publicProjection` (the browser-safe subset — itself a `$contract`
|
|
4
|
+
* document, and what the revision hashes), `toOpenApi` (OpenAPI 3.1
|
|
5
|
+
* through a JSLT stylesheet), `toTypeScript` and `toMarkdown` (on
|
|
6
|
+
* `@jarenjs/emit`'s type model), `contractTools` (`@jarenjs/ai` tool
|
|
7
|
+
* definitions, no import edge) and the same-document bundler they share.
|
|
8
|
+
* This is the one subpath that imports `@jarenjs/emit`; a consumer that
|
|
9
|
+
* never imports it never loads it.
|
|
10
|
+
*/
|
|
11
|
+
export { publicProjection } from '../public.js';
|
|
12
|
+
export { toOpenApi } from './openapi.js';
|
|
13
|
+
export { toTypeScript } from './typescript.js';
|
|
14
|
+
export { toMarkdown } from './markdown.js';
|
|
15
|
+
export { contractTools } from './tools.js';
|
|
16
|
+
export { reachableDefs, bundleSameDocument } from '../bundle.js';
|
|
17
|
+
export type PublicProjectionOptions = import('../public.js').PublicProjectionOptions;
|
|
18
|
+
export type OpenApiOptions = import('./openapi.js').OpenApiOptions;
|
|
19
|
+
export type OpenApiResult = import('./openapi.js').OpenApiResult;
|
|
20
|
+
export type DroppedKeyword = import('./openapi.js').DroppedKeyword;
|
|
21
|
+
export type TypeScriptOptions = import('./typescript.js').TypeScriptOptions;
|
|
22
|
+
export type MarkdownOptions = import('./markdown.js').MarkdownOptions;
|
|
23
|
+
export type ToolDefinition = import('./tools.js').ToolDefinition;
|
|
24
|
+
export type ContractToolsOptions = import('./tools.js').ContractToolsOptions;
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {import('../public.js').PublicProjectionOptions} PublicProjectionOptions
|
|
27
|
+
* @typedef {import('./openapi.js').OpenApiOptions} OpenApiOptions
|
|
28
|
+
* @typedef {import('./openapi.js').OpenApiResult} OpenApiResult
|
|
29
|
+
* @typedef {import('./openapi.js').DroppedKeyword} DroppedKeyword
|
|
30
|
+
* @typedef {import('./typescript.js').TypeScriptOptions} TypeScriptOptions
|
|
31
|
+
* @typedef {import('./markdown.js').MarkdownOptions} MarkdownOptions
|
|
32
|
+
* @typedef {import('./tools.js').ToolDefinition} ToolDefinition
|
|
33
|
+
* @typedef {import('./tools.js').ContractToolsOptions} ContractToolsOptions
|
|
34
|
+
*/
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file `toMarkdown`: a compiled contract as one reference document —
|
|
3
|
+
* the title and version, an operations table, one section per public
|
|
4
|
+
* operation (its doc, parameters, body, responses, errors) and the types
|
|
5
|
+
* rendered through `@jarenjs/emit`'s Markdown target over the SAME type
|
|
6
|
+
* model the TypeScript projection uses (`contractTypeModel`), so the
|
|
7
|
+
* names a reader meets in the operation sections are the names the
|
|
8
|
+
* `.d.ts` declares. The document shape is a JTLT stylesheet (compiled
|
|
9
|
+
* once at module scope); the JavaScript half derives the rows.
|
|
10
|
+
*/
|
|
11
|
+
export type Contract = import('../compile.js').Contract;
|
|
12
|
+
export type CompiledOperation = import('../compile.js').CompiledOperation;
|
|
13
|
+
export type MarkdownOptions = {
|
|
14
|
+
/**
|
|
15
|
+
* - the document title (default: the contract id, or `jaren-contract`)
|
|
16
|
+
*/
|
|
17
|
+
title?: string;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Project a compiled contract to Markdown reference documentation.
|
|
21
|
+
* @param {Contract} contract
|
|
22
|
+
* @param {MarkdownOptions} [options]
|
|
23
|
+
* @returns {string} Markdown
|
|
24
|
+
* @throws {ContractHostError} `JC1008` — not a compiled contract, or a malformed option
|
|
25
|
+
* @example
|
|
26
|
+
* writeFileSync('shop.md', toMarkdown(contract, { title: 'Shop API' }));
|
|
27
|
+
*/
|
|
28
|
+
export declare function toMarkdown(contract: Contract, options?: MarkdownOptions): string;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file `toOpenApi`: the public projection of a compiled contract as a
|
|
3
|
+
* valid, deterministic OpenAPI 3.1 document (docs/CONTRACT-FORMAT.md
|
|
4
|
+
* §12.2), in two stages that each own one concern:
|
|
5
|
+
*
|
|
6
|
+
* 1. **The keyword policy** (this file, JavaScript) walks every schema
|
|
7
|
+
* the projection carries and maps it into the OpenAPI 3.1 dialect —
|
|
8
|
+
* `#/$defs/X` → `#/components/schemas/X`, `nullable: true` → `null`
|
|
9
|
+
* in `type` — or refuses what it cannot map honestly (`JC0060`: a
|
|
10
|
+
* boolean `required`, a `components` member inside a schema, a
|
|
11
|
+
* same-document `$ref` that lands outside `$defs`) or drops and
|
|
12
|
+
* REPORTS what is Jaren-side (`$query`, `$data`, `errorMessage`,
|
|
13
|
+
* `x-form` and other `x-*`) in the `dropped` list with the `docPath`
|
|
14
|
+
* of the keyword in the contract document. Nothing unsupported passes
|
|
15
|
+
* through silently. It also derives, per operation, the facts a
|
|
16
|
+
* document cannot carry for a `$ref` input — the member table with
|
|
17
|
+
* each member's location, requiredness and effective schema — from
|
|
18
|
+
* the compiled operation.
|
|
19
|
+
* 2. **The stylesheet** (`openapi.jslt.json`, a JSLT document compiled
|
|
20
|
+
* once at module scope) shapes the document: `paths` grouped by
|
|
21
|
+
* canonical path and sorted by path then method, the operation
|
|
22
|
+
* object in a fixed member order, parameters, the request body, the
|
|
23
|
+
* success response, the declared errors grouped by status into
|
|
24
|
+
* wire-error schemas (`code` enum-pinned, `details` the declared
|
|
25
|
+
* schema), the binding's own statuses as shared
|
|
26
|
+
* `components.responses`, `components.schemas` from `$defs`, and the
|
|
27
|
+
* `x-jaren-policy` extension.
|
|
28
|
+
*
|
|
29
|
+
* Rendered twice, the document is byte-identical; a test validates it
|
|
30
|
+
* against the vendored OpenAPI 3.1 meta-schema with `JarenValidator`.
|
|
31
|
+
*/
|
|
32
|
+
export type Contract = import('../compile.js').Contract;
|
|
33
|
+
export type CompiledOperation = import('../compile.js').CompiledOperation;
|
|
34
|
+
export type DroppedKeyword = {
|
|
35
|
+
/**
|
|
36
|
+
* - JSON Pointer of the keyword in the contract document
|
|
37
|
+
*/
|
|
38
|
+
docPath: string;
|
|
39
|
+
keyword: string;
|
|
40
|
+
reason: string;
|
|
41
|
+
};
|
|
42
|
+
export type OpenApiOptions = {
|
|
43
|
+
/**
|
|
44
|
+
* -
|
|
45
|
+
* the `info` object; `title` defaults to the contract id, `version` to
|
|
46
|
+
* the contract version
|
|
47
|
+
*/
|
|
48
|
+
info?: {
|
|
49
|
+
title?: string;
|
|
50
|
+
version?: string;
|
|
51
|
+
[k: string]: unknown;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* - the `servers` array, verbatim
|
|
55
|
+
*/
|
|
56
|
+
servers?: {
|
|
57
|
+
url: string;
|
|
58
|
+
description?: string;
|
|
59
|
+
}[];
|
|
60
|
+
/**
|
|
61
|
+
* - drop and report a boolean `required`
|
|
62
|
+
* and an unmappable same-document `$ref` instead of refusing (`JC0060`)
|
|
63
|
+
*/
|
|
64
|
+
lenient?: boolean;
|
|
65
|
+
};
|
|
66
|
+
export type OpenApiResult = {
|
|
67
|
+
/**
|
|
68
|
+
* - the OpenAPI 3.1 document
|
|
69
|
+
*/
|
|
70
|
+
document: Record<string, unknown>;
|
|
71
|
+
/**
|
|
72
|
+
* - every keyword the keyword policy removed
|
|
73
|
+
*/
|
|
74
|
+
dropped: DroppedKeyword[];
|
|
75
|
+
};
|
|
76
|
+
export type MapContext = {
|
|
77
|
+
lenient: boolean;
|
|
78
|
+
dropped: DroppedKeyword[];
|
|
79
|
+
/**
|
|
80
|
+
* - the `$defs` names the projection carries
|
|
81
|
+
*/
|
|
82
|
+
defs: Set<string>;
|
|
83
|
+
/**
|
|
84
|
+
* - source schema node → its mapped
|
|
85
|
+
* copy; every node is mapped once (the projection's roots cover every
|
|
86
|
+
* member), so a member's mapped schema is a lookup, and a keyword is
|
|
87
|
+
* reported once
|
|
88
|
+
*/
|
|
89
|
+
mapped: Map<object, any>;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Project a compiled contract to an OpenAPI 3.1 document.
|
|
93
|
+
* @param {Contract} contract
|
|
94
|
+
* @param {OpenApiOptions} [options]
|
|
95
|
+
* @returns {OpenApiResult}
|
|
96
|
+
* @throws {ContractHostError} `JC1008` — not a compiled contract, or a malformed option
|
|
97
|
+
* @throws {ContractCompileError} `JC0060` — a schema keyword the projection refuses (see `lenient`)
|
|
98
|
+
* @example
|
|
99
|
+
* const { document, dropped } = toOpenApi(contract, { info: { title: 'Shop', version: '5' } });
|
|
100
|
+
* document.paths['/api/catalog'].get.operationId; // 'catalog.load'
|
|
101
|
+
*/
|
|
102
|
+
export declare function toOpenApi(contract: Contract, options?: OpenApiOptions): OpenApiResult;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file `contractTools`: the public, invokable operations of a contract as
|
|
3
|
+
* tool definitions for `@jarenjs/ai`'s toolbox — `{ name, description,
|
|
4
|
+
* inputSchema, execute }`, a plain object the toolbox and a WebMCP host
|
|
5
|
+
* read, so this package never imports the ai package (the generated-
|
|
6
|
+
* document rule). `execute` calls the client's `invoke` and answers the
|
|
7
|
+
* outcome JSON, so a model sees the same `{ ok, value | error, meta }`
|
|
8
|
+
* an app does; the toolbox validates the arguments against `inputSchema`
|
|
9
|
+
* before `execute` runs, and `invoke` validates them again against the
|
|
10
|
+
* same schema (the contract's own validator) before anything is sent.
|
|
11
|
+
*/
|
|
12
|
+
export type Contract = import('../compile.js').Contract;
|
|
13
|
+
export type CompiledOperation = import('../compile.js').CompiledOperation;
|
|
14
|
+
export type ToolDefinition = {
|
|
15
|
+
name: string;
|
|
16
|
+
description: string;
|
|
17
|
+
/**
|
|
18
|
+
* - the operation's input schema, self-contained
|
|
19
|
+
*/
|
|
20
|
+
inputSchema: any;
|
|
21
|
+
/**
|
|
22
|
+
* - `client.invoke(op, args)`, resolving the outcome
|
|
23
|
+
*/
|
|
24
|
+
execute: (args: any) => any;
|
|
25
|
+
};
|
|
26
|
+
export type ToolClient = {
|
|
27
|
+
invoke: (op: string, input: any, ctx?: any) => any;
|
|
28
|
+
};
|
|
29
|
+
export type ContractToolsOptions = {
|
|
30
|
+
/**
|
|
31
|
+
* - the operations to expose (default:
|
|
32
|
+
* every public, non-opaque, non-subscribe operation); an opaque,
|
|
33
|
+
* subscribe or unknown id is refused
|
|
34
|
+
*/
|
|
35
|
+
ops?: readonly string[];
|
|
36
|
+
/**
|
|
37
|
+
* - the tool name of an
|
|
38
|
+
* operation (default: the id with `.` → `_`); must satisfy OpenAI's
|
|
39
|
+
* `^[a-zA-Z0-9_-]{1,64}$` and be distinct per operation
|
|
40
|
+
*/
|
|
41
|
+
name?: (id: string) => string;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Tool definitions for the public, invokable operations of a contract.
|
|
45
|
+
* @param {Contract} contract
|
|
46
|
+
* @param {ToolClient} client - a client opened on the same contract (`openHttpClient`, or any binding's)
|
|
47
|
+
* @param {ContractToolsOptions} [options]
|
|
48
|
+
* @returns {ToolDefinition[]} in document order
|
|
49
|
+
* @throws {ContractHostError} `JC1008` — not a compiled contract, a client
|
|
50
|
+
* without `invoke`, `ops` naming an unknown or opaque operation, a tool
|
|
51
|
+
* name outside `^[a-zA-Z0-9_-]{1,64}$`, or two operations mapping to one name
|
|
52
|
+
* @example
|
|
53
|
+
* const toolbox = createToolbox(); // @jarenjs/ai
|
|
54
|
+
* for (const tool of contractTools(contract, client)) toolbox.add(tool);
|
|
55
|
+
* await toolbox.execute('product_save', { id: 1, revision: 2, product: { … } }); // → an outcome
|
|
56
|
+
*/
|
|
57
|
+
export declare function contractTools(contract: Contract, client: ToolClient, options?: ContractToolsOptions): ToolDefinition[];
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file `toTypeScript`: a compiled contract as one `.d.ts` — every public
|
|
3
|
+
* operation's input, output and error-details types through
|
|
4
|
+
* `@jarenjs/emit`'s type model (`compileEmitModel` + `renderTypeScript`,
|
|
5
|
+
* the suite's one declaration renderer), then a typed operation map and
|
|
6
|
+
* the client/handler declarations through a JTLT stylesheet
|
|
7
|
+
* (`typescript.jtlt.json`, compiled once at module scope).
|
|
8
|
+
*
|
|
9
|
+
* One emit model covers the whole contract: a synthetic root whose
|
|
10
|
+
* `$defs` holds the operation types first (`<PascalOp>Input`,
|
|
11
|
+
* `<PascalOp>Output`, `<PascalOp><PascalCode>Details`) and the reachable
|
|
12
|
+
* contract `$defs` after them. Emit declares `$defs` first and in
|
|
13
|
+
* document order and names each after its key, so the operation types
|
|
14
|
+
* keep the names this module gives them (a contract `$defs` entry that
|
|
15
|
+
* spells the same name is the one emit suffixes); their `#/$defs/X`
|
|
16
|
+
* references resolve against the same root, so a `Product` referenced
|
|
17
|
+
* from three operations is declared once. The root's own declaration
|
|
18
|
+
* (`unknown`) is not rendered.
|
|
19
|
+
*
|
|
20
|
+
* `Meta`, `WireError`, `Outcome<T>`, `InvokeContext`, `Client`,
|
|
21
|
+
* `Failure`, `HandlerContext` and `Handlers` are fixed text in the
|
|
22
|
+
* stylesheet — the D6 shapes as every binding carries them
|
|
23
|
+
* (`OUTCOME_META_MEMBERS` / `OUTCOME_ERROR_MEMBERS` in the client module
|
|
24
|
+
* are the runtime twins; a test holds the text to them).
|
|
25
|
+
*/
|
|
26
|
+
export type Contract = import('../compile.js').Contract;
|
|
27
|
+
export type CompiledOperation = import('../compile.js').CompiledOperation;
|
|
28
|
+
export type TypeScriptOptions = {
|
|
29
|
+
/**
|
|
30
|
+
* - emit the do-not-edit header
|
|
31
|
+
*/
|
|
32
|
+
banner?: boolean;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* The type model of a contract: the synthetic `$defs` root, the row per
|
|
36
|
+
* operation the stylesheet renders, and the rendered declarations.
|
|
37
|
+
* @param {Contract} contract
|
|
38
|
+
* @param {CompiledOperation[]} ops
|
|
39
|
+
* @param {string} source - what the model records as its source
|
|
40
|
+
* @returns {{ types: string, rows: Record<string, unknown>[], model: import('@jarenjs/emit/model').EmitModel }}
|
|
41
|
+
*/
|
|
42
|
+
export declare function contractTypeModel(contract: Contract, ops: CompiledOperation[], source: string): {
|
|
43
|
+
types: string;
|
|
44
|
+
rows: Record<string, unknown>[];
|
|
45
|
+
model: import('@jarenjs/emit/model').EmitModel;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Project a compiled contract to TypeScript declarations: the operation
|
|
49
|
+
* types, `Operations`, `UrlOperations`, `Meta`, `WireError`, `Outcome<T>`,
|
|
50
|
+
* `InvokeContext`, `Client`, `Failure`, `HandlerContext`, `Handlers`.
|
|
51
|
+
* @param {Contract} contract
|
|
52
|
+
* @param {TypeScriptOptions} [options]
|
|
53
|
+
* @returns {string} TypeScript source (`.d.ts`)
|
|
54
|
+
* @throws {ContractHostError} `JC1008` — not a compiled contract, or a malformed option
|
|
55
|
+
* @example
|
|
56
|
+
* writeFileSync('shop.d.ts', toTypeScript(contract));
|
|
57
|
+
* // import type { Client, Operations } from './shop.js';
|
|
58
|
+
*/
|
|
59
|
+
export declare function toTypeScript(contract: Contract, options?: TypeScriptOptions): string;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file `publicProjection`: the browser-safe subset of a compiled contract
|
|
3
|
+
* as a JSON document that is ITSELF a valid `$contract` 0.1 — what a
|
|
4
|
+
* client needs and no more, what the revision hashes, and what every other
|
|
5
|
+
* projection (OpenAPI, TypeScript, Markdown, tools) is built on.
|
|
6
|
+
*
|
|
7
|
+
* Retained: the operations whose `policy.audience` is not `server` (and,
|
|
8
|
+
* when the caller narrows with `ops`, only those), each with its resolved
|
|
9
|
+
* binding and policy minus the server-side knobs (`limits`,
|
|
10
|
+
* `errors.details`), plus the `$defs` the retained schemas reach. Member
|
|
11
|
+
* order is FIXED (docs/CONTRACT-FORMAT.md §12.1 — normative, because the
|
|
12
|
+
* revision is the SHA-256 of the canonical bytes of this document): root
|
|
13
|
+
* `$contract, id, version, compat, $defs, operations`; operation `kind,
|
|
14
|
+
* input, output, errors, policy, http, doc`; policy `task, idempotency,
|
|
15
|
+
* revision, cache, retry, stream, audience`; http `method, path, in, body, status,
|
|
16
|
+
* media`; error `status, schema`; operations in document order; `$defs` in
|
|
17
|
+
* first-reference order. Defaults are materialized (the projection says
|
|
18
|
+
* what the binding DOES, not what the author typed), so two documents
|
|
19
|
+
* that behave alike project alike, and the projection of a projection is
|
|
20
|
+
* the projection.
|
|
21
|
+
*/
|
|
22
|
+
export type Contract = import('./compile.js').Contract;
|
|
23
|
+
export type CompiledOperation = import('./compile.js').CompiledOperation;
|
|
24
|
+
export type PublicProjectionOptions = {
|
|
25
|
+
/**
|
|
26
|
+
* - the operations to keep, a subset
|
|
27
|
+
* of the contract's ids (default: all); `server` operations are never
|
|
28
|
+
* kept, listed or not
|
|
29
|
+
*/
|
|
30
|
+
ops?: readonly string[];
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* @typedef {import('./compile.js').Contract} Contract
|
|
34
|
+
* @typedef {import('./compile.js').CompiledOperation} CompiledOperation
|
|
35
|
+
*/
|
|
36
|
+
/**
|
|
37
|
+
* @typedef {Object} PublicProjectionOptions
|
|
38
|
+
* @property {readonly string[]} [ops] - the operations to keep, a subset
|
|
39
|
+
* of the contract's ids (default: all); `server` operations are never
|
|
40
|
+
* kept, listed or not
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* Whether `value` is a compiled contract — reads the members every
|
|
44
|
+
* projection needs.
|
|
45
|
+
* @param {unknown} value
|
|
46
|
+
* @returns {value is Contract}
|
|
47
|
+
*/
|
|
48
|
+
export declare function isCompiledContract(value: unknown): value is Contract;
|
|
49
|
+
/**
|
|
50
|
+
* The operations a projection retains, in document order: the public
|
|
51
|
+
* ones, narrowed by `ops` when given. Throws `JC1008` for a malformed
|
|
52
|
+
* `contract` or an `ops` entry that names no operation.
|
|
53
|
+
* @param {Contract} contract
|
|
54
|
+
* @param {readonly string[] | undefined} ops
|
|
55
|
+
* @param {string} who - the projection's name, for the message
|
|
56
|
+
* @returns {CompiledOperation[]}
|
|
57
|
+
*/
|
|
58
|
+
export declare function retainedOperations(contract: Contract, ops: readonly string[] | undefined, who: string): CompiledOperation[];
|
|
59
|
+
/**
|
|
60
|
+
* The public projection of a compiled contract: a `$contract` 0.1
|
|
61
|
+
* document (it compiles) holding the public operations with their
|
|
62
|
+
* resolved bindings and policies and the `$defs` they reach, in the fixed
|
|
63
|
+
* member order the revision hashes. Schema subtrees are the contract's own
|
|
64
|
+
* (frozen); the composition is fresh.
|
|
65
|
+
* @param {Contract} contract
|
|
66
|
+
* @param {PublicProjectionOptions} [options]
|
|
67
|
+
* @returns {Record<string, unknown>}
|
|
68
|
+
* @throws {ContractHostError} `JC1008` — not a compiled contract, or `ops` names no operation
|
|
69
|
+
* @example
|
|
70
|
+
* const pub = publicProjection(contract);
|
|
71
|
+
* compileContract(pub).ids; // the public operations, in document order
|
|
72
|
+
*/
|
|
73
|
+
export declare function publicProjection(contract: Contract, options?: PublicProjectionOptions): Record<string, unknown>;
|