@ayepi/node 0.1.0 → 0.2.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/README.md +1 -1
- package/ayepi-node.md +315 -0
- package/dist/index.d.cts +2 -604
- package/dist/index.d.ts +2 -604
- package/package.json +5 -4
package/dist/index.d.ts
CHANGED
|
@@ -1,611 +1,9 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
2
|
import { WebSocketServer } from "ws";
|
|
3
|
-
import {
|
|
3
|
+
import { AnySpec, Server } from "@ayepi/core";
|
|
4
4
|
|
|
5
|
-
//#region ../core/dist/types.d.ts
|
|
6
|
-
|
|
7
|
-
//#region src/types.d.ts
|
|
8
|
-
/**
|
|
9
|
-
* # Type utilities
|
|
10
|
-
*
|
|
11
|
-
* Small, dependency-free type-level helpers used across the library. None of
|
|
12
|
-
* these emit runtime code — they exist purely to make the public generic
|
|
13
|
-
* surface infer precisely without leaking `any`/`unknown` to consumers.
|
|
14
|
-
*
|
|
15
|
-
* @module
|
|
16
|
-
*/
|
|
17
|
-
/**
|
|
18
|
-
* Flatten an intersection (`A & B & …`) into a single object literal so editor
|
|
19
|
-
* tooltips and `Equal<>` comparisons see one clean shape instead of a chain of
|
|
20
|
-
* intersections.
|
|
21
|
-
*
|
|
22
|
-
* @example
|
|
23
|
-
* ```ts
|
|
24
|
-
* type Messy = { a: 1 } & { b: 2 }
|
|
25
|
-
* type Clean = Simplify<Messy> // { a: 1; b: 2 }
|
|
26
|
-
* ```
|
|
27
|
-
*/
|
|
28
|
-
type Simplify<T> = { [K in keyof T]: T[K] } & {};
|
|
29
|
-
/** A value that may be returned either synchronously or as a `Promise`. */
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* The empty object type. Used as the identity element when merging contributed
|
|
33
|
-
* shapes (middleware context, path params, etc.) so an absent contribution adds
|
|
34
|
-
* nothing rather than widening the result.
|
|
35
|
-
*/
|
|
36
|
-
type EmptyObject = {};
|
|
37
|
-
/**
|
|
38
|
-
* Convert a union `A | B | C` into the intersection `A & B & C`.
|
|
39
|
-
*
|
|
40
|
-
* Drives middleware-context merging: each middleware contributes a shape, and
|
|
41
|
-
* the handler sees the intersection of every contribution in its chain.
|
|
42
|
-
*/
|
|
43
|
-
|
|
44
|
-
/** Distribute `keyof` across a union, yielding the union of every member's keys. */
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Safe indexed access: resolves to `T[K]` when `K` is a key of `T`, otherwise
|
|
48
|
-
* `undefined`. Lets optional config properties be read without first proving
|
|
49
|
-
* they exist.
|
|
50
|
-
*/
|
|
51
|
-
type Get<T, K extends string> = K extends keyof T ? T[K] : undefined;
|
|
52
|
-
/**
|
|
53
|
-
* A JSON-shaped value — the closed set of things the OpenAPI/AsyncAPI doc
|
|
54
|
-
* generators produce and accept in their patch callbacks.
|
|
55
|
-
*/
|
|
56
|
-
type Json = string | number | boolean | null | Json[] | {
|
|
57
|
-
[k: string]: Json;
|
|
58
|
-
};
|
|
59
|
-
//#endregion
|
|
60
|
-
//#endregion
|
|
61
|
-
//#region ../core/dist/errors.d.ts
|
|
62
|
-
//#region src/manifest.d.ts
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* # Manifest
|
|
66
|
-
*
|
|
67
|
-
* The **zod-free runtime configuration** the client needs. It carries exactly
|
|
68
|
-
* enough structure (per-endpoint key tables, method, path, streaming flags) for
|
|
69
|
-
* the client to split a single `data` payload back into path/query/body/files
|
|
70
|
-
* and pick a transport — with no zod schemas, so the frontend bundle stays
|
|
71
|
-
* schema-free. Obtain it from `app.manifest()` or {@link manifestFromSpec}.
|
|
72
|
-
*
|
|
73
|
-
* Every field here is part of the **frozen v0 wire contract**.
|
|
74
|
-
*
|
|
75
|
-
* @module
|
|
76
|
-
*/
|
|
77
|
-
/** The HTTP methods ayepi endpoints may use. */
|
|
78
|
-
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
79
|
-
/**
|
|
80
|
-
* Runtime description of a single endpoint — everything the client must know to
|
|
81
|
-
* build a request and interpret a response without access to the zod schemas.
|
|
82
|
-
*/
|
|
83
|
-
interface ManifestEndpoint {
|
|
84
|
-
/** HTTP method (default `POST`). */
|
|
85
|
-
readonly method: HttpMethod;
|
|
86
|
-
/** Path pattern with `:key` segments, e.g. `/users/:id`. */
|
|
87
|
-
readonly path: string;
|
|
88
|
-
/** Explicit WebSocket id, or `null` to address the endpoint by `method + path`. */
|
|
89
|
-
readonly ws: string | null;
|
|
90
|
-
/** When `true`, the endpoint cannot be called over ws (raw streams / files). */
|
|
91
|
-
readonly httpOnly: boolean;
|
|
92
|
-
/** Content-type of the streamed request body (raw, or NDJSON for item streams); `null` if none. */
|
|
93
|
-
readonly streamIn: string | null;
|
|
94
|
-
/** `true` when `streamIn` is a typed NDJSON item stream (vs a raw byte stream). */
|
|
95
|
-
readonly itemsIn: boolean;
|
|
96
|
-
/** Content-type of the streamed response (raw, or NDJSON/SSE for item streams); `null` if none. */
|
|
97
|
-
readonly streamOut: string | null;
|
|
98
|
-
/** `true` when `streamOut` is a typed item stream (vs a raw byte stream). */
|
|
99
|
-
readonly items: boolean;
|
|
100
|
-
/** Path-param keys, in path order. */
|
|
101
|
-
readonly p: readonly string[];
|
|
102
|
-
/** Query-param keys. */
|
|
103
|
-
readonly q: readonly string[];
|
|
104
|
-
/** Body keys, `'raw'` when the body is the entire data payload, or `null` when there is no body. */
|
|
105
|
-
readonly b: readonly string[] | 'raw' | null;
|
|
106
|
-
/** Multipart file-field keys. */
|
|
107
|
-
readonly f: readonly string[];
|
|
108
|
-
/** Whether the endpoint declares a body at all. */
|
|
109
|
-
readonly hasBody: boolean;
|
|
110
|
-
/** Whether the endpoint declares typed request headers. */
|
|
111
|
-
readonly hasHeaders: boolean;
|
|
112
|
-
/** When `true`, `call()` resolves a `{ status, data }` discriminated union. */
|
|
113
|
-
readonly multi: boolean;
|
|
114
|
-
/** Body wire encoding, or `null` when there is no body. */
|
|
115
|
-
readonly bodyEnc: 'json' | 'urlencoded' | null;
|
|
116
|
-
}
|
|
117
|
-
/** Runtime description of a single server-pushed event channel. */
|
|
118
|
-
interface ManifestEvent {
|
|
119
|
-
/** WebSocket channel id. */
|
|
120
|
-
readonly ws: string;
|
|
121
|
-
/** Whether the channel is parameterized (subscriptions are keyed by params). */
|
|
122
|
-
readonly hasParams: boolean;
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* The complete zod-free runtime manifest consumed by {@link client} — obtained
|
|
126
|
-
* from `app.manifest()` or {@link manifestFromSpec}. Hand a client this manifest
|
|
127
|
-
* (instead of the spec) to talk to a server without shipping its schema code.
|
|
128
|
-
*/
|
|
129
|
-
interface Manifest {
|
|
130
|
-
readonly endpoints: Readonly<Record<string, ManifestEndpoint>>;
|
|
131
|
-
readonly events: Readonly<Record<string, ManifestEvent>>;
|
|
132
|
-
}
|
|
133
|
-
//#endregion
|
|
134
|
-
//#region src/path.d.ts
|
|
135
|
-
/**
|
|
136
|
-
* A single path segment: either a string literal or a named parameter.
|
|
137
|
-
*
|
|
138
|
-
* The whole path-matching machinery operates on arrays of these, which is why
|
|
139
|
-
* no part of it needs regex or `String.replace` over user input.
|
|
140
|
-
*/
|
|
141
|
-
type PathPart = {
|
|
142
|
-
readonly t: 'lit';
|
|
143
|
-
readonly v: string;
|
|
144
|
-
} | {
|
|
145
|
-
readonly t: 'param';
|
|
146
|
-
readonly k: string;
|
|
147
|
-
};
|
|
148
|
-
/**
|
|
149
|
-
* The erased (non-generic) shape of a {@link PathTemplate}. Used wherever a
|
|
150
|
-
* template is accepted without caring about its specific param types.
|
|
151
|
-
*/
|
|
152
|
-
interface AnyPathTemplate {
|
|
153
|
-
readonly kind: 'path';
|
|
154
|
-
/** Segment array — never matched or built via string replacement. */
|
|
155
|
-
readonly parts: readonly PathPart[];
|
|
156
|
-
/** Display/wire form, derived from {@link parts} (e.g. `/users/:id`). */
|
|
157
|
-
readonly pattern: string;
|
|
158
|
-
/** Declared param keys, in path order. */
|
|
159
|
-
readonly keys: readonly string[];
|
|
160
|
-
/** Per-key zod schemas; each must accept string input. */
|
|
161
|
-
readonly schemas: Readonly<Record<string, z.ZodType>>;
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* A typed path template produced by the {@link path} tag. Carries its pattern
|
|
165
|
-
* and per-segment schemas, and can build a URL path from typed params or parse
|
|
166
|
-
* one back into typed params.
|
|
167
|
-
*
|
|
168
|
-
* @typeParam PS - a `{ key: ZodType }` record describing each param segment.
|
|
169
|
-
*/
|
|
170
|
-
|
|
171
|
-
//#endregion
|
|
172
|
-
//#region src/payload.d.ts
|
|
173
|
-
/** Input vs output side of a schema (request parsing vs response/handler view). */
|
|
174
|
-
type IOMode = 'in' | 'out';
|
|
175
|
-
/** Pick `z.input` or `z.output` of a schema by {@link IOMode}. */
|
|
176
|
-
type ZIO<Z extends z.ZodType, M extends IOMode> = M extends 'in' ? z.input<Z> : z.output<Z>;
|
|
177
|
-
type CfgOf<E extends AnyEndpoint> = E['cfg'];
|
|
178
|
-
type LPOf<E extends AnyEndpoint> = E['__lp'];
|
|
179
|
-
type LPShape<E extends AnyEndpoint, M extends IOMode> = { [K in keyof LPOf<E>]: LPOf<E>[K] extends z.ZodType ? ZIO<LPOf<E>[K], M> : never };
|
|
180
|
-
/** Params contributed by a `path` template on `cfg.path`. */
|
|
181
|
-
type TplOf<E extends AnyEndpoint> = Get<CfgOf<E>, 'path'> extends {
|
|
182
|
-
readonly __ps: infer PS extends object;
|
|
183
|
-
} ? PS : EmptyObject;
|
|
184
|
-
/** Params contributed by stacked path prefixes. */
|
|
185
|
-
type PfxOf<E extends AnyEndpoint> = E extends {
|
|
186
|
-
readonly __pfx: infer PFX extends object;
|
|
187
|
-
} ? PFX : EmptyObject;
|
|
188
|
-
type PfxShape<E extends AnyEndpoint, M extends IOMode> = { -readonly [K in keyof PfxOf<E>]: PfxOf<E>[K] extends z.ZodType ? ZIO<PfxOf<E>[K], M> : never };
|
|
189
|
-
type TplShape<E extends AnyEndpoint, M extends IOMode> = { -readonly [K in keyof TplOf<E>]: TplOf<E>[K] extends z.ZodType ? ZIO<TplOf<E>[K], M> : never };
|
|
190
|
-
type PShape<E extends AnyEndpoint, M extends IOMode> = Simplify<(Get<CfgOf<E>, 'params'> extends z.ZodType ? ZIO<Get<CfgOf<E>, 'params'> & z.ZodType, M> : EmptyObject) & LPShape<E, M> & TplShape<E, M> & PfxShape<E, M>>;
|
|
191
|
-
type QShape<E extends AnyEndpoint, M extends IOMode> = Get<CfgOf<E>, 'query'> extends z.ZodType ? ZIO<Get<CfgOf<E>, 'query'> & z.ZodType, M> : EmptyObject;
|
|
192
|
-
/** File fields map to data keys; a file schema whose input accepts `undefined` (e.g. `z.file().optional()`) becomes an optional key. */
|
|
193
|
-
type FShape<E extends AnyEndpoint, M extends IOMode> = Get<CfgOf<E>, 'files'> extends infer F extends Readonly<Record<string, z.ZodType>> ? Simplify<{ -readonly [K in keyof F as undefined extends z.input<F[K]> ? never : K]: ZIO<F[K], M> } & { -readonly [K in keyof F as undefined extends z.input<F[K]> ? K : never]?: ZIO<F[K], M> }> : EmptyObject;
|
|
194
|
-
type HasBody<E extends AnyEndpoint> = Get<CfgOf<E>, 'body'> extends z.ZodType ? true : false;
|
|
195
|
-
type BRaw<E extends AnyEndpoint, M extends IOMode> = Get<CfgOf<E>, 'body'> extends z.ZodType ? ZIO<Get<CfgOf<E>, 'body'> & z.ZodType, M> : never;
|
|
196
|
-
/** A body merges into the flat object only when it is a plain string-keyed record. */
|
|
197
|
-
type BMergeable<E extends AnyEndpoint, M extends IOMode> = HasBody<E> extends true ? ([BRaw<E, M>] extends [Record<string, unknown>] ? true : false) : false;
|
|
198
|
-
type BFlat<E extends AnyEndpoint, M extends IOMode> = BMergeable<E, M> extends true ? BRaw<E, M> : EmptyObject;
|
|
199
|
-
type NonMergeableBody<E extends AnyEndpoint> = HasBody<E> extends true ? (BMergeable<E, 'in'> extends true ? false : true) : false;
|
|
200
|
-
type ClientFlat<E extends AnyEndpoint> = Simplify<PShape<E, 'in'> & QShape<E, 'in'> & BFlat<E, 'in'> & FShape<E, 'in'>>;
|
|
201
|
-
/**
|
|
202
|
-
* The single `data` argument the client passes to `call()` — the merged
|
|
203
|
-
* path/query/body/files object, or the raw value when the body is a non-object
|
|
204
|
-
* (then it *is* the data).
|
|
205
|
-
*/
|
|
206
|
-
type ClientData<E extends AnyEndpoint> = NonMergeableBody<E> extends true ? BRaw<E, 'in'> : ClientFlat<E>;
|
|
207
|
-
/** Accepted shapes for a raw streaming request body. */
|
|
208
|
-
|
|
209
|
-
/** The positional arguments to `emit(name, …)`: `(params, data)` for parameterized events, `(data)` otherwise. */
|
|
210
|
-
type EmitArgs<Ev extends EventConfig> = Get<Ev, 'params'> extends z.ZodType ? [params: z.input<Get<Ev, 'params'> & z.ZodType>, data: z.input<Ev['data']>] : [data: z.input<Ev['data']>];
|
|
211
|
-
/** The typed `emit` function for a spec's events, available on the server and in handlers. */
|
|
212
|
-
type EmitFn<S extends AnySpec> = <K extends keyof EventsOf<S> & string>(name: K, ...args: EmitArgs<EventsOf<S>[K] & EventConfig>) => void;
|
|
213
|
-
/**
|
|
214
|
-
* The object a handler receives. The middleware context spreads at the root,
|
|
215
|
-
* alongside a single merged `data`, the declared kinds (`stream`, `headers`,
|
|
216
|
-
* `cookies`), and the framework toolkit (`req`, `signal`, `emit`, `status()`,
|
|
217
|
-
* `header()`, `cookie()`, and — gated by config — `out`/`download()`/`length()`/`fail()`).
|
|
218
|
-
*
|
|
219
|
-
* @typeParam S - the owning spec (for the typed `emit`).
|
|
220
|
-
* @typeParam E - the endpoint.
|
|
221
|
-
*/
|
|
222
|
-
|
|
223
|
-
/** A live WebSocket connection, as seen by the server's ws handler. */
|
|
224
|
-
interface WsConn {
|
|
225
|
-
readonly id: number;
|
|
226
|
-
readonly req: Request;
|
|
227
|
-
/** internal */
|
|
228
|
-
readonly send: (frame: string) => void;
|
|
229
|
-
/** internal */
|
|
230
|
-
readonly subs: Set<string>;
|
|
231
|
-
/** internal */
|
|
232
|
-
readonly streams: Map<string, {
|
|
233
|
-
push(v: unknown): void;
|
|
234
|
-
end(): void;
|
|
235
|
-
fail(err: unknown): void;
|
|
236
|
-
}>;
|
|
237
|
-
/** internal: per-call abort controllers, so an `{ id, abort: true }` frame can cancel a call */
|
|
238
|
-
readonly calls: Map<string, AbortController>;
|
|
239
|
-
}
|
|
240
|
-
/** Cross-origin resource sharing configuration. */
|
|
241
|
-
|
|
242
|
-
/** The assembled server: a fetch handler, a ws handler, the manifest, `emit`, and doc generators. */
|
|
243
|
-
interface Server<S extends AnySpec> {
|
|
244
|
-
readonly spec: S;
|
|
245
|
-
/** The entire HTTP surface — pass any `Request`, get a `Response`. */
|
|
246
|
-
fetch(req: Request): Promise<Response>;
|
|
247
|
-
/** The zod-free runtime manifest the client routes from (also derivable via {@link manifestFromSpec}). */
|
|
248
|
-
manifest(): Manifest;
|
|
249
|
-
/** Publish a typed event to all subscribers (across instances via the {@link Broker}). */
|
|
250
|
-
emit: EmitFn<S>;
|
|
251
|
-
/** Generate the OpenAPI 3.1 document. */
|
|
252
|
-
openapi(info?: {
|
|
253
|
-
title?: string;
|
|
254
|
-
version?: string;
|
|
255
|
-
}): Json;
|
|
256
|
-
/** Generate the AsyncAPI 3.0 document. */
|
|
257
|
-
asyncapi(info?: {
|
|
258
|
-
title?: string;
|
|
259
|
-
version?: string;
|
|
260
|
-
}): Json;
|
|
261
|
-
/**
|
|
262
|
-
* Call one of this server's endpoints **in-process** by name with just its data
|
|
263
|
-
* payload — runs the full middleware chain + validation, but skips HTTP
|
|
264
|
-
* serialization. The invocation's `io.transport` is `'local'`. Pass `headers` via
|
|
265
|
-
* `opts` to satisfy auth-style middleware. Streaming/file endpoints are best
|
|
266
|
-
* called over a {@link client} instead.
|
|
267
|
-
*
|
|
268
|
-
* The result is loosely typed here (a low-level escape hatch) — for the typed
|
|
269
|
-
* surface use {@link localClient}, which returns a {@link LocalClient}.
|
|
270
|
-
*/
|
|
271
|
-
call<K extends keyof S['endpoints'] & string>(name: K, ...args: LocalCallArgs<S['endpoints'][K]>): Promise<unknown>;
|
|
272
|
-
/**
|
|
273
|
-
* Mount another spec + its builders into this **running** server — its endpoints,
|
|
274
|
-
* events, routes, and middleware go live immediately and the manifest/docs caches
|
|
275
|
-
* refresh. Typed like {@link server}: every endpoint needs a handler and every
|
|
276
|
-
* chain middleware a binding (a shared middleware def already bound by an earlier
|
|
277
|
-
* mount is reused, not re-bound). Collisions (endpoint name / route / ws / event)
|
|
278
|
-
* throw. Returns a {@link MountHandle} for {@link Server.uninstall}.
|
|
279
|
-
*/
|
|
280
|
-
install<S2 extends AnySpec, const H2 extends readonly {
|
|
281
|
-
readonly __hk?: keyof S2['endpoints'] & string;
|
|
282
|
-
}[]>(spec: S2, builders: H2, ...rest: [MissingHandlers<S2, H2>] extends [never] ? [] : [error: {
|
|
283
|
-
readonly missingHandlers: MissingHandlers<S2, H2>;
|
|
284
|
-
}]): MountHandle;
|
|
285
|
-
/** Remove a previously {@link Server.install | installed} mount — deletes exactly its endpoints, events, routes, and bindings, and clears its subscriptions. */
|
|
286
|
-
uninstall(handle: MountHandle): void;
|
|
287
|
-
/** WebSocket lifecycle hooks for an adapter to drive. */
|
|
288
|
-
readonly ws: {
|
|
289
|
-
/** Register a new connection given its `send` and the upgrade `Request`. */
|
|
290
|
-
open(send: (frame: string) => void, req: Request): WsConn;
|
|
291
|
-
/** Handle one inbound text frame. */
|
|
292
|
-
message(conn: WsConn, raw: string): Promise<void>;
|
|
293
|
-
/** Tear down a connection (cleans up its subscriptions). */
|
|
294
|
-
close(conn: WsConn): void;
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
/** Per-call options for an in-process {@link Server.call} / {@link LocalClient}. */
|
|
298
|
-
interface LocalCallOptions {
|
|
299
|
-
/** Request headers visible to the chain (e.g. an `authorization` token for auth middleware). */
|
|
300
|
-
readonly headers?: Readonly<Record<string, string>>;
|
|
301
|
-
/** Abort signal for the call. */
|
|
302
|
-
readonly signal?: AbortSignal;
|
|
303
|
-
}
|
|
304
|
-
/** The arguments an in-process call takes: `[opts?]` when the endpoint has no input, else `[data, opts?]`. */
|
|
305
|
-
type LocalCallArgs<E extends AnyEndpoint> = [keyof ClientData<E>] extends [never] ? [opts?: LocalCallOptions] : [data: ClientData<E>, opts?: LocalCallOptions];
|
|
306
|
-
/**
|
|
307
|
-
* A typed in-process caller for a spec's endpoints — the no-serialization loopback
|
|
308
|
-
* over {@link Server.call}, retyped against `S`. Use it to invoke another spec's
|
|
309
|
-
* endpoints (e.g. a dependency's) from server-side code with just a data payload.
|
|
310
|
-
*/
|
|
311
|
-
|
|
312
|
-
/** The endpoint-name union a builder has handled — read off its `__hk` phantom (never expands the methods). */
|
|
313
|
-
type BuilderKeys<B> = B extends {
|
|
314
|
-
readonly __hk?: infer K;
|
|
315
|
-
} ? K : never;
|
|
316
|
-
/** Endpoint names with no handler across the supplied builders — surfaced as a compile error. */
|
|
317
|
-
type MissingHandlers<S extends AnySpec, H extends readonly unknown[]> = Exclude<keyof S['endpoints'] & string, BuilderKeys<H[number]>>;
|
|
318
|
-
/** Loosest internal function type — replaces the banned `Function` in plumbing signatures. */
|
|
319
|
-
|
|
320
|
-
/** internal: a normalized event with its guard chain bound — held in the live `events` registry. */
|
|
321
|
-
interface LiveEvent {
|
|
322
|
-
readonly name: string;
|
|
323
|
-
readonly cfg: EventConfig;
|
|
324
|
-
readonly ws: string;
|
|
325
|
-
readonly chain: AnyMiddleware[];
|
|
326
|
-
}
|
|
327
|
-
/**
|
|
328
|
-
* An opaque handle to a mounted spec, returned by {@link Server.install} and passed
|
|
329
|
-
* back to {@link Server.uninstall} to remove exactly what that install added.
|
|
330
|
-
*/
|
|
331
|
-
interface MountHandle {
|
|
332
|
-
/** @internal the normalized endpoints this mount added. */
|
|
333
|
-
readonly eps: readonly NormalizedEp[];
|
|
334
|
-
/** @internal the live events this mount added. */
|
|
335
|
-
readonly events: readonly LiveEvent[];
|
|
336
|
-
/** @internal the middleware defs this mount bound (removed from the global impl map on uninstall). */
|
|
337
|
-
readonly impls: readonly AnyMiddleware[];
|
|
338
|
-
/** @internal this mount's spec-level doc patch, if any. */
|
|
339
|
-
readonly doc: SpecDoc | undefined;
|
|
340
|
-
}
|
|
341
|
-
/**
|
|
342
|
-
* Assemble a {@link Server} from a spec and one or more {@link implement} builders.
|
|
343
|
-
*
|
|
344
|
-
* Every endpoint must have exactly one handler: a missing handler is a **compile
|
|
345
|
-
* error** that names the offending endpoints (via the final `error` argument), and
|
|
346
|
-
* a duplicate/unknown handler throws at startup. Every middleware in an endpoint or
|
|
347
|
-
* event-guard chain must be bound via `.middleware(def, impl)` — an unbound def
|
|
348
|
-
* throws at assembly.
|
|
349
|
-
*
|
|
350
|
-
* @param spec - the validated spec from {@link spec}.
|
|
351
|
-
* @param builders - one or more builders from {@link implement}.
|
|
352
|
-
* @param rest - `[options?]` when all handlers are present, else `[{ missingHandlers }]`.
|
|
353
|
-
*
|
|
354
|
-
* @example
|
|
355
|
-
* ```ts
|
|
356
|
-
* const app = server(api, [implement(api).middleware(auth, authImpl).handlers({ … })], { cors, broker })
|
|
357
|
-
* const res = await app.fetch(new Request('http://x/getUser/u1', { method: 'POST' }))
|
|
358
|
-
* ```
|
|
359
|
-
*/
|
|
360
|
-
|
|
361
|
-
declare const MW_PROVIDES: unique symbol;
|
|
362
|
-
/**
|
|
363
|
-
* Opaque token returned by `next()`; carries (type-only) the context a
|
|
364
|
-
* middleware provides so the next link and the handler can see it.
|
|
365
|
-
*
|
|
366
|
-
* @typeParam P - the context shape this step contributes.
|
|
367
|
-
*/
|
|
368
|
-
interface MiddlewareResult<P extends object> {
|
|
369
|
-
readonly [MW_PROVIDES]?: P;
|
|
370
|
-
}
|
|
371
|
-
/**
|
|
372
|
-
* The argument passed to a middleware function.
|
|
373
|
-
*
|
|
374
|
-
* @typeParam Req - the accumulated context from earlier middleware in the chain.
|
|
375
|
-
*/
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
* Erased middleware shape used by the runtime and by variance-friendly
|
|
379
|
-
* constraints. Carries phantom fields (`__p`/`__req`/`__lp`/`__opt`/`__isLoader`/
|
|
380
|
-
* `__lz`) that are type-only.
|
|
381
|
-
*
|
|
382
|
-
* A middleware value is a **def** (contract only): its {@link AnyMiddleware.run}
|
|
383
|
-
* is an unbound placeholder that throws if executed. The runtime fn is supplied
|
|
384
|
-
* separately via `implement(api).middleware(def, impl)` and bound at `server()`.
|
|
385
|
-
*
|
|
386
|
-
* @internal
|
|
387
|
-
*/
|
|
388
|
-
interface AnyMiddleware {
|
|
389
|
-
readonly kind: 'middleware';
|
|
390
|
-
readonly name: string;
|
|
391
|
-
readonly requires: readonly AnyMiddleware[];
|
|
392
|
-
readonly optional: readonly AnyMiddleware[];
|
|
393
|
-
readonly paramKey: string | undefined;
|
|
394
|
-
readonly paramSchema: z.ZodType | undefined;
|
|
395
|
-
readonly doc: MiddlewareDoc | undefined;
|
|
396
|
-
/** internal: loosest possible run signature; on a def it is an unbound placeholder that throws. */
|
|
397
|
-
readonly run: (io: {
|
|
398
|
-
req: Request;
|
|
399
|
-
ctx: never;
|
|
400
|
-
next: (add?: object) => Promise<MiddlewareResult<object>>;
|
|
401
|
-
value?: never;
|
|
402
|
-
}) => Promise<MiddlewareResult<object> | Response>;
|
|
403
|
-
readonly __p: object;
|
|
404
|
-
readonly __req: readonly AnyMiddleware[];
|
|
405
|
-
readonly __lp: object;
|
|
406
|
-
/** internal phantom: the `optional` list, so an impl's `io.ctx` can include `Partial<provides-of-optional>`. */
|
|
407
|
-
readonly __opt: readonly AnyMiddleware[];
|
|
408
|
-
/** internal phantom: `true` only for `middleware.loader` defs — selects the loader-shaped impl. */
|
|
409
|
-
readonly __isLoader: boolean;
|
|
410
|
-
/** internal phantom: a loader def's own param schema (the `value` an impl receives); `ZodNever` for non-loaders. */
|
|
411
|
-
readonly __lz: z.ZodType;
|
|
412
|
-
}
|
|
413
|
-
/** Everything a middleware provides, including (recursively) what its `requires` provide. */
|
|
414
|
-
|
|
415
|
-
/**
|
|
416
|
-
* Middleware-level OpenAPI contributions, applied to every operation whose chain
|
|
417
|
-
* includes the middleware.
|
|
418
|
-
*/
|
|
419
|
-
interface MiddlewareDoc {
|
|
420
|
-
/** Named security schemes — merged into `components.securitySchemes` and required on each op. */
|
|
421
|
-
readonly security?: Readonly<Record<string, Json>>;
|
|
422
|
-
/** Patch applied to every operation whose chain includes this middleware. */
|
|
423
|
-
readonly openapi?: (op: Record<string, Json>) => Record<string, Json>;
|
|
424
|
-
}
|
|
425
|
-
/**
|
|
426
|
-
* Expand `requires` (auto-include) and topologically order a middleware list.
|
|
427
|
-
*
|
|
428
|
-
* `requires` edges pull dependencies in and force them earlier; `optional` edges
|
|
429
|
-
* only reorder middleware that are *already present*. Throws on a dependency
|
|
430
|
-
* cycle.
|
|
431
|
-
*
|
|
432
|
-
* @internal
|
|
433
|
-
*/
|
|
434
|
-
//#endregion
|
|
435
|
-
//#region src/endpoint.d.ts
|
|
436
|
-
/** Endpoint-level OpenAPI metadata, plus an escape hatch over the generated operation. */
|
|
437
|
-
interface EndpointDoc {
|
|
438
|
-
readonly summary?: string;
|
|
439
|
-
readonly description?: string;
|
|
440
|
-
readonly tags?: readonly string[];
|
|
441
|
-
readonly deprecated?: boolean;
|
|
442
|
-
readonly operationId?: string;
|
|
443
|
-
/** Final say over this endpoint's generated operation object. */
|
|
444
|
-
readonly openapi?: (op: Record<string, Json>) => Record<string, Json>;
|
|
445
|
-
}
|
|
446
|
-
/** Event-level AsyncAPI metadata, plus an escape hatch over the generated channel. */
|
|
447
|
-
interface EventDoc {
|
|
448
|
-
readonly summary?: string;
|
|
449
|
-
readonly description?: string;
|
|
450
|
-
/** Final say over this event's generated channel object. */
|
|
451
|
-
readonly asyncapi?: (channel: Record<string, Json>) => Record<string, Json>;
|
|
452
|
-
}
|
|
453
|
-
/** Spec-level final patches over the whole generated documents. */
|
|
454
|
-
interface SpecDoc {
|
|
455
|
-
readonly openapi?: (doc: Record<string, Json>) => Record<string, Json>;
|
|
456
|
-
readonly asyncapi?: (doc: Record<string, Json>) => Record<string, Json>;
|
|
457
|
-
}
|
|
458
|
-
/** Options for a `Set-Cookie` written via the handler's `cookie()`. */
|
|
459
|
-
|
|
460
|
-
/**
|
|
461
|
-
* The declarative configuration for one endpoint.
|
|
462
|
-
*
|
|
463
|
-
* Schemas are the single source of truth: each kind below contributes typed keys
|
|
464
|
-
* to the endpoint's single `data` payload (path params, query, body, files), and
|
|
465
|
-
* the kinds must be disjoint. Streaming, multi-status, typed errors, custom
|
|
466
|
-
* method/path, and documentation are all declared here.
|
|
467
|
-
*/
|
|
468
|
-
interface EndpointConfig {
|
|
469
|
-
/** Path params as a `z.object`; keys must be positioned in the path. */
|
|
470
|
-
readonly params?: z.ZodType;
|
|
471
|
-
/** Query params as a `z.object`. */
|
|
472
|
-
readonly query?: z.ZodType;
|
|
473
|
-
/** Request body — a `z.object` (merges into `data`) or any other schema (then it *is* `data`). */
|
|
474
|
-
readonly body?: z.ZodType;
|
|
475
|
-
/** Multipart file fields, keyed by form-field name. Declaring files forces `httpOnly`. */
|
|
476
|
-
readonly files?: Readonly<Record<string, z.ZodType>>;
|
|
477
|
-
/** Typed request headers (`z.object`, lowercase keys) — handler gets `headers`; never merged into `data`. */
|
|
478
|
-
readonly headers?: z.ZodType;
|
|
479
|
-
/** Typed request cookies (`z.object`) — handler gets `cookies`; server-side input only. */
|
|
480
|
-
readonly cookies?: z.ZodType;
|
|
481
|
-
/** Single success response schema. */
|
|
482
|
-
readonly response?: z.ZodType;
|
|
483
|
-
/** Multi-status success responses by code — handler returns `{ status, data }`, client gets a `{ status, data }` union. */
|
|
484
|
-
readonly responses?: Readonly<Record<number, z.ZodType>>;
|
|
485
|
-
/** Declared error responses by status — documents them and types the handler's `fail()`. */
|
|
486
|
-
readonly errors?: Readonly<Record<number, z.ZodType>>;
|
|
487
|
-
/** Body wire encoding (default `'json'`); `'urlencoded'` for `application/x-www-form-urlencoded` HTML forms. */
|
|
488
|
-
readonly bodyEncoding?: 'json' | 'urlencoded';
|
|
489
|
-
/** Typed item-stream output encoding (default `'ndjson'`); `'sse'` for `text/event-stream`. */
|
|
490
|
-
readonly streamEncoding?: 'ndjson' | 'sse';
|
|
491
|
-
readonly doc?: EndpointDoc;
|
|
492
|
-
/** HTTP method (default `POST`). */
|
|
493
|
-
readonly method?: HttpMethod;
|
|
494
|
-
/** Custom path: a `:key` string, or a {@link path} template whose schemas join the params kind. */
|
|
495
|
-
readonly path?: string | AnyPathTemplate;
|
|
496
|
-
/** Explicit WebSocket id (default: the un-injected url pattern + method). */
|
|
497
|
-
readonly ws?: string;
|
|
498
|
-
/** Force the endpoint to be HTTP-only (no ws). */
|
|
499
|
-
readonly httpOnly?: boolean;
|
|
500
|
-
/**
|
|
501
|
-
* Streaming request body. `string` → raw byte stream with that content-type
|
|
502
|
-
* (`stream: ReadableStream<Uint8Array>`); zod schema → typed NDJSON item stream
|
|
503
|
-
* (client passes an async iterable as `stream`, handler for-awaits validated items).
|
|
504
|
-
*/
|
|
505
|
-
readonly streamIn?: string | z.ZodType;
|
|
506
|
-
/**
|
|
507
|
-
* Streaming response. `string` → raw byte stream with that content-type
|
|
508
|
-
* (handler returns/pipes bytes); zod schema → typed item stream over
|
|
509
|
-
* NDJSON/SSE (handler is an async generator, client `for await`s typed items).
|
|
510
|
-
*/
|
|
511
|
-
readonly streamOut?: string | z.ZodType;
|
|
512
|
-
/** Raw `streamOut` only: serve with `Content-Disposition: attachment; filename="…"`. */
|
|
513
|
-
readonly download?: string;
|
|
514
|
-
}
|
|
515
|
-
/** Erased (non-generic) endpoint shape used by the runtime. @internal */
|
|
516
|
-
interface AnyEndpoint {
|
|
517
|
-
readonly kind: 'endpoint';
|
|
518
|
-
readonly cfg: EndpointConfig;
|
|
519
|
-
readonly mws: readonly AnyMiddleware[];
|
|
520
|
-
readonly prefixes: ReadonlyArray<string | AnyPathTemplate>;
|
|
521
|
-
readonly __ctx: object;
|
|
522
|
-
readonly __lp: object;
|
|
523
|
-
}
|
|
524
|
-
/**
|
|
525
|
-
* A fully-typed endpoint declaration.
|
|
526
|
-
*
|
|
527
|
-
* @typeParam C - the literal {@link EndpointConfig}.
|
|
528
|
-
* @typeParam Ctx - middleware-provided context visible to the handler.
|
|
529
|
-
* @typeParam LP - loader-param schemas in scope.
|
|
530
|
-
* @typeParam PFX - prefix-param schemas in scope.
|
|
531
|
-
*/
|
|
532
|
-
|
|
533
|
-
/** Configuration for a server-pushed event channel. */
|
|
534
|
-
interface EventConfig {
|
|
535
|
-
/** Channel params as a `z.object` — subscriptions are keyed by these. */
|
|
536
|
-
readonly params?: z.ZodType;
|
|
537
|
-
/** Event payload schema. */
|
|
538
|
-
readonly data: z.ZodType;
|
|
539
|
-
/** Middleware chain that must pass before a client may subscribe. */
|
|
540
|
-
readonly guard?: readonly AnyMiddleware[];
|
|
541
|
-
/** Explicit WebSocket channel id (default: the event name). */
|
|
542
|
-
readonly ws?: string;
|
|
543
|
-
readonly doc?: EventDoc;
|
|
544
|
-
}
|
|
545
|
-
/** The shape passed to {@link spec}. */
|
|
546
|
-
interface SpecShape {
|
|
547
|
-
readonly endpoints: Readonly<Record<string, AnyEndpoint>>;
|
|
548
|
-
readonly events?: Readonly<Record<string, EventConfig>>;
|
|
549
|
-
readonly doc?: SpecDoc;
|
|
550
|
-
}
|
|
551
|
-
/** Any normalized spec. */
|
|
552
|
-
type AnySpec = SpecShape;
|
|
553
|
-
/** Extract the events record of a spec (or `{}` when it has none). */
|
|
554
|
-
type EventsOf<S extends AnySpec> = S['events'] extends Readonly<Record<string, EventConfig>> ? S['events'] : EmptyObject;
|
|
555
|
-
/**
|
|
556
|
-
* Finalize a set of endpoints + events into a spec, validating every endpoint at
|
|
557
|
-
* definition time.
|
|
558
|
-
*
|
|
559
|
-
* Beyond the compile-time {@link CheckCfg} guarantees, this performs runtime
|
|
560
|
-
* sanity checks (flag exclusivity, kind shapes) and full
|
|
561
|
-
* {@link normalizeEndpoint | path/coverage/disjointness} validation — throwing
|
|
562
|
-
* immediately on any violation so misconfiguration fails at module init.
|
|
563
|
-
*
|
|
564
|
-
* @returns the same spec object, now type-branded and validated.
|
|
565
|
-
*/
|
|
566
|
-
|
|
567
|
-
/** Read the keys of a `z.object` schema, or `null` for any other (or absent) schema. @internal */
|
|
568
|
-
|
|
569
|
-
/** The fully-resolved runtime description of one endpoint. @internal */
|
|
570
|
-
interface NormalizedEp {
|
|
571
|
-
readonly name: string;
|
|
572
|
-
readonly def: AnyEndpoint;
|
|
573
|
-
readonly method: HttpMethod;
|
|
574
|
-
readonly parts: readonly PathPart[];
|
|
575
|
-
readonly path: string;
|
|
576
|
-
/** Explicit ws id only — the default ws identity is method + path pattern. */
|
|
577
|
-
readonly ws: string | null;
|
|
578
|
-
readonly wsEligible: boolean;
|
|
579
|
-
readonly httpOnly: boolean;
|
|
580
|
-
readonly streamInCt: string | null;
|
|
581
|
-
readonly itemsIn: boolean;
|
|
582
|
-
readonly streamOutCt: string | null;
|
|
583
|
-
readonly items: boolean;
|
|
584
|
-
readonly sse: boolean;
|
|
585
|
-
readonly multi: boolean;
|
|
586
|
-
readonly bodyEnc: 'json' | 'urlencoded' | null;
|
|
587
|
-
readonly p: readonly string[];
|
|
588
|
-
readonly q: readonly string[];
|
|
589
|
-
readonly b: readonly string[] | null;
|
|
590
|
-
readonly bRaw: boolean;
|
|
591
|
-
readonly f: readonly string[];
|
|
592
|
-
readonly loaders: ReadonlyMap<string, z.ZodType>;
|
|
593
|
-
/** Per-key schemas from own + prefix templates. */
|
|
594
|
-
readonly tplSchemas: ReadonlyMap<string, z.ZodType>;
|
|
595
|
-
readonly chain: readonly AnyMiddleware[];
|
|
596
|
-
}
|
|
597
|
-
/**
|
|
598
|
-
* Resolve an endpoint declaration into a {@link NormalizedEp}: assemble its path
|
|
599
|
-
* parts (prefixes → own path → default), verify exact-once param declaration and
|
|
600
|
-
* positioning, and enforce kind disjointness. Throws on any violation.
|
|
601
|
-
*
|
|
602
|
-
* @internal
|
|
603
|
-
*/
|
|
604
|
-
//#endregion
|
|
605
|
-
//#region src/caller.d.ts
|
|
606
|
-
/** Deterministic JSON (sorted keys) — the default cache key for a call's `data`. */
|
|
607
|
-
//#endregion
|
|
608
5
|
//#region src/index.d.ts
|
|
6
|
+
|
|
609
7
|
/** A minimal structural view of the ayepi server surface this adapter drives. */
|
|
610
8
|
type AnyApp = Server<AnySpec>;
|
|
611
9
|
/** Options for {@link serve}. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ayepi/node",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Node.js (node:http + ws) adapter for @ayepi/core — bridges IncomingMessage ⇄ fetch Request/Response and serves WebSocket upgrades",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"type": "module",
|
|
19
19
|
"sideEffects": false,
|
|
20
20
|
"files": [
|
|
21
|
-
"dist"
|
|
21
|
+
"dist",
|
|
22
|
+
"ayepi-*.md"
|
|
22
23
|
],
|
|
23
24
|
"exports": {
|
|
24
25
|
".": {
|
|
@@ -37,7 +38,7 @@
|
|
|
37
38
|
"node": ">=18"
|
|
38
39
|
},
|
|
39
40
|
"peerDependencies": {
|
|
40
|
-
"@ayepi/core": "^0.
|
|
41
|
+
"@ayepi/core": "^0.2.0"
|
|
41
42
|
},
|
|
42
43
|
"dependencies": {
|
|
43
44
|
"ws": "^8.18.0"
|
|
@@ -50,7 +51,7 @@
|
|
|
50
51
|
"tsdown": "^0.12.0",
|
|
51
52
|
"vitest": "^2.1.8",
|
|
52
53
|
"zod": "^4.4.3",
|
|
53
|
-
"@ayepi/core": "0.
|
|
54
|
+
"@ayepi/core": "0.2.0"
|
|
54
55
|
},
|
|
55
56
|
"keywords": [
|
|
56
57
|
"ayepi",
|