@jarenjs/linq 0.49.2 → 0.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +217 -0
- package/README.md +559 -17
- package/docs/APP-PEN.md +1143 -0
- package/docs/CONTRACT-PEN.md +1217 -0
- package/docs/DB-CLIENT.md +814 -0
- package/docs/FLOW-PEN.md +1026 -0
- package/docs/FORMS-PEN.md +940 -0
- package/docs/JSLT-PEN.md +955 -0
- package/docs/LINQ-FORMAT.md +771 -383
- package/docs/MIGRATION-PEN.md +781 -0
- package/docs/MODEL-PEN.md +1083 -0
- package/docs/QUERY-PEN.md +1636 -0
- package/docs/SCHEMA-PEN.md +1218 -0
- package/package.json +57 -4
- package/src/app/action.js +255 -0
- package/src/app/capture.js +63 -0
- package/src/app/define.js +260 -0
- package/src/app/index.js +20 -0
- package/src/app/patch.js +277 -0
- package/src/app/sub.js +106 -0
- package/src/async.js +329 -75
- package/src/capture-root.js +82 -0
- package/src/concurrency.js +9 -4
- package/src/contract/define.js +269 -0
- package/src/contract/http.js +247 -0
- package/src/contract/index.js +23 -0
- package/src/contract/operation.js +342 -0
- package/src/db/handle.js +86 -0
- package/src/db/include.js +316 -0
- package/src/db/index.js +19 -0
- package/src/db/live.js +43 -0
- package/src/db/membership.js +37 -0
- package/src/db/open.js +82 -0
- package/src/document.js +143 -13
- package/src/effect.js +65 -0
- package/src/errors.js +69 -6
- package/src/expression.js +437 -36
- package/src/flow/capture.js +33 -0
- package/src/flow/dag.js +302 -0
- package/src/flow/fsm.js +328 -0
- package/src/flow/index.js +22 -0
- package/src/forms/index.js +43 -0
- package/src/forms/rules.js +170 -0
- package/src/forms/submit.js +177 -0
- package/src/index.js +4 -2
- package/src/jslt/body.js +226 -0
- package/src/jslt/index.js +18 -0
- package/src/jslt/rules.js +207 -0
- package/src/json-boundary.js +90 -0
- package/src/migration/define.js +323 -0
- package/src/migration/index.js +15 -0
- package/src/migration/steps.js +248 -0
- package/src/model/collection.js +171 -0
- package/src/model/define.js +125 -0
- package/src/model/entity.js +307 -0
- package/src/model/index.js +47 -0
- package/src/model/relation.js +85 -0
- package/src/provider.js +137 -20
- package/src/schema/brand.js +31 -0
- package/src/schema/builders.js +526 -0
- package/src/schema/check.js +29 -0
- package/src/schema/emit.js +394 -0
- package/src/schema/factories.js +239 -0
- package/src/schema/index.js +37 -0
- package/src/schema-of.js +24 -0
- package/src/sequence.js +233 -103
- package/src/sources.js +10 -3
- package/types/app.d.ts +293 -0
- package/types/contract.d.ts +371 -0
- package/types/db.d.ts +188 -0
- package/types/flow.d.ts +285 -0
- package/types/forms.d.ts +253 -0
- package/types/index.d.ts +231 -26
- package/types/jslt.d.ts +193 -0
- package/types/migration.d.ts +201 -0
- package/types/model.d.ts +493 -0
- package/types/schema.d.ts +494 -0
package/types/app.d.ts
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand-authored declarations for `@jarenjs/linq/app` — the app pen's
|
|
3
|
+
* type contract, kept to the same line as `index.d.ts`: the common path
|
|
4
|
+
* is precisely typed, the exotic path is honestly `unknown`, nothing is
|
|
5
|
+
* ever a WRONG type.
|
|
6
|
+
*
|
|
7
|
+
* `AppDocument<State, Actions>` carries the state shape and the ACTION
|
|
8
|
+
* NAMES as phantoms, both read off the declarations themselves — the
|
|
9
|
+
* keys of `actions` are literal, so `ActionsOf<typeof app>` is the union
|
|
10
|
+
* a `bind<Names>()` is checked against and `bind<Names>('nope')` does
|
|
11
|
+
* not compile, before it is `JL0102` and long before the loop's
|
|
12
|
+
* `JA2001`.
|
|
13
|
+
*
|
|
14
|
+
* The honest limits, both of them TypeScript's own (a function's type
|
|
15
|
+
* arguments are all-or-none, and a sibling member's inferred type cannot
|
|
16
|
+
* contextually type a callback beside it):
|
|
17
|
+
*
|
|
18
|
+
* - an action's `s` is typed by ANNOTATION — `action((s: Expr<State>, x)
|
|
19
|
+
* => …)` — because `action()` is evaluated before `defineApp()` sees
|
|
20
|
+
* the `state` builder. What `defineApp({ state })` types is the
|
|
21
|
+
* DOCUMENT (`StateOf<typeof app>`), which is what a host reading
|
|
22
|
+
* `app.getState()` needs. `x.payload` needs no annotation:
|
|
23
|
+
* `action(fn, { payload })` declares it on the same call.
|
|
24
|
+
* - `bind()`'s action name is checked by annotating the call —
|
|
25
|
+
* `bind<Action>('todo/add')` — where `Action` is the union the author
|
|
26
|
+
* declared or `ActionsOf<>` read back. `defineApp()` checks the other
|
|
27
|
+
* direction at run time over the whole view, which is the half a type
|
|
28
|
+
* cannot reach: a view is a compiled stylesheet by then.
|
|
29
|
+
*
|
|
30
|
+
* Every claim here has a runtime twin in `test/linq/app-pen.test.js` and
|
|
31
|
+
* a compile-level pin in `test/consumer/linq-app.ts`; APP-PEN.md is the
|
|
32
|
+
* normative mapping table.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import type { Expr, MemberExpr, UnknownExpr } from './index.js';
|
|
36
|
+
import type { BuilderLike, Infer, Json, JsonSchema } from './schema.js';
|
|
37
|
+
|
|
38
|
+
type AnyBuilder = BuilderLike<any, any, any>;
|
|
39
|
+
|
|
40
|
+
/** `Expr<any>`/`Expr<unknown>` would pick a wrong arm; the honest top instead. */
|
|
41
|
+
type ValueExpr<T> = MemberExpr<T>;
|
|
42
|
+
|
|
43
|
+
// ————— the action scope —————
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* APP-FORMAT §3.1's default `$event` slice, plus one member per field
|
|
47
|
+
* the binding requested. Every value is a JSON primitive by
|
|
48
|
+
* construction: `$event` MUST survive `JSON.stringify`, the same
|
|
49
|
+
* invariant as state.
|
|
50
|
+
*/
|
|
51
|
+
export type EventSlice<Fields extends string = never> =
|
|
52
|
+
& { readonly type: unknown; readonly value: unknown; readonly checked: unknown; readonly key: unknown }
|
|
53
|
+
& { readonly [K in Fields]: unknown };
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The two externals §3.1 binds beside the state — and the whole ambient
|
|
57
|
+
* vocabulary an action has.
|
|
58
|
+
*/
|
|
59
|
+
export interface ActionScope<Payload = unknown, Fields extends string = never> {
|
|
60
|
+
/** The dispatch payload (a binding's `with`), `null` when absent. */
|
|
61
|
+
readonly payload: ValueExpr<Payload>;
|
|
62
|
+
/** The serializable event slice, `null` for a programmatic dispatch. */
|
|
63
|
+
readonly event: Expr<EventSlice<Fields>>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** A patch path: a lambda over the state, or an RFC 6901 pointer. The
|
|
67
|
+
* lambda sees the SAME scope the action does, so a computed index may
|
|
68
|
+
* read `$payload` — annotate it (`(c: Expr<State>, y: ActionScope<P>)`)
|
|
69
|
+
* exactly as an action's own callback is annotated. */
|
|
70
|
+
export type PatchPath<State = unknown, Payload = unknown> =
|
|
71
|
+
| ((state: ValueExpr<State>, externals: ActionScope<Payload>) => unknown)
|
|
72
|
+
| string;
|
|
73
|
+
|
|
74
|
+
/** One RFC 6902 operation of a transition's `patch` (§3.2). */
|
|
75
|
+
export interface PatchOp {
|
|
76
|
+
readonly op: 'add' | 'replace' | 'remove' | 'move' | 'copy' | 'test';
|
|
77
|
+
readonly from?: unknown;
|
|
78
|
+
readonly path: unknown;
|
|
79
|
+
readonly value?: unknown;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** One effect invocation (§5.1). */
|
|
83
|
+
export interface EffectDeclaration<Run extends string = string> {
|
|
84
|
+
readonly run: Run;
|
|
85
|
+
readonly with?: unknown;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** A transition object (§3.2), as the action's capture spells it. */
|
|
89
|
+
export interface Transition {
|
|
90
|
+
readonly state?: unknown;
|
|
91
|
+
readonly patch?: readonly PatchOp[];
|
|
92
|
+
readonly effects?: readonly EffectDeclaration[];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** One captured action document, carrying its payload type as a phantom. */
|
|
96
|
+
export interface ActionDeclaration<Payload = unknown> {
|
|
97
|
+
readonly __payload: Payload;
|
|
98
|
+
readonly document: Json;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ————— bindings and subscriptions —————
|
|
102
|
+
|
|
103
|
+
/** §4's object binding form. */
|
|
104
|
+
export interface Binding<Names extends string = string> {
|
|
105
|
+
readonly action: Names;
|
|
106
|
+
readonly with?: unknown;
|
|
107
|
+
readonly event?: readonly string[];
|
|
108
|
+
readonly preventDefault?: boolean;
|
|
109
|
+
readonly stopPropagation?: boolean;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** One subscription entry (§5.3). */
|
|
113
|
+
export interface SubDeclaration<Run extends string = string> {
|
|
114
|
+
readonly run: Run;
|
|
115
|
+
readonly with?: Json;
|
|
116
|
+
readonly when?: Json;
|
|
117
|
+
readonly withQuery?: Json;
|
|
118
|
+
readonly key?: Json;
|
|
119
|
+
readonly for?: Json;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** What a subscription's `withQuery`/`key` binds under a `for` fan-out. */
|
|
123
|
+
export interface FanScope<Item = unknown> {
|
|
124
|
+
/** The item this instance was fanned out over (`$item`). */
|
|
125
|
+
readonly item: ValueExpr<Item>;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** A subscription member: a callback over the state, or a query document. */
|
|
129
|
+
export type SubRule<State, Externals> =
|
|
130
|
+
| ((state: ValueExpr<State>, externals: Externals) => unknown)
|
|
131
|
+
| { readonly [keyword: string]: unknown }
|
|
132
|
+
| string;
|
|
133
|
+
|
|
134
|
+
// ————— the document —————
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* A `jaren-app` 0.1 document as the pen writes it, carrying the state
|
|
138
|
+
* shape and the declared action names as phantoms.
|
|
139
|
+
*/
|
|
140
|
+
export interface AppDocument<State = unknown, Actions extends string = string> {
|
|
141
|
+
readonly __state: State;
|
|
142
|
+
readonly __actions: Actions;
|
|
143
|
+
readonly $app: '0.1';
|
|
144
|
+
readonly state?: Json;
|
|
145
|
+
readonly view: unknown;
|
|
146
|
+
readonly actions?: { readonly [name: string]: Json };
|
|
147
|
+
readonly subs?: readonly SubDeclaration[];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* What `defineApp()` answers: the document, and the state's schema
|
|
152
|
+
* beside it — never merged, because the format has no slot for one.
|
|
153
|
+
*
|
|
154
|
+
* `Schema` is the third phantom because the SLOT is not always filled:
|
|
155
|
+
* `defineApp()` answers `null` for a plain-JSON state with no `schema`
|
|
156
|
+
* beside it, and a `JsonSchema | boolean` for the two overloads that
|
|
157
|
+
* were given a builder. Carrying that per overload is what lets the one
|
|
158
|
+
* line every consumer writes —
|
|
159
|
+
* `new JarenValidator().compile(stateSchema)` — compile without a narrow
|
|
160
|
+
* on the overloads that can never answer `null`. It defaults to the
|
|
161
|
+
* whole union, so `AppResult<State, Actions>` still names any result.
|
|
162
|
+
*/
|
|
163
|
+
export interface AppResult<
|
|
164
|
+
State = unknown,
|
|
165
|
+
Actions extends string = string,
|
|
166
|
+
Schema extends JsonSchema | boolean | null = JsonSchema | boolean | null,
|
|
167
|
+
> {
|
|
168
|
+
readonly document: AppDocument<State, Actions>;
|
|
169
|
+
readonly stateSchema: Schema;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** The state an app document describes — what `app.getState()` answers. */
|
|
173
|
+
export type StateOf<A> = A extends AppResult<infer S, any, any> ? S
|
|
174
|
+
: A extends AppDocument<infer S, any> ? S : never;
|
|
175
|
+
/** The action names an app declares — what a `bind<>()` is checked against. */
|
|
176
|
+
export type ActionsOf<A> = A extends AppResult<any, infer N, any> ? N
|
|
177
|
+
: A extends AppDocument<any, infer N> ? N : never;
|
|
178
|
+
|
|
179
|
+
// ————— the surface —————
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* One action document (§3): a callback captured over the state, `$event`
|
|
183
|
+
* and `$payload`, whose result is a transition. Annotate `s` to type it
|
|
184
|
+
* (`(s: Expr<State>, x) => …`); `payload` and `event` are TYPES only —
|
|
185
|
+
* the format carries no schema for either.
|
|
186
|
+
*/
|
|
187
|
+
export function action<State = unknown>(
|
|
188
|
+
fn: (state: ValueExpr<State>, externals: ActionScope<unknown, never>) => unknown,
|
|
189
|
+
): ActionDeclaration<unknown>;
|
|
190
|
+
export function action<
|
|
191
|
+
B extends AnyBuilder, const Fields extends readonly string[] = [], State = unknown,
|
|
192
|
+
>(
|
|
193
|
+
fn: (state: ValueExpr<State>, externals: ActionScope<Infer<B>, Fields[number]>) => unknown,
|
|
194
|
+
options: { readonly payload: B; readonly event?: Fields },
|
|
195
|
+
): ActionDeclaration<Infer<B>>;
|
|
196
|
+
export function action<const Fields extends readonly string[], State = unknown>(
|
|
197
|
+
fn: (state: ValueExpr<State>, externals: ActionScope<unknown, Fields[number]>) => unknown,
|
|
198
|
+
options: { readonly event: Fields },
|
|
199
|
+
): ActionDeclaration<unknown>;
|
|
200
|
+
|
|
201
|
+
/** A transition object (§3.2), in the order the runtime applies it. */
|
|
202
|
+
export function transition(spec: {
|
|
203
|
+
readonly state?: unknown;
|
|
204
|
+
readonly patch?: readonly PatchOp[];
|
|
205
|
+
readonly effects?: readonly EffectDeclaration[];
|
|
206
|
+
}): Transition;
|
|
207
|
+
|
|
208
|
+
/** One effect invocation (§5.1). Its props are a value in the ACTION's
|
|
209
|
+
* own scope: one document, one capture. */
|
|
210
|
+
export function effect<const Run extends string>(
|
|
211
|
+
run: Run, props?: unknown): EffectDeclaration<Run>;
|
|
212
|
+
|
|
213
|
+
/** `{ "op": "add", "path", "value" }` — sets a member, or REPLACES an
|
|
214
|
+
* array when the path names one; `append()` is the array insert. */
|
|
215
|
+
export function add<State = unknown, Payload = unknown>(
|
|
216
|
+
path: PatchPath<State, Payload>, value: unknown): PatchOp;
|
|
217
|
+
/** `{ "op": "add", "path": "<path>/-", "value" }` — RFC 6902's array append. */
|
|
218
|
+
export function append<State = unknown, Payload = unknown>(
|
|
219
|
+
path: PatchPath<State, Payload>, value: unknown): PatchOp;
|
|
220
|
+
/** `{ "op": "replace", "path", "value" }` — and the op an array ELEMENT needs. */
|
|
221
|
+
export function replace<State = unknown, Payload = unknown>(
|
|
222
|
+
path: PatchPath<State, Payload>, value: unknown): PatchOp;
|
|
223
|
+
/** `{ "op": "remove", "path" }`. */
|
|
224
|
+
export function remove<State = unknown, Payload = unknown>(
|
|
225
|
+
path: PatchPath<State, Payload>): PatchOp;
|
|
226
|
+
/** `{ "op": "move", "from", "path" }`. */
|
|
227
|
+
export function move<State = unknown, Payload = unknown>(
|
|
228
|
+
from: PatchPath<State, Payload>, path: PatchPath<State, Payload>): PatchOp;
|
|
229
|
+
/** `{ "op": "copy", "from", "path" }`. */
|
|
230
|
+
export function copy<State = unknown, Payload = unknown>(
|
|
231
|
+
from: PatchPath<State, Payload>, path: PatchPath<State, Payload>): PatchOp;
|
|
232
|
+
/** `{ "op": "test", "path", "value" }` — a failing test aborts the transition. */
|
|
233
|
+
export function test<State = unknown, Payload = unknown>(
|
|
234
|
+
path: PatchPath<State, Payload>, value: unknown): PatchOp;
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* One event binding (§4). Annotate the call with the declared action
|
|
238
|
+
* names — `bind<Action>('todo/add')` — and a name the app does not
|
|
239
|
+
* declare stops compiling.
|
|
240
|
+
*/
|
|
241
|
+
export function bind<Names extends string = string>(
|
|
242
|
+
name: Names,
|
|
243
|
+
options?: {
|
|
244
|
+
readonly payload?: unknown;
|
|
245
|
+
readonly event?: readonly string[];
|
|
246
|
+
readonly preventDefault?: boolean;
|
|
247
|
+
readonly stopPropagation?: boolean;
|
|
248
|
+
},
|
|
249
|
+
): Binding<Names>;
|
|
250
|
+
|
|
251
|
+
/** One subscription entry (§5.3). `with` is verbatim data and never
|
|
252
|
+
* restarts; `withQuery`/`key`/`for` are queries and make it dynamic. */
|
|
253
|
+
export function sub<const Run extends string, State = unknown, Item = unknown>(
|
|
254
|
+
run: Run,
|
|
255
|
+
options?: {
|
|
256
|
+
readonly with?: Json;
|
|
257
|
+
readonly when?: SubRule<State, Record<string, never>>;
|
|
258
|
+
readonly withQuery?: SubRule<State, FanScope<Item>>;
|
|
259
|
+
readonly key?: SubRule<State, FanScope<Item>>;
|
|
260
|
+
readonly for?: SubRule<State, Record<string, never>>;
|
|
261
|
+
},
|
|
262
|
+
): SubDeclaration<Run>;
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Write a `jaren-app` 0.1 document (§2) and the JSON Schema of its
|
|
266
|
+
* state. The initial state comes from the state builder's `default()`s
|
|
267
|
+
* unless `initial` names one; a required member that declares neither is
|
|
268
|
+
* `JL0102`.
|
|
269
|
+
*/
|
|
270
|
+
export function defineApp<
|
|
271
|
+
B extends AnyBuilder, const A extends Record<string, ActionDeclaration<any>> = {},
|
|
272
|
+
>(spec: {
|
|
273
|
+
readonly state: B;
|
|
274
|
+
readonly initial?: Infer<B>;
|
|
275
|
+
readonly view: unknown;
|
|
276
|
+
readonly actions?: A;
|
|
277
|
+
readonly subs?: readonly SubDeclaration[];
|
|
278
|
+
}): AppResult<Infer<B>, keyof A & string, JsonSchema | boolean>;
|
|
279
|
+
export function defineApp<
|
|
280
|
+
B extends AnyBuilder, const A extends Record<string, ActionDeclaration<any>> = {},
|
|
281
|
+
>(spec: {
|
|
282
|
+
readonly state?: Json;
|
|
283
|
+
readonly schema: B;
|
|
284
|
+
readonly view: unknown;
|
|
285
|
+
readonly actions?: A;
|
|
286
|
+
readonly subs?: readonly SubDeclaration[];
|
|
287
|
+
}): AppResult<Infer<B>, keyof A & string, JsonSchema | boolean>;
|
|
288
|
+
export function defineApp<const A extends Record<string, ActionDeclaration<any>> = {}>(spec: {
|
|
289
|
+
readonly state?: Json;
|
|
290
|
+
readonly view: unknown;
|
|
291
|
+
readonly actions?: A;
|
|
292
|
+
readonly subs?: readonly SubDeclaration[];
|
|
293
|
+
}): AppResult<unknown, keyof A & string, null>;
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@jarenjs/linq/contract` — the contract pen's declarations.
|
|
3
|
+
*
|
|
4
|
+
* `defineContract()` returns a `Contract<Ops>` whose phantom `Ops` is
|
|
5
|
+
* read by `ContractOf<>`: one entry per declared operation carrying its
|
|
6
|
+
* `kind`, the `Infer<>` of its input (or `null` when it declares none),
|
|
7
|
+
* the `Infer<>` of its output, the union of its declared error codes,
|
|
8
|
+
* and whether its media makes it opaque. Every one of those is a
|
|
9
|
+
* compile-time reading of the SAME builders the emitted document was
|
|
10
|
+
* written from (D2), so the three wrappers below can type a client, a
|
|
11
|
+
* handler table and an AI toolbox with no `generate` step.
|
|
12
|
+
*
|
|
13
|
+
* The agreement is a gate: `test/consumer/linq-contract.ts` proves
|
|
14
|
+
* `ContractOf<>`'s members EQUAL to what
|
|
15
|
+
* `@jarenjs/contract/project`'s `toTypeScript` declares for the document
|
|
16
|
+
* the pen emitted (`test/consumer/linq-contract-generated.ts`,
|
|
17
|
+
* regenerated byte-identically by `test/linq/contract-pen.test.js`),
|
|
18
|
+
* for every worked example. Where the projection widens — an opaque or
|
|
19
|
+
* boolean `output` is `unknown`, an error's `details` is `unknown` — the
|
|
20
|
+
* pen widens identically. The shapes `Meta`, `WireError`, `Outcome<T>`,
|
|
21
|
+
* `InvokeContext`, `Failure`, `HandlerContext` restate
|
|
22
|
+
* CONTRACT-FORMAT §10.1's fixed D6 members and §12.3's rendering of
|
|
23
|
+
* them, and the same file pins them equal.
|
|
24
|
+
*
|
|
25
|
+
* The runtime is in src/contract/*; CONTRACT-PEN.md is the normative
|
|
26
|
+
* mapping table.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import type { BuilderLike, Infer, Input, Json, JsonSchema, Simplify } from './schema.js';
|
|
30
|
+
|
|
31
|
+
/** A `$contract` 0.1 document as the pen writes it: members, verbatim. */
|
|
32
|
+
export interface ContractDocument {
|
|
33
|
+
readonly [member: string]: unknown;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ——— the declaration surface ———
|
|
37
|
+
|
|
38
|
+
/** The head members §2.1 declares beside `operations`. */
|
|
39
|
+
export interface ContractMeta {
|
|
40
|
+
/** An identifier for the contract (`[A-Za-z_][A-Za-z0-9_-]*`). */
|
|
41
|
+
readonly id?: string;
|
|
42
|
+
/** The consumer's version string — a compatibility claim. */
|
|
43
|
+
readonly version?: string;
|
|
44
|
+
/** Peer `version` strings this contract accepts. */
|
|
45
|
+
readonly compat?: readonly string[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** An uppercase method token §4's table lists. */
|
|
49
|
+
export type HttpMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS';
|
|
50
|
+
|
|
51
|
+
/** Where one input member travels (§4.1). */
|
|
52
|
+
export type Location = 'path' | 'query' | 'header' | 'body';
|
|
53
|
+
|
|
54
|
+
/** The REST binding of one operation (§4). */
|
|
55
|
+
export interface HttpSpec {
|
|
56
|
+
readonly method: HttpMethod;
|
|
57
|
+
/** A path template (§4.2): `/api/products/{id}` or `/docs/:id`. */
|
|
58
|
+
readonly path: string;
|
|
59
|
+
/** Input member → location; the ones the default does not place. */
|
|
60
|
+
readonly in?: { readonly [member: string]: Location };
|
|
61
|
+
/** The input member whose value IS the request body. */
|
|
62
|
+
readonly body?: string;
|
|
63
|
+
/** The success status, 200–299. Default `200`, never written. */
|
|
64
|
+
readonly status?: number;
|
|
65
|
+
/** The response media type. Default `application/json`, never written. */
|
|
66
|
+
readonly media?: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** A checked binding: the spec it was written from, carried as a phantom. */
|
|
70
|
+
export interface HttpBinding<H extends HttpSpec = HttpSpec> {
|
|
71
|
+
readonly __http: H;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** The stream knobs of a subscribe operation (§3.1). */
|
|
75
|
+
export interface StreamPolicy {
|
|
76
|
+
readonly resume?: 'snapshot' | 'replay';
|
|
77
|
+
readonly heartbeatMs?: number;
|
|
78
|
+
readonly maxPatchBytes?: number;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The declared behavior of one operation (§3.1). Every member has a
|
|
82
|
+
* default the COMPILER materializes; the pen writes only what is here. */
|
|
83
|
+
export interface Policy {
|
|
84
|
+
readonly task?: 'switch' | 'exhaust' | 'concat' | 'parallel';
|
|
85
|
+
readonly idempotency?: 'none' | 'optional' | 'required';
|
|
86
|
+
/** `"input:<json-pointer>"` — where the revision a command asserts lives. */
|
|
87
|
+
readonly revision?: string;
|
|
88
|
+
readonly cache?: 'none' | 'revision';
|
|
89
|
+
readonly limits?: { readonly maxBodyBytes: number };
|
|
90
|
+
readonly errors?: { readonly details: 'none' | 'paths' | 'full' };
|
|
91
|
+
readonly retry?: { readonly max: number; readonly on: readonly string[] };
|
|
92
|
+
readonly stream?: StreamPolicy;
|
|
93
|
+
readonly audience?: 'public' | 'server';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** A schema position: a schema-pen builder, or a JSON Schema by hand. */
|
|
97
|
+
export type SchemaSpec = BuilderLike<any, any, any> | JsonSchema | boolean;
|
|
98
|
+
|
|
99
|
+
/** One entry of an operation's `errors` map (§3). */
|
|
100
|
+
export interface ErrorSpec {
|
|
101
|
+
/** 100–599. Default `400`, never written. */
|
|
102
|
+
readonly status?: number;
|
|
103
|
+
/** A JSON Schema for the error's details. */
|
|
104
|
+
readonly schema?: SchemaSpec;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** A checked error declaration: the spec it was written from. */
|
|
108
|
+
export interface ErrorDeclaration<E extends ErrorSpec = ErrorSpec> {
|
|
109
|
+
readonly __error: E;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** What `read()`/`command()`/`subscribe()` take (§3). */
|
|
113
|
+
export interface OperationSpec {
|
|
114
|
+
/** A schema whose effective type is `object`; absent takes no input. */
|
|
115
|
+
readonly input?: SchemaSpec;
|
|
116
|
+
readonly output: SchemaSpec;
|
|
117
|
+
readonly errors?: { readonly [code: string]: ErrorDeclaration<any> | ErrorSpec };
|
|
118
|
+
readonly policy?: Policy;
|
|
119
|
+
readonly http?: HttpBinding<any> | HttpSpec;
|
|
120
|
+
readonly doc?: string;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** A declared operation: its kind and the spec it was written from. */
|
|
124
|
+
export interface OperationDeclaration<K extends OperationKind, S extends OperationSpec> {
|
|
125
|
+
readonly __kind: K;
|
|
126
|
+
readonly __spec: S;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** The three kinds §3 declares. */
|
|
130
|
+
export type OperationKind = 'read' | 'command' | 'subscribe';
|
|
131
|
+
|
|
132
|
+
/** An operation of any kind. */
|
|
133
|
+
export type AnyOperation = OperationDeclaration<OperationKind, any>;
|
|
134
|
+
|
|
135
|
+
// ——— the phantom reading ———
|
|
136
|
+
|
|
137
|
+
/** The output shape of a schema position; `unknown` for JSON by hand. */
|
|
138
|
+
type SchemaOut<S> = S extends BuilderLike<any, any, any> ? Infer<S> : unknown;
|
|
139
|
+
/** The accepted shape of a schema position; `unknown` for JSON by hand. */
|
|
140
|
+
type SchemaIn<S> = S extends BuilderLike<any, any, any> ? Input<S> : unknown;
|
|
141
|
+
|
|
142
|
+
/** The media a binding declares; the default when it declares none. A
|
|
143
|
+
* spec is matched by PATTERN, never indexed: an absent optional member
|
|
144
|
+
* of the constraint is not the same as one the author declared. */
|
|
145
|
+
type MediaOf<S> =
|
|
146
|
+
S extends { http: HttpBinding<infer H> }
|
|
147
|
+
? (H extends { media: infer M extends string } ? M : 'application/json')
|
|
148
|
+
: S extends { http: { media: infer M extends string } } ? M : 'application/json';
|
|
149
|
+
|
|
150
|
+
/** A non-JSON media marks the operation opaque (§4.5); a subscribe never is. */
|
|
151
|
+
type IsOpaque<K extends OperationKind, S> =
|
|
152
|
+
K extends 'subscribe' ? false
|
|
153
|
+
: MediaOf<S> extends 'application/json' ? false
|
|
154
|
+
: MediaOf<S> extends `${string}+json` ? false : true;
|
|
155
|
+
|
|
156
|
+
/** The codes an operation declares, as a literal union. */
|
|
157
|
+
type CodesOf<S> = S extends { errors: infer E } ? Extract<keyof E, string> : never;
|
|
158
|
+
|
|
159
|
+
/** One operation, as the type system reads it. */
|
|
160
|
+
export interface OperationType<
|
|
161
|
+
K extends OperationKind, I, In, O, E extends string, Opaque extends boolean,
|
|
162
|
+
> {
|
|
163
|
+
readonly kind: K;
|
|
164
|
+
/** The output shape of the input schema; `null` when none is declared. */
|
|
165
|
+
readonly input: I;
|
|
166
|
+
/** The accepted (pre-normalization) shape of the input schema. */
|
|
167
|
+
readonly accepts: In;
|
|
168
|
+
readonly output: O;
|
|
169
|
+
/** The declared error codes; `never` when none is declared. */
|
|
170
|
+
readonly errors: E;
|
|
171
|
+
readonly opaque: Opaque;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** The reading of one declared operation. */
|
|
175
|
+
type Read<D> = D extends OperationDeclaration<infer K, infer S>
|
|
176
|
+
? OperationType<
|
|
177
|
+
K,
|
|
178
|
+
S extends { input: infer I } ? SchemaOut<I> : null,
|
|
179
|
+
S extends { input: infer I } ? SchemaIn<I> : null,
|
|
180
|
+
S extends { output: infer O } ? SchemaOut<O> : unknown,
|
|
181
|
+
CodesOf<S>,
|
|
182
|
+
IsOpaque<K, S>>
|
|
183
|
+
: never;
|
|
184
|
+
|
|
185
|
+
/** The reading of a whole operation map. */
|
|
186
|
+
export type OperationsOf<O> = Simplify<{ readonly [K in keyof O]: Read<O[K]> }>;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* The contract a pen wrote: `document` and `toJSON()` are the same
|
|
190
|
+
* deep-frozen `$contract` 0.1 JSON, and `Ops` is the phantom
|
|
191
|
+
* `ContractOf<>` reads.
|
|
192
|
+
*/
|
|
193
|
+
export class Contract<Ops = Record<string, never>> {
|
|
194
|
+
private constructor();
|
|
195
|
+
/** Declared, never present at runtime. */
|
|
196
|
+
readonly __ops: Ops;
|
|
197
|
+
/** The deep-frozen `$contract` 0.1 document. */
|
|
198
|
+
readonly document: ContractDocument;
|
|
199
|
+
toJSON(): ContractDocument;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Every operation a contract declares, opaque ones included. */
|
|
203
|
+
export type ContractOf<C> = C extends Contract<infer Ops> ? Ops : never;
|
|
204
|
+
|
|
205
|
+
/** The invokable operations: everything the AI tools and `invoke` reach —
|
|
206
|
+
* the opaque ones are excluded, exactly as §12.3's `Operations` is. */
|
|
207
|
+
export type InvokableOf<C> = Simplify<{
|
|
208
|
+
[K in keyof ContractOf<C> as ContractOf<C>[K] extends { opaque: true } ? never : K]:
|
|
209
|
+
ContractOf<C>[K]
|
|
210
|
+
}>;
|
|
211
|
+
|
|
212
|
+
/** The subscribe operations of a contract. */
|
|
213
|
+
export type SubscribableOf<C> = Simplify<{
|
|
214
|
+
[K in keyof ContractOf<C> as ContractOf<C>[K] extends { kind: 'subscribe' } ? K : never]:
|
|
215
|
+
ContractOf<C>[K]
|
|
216
|
+
}>;
|
|
217
|
+
|
|
218
|
+
// ——— the fixed outcome shapes (§10.1, rendered by §12.3) ———
|
|
219
|
+
|
|
220
|
+
/** The correlation members of every outcome. A member a binding cannot
|
|
221
|
+
* carry is null (or false for `notModified`), never omitted. */
|
|
222
|
+
export type Meta = {
|
|
223
|
+
op: string; attempt: unknown; trace: string | null; revision: string | null;
|
|
224
|
+
etag: string | null; notModified: boolean;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
/** The error member of a failed outcome. */
|
|
228
|
+
export type WireError = {
|
|
229
|
+
code: string; message: string; status: number | null; details: unknown; retryable: boolean;
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
/** What every invoke resolves to — JSON, never a thrown error. */
|
|
233
|
+
export type Outcome<T> =
|
|
234
|
+
| { ok: true; value: T; meta: Meta }
|
|
235
|
+
| {
|
|
236
|
+
ok: false; kind: 'failure' | 'network' | 'contract' | 'cancelled';
|
|
237
|
+
error: WireError; meta: Meta;
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
/** Per-call options of `invoke`. */
|
|
241
|
+
export interface InvokeContext {
|
|
242
|
+
signal?: AbortSignal; attempt?: unknown; idempotencyKey?: string;
|
|
243
|
+
headers?: Record<string, string>; ifNoneMatch?: string; ifMatch?: string;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** A declared failure a handler returns (`ctx.fail`). */
|
|
247
|
+
export type Failure = {
|
|
248
|
+
code: string; params: Readonly<Record<string, unknown>>; details: unknown;
|
|
249
|
+
retryable: boolean | null;
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
/** The per-request context a server binding hands a handler. */
|
|
253
|
+
export interface HandlerContext {
|
|
254
|
+
op: unknown;
|
|
255
|
+
trace: string;
|
|
256
|
+
method: string;
|
|
257
|
+
path: string;
|
|
258
|
+
params: Readonly<Record<string, string>>;
|
|
259
|
+
headers: Readonly<Record<string, string>>;
|
|
260
|
+
body: string | Uint8Array | null;
|
|
261
|
+
signal: AbortSignal | null;
|
|
262
|
+
idempotency: Readonly<{ key: string; scope: string }> | null;
|
|
263
|
+
fail(code: string, params?: Record<string, unknown>, details?: unknown,
|
|
264
|
+
options?: { retryable?: boolean }): Failure;
|
|
265
|
+
etag(tag: string, options?: { strong?: boolean }): void;
|
|
266
|
+
status(status: number): void;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** What `client.subscribe` takes (§19); every callback is optional. */
|
|
270
|
+
export interface SubscribeHandlers<T> {
|
|
271
|
+
onSnapshot?(value: T, info: { seq: number; resumed: boolean }): void;
|
|
272
|
+
onPatch?(emission: { patch: readonly Json[]; seq: number }): void;
|
|
273
|
+
onError?(outcome: Outcome<never>): void;
|
|
274
|
+
onEnd?(info: { reason: string }): void;
|
|
275
|
+
signal?: AbortSignal;
|
|
276
|
+
lastSeq?: number;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** A live subscription: `stop()` releases it (§19). */
|
|
280
|
+
export interface Subscription {
|
|
281
|
+
stop(): void;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ——— the three identity wrappers ———
|
|
285
|
+
|
|
286
|
+
/** A contract client typed by one contract's operations. */
|
|
287
|
+
export interface TypedClient<C> {
|
|
288
|
+
invoke<K extends keyof InvokableOf<C>>(
|
|
289
|
+
op: K,
|
|
290
|
+
input: InvokableOf<C>[K] extends { input: infer I } ? I : never,
|
|
291
|
+
ctx?: InvokeContext,
|
|
292
|
+
): Promise<Outcome<InvokableOf<C>[K] extends { output: infer O } ? O : never>>;
|
|
293
|
+
subscribe<K extends keyof SubscribableOf<C>>(
|
|
294
|
+
op: K,
|
|
295
|
+
input: SubscribableOf<C>[K] extends { input: infer I } ? I : never,
|
|
296
|
+
handlers: SubscribeHandlers<
|
|
297
|
+
SubscribableOf<C>[K] extends { output: infer O } ? O : never>,
|
|
298
|
+
): Subscription;
|
|
299
|
+
url<K extends keyof ContractOf<C>>(
|
|
300
|
+
op: K,
|
|
301
|
+
input: ContractOf<C>[K] extends { input: infer I } ? I : never,
|
|
302
|
+
): string;
|
|
303
|
+
close(): void;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** The handler table of a server binding, one handler per invokable operation. */
|
|
307
|
+
export type TypedHandlerTable<C> = {
|
|
308
|
+
[K in keyof InvokableOf<C>]: (
|
|
309
|
+
input: InvokableOf<C>[K] extends { input: infer I } ? I : never,
|
|
310
|
+
ctx: HandlerContext,
|
|
311
|
+
) => (InvokableOf<C>[K] extends { output: infer O } ? O : never)
|
|
312
|
+
| Failure
|
|
313
|
+
| Promise<(InvokableOf<C>[K] extends { output: infer O } ? O : never) | Failure>;
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
/** An operation id as `contractTools` names it: `.` → `_`. */
|
|
317
|
+
export type ToolName<S extends string> =
|
|
318
|
+
S extends `${infer A}.${infer B}` ? `${A}_${ToolName<B>}` : S;
|
|
319
|
+
|
|
320
|
+
/** One tool definition, typed by the operation it invokes — the
|
|
321
|
+
* `ToolDef` shape `@jarenjs/ai`'s `createToolbox().add` takes. */
|
|
322
|
+
export type TypedTool<C> = {
|
|
323
|
+
[K in keyof InvokableOf<C>]: {
|
|
324
|
+
name: ToolName<Extract<K, string>>;
|
|
325
|
+
description: string;
|
|
326
|
+
inputSchema: JsonSchema;
|
|
327
|
+
execute: InvokableOf<C>[K] extends { accepts: infer In; output: infer O }
|
|
328
|
+
? (In extends null ? null : (args: In) => Promise<Outcome<O>>)
|
|
329
|
+
: never;
|
|
330
|
+
};
|
|
331
|
+
}[keyof InvokableOf<C>];
|
|
332
|
+
|
|
333
|
+
// ——— the functions ———
|
|
334
|
+
|
|
335
|
+
/** A `read` operation: a query whose input members default to the query
|
|
336
|
+
* string and whose result may be cached by revision. */
|
|
337
|
+
export function read<const S extends OperationSpec>(spec: S): OperationDeclaration<'read', S>;
|
|
338
|
+
|
|
339
|
+
/** A `command` operation: a state change whose input members default to
|
|
340
|
+
* the request body. */
|
|
341
|
+
export function command<const S extends OperationSpec>(spec: S): OperationDeclaration<'command', S>;
|
|
342
|
+
|
|
343
|
+
/** A `subscribe` operation (§17): `output` is the snapshot schema and
|
|
344
|
+
* the emissions travel the stream binding. */
|
|
345
|
+
export function subscribe<const S extends OperationSpec>(spec: S):
|
|
346
|
+
OperationDeclaration<'subscribe', S>;
|
|
347
|
+
|
|
348
|
+
/** One entry of an operation's `errors` map. */
|
|
349
|
+
export function error<const E extends ErrorSpec>(spec?: E): ErrorDeclaration<E>;
|
|
350
|
+
|
|
351
|
+
/** The REST binding of one operation; the path template is checked here
|
|
352
|
+
* (`JL0102` naming the reserved form), earlier than `JC0008`. */
|
|
353
|
+
export function http<const H extends HttpSpec>(spec: H): HttpBinding<H>;
|
|
354
|
+
|
|
355
|
+
/** Write a `$contract` 0.1 document. */
|
|
356
|
+
export function defineContract<O extends Record<string, AnyOperation | ({
|
|
357
|
+
kind: OperationKind } & OperationSpec)>>(
|
|
358
|
+
meta: ContractMeta, operations: O): Contract<OperationsOf<O>>;
|
|
359
|
+
|
|
360
|
+
/** Bind a contract client to the contract that types it. Identity at runtime. */
|
|
361
|
+
export function typedClient<C extends Contract<any>>(client: unknown, contract: C): TypedClient<C>;
|
|
362
|
+
|
|
363
|
+
/** Bind a handler table to the contract it serves. Identity at runtime;
|
|
364
|
+
* a missing or misspelled operation is a type error. */
|
|
365
|
+
export function typedHandlers<C extends Contract<any>>(
|
|
366
|
+
contract: C, handlers: TypedHandlerTable<C>): TypedHandlerTable<C>;
|
|
367
|
+
|
|
368
|
+
/** Bind `contractTools`' output to the contract that types it. Identity
|
|
369
|
+
* at runtime. */
|
|
370
|
+
export function typedTools<C extends Contract<any>>(
|
|
371
|
+
tools: readonly unknown[], contract: C): TypedTool<C>[];
|