@defjs/core 0.4.0-beta.1 → 0.5.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.
Files changed (5) hide show
  1. package/README.md +44 -61
  2. package/index.d.ts +1923 -579
  3. package/index.js +1927 -1419
  4. package/index.min.js +21 -27
  5. package/package.json +2 -1
package/index.d.ts CHANGED
@@ -1,13 +1,29 @@
1
- //#region src/internal/http_request.d.ts
2
1
  /**
3
- * How the HTTP response body should be parsed before struct validation.
4
- * Chosen from the declared struct kind: string is text, blob and file are blobs,
5
- * arrayBuffer is bytes, and every other kind is JSON.
2
+ * Representation used to read an HTTP body before struct validation.
3
+ *
4
+ * Chosen from the declared struct kind: `string` is `text`, `blob` and `file`
5
+ * are `blob`, `arrayBuffer` is `arraybuffer` (bytes), and every other kind is
6
+ * `json`. Not a field on `defineRequest`. It appears on `HttpRequest` as
7
+ * `responseType` and `errorResponseType`.
8
+ *
9
+ * @group http
6
10
  */
7
- type HttpResponseType = 'arraybuffer' | 'blob' | 'json' | 'text';
11
+ type HttpResponseType = "arraybuffer" | "blob" | "json" | "text";
8
12
  /**
9
- * Progress snapshot for upload or download callbacks.
10
- * Mirrors the familiar `ProgressEvent` shape (`loaded` / `total` / `lengthComputable`).
13
+ * Progress snapshot passed to upload and download callbacks.
14
+ *
15
+ * Mirrors the `ProgressEvent` fields `loaded`, `total`, and `lengthComputable`.
16
+ * `loaded` counts bytes read from the upload or download stream, not bytes
17
+ * acknowledged by the server. Download bytes may already be decompressed.
18
+ * Unknown lengths use `total: 0` and `lengthComputable: false`. A download total
19
+ * is computable only with a positive `Content-Length` and an absent or
20
+ * `identity` `Content-Encoding`; compressed downloads, other encoded responses,
21
+ * and bytes exceeding `Content-Length` have an unknown decoded length. A
22
+ * streamed FormData upload also has an unknown total unless a valid explicit
23
+ * `Content-Length` supplies it. Other unknown-length streams use the same
24
+ * `0` / `false` fields.
25
+ *
26
+ * @group http
11
27
  */
12
28
  interface HttpProgressEvent {
13
29
  readonly lengthComputable: boolean;
@@ -16,24 +32,67 @@ interface HttpProgressEvent {
16
32
  }
17
33
  /**
18
34
  * Callback invoked as request bytes are uploaded or response bytes are downloaded.
35
+ *
36
+ * Passed as `onDownloadProgress` or `onUploadProgress` in `HttpExecuteOptions`.
37
+ * Callbacks may be async and are awaited before the next chunk is processed. A
38
+ * callback that throws or rejects fails the call with `EXT_OBSERVER_FAILED`,
39
+ * with the thrown value as `cause`, for downloads and uploads alike. Caller
40
+ * cancellation still takes precedence and reports `NET_ABORTED`, or `NET_TIMEOUT`
41
+ * when the abort reason is a timeout.
42
+ *
43
+ * Upload progress for a `Blob`, `File`, `ArrayBuffer`, or `FormData` body is
44
+ * reported only where the runtime can stream request bodies (Bun, Node). In
45
+ * browsers that body is sent unchanged and `onUploadProgress` is not called. A
46
+ * caller-supplied `ReadableStream` still reports progress wherever the runtime
47
+ * accepts it. Download progress is unaffected.
48
+ *
49
+ * On server runtimes, FormData with `onUploadProgress` reads its serialized
50
+ * stream directly, keeps its multipart boundary, and is sent without a
51
+ * library-generated `Content-Length` (chunked over HTTP/1.1), so progress
52
+ * and abort work without buffering the whole body. It reports an
53
+ * unknown total unless a valid explicit `Content-Length` supplies it. For an
54
+ * endpoint that requires `Content-Length`, such as some presigned upload
55
+ * targets, omit `onUploadProgress`; platform Fetch then sends FormData with its
56
+ * length. Non-identity encoded downloads also report `total: 0` and
57
+ * `lengthComputable: false`, since `loaded` counts decoded stream bytes.
58
+ * Cancellation releases the wrapped upload stream.
59
+ *
60
+ * @group http
19
61
  */
20
62
  type HttpProgressFn = (event: HttpProgressEvent) => Promise<void> | void;
21
63
  /**
22
- * Normalized outgoing HTTP request used by handlers, interceptors, and `fetchHandler`.
64
+ * Normalized outgoing HTTP request seen by transports, handlers, interceptors, and `fetchHandler` after the builder runs.
23
65
  *
24
- * After the request builder runs, `body` is already the wire value — for JSON that means a
25
- * **JSON text string**. Sign HMAC / digests against that string (or other final bytes), not the
66
+ * `body` is already the wire value. For JSON that is a **JSON text string**;
67
+ * sign HMAC or digests against that string (or other final bytes), not the
26
68
  * original struct input.
69
+ *
70
+ * - `bodyContentType` holds the inferred type of the builder's body. `null` suppresses that header. FormData owns its multipart boundary. HTTP inference follows a direct write to `body` and is not copied by object spread; an explicit `bodyContentType` assignment is copied. A replacement body is detected again, so declare its type with a `content-type` header. For the unchanged body, `null` suppresses library inference when there is no explicit header, but native Fetch may add its own string type.
71
+ * - HTTP never writes inferred Content-Type into `headers`; interceptors see only declared headers. An explicit `content-type` header always wins over `bodyContentType` and detection, whether set by the builder, client defaults, an interceptor, or a raw `fetchHandler` request. `body: undefined` has no library-inferred Content-Type. Signers should read `headers.get('content-type') ?? request.bodyContentType`; do not configure internal fields.
72
+ * - `endpoint` is the path with path parameters filled in; `baseEndpoint` is the client endpoint.
73
+ * - Interceptors see `queryParams` as `new URLSearchParams(queryString)`. A builder that returns `URLSearchParams` passes that instance through.
74
+ * - `operation` is a static, low-cardinality endpoint identity for telemetry and diagnostics.
75
+ * - `downloadProgress` and `uploadProgress` are the `onDownloadProgress` and `onUploadProgress` hooks of `HttpExecuteOptions`.
76
+ * - `responseType` and `errorResponseType` are readonly: they are derived from the endpoint's `output` and `error` struct kinds and must not be changed. Each is set only when its struct is declared. An interceptor that short-circuits with `makeResponse` reads them to match the transport.
77
+ *
78
+ * @group http
27
79
  */
28
80
  interface HttpRequest {
29
81
  abort?: AbortSignal;
82
+ /** Trusted absolute base; its origin and directory bound the resolved endpoint path. */
30
83
  baseEndpoint?: string;
31
84
  body?: Blob | ArrayBuffer | FormData | URLSearchParams | ReadableStream<Uint8Array> | object | string | number | boolean | null;
85
+ /**
86
+ * Current body media type; null suppresses the header. FormData owns its boundary.
87
+ * HTTP inference follows direct body mutations and is not copied by object spread; explicit assignments are copied.
88
+ */
32
89
  bodyContentType?: string | null;
33
90
  /** @internal Tracks which body value produced bodyContentType so interceptors cannot leave stale metadata behind. */
34
91
  bodyContentTypeSource?: unknown;
35
92
  downloadProgress?: HttpProgressFn;
93
+ /** Relative path validated after building and again when an interceptor calls the transport. */
36
94
  endpoint: string;
95
+ /** HTTP interceptors see declared headers; detected Content-Type is applied at the Fetch boundary. */
37
96
  headers?: Headers;
38
97
  method: string;
39
98
  /** Static, low-cardinality endpoint identity for telemetry and diagnostics. */
@@ -52,7 +111,6 @@ interface HttpRequest {
52
111
  * unread and does not fall back to `responseType`.
53
112
  */
54
113
  readonly errorResponseType?: HttpResponseType;
55
- timeout?: number;
56
114
  uploadProgress?: HttpProgressFn;
57
115
  withCredentials?: boolean;
58
116
  xsrf?: {
@@ -63,12 +121,19 @@ interface HttpRequest {
63
121
  }) => string | null | undefined;
64
122
  };
65
123
  }
66
- //#endregion
67
- //#region src/internal/http_response.d.ts
68
124
  declare const HTTP_RESPONSE: unique symbol;
69
125
  /**
70
- * Wire-shape HTTP response returned by transports and accepted by interceptors.
71
- * `ok` is true for status codes in the 2xx range. A transport failure is a fault, never a response.
126
+ * Wire-shape HTTP response that transports produce and interceptors accept.
127
+ *
128
+ * `body` is parsed JSON, text, a `Blob`, or an `ArrayBuffer`, according to
129
+ * `responseType`, not a decoded struct value. `body` is `undefined` when no body
130
+ * was present; a JSON `null` stays `null`. For a JSON representation, a string
131
+ * is a JSON string root and is not parsed again; bytes and blobs are raw and
132
+ * are parsed once. `ok` is true for status codes in the 2xx range. A transport
133
+ * failure is a fault, never a response. `DecodedResponse` is what reaches the
134
+ * caller. The symbol key is an internal brand. Build values with `makeResponse`.
135
+ *
136
+ * @group http
72
137
  */
73
138
  type HttpResponse<R> = {
74
139
  readonly [HTTP_RESPONSE]: true;
@@ -83,7 +148,9 @@ type HttpResponse<R> = {
83
148
  /**
84
149
  * Response metadata, available whenever a response reached the client.
85
150
  *
86
- * Carries no body: a body only exists once a declared struct decoded it.
151
+ * It carries no `body`: a body only exists once a declared struct decoded it.
152
+ *
153
+ * @group http
87
154
  */
88
155
  type HttpMeta = {
89
156
  readonly headers: Headers;
@@ -93,16 +160,27 @@ type HttpMeta = {
93
160
  readonly url: string;
94
161
  };
95
162
  /**
96
- * Response whose body decoded successfully against a declared struct.
163
+ * Response metadata plus a body that decoded against a declared struct.
97
164
  *
98
- * Reaching this type is the proof that decoding happened; a failed decode is reported
99
- * as a fault carrying `HttpMeta` alone.
165
+ * Holding this type is the proof that decoding happened; a failed decode is
166
+ * reported as a fault carrying `HttpMeta` alone.
167
+ *
168
+ * @group http
100
169
  */
101
170
  type DecodedResponse<TBody> = HttpMeta & {
102
171
  readonly body: TBody;
103
172
  };
104
173
  /**
105
- * Fields accepted by `makeResponse` when building a synthetic `HttpResponse` (for example in interceptors).
174
+ * Fields accepted by `makeResponse` when building a synthetic `HttpResponse`, for example in interceptors.
175
+ *
176
+ * `status` defaults to `0` and `statusText` to `''`. `url` and `headers` default
177
+ * to values taken from `request`: its resolved URL (base endpoint, path and
178
+ * query) and a copy of its headers. A request without `baseEndpoint` gives its
179
+ * `endpoint` as written. Without a request, `url` is `''` and `headers` is
180
+ * empty. An omitted `body` stays `undefined`; only an explicit `null` is a JSON
181
+ * `null`.
182
+ *
183
+ * @group http
106
184
  */
107
185
  type MakeResponseOptions<R> = {
108
186
  status?: number;
@@ -113,34 +191,124 @@ type MakeResponseOptions<R> = {
113
191
  request?: HttpRequest;
114
192
  };
115
193
  /**
116
- * Build an `HttpResponse` value without performing a network call.
117
- * Useful in interceptors that short-circuit `next`, and in tests.
194
+ * Builds an `HttpResponse` without a network call, for interceptors that short-circuit `next` and for tests.
195
+ *
196
+ * Default status is `0`, so pass a 2xx `status` for a success. `ok` is true for
197
+ * 2xx. The value it returns goes through the same media-type check and the same
198
+ * declared struct as one off the wire; there is no trusted short-circuit. For a
199
+ * JSON body, set a JSON `content-type` header.
118
200
  *
119
201
  * @param options - Status, headers, body, and an optional request to copy headers/url from; defaults yield status `0`.
120
202
  * @returns An `HttpResponse` with `ok` derived from the status code.
203
+ * @group http
121
204
  */
122
205
  export declare function makeResponse<R>(options?: MakeResponseOptions<R>): HttpResponse<R>;
123
- //#endregion
124
- //#region src/error/cause.d.ts
125
- /** Shared cause/message sentinel for aborted requests. */
206
+ /**
207
+ * Shared Error sentinel that marks a request as aborted.
208
+ *
209
+ * Its `message` is `Request was aborted`. Shared `cause` and message value for caller cancellation. `createNetworkFault`
210
+ * maps it, and a `DOMException` named `AbortError`, to `NET_ABORTED`. An HTTP
211
+ * interceptor that throws it makes the call resolve with a `NET_ABORTED` fault.
212
+ * SSE and WebSocket interceptors that throw it are reported as
213
+ * `EXT_INTERCEPTOR_FAILED`.
214
+ *
215
+ * The sentinel carries a non-enumerable `Symbol.for('defjs.cause.aborted')`
216
+ * brand. {@link isAbortCause} recognizes this sentinel from any Core copy
217
+ * without comparing object identity. Classification is independent of unrelated
218
+ * earlier executions.
219
+ *
220
+ * @group errors
221
+ */
126
222
  export declare const ERR_ABORTED: Error;
127
- /** Shared cause/message sentinel for timed-out requests. */
223
+ /**
224
+ * Shared Error sentinel that marks a request as timed out.
225
+ *
226
+ * Its `message` is `Request timed out`. Shared `cause` and message value for timeouts. `createNetworkFault` maps it,
227
+ * and any error named `TimeoutError`, to `NET_TIMEOUT`. An HTTP interceptor
228
+ * that throws it makes the call resolve with a `NET_TIMEOUT` fault. SSE and
229
+ * WebSocket interceptors that throw it are reported as `EXT_INTERCEPTOR_FAILED`.
230
+ *
231
+ * The sentinel carries a non-enumerable `Symbol.for('defjs.cause.timeout')`
232
+ * brand. {@link isTimeoutCause} recognizes this sentinel from any Core copy
233
+ * without comparing object identity. Classification is independent of unrelated
234
+ * earlier executions.
235
+ *
236
+ * @group errors
237
+ */
128
238
  export declare const ERR_TIMEOUT: Error;
129
- //#endregion
130
- //#region src/struct/errors.d.ts
131
239
  /**
132
- * Optional callback that rewrites a `StructIssue` message before it is stored.
240
+ * Whether `cause` is caller cancellation, across Core copies and platform abort errors.
241
+ *
242
+ * Returns `true` for an `Error` carrying the non-enumerable
243
+ * `Symbol.for('defjs.cause.aborted')` brand, including `ERR_ABORTED` from
244
+ * another Core copy, or a `DOMException` named `AbortError`. An ordinary
245
+ * unbranded `Error` with that name does not match. Other values return `false`.
246
+ *
247
+ * Pass the underlying `fault.cause`; this function does not unwrap faults.
248
+ * `createNetworkFault` uses this check to select `NET_ABORTED`. OpenTelemetry
249
+ * uses it to suppress caller-cancellation logs while preserving timeout logs.
250
+ *
251
+ * @group errors
252
+ * @see {@link ERR_ABORTED}
253
+ * @see {@link isTimeoutCause}
254
+ */
255
+ export declare function isAbortCause(cause: unknown): boolean;
256
+ /**
257
+ * Whether `cause` is a timeout, across Core copies and platform timeout errors.
258
+ *
259
+ * Returns `true` for an `Error` carrying the non-enumerable
260
+ * `Symbol.for('defjs.cause.timeout')` brand, including `ERR_TIMEOUT` from
261
+ * another Core copy, or a `DOMException` or `Error` named `TimeoutError`.
262
+ * Other values return `false`.
263
+ *
264
+ * Pass the underlying `fault.cause`; this function does not unwrap faults.
265
+ * `createNetworkFault` uses this check to select `NET_TIMEOUT`. OpenTelemetry
266
+ * keeps logging timeouts even when the abort signal is also set.
267
+ *
268
+ * @group errors
269
+ * @see {@link ERR_TIMEOUT}
270
+ * @see {@link isAbortCause}
271
+ */
272
+ export declare function isTimeoutCause(cause: unknown): boolean;
273
+ /**
274
+ * Type-only export of the internal enumerable tag key on objects produced while parsing endpoint input, used by the outbound encoder.
275
+ *
276
+ * `STRUCT` is exported as a **type** only (`export type { STRUCT }`). `@defjs/core` has no runtime `STRUCT` export, so `import { STRUCT } from '@defjs/core'` fails to compile and gives no value.
277
+ *
278
+ * It marks those objects so the outbound encoder knows which struct produced them. Response decoding and `struct.parse` leave it unset. The symbol is internal to the outbound encoder; application code should not depend on it.
279
+ *
280
+ * @internal
281
+ * @group struct
282
+ */
283
+ declare const STRUCT: unique symbol;
284
+ /**
285
+ * Optional callback that rewrites a {@link StructIssue} message before it is stored.
133
286
  *
134
- * Return a string to replace the default message, or `undefined` to keep it.
287
+ * Passed as the `errorMap` option of `struct.parse`. It is called for each issue (`code`, `expected`, `message`, `path`, `received`) before the issue is recorded. Any string, including `''`, replaces the default message; only `undefined` keeps it. The map belongs to this parse call and also handles bigint/date conversion failures from a struct created by another Core copy. Default messages omit received values; custom text is used verbatim, so avoid including sensitive `received` values.
135
288
  *
136
289
  * @param issue - Issue about to be recorded.
137
290
  * @returns Replacement message, or `undefined` to keep the default.
291
+ * @group struct
138
292
  */
139
293
  type ErrorMap = (issue: StructIssue) => string | undefined;
140
294
  /**
141
- * Aggregate of one or more `StructIssue`s from a failed parse.
295
+ * Error returned by a failed struct parse, aggregating one or more {@link StructIssue}s and their formatters.
296
+ *
297
+ * `name` is `'StructError'`. Construct it with `constructor(issues: StructIssue[])`. `issues` is the `readonly` list of those issues. `message` is the issue's message for a single issue, `N struct issues: <first message>` for several, and `Struct parse failed` for none.
298
+ *
299
+ * Default issue messages name the received kind and omit the value, so `message` and `prettify()` can be logged. For example, `Expected number at id, received string`. `message` and the formatters use that text. The raw value stays on `StructIssue.received`; custom `errorMap` text is used verbatim and can reintroduce sensitive values.
142
300
  *
143
301
  * Use `format()`, `flatten()`, or `prettify()` to present issues to callers.
302
+ *
303
+ * - `struct.parse` stops at the first failure, so `issues` holds one {@link StructIssue} there.
304
+ *
305
+ * - `format()` returns a {@link FormattedStructError} tree. Each node has an `_errors` array, and nested keys follow `issue.path`.
306
+ *
307
+ * - `flatten()` returns a {@link FlattenedStructError}: messages with an empty path go to `formErrors`, the others are grouped by the first path segment in `fieldErrors`.
308
+ *
309
+ * - `prettify()` returns one `× path: message` line per issue. An empty path is shown as `<root>`.
310
+ *
311
+ * @group struct
144
312
  */
145
313
  export declare class StructError extends Error {
146
314
  /** Issues collected for this failure, in encounter order. */
@@ -164,27 +332,19 @@ export declare class StructError extends Error {
164
332
  /**
165
333
  * Render a multi-line, human-readable summary of all issues.
166
334
  *
335
+ * Each line uses the issue message, which names the received kind and omits the value.
336
+ *
167
337
  * @returns One `× path: message` line per issue.
168
338
  */
169
339
  prettify(): string;
170
340
  }
171
- //#endregion
172
- //#region src/struct/symbols.d.ts
173
- /**
174
- * Enumerable tag on objects produced while parsing endpoint input.
175
- * Response decoding and `struct.parse` leave it unset. Internal to the outbound encoder.
176
- *
177
- * @internal
178
- */
179
- declare const STRUCT: unique symbol;
180
- //#endregion
181
- //#region src/struct/types.d.ts
182
341
  type Path = Array<number | string>;
183
342
  /**
184
- * Error-first parse tuple from `struct.parse`.
343
+ * Error-first tuple returned by `struct.parse`.
185
344
  *
186
- * On success the error is `null` and `value` is the parsed output. On failure
187
- * the error is a `StructError` and `value` is always `undefined`.
345
+ * On success the error is `null` and `value` is the parsed output `O`. On failure the error is a `StructError` and `value` is always `undefined`.
346
+ *
347
+ * @group struct
188
348
  */
189
349
  type ParseResult<O> = [error: null, value: O] | [error: StructError, value: undefined];
190
350
  type LiteralValue = boolean | null | number | string;
@@ -196,9 +356,14 @@ interface StructTypes<Input = unknown, Output = unknown, OptionalOut extends boo
196
356
  /**
197
357
  * Minimal structural brand carried by every struct instance.
198
358
  *
199
- * `_struct` holds input/output phantom types used by `Infer` and `StructInput`.
359
+ * Anything with a `_struct` type payload. `_struct` holds input and output phantom types: {@link StructInput} and {@link Infer} read `input` and `output` from it, and object structs use `optionalOut` to decide which output fields may be absent.
360
+ *
361
+ * `_struct` exists only in the type system. Structs have no `_struct` property at runtime, and a plain object with that type is not a valid struct. `input` and `output` are the phantom types. `optionalOut` is `true` when the third type argument is `true`, and `undefined` otherwise.
362
+ *
363
+ * @group struct
200
364
  */
201
365
  interface StructLike<I = unknown, O = unknown, OO extends boolean = boolean> {
366
+ /** Phantom payload. `optionalOut` is `true` only when this struct's output fields may be absent. */
202
367
  readonly _struct: StructTypes<I, O, OO>;
203
368
  }
204
369
  type OptionalOutputStruct = {
@@ -208,13 +373,25 @@ type OptionalOutputStruct = {
208
373
  };
209
374
  /**
210
375
  * A single validation failure produced while parsing a struct.
376
+ *
377
+ * - `code` is the machine-readable category. The parser produces `invalid_type`, `invalid_literal`, `invalid_enum`, `invalid_union`, `invalid_intersection`, and `missing_key`; `custom` is part of the type, but the built-in structs never produce it.
378
+ *
379
+ * - `expected` describes the expected type or value.
380
+ *
381
+ * - `message` is the final text, after an optional {@link ErrorMap} rewrite.
382
+ *
383
+ * - `path` runs from the parse root to the failing location: field names and array indices. It is empty for a root-level failure.
384
+ *
385
+ * - `received` is the value that failed at `path`.
386
+ *
387
+ * @group struct
211
388
  */
212
389
  interface StructIssue {
213
390
  /** Machine-readable failure category. */
214
- code: 'custom' | 'invalid_enum' | 'invalid_intersection' | 'invalid_literal' | 'invalid_type' | 'invalid_union' | 'missing_key';
391
+ code: "custom" | "invalid_enum" | "invalid_intersection" | "invalid_literal" | "invalid_type" | "invalid_union" | "missing_key";
215
392
  /** Human-readable description of the expected type or value. */
216
393
  expected: string;
217
- /** Final message after optional `ErrorMap` rewriting. */
394
+ /** Final message after optional `ErrorMap` rewriting. The default names the received kind, not the value. */
218
395
  message: string;
219
396
  /** Path from the parse root to the failing location. */
220
397
  path: Path;
@@ -224,14 +401,20 @@ interface StructIssue {
224
401
  /**
225
402
  * Nested error tree returned by `StructError.format()`.
226
403
  *
227
- * Leaf messages live under `_errors`; nested keys mirror the failing path.
404
+ * Leaf messages live under `_errors`; nested keys mirror the failing path. A path segment literally named `_errors` is stored as `\_errors`.
405
+ *
406
+ * @group struct
228
407
  */
229
408
  interface FormattedStructError {
230
409
  _errors: string[];
231
410
  [key: string]: FormattedStructError | string[];
232
411
  }
233
412
  /**
234
- * Flat field/form error bags returned by `StructError.flatten()`.
413
+ * Flat field and form error bags returned by `StructError.flatten()`.
414
+ *
415
+ * `formErrors` holds messages of issues with an empty path (root-level issues). `fieldErrors` holds messages keyed by the first path segment, so nested issues are grouped under their top-level field.
416
+ *
417
+ * @group struct
235
418
  */
236
419
  interface FlattenedStructError {
237
420
  /** Messages whose path is empty (root-level issues). */
@@ -243,6 +426,18 @@ interface FlattenedStructError {
243
426
  }
244
427
  /**
245
428
  * Chainable modifiers available on every `Struct` instance.
429
+ *
430
+ * Each method returns a new struct and leaves the original unchanged.
431
+ *
432
+ * - `alias(name)` sets the wire name of an object field. `struct.parse` reads logical names unless `aliases: true` is passed; JSON and flat request codecs write the alias. A non-string `name` throws `TypeError`.
433
+ *
434
+ * - `null()` and `nullable()` are the same: they also accept `null`.
435
+ *
436
+ * - `optional()` also accepts `undefined`. In an object, the key may be missing, and an absent field stays absent in the output.
437
+ *
438
+ * - `nullish()` accepts both `null` and `undefined`.
439
+ *
440
+ * @group struct
246
441
  */
247
442
  interface StructMethods<I, O, OO extends boolean> {
248
443
  /**
@@ -278,9 +473,13 @@ interface StructMethods<I, O, OO extends boolean> {
278
473
  optional(): Struct<I | undefined, O | undefined, true>;
279
474
  }
280
475
  /**
281
- * Public struct contract: modifiers plus the `_struct` type brand.
476
+ * Public struct contract: input and output types, modifier methods, and the `_struct` type brand.
477
+ *
478
+ * A parseable shape. `Input` is what it accepts ({@link StructInput}), `Output` is what a successful parse returns ({@link Infer}), and `OptionalOut` marks a field that may be absent from object output.
479
+ *
480
+ * Build structs with the {@link struct} facade (`struct.string()`, `struct.object()`, …). Every modifier in {@link StructMethods} returns a new struct and leaves the original unchanged.
282
481
  *
283
- * Built via the `struct` facade (`struct.string()`, `struct.object()`, …).
482
+ * @group struct
284
483
  */
285
484
  type Struct<Input = unknown, Output = Input, OptionalOut extends boolean = false> = StructMethods<Input, Output, OptionalOut> & StructLike<Input, Output, OptionalOut>;
286
485
  type PresentValue = NonNullable<unknown>;
@@ -299,39 +498,61 @@ interface PreservedStruct<T, I, O, OO extends boolean> extends Struct<I, O, OO>,
299
498
  optional(): PreservedStruct<T, I | undefined, O | undefined, true>;
300
499
  }
301
500
  /**
302
- * Broad struct constraint for endpoint and client generics.
501
+ * Broad struct constraint that accepts any struct, for endpoint and client generics.
303
502
  *
304
- * Prefer a concrete `Struct<…>` when you need precise input/output inference.
503
+ * Used by generics such as `defineRequest` and `Fault`. Prefer a concrete `Struct<…>` when you need precise input and output inference.
504
+ *
505
+ * @group struct
305
506
  */
306
507
  type AnyStruct = Struct<any, any, boolean>;
307
508
  /**
308
- * Infer the accepted input type of a struct.
509
+ * Type helper that extracts the accepted input type of a struct.
510
+ *
511
+ * The type a struct accepts, as opposed to {@link Infer}, which is what a successful parse returns. `T` is a struct, or any {@link StructLike}. Anything else gives `never`.
512
+ *
513
+ * The two differ when a struct converts its input:
514
+ *
515
+ * ```ts
516
+ * const Created = struct.date();
517
+ *
518
+ * type In = StructInput<typeof Created>; // Date | number | string
519
+ * type Out = Infer<typeof Created>; // Date
520
+ * ```
309
521
  *
310
522
  * @typeParam T - A struct-like value with a `_struct.input` brand.
523
+ * @group struct
311
524
  */
312
525
  type StructInput<T> = T extends {
313
526
  readonly _struct: {
314
527
  readonly input: unknown;
315
528
  };
316
- } ? T['_struct']['input'] : never;
529
+ } ? T["_struct"]["input"] : never;
317
530
  type StructOutput<T> = T extends {
318
531
  readonly _struct: {
319
532
  readonly output: unknown;
320
533
  };
321
- } ? T['_struct']['output'] : never;
534
+ } ? T["_struct"]["output"] : never;
322
535
  /**
323
- * Infer the parsed output type of a struct (alias of the internal output brand).
536
+ * Infers the parsed output type of a struct, as an alias of the internal output brand.
537
+ *
538
+ * The type a successful parse produces. Use `Infer<typeof User>` for the decoded value of a struct, and `StructInput` for the accepted input type. A type that is not a struct yields `never`.
539
+ *
540
+ * ```ts
541
+ * const User = struct.object({ id: struct.number(), name: struct.string() });
542
+ * type User = Infer<typeof User>; // { id: number; name: string }
543
+ * ```
324
544
  *
325
545
  * @typeParam T - A struct-like value with a `_struct.output` brand.
546
+ * @group struct
326
547
  */
327
548
  type Infer<T> = StructOutput<T>;
328
- type FieldOutput<S> = S extends StructLike<unknown, unknown, boolean> ? S extends OptionalOutputStruct ? Exclude<S['_struct']['output'], undefined> : S['_struct']['output'] : never;
549
+ type FieldOutput<S> = S extends StructLike<unknown, unknown, boolean> ? S extends OptionalOutputStruct ? Exclude<S["_struct"]["output"], undefined> : S["_struct"]["output"] : never;
329
550
  type Simplify<T> = { [K in keyof T]: T[K]; } & {};
330
551
  type ObjectShape = {
331
552
  [key: string]: any;
332
553
  };
333
- type ObjectInput<T extends ObjectShape> = Simplify<{ -readonly [K in keyof T as T[K] extends OptionalOutputStruct ? never : K]: T[K]['_struct']['input']; } & { -readonly [K in keyof T as T[K] extends OptionalOutputStruct ? K : never]?: Exclude<T[K]['_struct']['input'], undefined>; }>;
334
- type ObjectOutput<T extends ObjectShape> = Simplify<{ -readonly [K in keyof T as T[K] extends OptionalOutputStruct ? never : K]: FieldOutput<T[K]>; } & { -readonly [K in keyof T as T[K] extends OptionalOutputStruct ? K : never]?: FieldOutput<T[K]>; }>;
554
+ type ObjectInput<T extends ObjectShape> = Simplify<{ -readonly [K in keyof T as true extends T[K]["_struct"]["optionalOut"] ? never : K]: T[K]["_struct"]["input"]; } & { -readonly [K in keyof T as true extends T[K]["_struct"]["optionalOut"] ? K : never]?: Exclude<T[K]["_struct"]["input"], undefined>; }>;
555
+ type ObjectOutput<T extends ObjectShape> = Simplify<{ -readonly [K in keyof T as true extends T[K]["_struct"]["optionalOut"] ? never : K]: FieldOutput<T[K]>; } & { -readonly [K in keyof T as true extends T[K]["_struct"]["optionalOut"] ? K : never]?: FieldOutput<T[K]>; }>;
335
556
  type TupleInput<T extends readonly StructLike<unknown, unknown, boolean>[]> = { -readonly [K in keyof T]: StructInput<T[K]>; };
336
557
  type TupleOutput<T extends readonly StructLike<unknown, unknown, boolean>[]> = { -readonly [K in keyof T]: StructOutput<T[K]>; };
337
558
  type UnionOutput<T extends readonly StructLike<unknown, unknown, boolean>[]> = Exclude<StructOutput<T[number]>, undefined>;
@@ -363,10 +584,11 @@ interface ObjectStructTypes<T extends ObjectShape> extends StructTypes<ObjectInp
363
584
  output: ObjectOutput<T>;
364
585
  }
365
586
  /**
366
- * Object-shaped struct produced by `struct.object(shape)`.
587
+ * Object-shaped struct type produced by `struct.object(shape)`.
588
+ *
589
+ * Fields are required by default. A field marked `optional()` or `nullish()` becomes an optional key, so field presence follows those modifiers. Unknown keys are stripped when parsing. Wire names can differ from TypeScript keys via `.alias()`. A failed parse returns `[StructError, undefined]`. `ObjectInput`, `ObjectOutput`, `ObjectStructTypes`, and `PreservedStruct` are internal helper types.
367
590
  *
368
- * Field presence follows each field's optional/nullish modifiers; wire names
369
- * can differ from TypeScript keys via `.alias()`.
591
+ * @group struct
370
592
  */
371
593
  interface ObjectStruct<T extends ObjectShape> extends StructMethods<ObjectInput<T>, ObjectOutput<T>, false>, StructLike<ObjectInput<T>, ObjectOutput<T>, false> {
372
594
  readonly _struct: ObjectStructTypes<T>;
@@ -423,8 +645,6 @@ interface IntersectionStruct<T extends readonly StructLike<unknown, unknown, boo
423
645
  nullish(): PreservedStruct<IntersectionStruct<T>, IntersectionInput<T> | null | undefined, IntersectionOutput<T> | null | undefined, true>;
424
646
  optional(): PreservedStruct<IntersectionStruct<T>, IntersectionInput<T> | undefined, IntersectionOutput<T> | undefined, true>;
425
647
  }
426
- //#endregion
427
- //#region src/error/types.d.ts
428
648
  /**
429
649
  * Every failure Defjs reports, as one closed set of codes.
430
650
  *
@@ -434,7 +654,7 @@ interface IntersectionStruct<T extends readonly StructLike<unknown, unknown, boo
434
654
  * | Class | Meaning |
435
655
  * | ------ | -------------------------------------------------------------- |
436
656
  * | `HTTP` | The peer answered with a non-2xx status. |
437
- * | `REQ` | The request never left: input, execute options, or build. |
657
+ * | `REQ` | The request never left: input, request options, or build. |
438
658
  * | `NET` | No usable response: cancel, timeout, connect, truncated body. |
439
659
  * | `RES` | A response arrived but could not be read as declared. |
440
660
  * | `EXT` | Code you attached to the pipeline threw. |
@@ -443,44 +663,82 @@ interface IntersectionStruct<T extends readonly StructLike<unknown, unknown, boo
443
663
  *
444
664
  * The set is closed on purpose: it keeps `switch (fault.code)` exhaustive. Extensions
445
665
  * report their own detail through `cause`, not through a new code.
666
+ *
667
+ * @group errors
446
668
  */
447
- export type FaultCode = 'CAP_BUFFER_EXCEEDED' | 'CAP_QUEUE_OVERFLOW' | 'ENV_UNSUPPORTED' | 'EXT_HOOK_FAILED' | 'EXT_INTERCEPTOR_FAILED' | 'EXT_OBSERVER_FAILED' | 'HTTP_STATUS' | 'NET_ABORTED' | 'NET_BODY_INCOMPLETE' | 'NET_TIMEOUT' | 'NET_UNREACHABLE' | 'REQ_BUILD_FAILED' | 'REQ_INPUT_INVALID' | 'REQ_OPTIONS_INVALID' | 'RES_DECODE_FAILED' | 'RES_MEDIA_TYPE_INVALID' | 'RES_STRUCT_MISMATCH';
669
+ export type FaultCode = "CAP_BUFFER_EXCEEDED" | "CAP_QUEUE_OVERFLOW" | "ENV_UNSUPPORTED" | "EXT_HOOK_FAILED" | "EXT_INTERCEPTOR_FAILED" | "EXT_OBSERVER_FAILED" | "HTTP_STATUS" | "NET_ABORTED" | "NET_BODY_INCOMPLETE" | "NET_TIMEOUT" | "NET_UNREACHABLE" | "REQ_BUILD_FAILED" | "REQ_INPUT_INVALID" | "REQ_OPTIONS_INVALID" | "RES_DECODE_FAILED" | "RES_MEDIA_TYPE_INVALID" | "RES_STRUCT_MISMATCH";
448
670
  /**
449
- * The class of a fault code: the segment before its first `_`.
671
+ * Extracts the class prefix from a fault code: the segment before its first `_`.
672
+ *
673
+ * `FaultClass<FaultCode>` is
674
+ * `'CAP' | 'ENV' | 'EXT' | 'HTTP' | 'NET' | 'REQ' | 'RES'`.
450
675
  *
451
676
  * @typeParam C - A `FaultCode`, or a union of them.
677
+ * @group errors
452
678
  */
453
679
  export type FaultClass<C extends string> = C extends `${infer TClass}_${string}` ? TClass : never;
454
- /** Codes whose fault carries response metadata but no body. */
455
- export type DecodeFaultCode = 'RES_DECODE_FAILED' | 'RES_MEDIA_TYPE_INVALID' | 'RES_STRUCT_MISMATCH';
456
- /** Codes whose fault may never have reached a response. */
457
- export type PreflightFaultCode = Exclude<FaultCode, DecodeFaultCode | 'HTTP_STATUS'>;
680
+ /**
681
+ * Codes whose fault carries response metadata but no body.
682
+ *
683
+ * These are the `code` values of `DecodeFault`. `PreflightFaultCode` is every
684
+ * other `FaultCode` except `HTTP_STATUS`.
685
+ *
686
+ * @group errors
687
+ */
688
+ export type DecodeFaultCode = "RES_DECODE_FAILED" | "RES_MEDIA_TYPE_INVALID" | "RES_STRUCT_MISMATCH";
689
+ /**
690
+ * Codes whose fault may never have reached a response.
691
+ *
692
+ * Every `FaultCode` except `HTTP_STATUS` and the decode codes:
693
+ * `CAP_BUFFER_EXCEEDED`, `CAP_QUEUE_OVERFLOW`, `ENV_UNSUPPORTED`,
694
+ * `EXT_HOOK_FAILED`, `EXT_INTERCEPTOR_FAILED`, `EXT_OBSERVER_FAILED`,
695
+ * `NET_ABORTED`, `NET_BODY_INCOMPLETE`, `NET_TIMEOUT`, `NET_UNREACHABLE`,
696
+ * `REQ_BUILD_FAILED`, `REQ_INPUT_INVALID`, and `REQ_OPTIONS_INVALID`. These
697
+ * are the `code` values of `PreflightFault`.
698
+ *
699
+ * @group errors
700
+ */
701
+ export type PreflightFaultCode = Exclude<FaultCode, DecodeFaultCode | "HTTP_STATUS">;
458
702
  type ErrorBody<TErr> = [TErr] extends [undefined] ? undefined : Infer<TErr>;
459
703
  /**
460
- * `HttpStatusFault` expressed by its decoded body type instead of the struct that produced it.
704
+ * Non-2xx fault expressed by its decoded body type instead of the struct that produced it.
461
705
  *
462
706
  * Factories work in decoded values, so they use this form; endpoint types use
463
- * `HttpStatusFault` and let `Infer` bridge the two.
707
+ * `HttpStatusFault` and let `Infer` bridge the two. When `TData` is `undefined`,
708
+ * the body was never read and `response` is metadata alone.
709
+ *
710
+ * @group errors
464
711
  */
465
712
  export type HttpStatusFaultOf<TData> = Error & {
466
- code: 'HTTP_STATUS';
713
+ code: "HTTP_STATUS";
467
714
  data: TData;
468
715
  response: [TData] extends [undefined] ? HttpMeta : DecodedResponse<TData>;
469
716
  status: number;
470
717
  };
471
718
  /**
472
- * The peer answered with a non-2xx status and the declared `error` struct decoded its body.
719
+ * Fault for a non-2xx response, typed by the declared `error` struct.
720
+ *
721
+ * `TErr` is the endpoint's declared `error` struct, or `undefined` when it
722
+ * declares none. Any non-2xx status. When the endpoint declared `error` and the
723
+ * body decoded, `data` is that body and `response` carries it. Omitting `error`
724
+ * skips decoding, which leaves `data` as `undefined` and `response` without a
725
+ * `body` field. `ErrorBody<TErr>` is that decoded body: `undefined` when no
726
+ * error struct was declared, otherwise `Infer<TErr>`. {@link HttpStatusFaultOf}
727
+ * is the member shape.
473
728
  *
474
- * Omitting `error` skips decoding, which leaves `data` as `undefined` and `response`
475
- * without a `body` field.
729
+ * @group errors
476
730
  */
477
731
  export type HttpStatusFault<TErr extends AnyStruct | undefined = undefined> = HttpStatusFaultOf<ErrorBody<TErr>>;
478
732
  /**
479
733
  * A response arrived but could not be read as declared.
480
734
  *
481
- * `response` is metadata only: a body is a decoded value, and decoding is what failed.
482
- * The offending detail lives on `cause` (a `StructError` for a struct mismatch, the
735
+ * `code` is one of `RES_DECODE_FAILED`, `RES_MEDIA_TYPE_INVALID`, or
736
+ * `RES_STRUCT_MISMATCH`. `response` is metadata only: a body is a decoded value,
737
+ * and decoding is what failed, so there is no `body` field to reach for. The
738
+ * offending detail lives on `cause` (a `StructError` for a struct mismatch, the
483
739
  * parser failure for an unreadable representation).
740
+ *
741
+ * @group errors
484
742
  */
485
743
  export type DecodeFault = Error & {
486
744
  cause: unknown;
@@ -491,8 +749,11 @@ export type DecodeFault = Error & {
491
749
  /**
492
750
  * A failure that may have happened before any response existed.
493
751
  *
494
- * `response` is present only where the transport already had metadata to report,
495
- * such as a body that truncated mid-download.
752
+ * Covers `REQ_*`, `NET_*`, `EXT_*`, `CAP_*`, and `ENV_UNSUPPORTED`. `response`
753
+ * is present only where the transport already had metadata to report, such as a
754
+ * body that truncated mid-download.
755
+ *
756
+ * @group errors
496
757
  */
497
758
  export type PreflightFault = Error & {
498
759
  cause?: unknown;
@@ -500,141 +761,595 @@ export type PreflightFault = Error & {
500
761
  response?: HttpMeta;
501
762
  };
502
763
  /**
503
- * Any failure returned by `client.execute`, expressed by its decoded error-body type.
764
+ * Any failure returned by an operation, expressed by its decoded error-body type.
765
+ *
766
+ * `Fault<TErr>` is `FaultOf` applied to `Infer<TErr>`, and `HttpAwaitResult` uses
767
+ * `FaultOf<TErrorData>`. Pass a decoded type, not a struct: write
768
+ * `FaultOf<Infer<typeof errorStruct>>`, or use `Fault<typeof errorStruct>`.
504
769
  *
505
770
  * @typeParam TData - The decoded `error` body, or `undefined` when no error struct was declared.
771
+ * @group errors
506
772
  */
507
773
  export type FaultOf<TData = undefined> = DecodeFault | HttpStatusFaultOf<TData> | PreflightFault;
508
774
  /**
509
- * Any failure returned by `client.execute`, discriminated by `code`.
775
+ * Union of every failure an operation can return, discriminated by `code`.
776
+ *
777
+ * `Fault<TErr>` is {@link FaultOf} applied to the decoded body type, so it is the
778
+ * union of {@link DecodeFault}, {@link HttpStatusFault}<TErr>, and
779
+ * {@link PreflightFault}. `ErrorBody<TErr>` is `undefined` when `TErr` is
780
+ * `undefined`, and `Infer<TErr>` otherwise. Switch on `fault.code`.
781
+ *
782
+ * Every variant is a native `Error` named `DefjsFault`, so `String(fault)` produces
783
+ * `DefjsFault: <message>`. `code` and the variant metadata — `status`, `response`,
784
+ * `data` — are enumerable own properties. The native `cause` chain is non-enumerable.
785
+ *
786
+ * Default Struct messages say `received <kind>`, never the received value. A JSON
787
+ * `SyntaxError` behind `RES_DECODE_FAILED` gives the message
788
+ * `Response body could not be decoded`. Structural paths retain record keys, which
789
+ * may be sensitive. Raw `StructIssue.received` and parser causes remain sensitive;
790
+ * custom `errorMap` and extension messages are used verbatim and need review before
791
+ * logging.
792
+ *
793
+ * Call `format()`, `flatten()`, or `prettify()` only after narrowing `fault.cause`
794
+ * to `StructError`; those helpers are not copied onto the fault.
510
795
  *
511
796
  * @typeParam TErr - The endpoint's declared `error` struct, or `undefined` when it declares none.
797
+ * @group errors
798
+ * @exampleFile examples/api/fault.ts#example
512
799
  */
513
800
  export type Fault<TErr extends AnyStruct | undefined = undefined> = FaultOf<ErrorBody<TErr>>;
514
801
  /**
515
802
  * Any fault at all, whatever its decoded error-body type.
516
803
  *
517
- * Use it for handlers that classify faults without caring which endpoint produced them.
518
- * Prefer `Fault<typeof yourErrorStruct>` where the body type matters.
804
+ * Use it for handlers that classify faults without caring which endpoint produced
805
+ * them. Prefer `Fault<typeof yourErrorStruct>` where the body type matters.
806
+ *
807
+ * @group errors
519
808
  */
520
809
  export type AnyFault = DecodeFault | HttpStatusFaultOf<undefined> | HttpStatusFaultOf<unknown> | PreflightFault;
521
- //#endregion
522
- //#region src/error/factory.d.ts
523
810
  /**
524
- * Build the fault for a non-2xx response whose body decoded against the declared `error` struct.
811
+ * Builds an `HTTP_STATUS` fault for a non-2xx response whose body decoded against the declared `error` struct.
812
+ *
813
+ * The fault is a native `Error` named `DefjsFault` with `code: 'HTTP_STATUS'`,
814
+ * `data` set to `response.body`, `status` copied from the response, and
815
+ * `response` as passed. The message is `Http failure response: <status>`,
816
+ * followed by ` - <statusText>` when there is one.
817
+ *
818
+ * Use {@link createUndecodedHttpStatusFault} when no body was decoded.
525
819
  *
526
820
  * @param response - The response, carrying the decoded body.
527
821
  * @returns An `HTTP_STATUS` fault whose `data` is that body.
822
+ * @group errors
528
823
  */
529
824
  export declare function createHttpStatusFault<TData>(response: DecodedResponse<TData>): HttpStatusFaultOf<TData>;
530
825
  /**
531
- * Build the fault for a non-2xx response that was never decoded, because the endpoint
532
- * declared no `error` struct.
826
+ * Builds an `HTTP_STATUS` fault for a non-2xx response that was never decoded, because the endpoint declared no `error` struct.
827
+ *
828
+ * The fault is a native `Error` named `DefjsFault` with `code: 'HTTP_STATUS'`,
829
+ * `data: undefined`, `status` copied from the response, and `response` as passed.
830
+ *
831
+ * Use {@link createHttpStatusFault} when the body was decoded.
533
832
  *
534
833
  * @param response - Metadata of the response; no body was read.
535
834
  * @returns An `HTTP_STATUS` fault whose `data` is `undefined`.
835
+ * @group errors
536
836
  */
537
837
  export declare function createUndecodedHttpStatusFault(response: HttpMeta): HttpStatusFaultOf<undefined>;
538
838
  /**
539
- * Build the fault for a response that arrived but could not be read as declared.
839
+ * Builds a decode fault for a response that arrived but could not be read as declared.
540
840
  *
541
- * Carries metadata only: a body is a decoded value, and decoding is what failed.
841
+ * `code` is `RES_DECODE_FAILED`, `RES_MEDIA_TYPE_INVALID`, or
842
+ * `RES_STRUCT_MISMATCH`. The fault carries metadata only: a body is a decoded
843
+ * value, and decoding is what failed, so there is no `body`. `status` is copied
844
+ * from `response`. The result is a native `Error` named `DefjsFault`.
845
+ *
846
+ * `cause` is the underlying parser or struct failure and is kept as `cause`.
847
+ * Its message normally becomes the fault message (`String(cause)` when it is
848
+ * not an `Error`). For `RES_DECODE_FAILED` with an `Error` named `SyntaxError`,
849
+ * the message is `Response body could not be decoded`, so `String(fault)` does
850
+ * not include a body fragment. Default Struct messages contain received kinds,
851
+ * not values; raw values and parser text remain on the cause.
542
852
  *
543
853
  * @param code - Which read step failed.
544
854
  * @param cause - Underlying parser or struct failure.
545
855
  * @param response - Metadata of the response that could not be read.
546
856
  * @returns A decode fault with no body.
857
+ * @group errors
547
858
  */
548
859
  export declare function createDecodeFault(code: DecodeFaultCode, cause: unknown, response: HttpMeta): DecodeFault;
549
860
  /**
550
- * Build a fault for a failure that may have happened before any response existed.
861
+ * Builds a fault for a failure that may have happened before any response existed.
862
+ *
863
+ * When `cause` is given, its message becomes the fault message; otherwise the
864
+ * code is the message. The result is a native `Error` named `DefjsFault` with
865
+ * `code`, plus `response` when one was given.
551
866
  *
552
867
  * @param code - The specific failure.
553
868
  * @param cause - Underlying value, when there is one; the code becomes the message otherwise.
554
869
  * @param response - Metadata, only where the transport already had some to report.
555
870
  * @returns A preflight fault.
871
+ * @group errors
556
872
  */
557
873
  export declare function createPreflightFault(code: PreflightFaultCode, cause?: unknown, response?: HttpMeta): PreflightFault;
558
874
  /**
559
- * Build a `NET_*` fault, choosing the code from the shape of `cause`.
875
+ * Builds a `NET_*` `PreflightFault`, choosing the code from the shape of `cause`.
560
876
  *
561
- * Recognizes the shared abort/timeout sentinels and the platform's `AbortError`/`TimeoutError`;
562
- * anything else is reported as unreachable.
877
+ * Optional `response` metadata is attached when the transport already had some
878
+ * to report. The cause is kept as `cause`.
879
+ *
880
+ * - `NET_ABORTED` when {@link isAbortCause} recognizes an abort-branded `Error`
881
+ * (including `ERR_ABORTED` from another Core copy) or a `DOMException` named
882
+ * `AbortError`. The message is `Request was aborted`.
883
+ * - `NET_TIMEOUT` when {@link isTimeoutCause} recognizes a timeout-branded
884
+ * `Error` (including `ERR_TIMEOUT` from another Core copy) or a `DOMException`
885
+ * or `Error` named `TimeoutError`. The message is the cause's message, or
886
+ * `Request timed out` when that is empty.
887
+ * - `NET_UNREACHABLE` for anything else, with the cause's message.
563
888
  *
564
889
  * @param cause - Underlying cancellation, timeout, or transport failure.
565
890
  * @param response - Metadata, only where the transport already had some to report.
566
891
  * @returns A `NET_ABORTED`, `NET_TIMEOUT`, or `NET_UNREACHABLE` fault.
892
+ * @group errors
567
893
  */
568
894
  export declare function createNetworkFault(cause: unknown, response?: HttpMeta): PreflightFault;
569
- //#endregion
570
- //#region src/struct/constructors.d.ts
895
+ /**
896
+ * Struct that accepts string values.
897
+ *
898
+ * - Accepts `string` values only; anything else fails with `invalid_type`.
899
+ *
900
+ * - As a body root, or as an inbound `output` / `error` struct, the bytes stay text instead of being parsed as JSON.
901
+ *
902
+ * @group struct
903
+ */
571
904
  declare function createStringStruct(): StringStruct;
905
+ /**
906
+ * Struct that accepts number values except NaN.
907
+ *
908
+ * - Accepts `number` values, including `Infinity`. `NaN` and non-numbers fail with `invalid_type`.
909
+ *
910
+ * - Strings are not coerced.
911
+ *
912
+ * @group struct
913
+ */
572
914
  declare function createNumberStruct(): NumberStruct;
915
+ /**
916
+ * Struct that accepts boolean values.
917
+ *
918
+ * Accepts `true` and `false` only. Other values, including `'true'` and `1`, fail with `invalid_type`.
919
+ *
920
+ * @group struct
921
+ */
573
922
  declare function createBooleanStruct(): Struct<boolean, boolean>;
923
+ /**
924
+ * Struct that accepts only null.
925
+ *
926
+ * Accepts `null` only. Every other value fails with `invalid_type`. That includes `undefined` at the root or in an array or tuple; a missing object or record field reports `missing_key` instead.
927
+ *
928
+ * This is not the `.null()` modifier. The modifier adds `null` to the values another struct accepts.
929
+ *
930
+ * @group struct
931
+ */
574
932
  declare function createNullStruct(): Struct<null, null>;
933
+ /**
934
+ * Struct that accepts any value except null and undefined, without validation.
935
+ *
936
+ * The value is returned unchanged, and nothing inside it is validated or stripped.
937
+ *
938
+ * - Chain `.null()`, `.optional()`, or `.nullish()` to accept `null` or `undefined`.
939
+ *
940
+ * - The output type is `any`. Prefer {@link struct.unknown}() when the caller should narrow the value.
941
+ *
942
+ * @group struct
943
+ */
575
944
  declare function createAnyStruct(): Struct<PresentValue, any>;
945
+ /**
946
+ * Struct that accepts any value except null and undefined, typed as unknown.
947
+ *
948
+ * The value is returned unchanged, and nothing inside it is validated or stripped.
949
+ *
950
+ * - Chain `.null()`, `.optional()`, or `.nullish()` to accept `null` or `undefined`.
951
+ *
952
+ * - The output type is `unknown`, so the caller must narrow it before use.
953
+ *
954
+ * @group struct
955
+ */
576
956
  declare function createUnknownStruct(): UnknownStruct;
957
+ /**
958
+ * Struct that accepts exactly one boolean, null, number, or string value.
959
+ *
960
+ * - Accepts only `value`. The comparison uses `Object.is`, so any other value fails with `invalid_literal`.
961
+ *
962
+ * - `value` must be a `boolean`, `null`, `number`, or `string`.
963
+ *
964
+ * - Use it for the tag field of {@link struct.discriminatedUnion}().
965
+ *
966
+ * @group struct
967
+ */
577
968
  declare function createLiteralStruct<const T extends LiteralValue>(value: T): Struct<T, T>;
969
+ /**
970
+ * Struct that parses an array with one item struct.
971
+ *
972
+ * - Parses every element with `item` and outputs a new array. A non-array fails with `invalid_type` (expected `array`).
973
+ *
974
+ * - Parsing stops at the first failing element, and `issue.path` holds its index.
975
+ *
976
+ * - Throws `TypeError` when `item` is not a struct.
977
+ *
978
+ * `ArrayStruct<S>` is not exported. It is a `Struct<StructInput<S>[], Infer<S>[]>`.
979
+ *
980
+ * @group struct
981
+ */
578
982
  declare function createArrayStruct<S extends StructLike<unknown, unknown, boolean>>(item: S): ArrayStruct<S>;
983
+ /**
984
+ * Struct that parses a plain object with a fixed set of fields.
985
+ *
986
+ * `shape` maps field names to structs.
987
+ *
988
+ * - Every field is **required** by default. A missing key fails with `missing_key`.
989
+ *
990
+ * - `.optional()` and `.nullish()` allow a missing key. An absent optional field stays absent in the output.
991
+ *
992
+ * - `.null()` / `.nullable()` allow `null`.
993
+ *
994
+ * - `.alias(name)` sets the field's wire key. `struct.parse` reads logical keys by default; `{ aliases: true }` and HTTP JSON decoding read wire keys, and HTTP JSON encoding writes them. Parsed output uses logical keys.
995
+ *
996
+ * - Keys that are not in `shape` are removed from the output. The output is a new object with a `null` prototype.
997
+ *
998
+ * - The input must be a plain object. Arrays, `Map`, and class instances fail with `invalid_type` (expected `object`).
999
+ *
1000
+ * - Parsing stops at the first failing field, and `issue.path` holds its key.
1001
+ *
1002
+ * - Throws `TypeError` when `shape` is not a plain object.
1003
+ *
1004
+ * `ObjectShape` is `{ [key: string]: any }`; each value must be a struct.
1005
+ *
1006
+ * @group struct
1007
+ */
579
1008
  declare function createObjectStruct<T extends ObjectShape>(shape: T): ObjectStruct<T>;
1009
+ /**
1010
+ * Object struct that is encoded as an URL-encoded form when it is the request body.
1011
+ *
1012
+ * Parses like {@link struct.object}(), and carries a `urlencoded` codec tag.
1013
+ *
1014
+ * - The tag applies only when this struct's value is the request body root. The body is then sent as `URLSearchParams` with `application/x-www-form-urlencoded;charset=UTF-8`.
1015
+ *
1016
+ * - Arrays repeat the field name and `undefined` fields are skipped. A nested object or a `Blob` fails the request with `REQ_BUILD_FAILED`.
1017
+ *
1018
+ * - `struct.parse` ignores the tag. It does not accept a `URLSearchParams` instance, only a plain object.
1019
+ *
1020
+ * @group struct
1021
+ */
580
1022
  declare function createUrlencodedBodyStruct<T extends ObjectShape>(shape: T): ObjectStruct<T>;
1023
+ /**
1024
+ * Object struct that is encoded as multipart FormData when it is the request body.
1025
+ *
1026
+ * Parses like {@link struct.object}(), and carries a `formData` codec tag.
1027
+ *
1028
+ * - The tag applies only when this struct's value is the request body root. The body is then sent as `FormData` with a platform multipart boundary.
1029
+ *
1030
+ * - Arrays repeat the field name, `undefined` fields are skipped, and `Blob` / `File` values are appended as files. A nested object fails the request with `REQ_BUILD_FAILED`.
1031
+ *
1032
+ * - `struct.parse` ignores the tag. It does not accept a `FormData` instance, only a plain object.
1033
+ *
1034
+ * @group struct
1035
+ */
581
1036
  declare function createFormDataBodyStruct<T extends ObjectShape>(shape: T): ObjectStruct<T>;
1037
+ /**
1038
+ * Struct that parses an object with string keys and one value struct.
1039
+ *
1040
+ * - Parses every own key of a plain object with `value`. Anything else fails with `invalid_type` (expected `record`).
1041
+ *
1042
+ * - Parsing stops at the first failing value, and `issue.path` holds its key.
1043
+ *
1044
+ * - The output is a new object with a `null` prototype.
1045
+ *
1046
+ * - Throws `TypeError` when `value` is not a struct.
1047
+ *
1048
+ * `RecordStruct<S>` is not exported. It is a `Struct<{ [key: string]: StructInput<S> }, { [key: string]: Output }>`, where `Output` is `Infer<S>` without `undefined`. When `S` is an optional struct, a value that is `undefined` omits its key from the output.
1049
+ *
1050
+ * @group struct
1051
+ */
582
1052
  declare function createRecordStruct<S extends StructLike<unknown, unknown, boolean>>(value: S): RecordStruct<S>;
1053
+ /**
1054
+ * Struct that parses a fixed-length array with one struct per position.
1055
+ *
1056
+ * `struct.tuple` takes **one array** of structs, not separate arguments:
1057
+ *
1058
+ * ```ts
1059
+ * const Pair = struct.tuple([struct.string(), struct.number()]);
1060
+ * ```
1061
+ *
1062
+ * - Each position is parsed with the struct at the same index. The input must be an array of exactly the same length, otherwise it fails with `invalid_type` (expected `tuple of length N`).
1063
+ *
1064
+ * - Passing the structs as separate arguments, `struct.tuple(a, b)`, throws a `TypeError`.
1065
+ *
1066
+ * - Throws `TypeError` when an item is not a struct.
1067
+ *
1068
+ * `TupleStruct<T>` is not exported. It is a `Struct` whose input and output are tuples of the item types.
1069
+ *
1070
+ * @group struct
1071
+ */
583
1072
  declare function createTupleStruct<const T extends readonly [StructLike<unknown, unknown, boolean>, ...StructLike<unknown, unknown, boolean>[]]>(items: T): TupleStruct<T>;
584
1073
  type RequiredDiscriminatorOption<TDiscriminator extends string, TOption extends ObjectStruct<ObjectShape>> = TOption extends ObjectStruct<infer TShape> ? TDiscriminator extends keyof TShape ? TShape[TDiscriminator] extends StructLike<infer TInput, unknown, false> ? undefined extends TInput ? never : null extends TInput ? [TInput] extends [null] ? TOption : never : TOption : never : never : never;
585
1074
  type RequiredDiscriminatorOptions<TDiscriminator extends string, TOptions extends readonly ObjectStruct<ObjectShape>[]> = { [K in keyof TOptions]: TOptions[K] extends ObjectStruct<ObjectShape> ? RequiredDiscriminatorOption<TDiscriminator, TOptions[K]> : never; };
1075
+ /**
1076
+ * Struct that picks one object struct by the value of a tag field.
1077
+ *
1078
+ * ```ts
1079
+ * const Event = struct.discriminatedUnion("kind", [
1080
+ * struct.object({ kind: struct.literal("created"), id: struct.number() }),
1081
+ * struct.object({ kind: struct.literal("deleted"), id: struct.number() }),
1082
+ * ]);
1083
+ * ```
1084
+ *
1085
+ * - Every option must be an object struct whose `discriminator` field is a required {@link struct.literal}(). A missing field, a non-literal field, an optional field, or a duplicate tag value throws `TypeError` when the struct is created.
1086
+ *
1087
+ * - The input must be a plain object. The tag value selects the option, and that option parses the whole object.
1088
+ *
1089
+ * - A missing tag fails with `missing_key`, and an unknown tag fails with `invalid_union`. Both report the tag field's path. Other failures come from the selected option.
1090
+ *
1091
+ * - With `struct.parse(..., { aliases: true })` the tag is read from the field's alias.
1092
+ *
1093
+ * `RequiredDiscriminatorOptions` and `DiscriminatedUnionStruct` are not exported. The type check rejects an option whose tag field is missing or may be `undefined`.
1094
+ *
1095
+ * @group struct
1096
+ */
586
1097
  declare function createDiscriminatedUnionStruct<const TDiscriminator extends string, const TOptions extends readonly [ObjectStruct<ObjectShape>, ...ObjectStruct<ObjectShape>[]]>(discriminator: TDiscriminator, options: TOptions & RequiredDiscriminatorOptions<TDiscriminator, TOptions>): DiscriminatedUnionStruct<TOptions>;
1098
+ /**
1099
+ * Struct that accepts a Blob.
1100
+ *
1101
+ * - Accepts a `Blob` (a `File` is a `Blob`). Anything else fails with `invalid_type`.
1102
+ *
1103
+ * - As a body root it is sent as is, with the blob's own type or `application/octet-stream`.
1104
+ *
1105
+ * - As an `output` or `error` struct it makes the response body be read as a `Blob`.
1106
+ *
1107
+ * @group struct
1108
+ */
587
1109
  declare function createBlobStruct(): Struct<Blob, Blob>;
588
- declare function createBigIntStruct(): Struct<bigint | string, bigint>;
589
- declare function createDateStruct(): Struct<Date | number | string, Date>;
590
- declare function createIntersectionStruct<const T extends readonly [StructLike<unknown, unknown, boolean>, ...StructLike<unknown, unknown, boolean>[]]>(...structs: T): IntersectionStruct<T>;
591
- declare function createFileStruct(): Struct<File, File>;
592
- declare function createArrayBufferStruct(): Struct<ArrayBuffer, ArrayBuffer>;
593
- //#endregion
594
- //#region src/struct/introspection.d.ts
595
- declare function parseStructTuple<S extends StructLike<unknown, unknown, boolean>>(struct: S, value: unknown, options?: {
596
- aliases?: boolean;
597
- errorMap?: ErrorMap;
598
- }): ParseResult<S['_struct']['output']>;
599
- //#endregion
600
- //#region src/struct/facade.d.ts
601
- type EnumValue<T> = T extends readonly (infer U extends string)[] ? U : T extends {
602
- [key: string]: infer U extends number | string;
603
- } ? U : never;
604
- declare function structEnum<const T extends readonly [string, ...string[]]>(value: T): Struct<EnumValue<T>, EnumValue<T>>;
605
- declare function structEnum<const T extends {
606
- [key: string]: number | string;
607
- }>(value: T): Struct<EnumValue<T>, EnumValue<T>>;
608
- declare function structOr<const T extends readonly [StructLike, ...StructLike[]]>(...options: T): UnionStruct<T>;
609
1110
  /**
610
- * Facade for building protocol-boundary structs and parsing values.
1111
+ * Struct that accepts a bigint or a bigint string and outputs a bigint.
611
1112
  *
612
- * Prefer this entry point over internal constructors. Compose shapes with
613
- * `struct.object`, then parse with `struct.parse` or let HTTP/SSE/WebSocket
614
- * endpoints apply the same contracts at the wire boundary.
1113
+ * - Accepts a `bigint`, or a string that `BigInt()` can parse. A `number` is rejected.
615
1114
  *
616
- * @example
617
- * ```ts
618
- * const User = struct.object({
619
- * id: struct.number(),
620
- * displayName: struct.string().alias('display_name'),
621
- * })
1115
+ * - Output is always a `bigint`; an unparsable string fails with `invalid_type`.
622
1116
  *
623
- * const getUser = defineRequest({
624
- * method: 'GET',
625
- * path: '/users/:id',
626
- * input: struct.object({ id: struct.number() }),
627
- * builder: (input) => ({ path: input }),
628
- * output: User,
629
- * })
1117
+ * - When encoded for the wire, the value becomes a decimal string.
630
1118
  *
631
- * const [error, user] = struct.parse(User, { id: 1, displayName: 'Ada' })
632
- * ```
1119
+ * @group struct
633
1120
  */
634
- export declare const struct: {
635
- readonly any: typeof createAnyStruct;
636
- readonly array: typeof createArrayStruct;
637
- readonly arrayBuffer: typeof createArrayBufferStruct;
1121
+ declare function createBigIntStruct(): Struct<bigint | string, bigint>;
1122
+ /**
1123
+ * Struct that accepts a Date, timestamp, or date string and outputs a Date.
1124
+ *
1125
+ * - Accepts a `Date`, a millisecond timestamp, or a string. A valid `Date` is returned as the same object. A number or string is converted with `new Date(value)`.
1126
+ *
1127
+ * - Output is a `Date`. A value that produces an invalid date fails with `invalid_type`.
1128
+ *
1129
+ * - When encoded for the wire, the value becomes an ISO 8601 string (`toISOString()`).
1130
+ *
1131
+ * @group struct
1132
+ */
1133
+ declare function createDateStruct(): Struct<Date | number | string, Date>;
1134
+ /**
1135
+ * Struct that requires a value to satisfy every given struct.
1136
+ *
1137
+ * `struct.intersection` is variadic: pass each struct as its own argument.
1138
+ *
1139
+ * - Parses the input with every struct and merges the results.
1140
+ *
1141
+ * - When every struct is an object struct, the output is one object with the fields of all of them. Keys that none of them declares are removed.
1142
+ *
1143
+ * - When two structs produce incompatible values for the same key, it fails with `invalid_intersection`.
1144
+ *
1145
+ * - Accepts `null` only when every struct accepts it, including a nested intersection marked `.null()`.
1146
+ *
1147
+ * - A single struct is returned unchanged, so it keeps its own identity and type, including `.optional()` and `.null()`. Calling it with no structs throws `TypeError`, and so does a non-struct argument.
1148
+ *
1149
+ * - A nonempty variadic tuple must account for that singleton. Spreading a tuple that may contain only one member infers both the singleton and multi-member results. Its object fields may therefore be optional. A tuple known to contain at least two members keeps the required intersection semantics.
1150
+ *
1151
+ * `IntersectionStruct<T>` is not exported. It is a `Struct` whose input and output are the intersections of the member types.
1152
+ *
1153
+ * @group struct
1154
+ */
1155
+ declare function createIntersectionStruct<const T extends readonly [StructLike<unknown, unknown, boolean>, ...StructLike<unknown, unknown, boolean>[]]>(...structs: T): T extends readonly [unknown] ? T[0] : T extends readonly [unknown, unknown, ...unknown[]] ? IntersectionStruct<T> : T[0] | IntersectionStruct<T>;
1156
+ /**
1157
+ * Struct that accepts a File.
1158
+ *
1159
+ * - Accepts a `File` only; a plain `Blob` fails with `invalid_type`.
1160
+ *
1161
+ * - Use it for a field of {@link struct.formData}(), or for a raw file body.
1162
+ *
1163
+ * - As an `output` or `error` struct it makes the response body be read as a blob.
1164
+ *
1165
+ * @group struct
1166
+ */
1167
+ declare function createFileStruct(): Struct<File, File>;
1168
+ /**
1169
+ * Struct that accepts an ArrayBuffer.
1170
+ *
1171
+ * - Accepts an `ArrayBuffer` only. Typed arrays and other views fail with `invalid_type`.
1172
+ *
1173
+ * - As a body root it is sent as bytes with `application/octet-stream`.
1174
+ *
1175
+ * - As an `output` or `error` struct it makes the response body be read as an `ArrayBuffer`.
1176
+ *
1177
+ * @group struct
1178
+ */
1179
+ declare function createArrayBufferStruct(): Struct<ArrayBuffer, ArrayBuffer>;
1180
+ /**
1181
+ * Parses an unknown value with a struct and returns an error-first tuple.
1182
+ *
1183
+ * - **struct** is the struct to parse with. A value that is not a struct throws `TypeError`.
1184
+ *
1185
+ * - **value** is the unknown input.
1186
+ *
1187
+ * - **options.aliases** defaults to `false`: object keys are read by their **logical** names (`displayName`). With `true` they are read by their wire names (`display_name`), the way HTTP/JSON codecs read them. The output always uses logical names.
1188
+ *
1189
+ * - **options.errorMap** is an {@link ErrorMap} that rewrites issue messages for this call only, including bigint/date conversion failures from a struct created by another Core copy. Any string, including `''`, replaces the message; only `undefined` keeps the default. Custom text is used verbatim.
1190
+ *
1191
+ * - Parsing is fail-fast: it stops at the first failure, so the error holds one issue. Unknown object keys are stripped from the output, and the input is not modified.
1192
+ *
1193
+ * - At most **256 nested containers** (`object`, `array`, `record`, or `tuple`) are visited along a path, counting the root container as the first. The next container fails with `invalid_type` at its logical path, with `expected: 'value nested at most 256 levels deep'`. In a union, later candidates may still succeed; if none match, the union reports `invalid_union`. Cycles through declared container fields also reach this limit; shared non-cyclic children are allowed. Union and intersection branches do not add a container level, and `any`/`unknown` values remain opaque rather than being traversed.
1194
+ *
1195
+ * ```ts
1196
+ * import { struct } from "@defjs/core";
1197
+ *
1198
+ * const User = struct.object({ id: struct.number(), displayName: struct.string() });
1199
+ *
1200
+ * function readUser() {
1201
+ * const [error, user] = struct.parse(User, { id: 1, displayName: "Ada" });
1202
+ * if (error) {
1203
+ * console.error(error.prettify());
1204
+ * return;
1205
+ * }
1206
+ * console.log(user.displayName);
1207
+ * }
1208
+ *
1209
+ * readUser();
1210
+ * ```
1211
+ *
1212
+ * `any` and `unknown` do not traverse their values, but an intersection may recursively merge two distinct object or array values returned by those leaves. This merge shares the same 256-container budget with its enclosing logical path. Exceeding it produces `invalid_intersection` at the rejected container's logical path. Identical references are retained without traversal, including a shared cyclic value.
1213
+ *
1214
+ * @returns `[null, output]` or `[StructError, undefined]`. Validation failures do not throw. Exceptions from caller-provided getters and `errorMap` callbacks propagate unchanged.
1215
+ * @group struct
1216
+ * @see {@link Struct}
1217
+ * @see {@link ParseResult}
1218
+ */
1219
+ declare function parseStructTuple<S extends StructLike<unknown, unknown, boolean>>(struct: S, value: unknown, options?: {
1220
+ aliases?: boolean;
1221
+ errorMap?: ErrorMap;
1222
+ }): ParseResult<S["_struct"]["output"]>;
1223
+ type EnumValue<T> = T extends readonly (infer U extends string)[] ? U : T extends {
1224
+ [key: string]: infer U extends number | string;
1225
+ } ? U : never;
1226
+ /**
1227
+ * Struct that accepts one value from a list of strings or from the values of an object.
1228
+ *
1229
+ * - With a non-empty string array, the accepted values are the array items.
1230
+ *
1231
+ * - With an object, the accepted values are the object's string and number **values**, not its keys. `{ A: 'a', B: 2 }` accepts `'a'` and `2`.
1232
+ *
1233
+ * - A TypeScript numeric `enum` object also accepts its member names, because the reverse mapping is part of its values.
1234
+ *
1235
+ * - A value outside the set fails with `invalid_enum`. An object with no string or number value throws `TypeError` when the struct is created.
1236
+ *
1237
+ * @group struct
1238
+ */
1239
+ declare function structEnum<const T extends readonly [string, ...string[]]>(value: T): Struct<EnumValue<T>, EnumValue<T>>;
1240
+ declare function structEnum<const T extends {
1241
+ [key: string]: number | string;
1242
+ }>(value: T): Struct<EnumValue<T>, EnumValue<T>>;
1243
+ /**
1244
+ * Struct that accepts a value matching any one of several structs.
1245
+ *
1246
+ * `struct.or` is variadic: pass each option as its own argument.
1247
+ *
1248
+ * - Tries the options in order and returns the result of the first one that parses.
1249
+ *
1250
+ * - If none matches, it fails with one `invalid_union` issue at the union's own path. The issues of the individual options are not reported.
1251
+ *
1252
+ * - Throws `TypeError` when an option is not a struct.
1253
+ *
1254
+ * `UnionStruct<T>` is not exported. It is a `Struct` whose input and output are the unions of the option types, without `undefined`. For tagged objects prefer {@link struct.discriminatedUnion}(), which reports the issue of the selected option.
1255
+ *
1256
+ * @group struct
1257
+ */
1258
+ declare function structOr<const T extends readonly [StructLike, ...StructLike[]]>(...options: T): UnionStruct<T>;
1259
+ /**
1260
+ * Facade object for building protocol-boundary structs and parsing values with its constructors and `struct.parse`.
1261
+ *
1262
+ * Prefer this object over importing the constructor functions one by one. Compose shapes with
1263
+ * `struct.object`, then parse with `struct.parse` or let HTTP/SSE/WebSocket
1264
+ * endpoints apply the same contracts at the wire boundary. `struct` is a plain object of helpers.
1265
+ *
1266
+ * ```ts
1267
+ * import { struct } from "@defjs/core";
1268
+ *
1269
+ * const User = struct.object({
1270
+ * id: struct.number(),
1271
+ * displayName: struct.string().alias("display_name"),
1272
+ * });
1273
+ *
1274
+ * function readUser() {
1275
+ * const [error, user] = struct.parse(User, { id: 1, displayName: "Ada" });
1276
+ * if (error) {
1277
+ * console.error(error.message);
1278
+ * return;
1279
+ * }
1280
+ * console.log(user.displayName);
1281
+ * }
1282
+ *
1283
+ * readUser();
1284
+ * ```
1285
+ *
1286
+ * ### Primitives and containers
1287
+ *
1288
+ * | Helper | Role |
1289
+ * | -------------------------------------------------------------------------------- | --------------------------------------------------------- |
1290
+ * | {@link struct.string}() | Text |
1291
+ * | {@link struct.number}() | Number, `NaN` rejected |
1292
+ * | {@link struct.boolean}() | Boolean |
1293
+ * | {@link struct.bigint}() | `bigint`, or a string that parses as one |
1294
+ * | {@link struct.date}() | `Date`, from a `Date`, timestamp, or string |
1295
+ * | {@link struct.literal}(value) | Exact literal |
1296
+ * | {@link struct.null}() | Only `null` |
1297
+ * | {@link struct.any}() / unknown() ({@link struct.unknown}) | Any value except `null` and `undefined` |
1298
+ * | {@link struct.array}(item) | Array of one item struct |
1299
+ * | {@link struct.tuple}(items) | Fixed-length array; `items` is one array of structs |
1300
+ * | {@link struct.record}(value) | Plain object with string keys and one value struct |
1301
+ * | {@link struct.object}(shape) | Plain object with fixed fields; unknown keys are stripped |
1302
+ * | {@link struct.or}(...options) | First matching option |
1303
+ * | {@link struct.intersection}(...structs) | Every struct must match; object results are merged |
1304
+ * | {@link struct.discriminatedUnion}(tag, options) | Object option picked by the value of the `tag` field |
1305
+ * | {@link struct.enum}(values) | String array, or the values of a `{ Name: value }` object |
1306
+ *
1307
+ * ### Body codecs and binary values
1308
+ *
1309
+ * | Helper | Role |
1310
+ * | ------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------- |
1311
+ * | {@link struct.formData}(shape) / urlencoded(shape) ({@link struct.urlencoded}) | Object struct plus a codec tag. The tag applies only when that value is the request body root |
1312
+ * | {@link struct.arrayBuffer}() / blob() ({@link struct.blob}) / file() ({@link struct.file}) | Binary values. As a body root they are sent as bytes; as `output` / `error` they select that representation |
1313
+ * | {@link struct.string}() | Text. As a body root or an inbound `string` struct, the bytes stay text |
1314
+ *
1315
+ * ### Parsing and modifiers
1316
+ *
1317
+ * {@link struct.parse}(struct, value, options?) parses an unknown value and returns `[null, value]` or `[StructError, undefined]`.
1318
+ *
1319
+ * Every struct has the chainable modifiers `.optional()`, `.null()` / `.nullable()`, `.nullish()`, and `.alias(name)`. See {@link StructMethods}.
1320
+ *
1321
+ * @example
1322
+ * ```ts
1323
+ * const User = struct.object({
1324
+ * id: struct.number(),
1325
+ * displayName: struct.string().alias('display_name'),
1326
+ * })
1327
+ *
1328
+ * const getUser = defineRequest({
1329
+ * method: 'GET',
1330
+ * path: '/users/:id',
1331
+ * input: struct.object({ id: struct.number() }),
1332
+ * builder: (input) => ({ path: input }),
1333
+ * output: User,
1334
+ * })
1335
+ *
1336
+ * function readUser() {
1337
+ * const [error, user] = struct.parse(User, { id: 1, displayName: 'Ada' })
1338
+ * if (error) {
1339
+ * console.error(error.message)
1340
+ * return
1341
+ * }
1342
+ * console.log(user.displayName)
1343
+ * }
1344
+ *
1345
+ * readUser()
1346
+ * ```
1347
+ * @group struct
1348
+ */
1349
+ export declare const struct: {
1350
+ readonly any: typeof createAnyStruct;
1351
+ readonly array: typeof createArrayStruct;
1352
+ readonly arrayBuffer: typeof createArrayBufferStruct;
638
1353
  readonly bigint: typeof createBigIntStruct;
639
1354
  readonly blob: typeof createBlobStruct;
640
1355
  readonly boolean: typeof createBooleanStruct;
@@ -656,20 +1371,18 @@ export declare const struct: {
656
1371
  readonly unknown: typeof createUnknownStruct;
657
1372
  readonly urlencoded: typeof createUrlencodedBodyStruct;
658
1373
  };
659
- //#endregion
660
- //#region src/sse/transport/parser.d.ts
661
- /**
662
- * Portions of this file are adapted from Azure/fetch-event-source.
663
- * See packages/core/THIRD_PARTY_NOTICES.md for the license text.
664
- */
665
1374
  interface EventStreamMessage {
666
1375
  id: string;
667
1376
  event: string;
668
1377
  data: string;
669
1378
  }
670
- //#endregion
671
- //#region src/sse/transport/event_stream.d.ts
672
- /** Metadata available after an SSE response opens successfully. */
1379
+ /**
1380
+ * Metadata available once an SSE response has opened.
1381
+ *
1382
+ * `response` is the handshake response and `url` is the resolved request URL. It is the third element of a successful event-stream result and the value of `EventStreamHandle.open`.
1383
+ *
1384
+ * @group sse
1385
+ */
673
1386
  interface EventStreamOpenInfo {
674
1387
  response: HttpResponse<unknown>;
675
1388
  url: string;
@@ -677,9 +1390,11 @@ interface EventStreamOpenInfo {
677
1390
  /**
678
1391
  * Fault codes an event stream can end with, drawn from the one shared `FaultCode` namespace.
679
1392
  *
680
- * SSE used to carry a parallel set with no mapping to the request error model.
1393
+ * A subset of the shared `FaultCode` set. It is the `code` of an `EventStreamCloseInfo` with `kind: 'error'`.
1394
+ *
1395
+ * @group sse
681
1396
  */
682
- type EventStreamFaultCode = Extract<FaultCode, 'CAP_BUFFER_EXCEEDED' | 'CAP_QUEUE_OVERFLOW' | 'EXT_HOOK_FAILED' | 'EXT_OBSERVER_FAILED' | 'NET_TIMEOUT' | 'NET_UNREACHABLE' | 'RES_DECODE_FAILED' | 'RES_MEDIA_TYPE_INVALID'>;
1397
+ type EventStreamFaultCode = Extract<FaultCode, "CAP_BUFFER_EXCEEDED" | "CAP_QUEUE_OVERFLOW" | "EXT_HOOK_FAILED" | "EXT_OBSERVER_FAILED" | "NET_TIMEOUT" | "NET_UNREACHABLE" | "RES_DECODE_FAILED" | "RES_MEDIA_TYPE_INVALID">;
683
1398
  interface EventStreamCloseInfoBase {
684
1399
  reason?: string;
685
1400
  cause?: unknown;
@@ -688,40 +1403,47 @@ interface EventStreamCloseInfoBase {
688
1403
  * How an SSE stream ended: clean EOF, abort, or an error carrying its fault `code`.
689
1404
  *
690
1405
  * The discriminant is `kind` so that `code` can mean the same thing it means on a fault.
1406
+ *
1407
+ * The value of `EventStreamHandle.closed`. `eof` means the response ended normally. `aborted` means the stream was closed with `close()` or disposal, or its caller signal aborted with a non-timeout reason; `reason` and `cause` carry the value passed to `close`. If the caller signal aborts after open with a `TimeoutError` or `ERR_TIMEOUT`, the iterator throws `ERR_TIMEOUT` and `closed` is `{ kind: 'error', code: 'NET_TIMEOUT', ... }`. Explicit `close(timeoutError)` still reports `aborted`. `error` carries the fault `code` (see `EventStreamFaultCode`) and the failure as `cause`.
1408
+ *
1409
+ * @group sse
691
1410
  */
692
1411
  type EventStreamCloseInfo = (EventStreamCloseInfoBase & {
693
- kind: 'eof';
1412
+ kind: "eof";
694
1413
  }) | (EventStreamCloseInfoBase & {
695
- kind: 'aborted';
1414
+ kind: "aborted";
696
1415
  }) | (EventStreamCloseInfoBase & {
697
1416
  code: EventStreamFaultCode;
698
- kind: 'error';
1417
+ kind: "error";
699
1418
  });
700
- /** Open SSE stream: iterate events, inspect open metadata, and close early. */
1419
+ /**
1420
+ * Open SSE stream: iterate events, read open metadata, and close early.
1421
+ *
1422
+ * `EventStreamMessage` is the raw `{ id: string; event: string; data: string }` message. Handles returned by an event-stream operation carry parsed {@link EventStreamData}.
1423
+ *
1424
+ * `open` is the startup snapshot ({@link EventStreamOpenInfo}); after a reconnect it can change, so read `stream.open` again. `closed` resolves with an {@link EventStreamCloseInfo} when the stream ends.
1425
+ *
1426
+ * Prefer `await using stream = handle` for owned scope cleanup. The disposer stops Defjs reading and reconnect work and waits for its reader lock to be released; it does not promise that a provider-controlled, stuck `ReadableStream.cancel()` promise will finish. Manual `close()` / `closed` remains available.
1427
+ *
1428
+ * ```ts
1429
+ * async function consume(handle: EventStreamHandle<unknown>): Promise<void> {
1430
+ * await using stream = handle;
1431
+ * for await (const event of stream) console.log(event);
1432
+ * }
1433
+ * ```
1434
+ *
1435
+ * Structural implementations of `EventStreamHandle` must implement `[Symbol.asyncDispose]()` and return the same owned teardown path. This is a compile-time requirement for custom handles; code that only receives handles returned by Defjs has no additional runtime call requirement.
1436
+ *
1437
+ * @group sse
1438
+ */
701
1439
  interface EventStreamHandle<TEvent = EventStreamMessage> extends AsyncIterable<TEvent>, AsyncDisposable {
702
1440
  readonly open: EventStreamOpenInfo;
703
1441
  readonly closed: Promise<EventStreamCloseInfo>;
704
1442
  close(reason?: unknown): void;
705
1443
  [Symbol.asyncDispose](): PromiseLike<void>;
706
1444
  }
707
- //#endregion
708
- //#region src/internal/abort.d.ts
709
- type UseCancellationConfig = {
710
- abort?: AbortSignal;
711
- timeout?: never;
712
- } | {
713
- abort?: never;
714
- timeout?: number;
715
- };
716
- //#endregion
717
- //#region src/internal/endpoint_input.d.ts
718
- type EndpointInput<TInput extends AnyStruct | undefined> = TInput extends AnyStruct ? StructInput<TInput> : unknown;
719
- //#endregion
720
- //#region src/internal/endpoint_command.d.ts
721
- type IsEndpointInputOptional<TInput extends AnyStruct | undefined> = [TInput] extends [undefined] ? true : undefined extends EndpointInput<TInput> ? true : false;
722
- type EndpointCallable<TInput extends AnyStruct | undefined, TCommand> = [TInput] extends [undefined] ? () => TCommand : IsEndpointInputOptional<TInput> extends true ? (input?: EndpointInput<TInput>) => TCommand : (input: EndpointInput<TInput>) => TCommand;
723
- //#endregion
724
- //#region src/internal/builder_types.d.ts
1445
+ /** Argument list after the client, decided by the input struct. */
1446
+ type EndpointArgs<S extends AnyStruct | undefined> = [S] extends [undefined] ? [] : undefined extends StructInput<S> ? [input?: StructInput<S>] : [input: StructInput<S>];
725
1447
  /** Object produced by endpoint input parsing. The symbol points at the struct that produced the node. */
726
1448
  type Typed = {
727
1449
  readonly [STRUCT]: AnyStruct;
@@ -737,272 +1459,156 @@ type QueryValue = Scalar | readonly QueryValue[] | {
737
1459
  type QueryRecord = {
738
1460
  readonly [key: string]: QueryValue;
739
1461
  };
1462
+ /** A builder body: a wire value Fetch sends as is, or JSON data. An `interface` value or class instance needs `{ ...value }`. */
1463
+ type BodyValue = Scalar | null | Blob | ArrayBuffer | ArrayBufferView | FormData | URLSearchParams | ReadableStream<Uint8Array> | readonly unknown[] | {
1464
+ readonly [key: string]: unknown;
1465
+ };
740
1466
  type PathParams<P extends string> = P extends `${string}:${infer Name}/${infer Rest}` ? Name | PathParams<`/${Rest}`> : P extends `${string}:${infer Name}` ? Name : never;
741
1467
  type PathValue<P extends string> = [PathParams<P>] extends [never] ? never : ({ readonly [K in PathParams<P>]: Scalar; } & {
742
1468
  readonly [STRUCT]?: never;
743
1469
  }) | (Typed & {
744
1470
  readonly [key: string]: Scalar;
745
1471
  });
1472
+ /**
1473
+ * Request shape an HTTP builder returns: path, query, headers, and body.
1474
+ *
1475
+ * `P` is the endpoint path and `M` its method. `GET` and `HEAD` cannot return a
1476
+ * body. `BodyValue` is a wire value Fetch sends as is (`string`, `Blob`,
1477
+ * `ArrayBuffer`, a typed array or `DataView`, `FormData`, `URLSearchParams`,
1478
+ * `ReadableStream<Uint8Array>`) or JSON data (`number`, `boolean`, `bigint`,
1479
+ * `Date`, `null`, an array, or an object type with string keys); an `interface`
1480
+ * value or class instance needs `{ ...value }`. `path` values must be scalars.
1481
+ * A header value may be a scalar or an array of scalars, and `HeadersInit` is
1482
+ * accepted. `query` may nest, and a nested object needs `withQuerySerializer`
1483
+ * at runtime. `FlatRecord`, `QueryRecord`, `PathValue`, and `BodyValue` are
1484
+ * internal helper types.
1485
+ *
1486
+ * @group http
1487
+ */
746
1488
  type HttpRequestShape<P extends string, M extends string> = {
747
- body?: Uppercase<M> extends 'GET' | 'HEAD' ? never : unknown;
1489
+ /**
1490
+ * Request body. Not allowed for GET or HEAD.
1491
+ * Other methods send a native body or encoded JSON, depending on the value.
1492
+ */
1493
+ body?: Uppercase<M> extends "GET" | "HEAD" ? never : BodyValue;
1494
+ /**
1495
+ * HeadersInit or a flat record of scalars or scalar arrays.
1496
+ * Put cross-request policies on the Client.
1497
+ */
748
1498
  headers?: HeadersInit | FlatRecord;
1499
+ /**
1500
+ * Explicit values for path placeholders. Anonymous objects use template keys;
1501
+ * typed objects apply their struct aliases.
1502
+ */
749
1503
  path?: PathValue<P>;
1504
+ /**
1505
+ * URLSearchParams passes through unchanged; objects are encoded first.
1506
+ * Configure the Client's query serializer for nested query values.
1507
+ */
750
1508
  query?: URLSearchParams | QueryRecord;
751
1509
  };
1510
+ /**
1511
+ * Request shape the `builder` of a {@link WebSocketDefinition} may return, where `P` is the endpoint path.
1512
+ *
1513
+ * - `path` supplies the `:name` segments of `P`: one scalar value per name. It is `never` when the path has none.
1514
+ * - `query` is a `URLSearchParams` or a record of scalars, arrays, and nested records.
1515
+ * - `headers` and `body` are `never`. A WebSocket handshake takes neither from the builder; put credentials in `beforeConnect` or an interceptor.
1516
+ *
1517
+ * @group web-socket
1518
+ */
752
1519
  type WebSocketRequestShape<P extends string> = {
1520
+ /** WebSocket handshakes cannot have a body. Use session.send after connecting. */
753
1521
  body?: never;
1522
+ /** WebSocket builders cannot set handshake headers. */
754
1523
  headers?: never;
1524
+ /**
1525
+ * Explicit values for path placeholders. Anonymous objects use template keys;
1526
+ * typed objects apply their struct aliases.
1527
+ */
755
1528
  path?: PathValue<P>;
1529
+ /**
1530
+ * URLSearchParams passes through unchanged; objects are encoded first.
1531
+ * Configure the Client's query serializer for nested query values.
1532
+ */
756
1533
  query?: URLSearchParams | QueryRecord;
757
1534
  };
1535
+ /**
1536
+ * Request shape an SSE builder returns: the same fields as {@link HttpRequestShape} for method `M` and path `P`.
1537
+ *
1538
+ * @group sse
1539
+ */
758
1540
  type EventStreamRequestShape<P extends string, M extends string> = HttpRequestShape<P, M>;
759
1541
  type NullishBuilderInput<S, Inner> = null extends Infer<S> ? undefined extends Infer<S> ? BuilderInput<Inner> | null | undefined : BuilderInput<Inner> | null : undefined extends Infer<S> ? BuilderInput<Inner> | undefined : BuilderInput<Inner>;
760
- type IntersectBuilderInputs<T extends readonly StructLike<unknown, unknown, boolean>[]> = T extends readonly [infer Head extends StructLike<unknown, unknown, boolean>, ...infer Tail extends readonly StructLike<unknown, unknown, boolean>[]] ? BuilderInput<Head> & (Tail['length'] extends 0 ? unknown : IntersectBuilderInputs<Tail>) : unknown;
1542
+ type IntersectBuilderInputs<T extends readonly StructLike<unknown, unknown, boolean>[]> = T extends readonly [infer Head extends StructLike<unknown, unknown, boolean>, ...infer Tail extends readonly StructLike<unknown, unknown, boolean>[]] ? BuilderInput<Head> & (Tail["length"] extends 0 ? unknown : IntersectBuilderInputs<Tail>) : unknown;
761
1543
  /**
762
- * Parsed endpoint input seen by a builder.
1544
+ * Parsed endpoint input that a request builder receives.
1545
+ *
1546
+ * Object nodes are intersected with the internal `Typed` brand, so they carry the struct that
1547
+ * produced them and alias, `Date`, and `bigint` encoding follow the value into
1548
+ * the request. Record entries and tuple items are branded the same way. At
1549
+ * runtime a record container is also tagged when the parsed value is an object,
1550
+ * whether or not it was merged. Array and tuple containers are not. `null` and
1551
+ * `undefined` are never tagged. Nullish modifiers recover the struct they
1552
+ * wrapped. Union options are distributed; intersection options are combined.
1553
+ * Any other struct falls back to `Infer<S>`.
1554
+ *
1555
+ * `CarriedStruct`, `NullishBuilderInput`, `IntersectBuilderInputs`, and `Typed`
1556
+ * are internal helper types.
763
1557
  *
764
- * Object nodes are intersected with {@link Typed}. Record entries and tuple items are too.
765
- * At runtime a record container is also tagged when the parsed value is an object, whether or not it
766
- * was merged. Array and tuple containers are not. `null` and `undefined` are never tagged.
767
- * Modifiers recover the struct they wrapped. Union options are distributed; intersection options are combined.
1558
+ * @group http
768
1559
  */
769
1560
  type BuilderInput<S> = S extends CarriedStruct<infer Inner> ? NullishBuilderInput<S, Inner> : S extends UnionStruct<infer Options> ? BuilderInput<Options[number]> : S extends DiscriminatedUnionStruct<infer Options> ? BuilderInput<Options[number]> : S extends IntersectionStruct<infer Options> ? IntersectBuilderInputs<Options> : S extends RecordStruct<infer Value> ? {
770
1561
  [key: string]: BuilderInput<Value>;
771
1562
  } : S extends TupleStruct<infer Items> ? { [K in keyof Items]: Items[K] extends StructLike<unknown, unknown, boolean> ? BuilderInput<Items[K]> : Items[K]; } : S extends ArrayStruct<infer Item> ? BuilderInput<Item>[] : S extends ObjectStruct<infer Shape> ? { [K in keyof Shape]: BuilderInput<Shape[K]>; } & Typed : Infer<S>;
772
1563
  type BuilderField<TInput, TShape> = [TInput] extends [undefined] ? {
1564
+ /** Pure, synchronous request builder, evaluated when the operation runs. Optional without input. */
773
1565
  builder?: () => TShape;
774
- } : BuilderInput<TInput> extends TShape ? {
775
- builder?: (input: BuilderInput<TInput>) => TShape;
776
1566
  } : {
1567
+ /** Pure, synchronous function that explicitly places parsed input on the request when the operation runs. */
777
1568
  builder: (input: BuilderInput<TInput>) => TShape;
778
1569
  };
779
- //#endregion
780
- //#region src/internal/outbound_request.d.ts
781
1570
  /** Accepts any concrete builder. `never` keeps the parameter contravariant. */
782
1571
  type OutboundBuilder = (input: never) => unknown;
783
- //#endregion
784
- //#region src/http/http.d.ts
785
- /**
786
- * Per-request options for HTTP execution: progress callbacks and cancellation.
787
- * Cancellation is either `abort` or `timeout` (not both).
788
- */
789
- export type UseRequestConfig = {
790
- onDownloadProgress?: HttpProgressFn;
791
- onUploadProgress?: HttpProgressFn;
792
- } & UseCancellationConfig;
793
- /**
794
- * Executable HTTP command produced by a `defineRequest` endpoint.
795
- * Carries the request definition and optional typed input for `client.execute`.
796
- */
797
- export interface HttpCommand<TInput extends AnyStruct | undefined, TOutput extends AnyStruct | undefined, TError extends AnyStruct | undefined = undefined> extends BaseCommand<typeof HTTP_COMMAND> {
798
- readonly definition: RequestDefinition<TInput, TOutput, TError>;
799
- readonly input: EndpointInput<TInput> | undefined;
1572
+ interface WebSocketCloseSnapshot {
1573
+ cause?: unknown;
1574
+ code?: number;
1575
+ reason?: string;
1576
+ wasClean?: boolean;
800
1577
  }
801
1578
  /**
802
- * Options passed to `client.execute` / `executeHttpCommand` for a single HTTP call.
803
- * `signal` combines with `abort` or `timeout`; it is not an alias for `abort`.
804
- * `abort` and `timeout` are mutually exclusive (`abort` XOR `timeout`).
805
- */
806
- export type HttpExecuteOptions = UseRequestConfig & {
807
- signal?: AbortSignal;
808
- };
809
- /**
810
- * Endpoint returned by `defineRequest`; call with input to create an `HttpCommand`.
811
- */
812
- export type RequestEndpoint<TInput extends AnyStruct | undefined, TOutput extends AnyStruct | undefined, TError extends AnyStruct | undefined = undefined> = EndpointCallable<TInput, HttpCommand<TInput, TOutput, TError>>;
813
- type DeclaredBody<TStruct> = [TStruct] extends [undefined] ? undefined : Infer<TStruct>;
814
- type DeclaredResponse<TStruct> = [TStruct] extends [undefined] ? HttpMeta : DecodedResponse<Infer<TStruct>>;
815
- /** Inferred success body for the declared `output` struct, or `undefined` when none is declared. */
816
- export type RequestSuccessData<TOutput extends AnyStruct | undefined> = DeclaredBody<TOutput>;
817
- /** Inferred error body for the declared `error` struct, or `undefined` when none is declared. */
818
- export type RequestErrorData<TError extends AnyStruct | undefined> = DeclaredBody<TError>;
819
- type ResponseDeclaration<TOutput extends AnyStruct | undefined, TError extends AnyStruct | undefined> = ([TOutput] extends [undefined] ? {
820
- output?: never;
821
- } : {
822
- output: TOutput;
823
- }) & ([TError] extends [undefined] ? {
824
- error?: never;
825
- } : {
826
- error: TError;
827
- });
828
- /**
829
- * Contract for an HTTP endpoint: method, path, optional input struct, and the structs that
830
- * decode its bodies.
1579
+ * Lifecycle state of a managed WebSocket session.
831
1580
  *
832
- * `output` declares the body for a 2xx response, `error` the body for anything else. Only one
833
- * of the two ever runs, and omitting one means that body is never read. Pass to `defineRequest`
834
- * to get a typed endpoint.
835
- */
836
- export type RequestDefinition<TInput extends AnyStruct | undefined = undefined, TOutput extends AnyStruct | undefined = undefined, TError extends AnyStruct | undefined = undefined> = ResponseDeclaration<TOutput, TError> & {
837
- builder?: OutboundBuilder;
838
- input?: TInput;
839
- method: string;
840
- operation?: string;
841
- path: string;
842
- };
843
- /**
844
- * Await-result tuple for an HTTP call.
1581
+ * | State | Meaning |
1582
+ * | -------------- | ---------------------------------------------------------------------- |
1583
+ * | `idle` | Before the first connect attempt |
1584
+ * | `connecting` | Preparing and opening the first connection |
1585
+ * | `open` | The physical socket is open |
1586
+ * | `reconnecting` | Preparing or waiting for a replacement socket after a close |
1587
+ * | `closing` | Closure was requested with `session.close()` or disposal |
1588
+ * | `closed` | Terminal: the session ended without an error or cancellation |
1589
+ * | `aborted` | Terminal: the session was cancelled through `abort` or `signal` |
1590
+ * | `error` | Terminal: the session failed, including a startup failure or `timeout` |
845
1591
  *
846
- * Success is `[null, data, response]`; failure is `[fault, undefined, undefined]` because the
847
- * fault carries whatever response metadata existed.
848
- */
849
- export type HttpAwaitResult<TData = undefined, TErrorData = undefined> = [error: null, result: TData, response: [TData] extends [undefined] ? HttpMeta : DecodedResponse<TData>] | [error: FaultOf<TErrorData>, result: undefined, response: undefined];
850
- type HttpExecuteAwaitResult<TOutput extends AnyStruct | undefined, TError extends AnyStruct | undefined> = [error: null, result: RequestSuccessData<TOutput>, response: DeclaredResponse<TOutput>] | [error: Fault<TError>, result: undefined, response: undefined];
851
- /**
852
- * Declare a typed HTTP request endpoint.
853
- *
854
- * Pass method, path, an optional input struct, and the single `output` and `error` structs.
855
- * Call the returned endpoint with input to get an `HttpCommand` for `client.execute`.
1592
+ * Observe it with `session.state` and `session.onStateChange`.
856
1593
  *
857
- * @param definition - Request contract (method, path, input, builder, output).
858
- * @returns An endpoint that creates `HttpCommand` values from input.
859
- *
860
- * @example
861
- * ```ts
862
- * const getUser = defineRequest({
863
- * method: 'GET',
864
- * path: '/users/:id',
865
- * input: struct.object({ id: struct.number() }),
866
- * builder: (input) => ({ path: { id: input.id } }),
867
- * output: struct.object({ id: struct.number(), name: struct.string() }),
868
- * error: struct.object({ message: struct.string() }),
869
- * })
870
- * ```
1594
+ * @group web-socket
871
1595
  */
872
- export declare function defineRequest<const M extends string, const P extends string, TInput extends AnyStruct | undefined = undefined, TOutput extends AnyStruct | undefined = undefined, TError extends AnyStruct | undefined = undefined>(definition: ResponseDeclaration<TOutput, TError> & {
873
- input?: TInput;
874
- method: M;
875
- operation?: string;
876
- path: P;
877
- } & BuilderField<NoInfer<TInput>, HttpRequestShape<NoInfer<P>, NoInfer<M>>>): RequestEndpoint<TInput, TOutput, TError>;
1596
+ export type WebSocketState = "aborted" | "closed" | "closing" | "connecting" | "error" | "idle" | "open" | "reconnecting";
878
1597
  /**
879
- * Run an `HttpCommand` against client config: validate input, send the request, parse the response.
880
- * Prefer `client.execute(command)` in application code; this is the underlying implementation.
1598
+ * Decoded type of a received WebSocket message.
1599
+ *
1600
+ * The type of each message yielded by `session.receive`: the {@link Infer} output of the endpoint's `incoming` struct. Aliased fields use their logical names.
881
1601
  *
882
- * @param clientConfig - Resolved HTTP client configuration (endpoint, interceptors, handler).
883
- * @param command - Command from a `defineRequest` endpoint.
884
- * @param options - Per-request progress and cancellation options.
885
- * @returns An await-result tuple of success body or typed request error.
1602
+ * @group web-socket
886
1603
  */
887
- export declare function executeHttpCommand<TInput extends AnyStruct | undefined, TOutput extends AnyStruct | undefined, TError extends AnyStruct | undefined>(clientConfig: HttpClientConfig, command: HttpCommand<TInput, TOutput, TError>, options?: HttpExecuteOptions): Promise<HttpExecuteAwaitResult<TOutput, TError>>;
888
- //#endregion
889
- //#region src/sse/sse.d.ts
890
- /** Map of SSE event names to payload structs. Declare `__proto__` with a computed own key. */
891
- export type EventStructs = {
892
- [key: string]: AnyStruct;
893
- };
894
- type EventName<TKey extends string | number> = `${TKey}`;
895
- type KnownEventUnion<TEvents extends EventStructs> = { [K in keyof TEvents & (number | string)]: EventName<K> extends 'default' ? never : {
896
- data: Infer<TEvents[K]>;
897
- event: EventName<K>;
898
- id?: string;
899
- }; }[keyof TEvents & (number | string)];
900
- type DefaultEventUnion<TEvents extends EventStructs> = 'default' extends keyof TEvents ? {
901
- data: Infer<TEvents['default']>;
902
- event: string;
903
- id?: string;
904
- } : never;
905
- /** Parsed SSE event payload inferred from an `EventStructs` map. */
906
- export type EventStreamData<TEvents extends EventStructs> = TEvents extends EventStructs ? KnownEventUnion<TEvents> | DefaultEventUnion<TEvents> : never;
907
- interface EventStreamDefinitionBase<TEvents extends EventStructs = EventStructs> {
908
- events: TEvents;
909
- maxBufferSize: number;
910
- maxQueueSize: number;
911
- method?: string;
912
- operation?: string;
913
- path: string;
914
- }
915
- /** Makes `error` required only when an error struct is declared, so omission stays `Fault<undefined>`. */
916
- type EventStreamErrorField<TError extends AnyStruct | undefined> = [TError] extends [undefined] ? {
917
- error?: never;
918
- } : {
919
- error: TError;
920
- };
921
- /** Contract describing an SSE endpoint: path, events, and buffer/queue limits. */
922
- export type EventStreamDefinition<TInput extends AnyStruct | undefined = undefined, TEvents extends EventStructs = EventStructs, TError extends AnyStruct | undefined = undefined> = EventStreamDefinitionBase<TEvents> & EventStreamErrorField<TError> & {
923
- builder?: OutboundBuilder;
924
- input?: TInput;
925
- };
926
- /** Await-result tuple from opening an SSE stream via `client.execute`. */
927
- export type StreamAwaitResult<TEvent, TError extends AnyStruct | undefined = undefined> = [error: null, stream: EventStreamHandle<TEvent>, open: EventStreamOpenInfo] | [error: Fault<TError>, stream: undefined, open: EventStreamOpenInfo | undefined];
928
- /** Executable SSE command produced by an `EventStreamEndpoint`. */
929
- export interface EventStreamCommand<TInput extends AnyStruct | undefined, TEvents extends EventStructs, TError extends AnyStruct | undefined = undefined> extends BaseCommand<typeof EVENT_STREAM_COMMAND> {
930
- readonly endpoint: ResolvedEventStreamDefinition<TInput, TEvents, TError>;
931
- readonly input: EndpointInput<TInput> | undefined;
932
- }
933
- /** Per-execute options for SSE commands (abort, timeout). */
934
- export type EventStreamExecuteOptions = UseCancellationConfig & {
935
- signal?: AbortSignal;
936
- };
937
- /** Endpoint returned by `defineEventStream`; call it to create an `EventStreamCommand`. */
938
- export type EventStreamEndpoint<TInput extends AnyStruct | undefined, TEvents extends EventStructs, TError extends AnyStruct | undefined = undefined> = EndpointCallable<TInput, EventStreamCommand<TInput, TEvents, TError>>;
939
- type ResolvedEventStreamDefinition<TInput extends AnyStruct | undefined = undefined, TEvents extends EventStructs = EventStructs, TError extends AnyStruct | undefined = undefined> = EventStreamDefinition<TInput, TEvents, TError> & {
940
- readonly method: string;
941
- };
1604
+ export type WebSocketIncomingData<TIncoming extends AnyStruct> = Infer<TIncoming>;
942
1605
  /**
943
- * Declare a typed Server-Sent Events endpoint.
944
- *
945
- * Pass path, event structs, and buffer/queue limits. Call the returned endpoint
946
- * with input to get an `EventStreamCommand` for `client.execute`.
1606
+ * Type accepted by WebSocket session send.
947
1607
  *
948
- * @param definition - SSE contract (path, events, buffer limits, optional input).
949
- * @returns An endpoint that creates `EventStreamCommand` values from input.
1608
+ * The type `session.send` accepts: the {@link StructInput} of the endpoint's `outgoing` struct, or `never` when the endpoint declares none, so `send` cannot be called with a value.
950
1609
  *
951
- * @example
952
- * ```ts
953
- * const useEvents = defineEventStream({
954
- * maxBufferSize: 1024,
955
- * maxQueueSize: 16,
956
- * path: '/events',
957
- * events: { message: struct.object({ text: struct.string() }) },
958
- * })
959
- * ```
1610
+ * @group web-socket
960
1611
  */
961
- export declare function defineEventStream<const P extends string, const M extends string = 'GET', TInput extends AnyStruct | undefined = undefined, TEvents extends EventStructs = EventStructs, TError extends AnyStruct | undefined = undefined>(definition: Omit<EventStreamDefinitionBase<TEvents>, 'method' | 'path'> & EventStreamErrorField<TError> & {
962
- input?: TInput;
963
- method?: M;
964
- path: P;
965
- } & BuilderField<NoInfer<TInput>, EventStreamRequestShape<NoInfer<P>, NoInfer<M>>>): EventStreamEndpoint<TInput, TEvents, TError>;
966
- /**
967
- * Open an SSE command against the given client config.
968
- *
969
- * Prefer `client.execute(command)` in application code; this is the low-level entry used by the client.
970
- *
971
- * @param clientConfig - Resolved client configuration.
972
- * @param command - SSE command from an `EventStreamEndpoint`.
973
- * @param options - Optional execute options (abort, timeout).
974
- * @returns Await-result tuple of `[error, stream, open]`.
975
- */
976
- export declare function executeEventStreamCommand<TInput extends AnyStruct | undefined, TEvents extends EventStructs, TError extends AnyStruct | undefined = undefined>(clientConfig: ClientConfig, command: EventStreamCommand<TInput, TEvents, TError>, options?: EventStreamExecuteOptions): Promise<StreamAwaitResult<EventStreamData<TEvents>, TError>>;
977
- //#endregion
978
- //#region src/client/command.d.ts
979
- declare const COMMAND_TYPE: unique symbol;
980
- declare const HTTP_COMMAND: unique symbol;
981
- declare const EVENT_STREAM_COMMAND: unique symbol;
982
- declare const WEB_SOCKET_COMMAND: unique symbol;
983
- /** The command's own runner, so `client.execute` never imports a transport the app does not use. */
984
- declare const COMMAND_EXECUTE: unique symbol;
985
- type CommandExecutor = (config: ClientConfig, command: never, options?: never) => Promise<unknown>;
986
- type CommandType = typeof HTTP_COMMAND | typeof EVENT_STREAM_COMMAND | typeof WEB_SOCKET_COMMAND;
987
- interface BaseCommand<TCommandType extends CommandType> {
988
- readonly [COMMAND_EXECUTE]: CommandExecutor;
989
- readonly [COMMAND_TYPE]: TCommandType;
990
- }
991
- //#endregion
992
- //#region src/web_socket/codec.d.ts
993
- interface WebSocketCloseSnapshot {
994
- cause?: unknown;
995
- code?: number;
996
- reason?: string;
997
- wasClean?: boolean;
998
- }
999
- //#endregion
1000
- //#region src/web_socket/web_socket.d.ts
1001
- /** Lifecycle state of a managed WebSocket session. */
1002
- export type WebSocketState = 'aborted' | 'closed' | 'closing' | 'connecting' | 'error' | 'idle' | 'open' | 'reconnecting';
1003
- /** Decoded incoming frame. Alias fields use their logical names. */
1004
- export type WebSocketIncomingData<TIncoming extends AnyStruct> = Infer<TIncoming>;
1005
- /** Value accepted by `session.send`. `never` when the endpoint declares no outgoing struct. */
1006
1612
  export type WebSocketOutgoingData<TOutgoing extends AnyStruct | undefined> = TOutgoing extends AnyStruct ? StructInput<TOutgoing> : never;
1007
1613
  interface WebSocketDefinitionBase<TIncoming extends AnyStruct = AnyStruct, TOutgoing extends AnyStruct | undefined = undefined> {
1008
1614
  incoming: TIncoming;
@@ -1013,38 +1619,113 @@ interface WebSocketDefinitionBase<TIncoming extends AnyStruct = AnyStruct, TOutg
1013
1619
  path: string;
1014
1620
  protocols?: readonly string[];
1015
1621
  }
1016
- /** Contract describing a WebSocket endpoint: path, message structs, and queue limits. */
1622
+ /**
1623
+ * Contract object passed to defineWebSocket.
1624
+ *
1625
+ * - `incoming` is the struct every received frame is parsed with. Frames are JSON text.
1626
+ *
1627
+ * - `outgoing` is the struct `session.send` validates and encodes with. Without it, `send` throws.
1628
+ *
1629
+ * - `maxIncomingQueueSize` is required and must be a positive safe integer. `maxOutgoingQueueSize` must be a non-negative safe integer; it defaults to `0`, which disables queueing of messages sent while the session is `reconnecting`.
1630
+ *
1631
+ * - `protocols` is the static default subprotocol list. `RequestOptions.protocols` and {@link withWebSocketProtocols} override it.
1632
+ *
1633
+ * - `operation` is a static, low-cardinality name for telemetry.
1634
+ *
1635
+ * - `input` is the struct for the operation input. A declared `input` always requires an explicit `builder`. `input` only defines the parameters; the builder alone decides where each value goes. Only definitions without `input` may omit `builder`. The builder may return `path` and `query` only; see {@link WebSocketRequestShape}.
1636
+ *
1637
+ * Invalid queue limits are reported when the operation runs, as a `REQ_OPTIONS_INVALID` fault.
1638
+ *
1639
+ * @group web-socket
1640
+ */
1017
1641
  export type WebSocketDefinition<TInput extends AnyStruct | undefined = undefined, TIncoming extends AnyStruct = AnyStruct, TOutgoing extends AnyStruct | undefined = undefined> = WebSocketDefinitionBase<TIncoming, TOutgoing> & {
1018
1642
  builder?: OutboundBuilder;
1019
1643
  input?: TInput;
1020
1644
  };
1021
- /** Connection metadata for the current WebSocket generation. */
1645
+ /**
1646
+ * Connection metadata for the current WebSocket generation.
1647
+ *
1648
+ * - `url` is the URL the socket reports.
1649
+ *
1650
+ * - `protocol` is the negotiated subprotocol and `extensions` the negotiated extensions. Both are `undefined` when the socket reports an empty string.
1651
+ *
1652
+ * - `generation` counts the physical sockets that reached `open`: `1` after the first open, one more after each reconnect. A snapshot taken before any open has `0`.
1653
+ *
1654
+ * `session.connection` exposes the session's current public connection info. The `connection` in the operation result preserves the first successful snapshot only for a native Defjs session or a wrapper forwarding the same live `connection` object. Custom sessions and wrappers that transform `connection` instead return their current public `connection`. Sharing `closed` cannot bypass URL sanitization or metadata filtering, and Defjs does not reconstruct first-connection metadata from arbitrary transformations.
1655
+ *
1656
+ * @group web-socket
1657
+ */
1022
1658
  export interface WebSocketConnectionInfo {
1023
1659
  extensions?: string;
1024
1660
  generation: number;
1025
1661
  protocol?: string;
1026
1662
  url?: string;
1027
1663
  }
1028
- /** How a WebSocket session ended: closed, aborted, or error. */
1664
+ /**
1665
+ * Logical terminal outcome of a WebSocket session.
1666
+ *
1667
+ * What `session.closed` resolves with, once the session has ended for good.
1668
+ *
1669
+ * - `closed`: the session ended normally, including a manual `session.close()`.
1670
+ *
1671
+ * - `aborted`: the session was cancelled; `cause` is the abort reason.
1672
+ *
1673
+ * - `error`: the session failed; `cause` is required.
1674
+ *
1675
+ * - `code`, `reason`, and `wasClean` come from the native close event when one was observed. A manual `session.close(code, reason)` fills `code` and `reason` when the close event does not.
1676
+ *
1677
+ * @group web-socket
1678
+ */
1029
1679
  export type WebSocketCloseInfo = {
1030
- kind: 'closed';
1680
+ kind: "closed";
1031
1681
  code?: number;
1032
1682
  reason?: string;
1033
1683
  wasClean?: boolean;
1034
1684
  } | {
1035
- kind: 'aborted';
1685
+ kind: "aborted";
1036
1686
  cause?: unknown;
1037
1687
  code?: number;
1038
1688
  reason?: string;
1039
1689
  wasClean?: boolean;
1040
1690
  } | {
1041
- kind: 'error';
1691
+ kind: "error";
1042
1692
  cause: unknown;
1043
1693
  code?: number;
1044
1694
  reason?: string;
1045
1695
  wasClean?: boolean;
1046
1696
  };
1047
- /** Typed WebSocket session: send/receive messages, observe state, and close. */
1697
+ /**
1698
+ * Typed WebSocket session: send and receive messages, observe state, and close.
1699
+ *
1700
+ * - `state` is the logical {@link WebSocketState}; `connection` is the latest {@link WebSocketConnectionInfo}. `bufferedAmount` is the native unsent byte count, or `0` with no physical socket.
1701
+ *
1702
+ * - `receive` yields incoming messages already parsed with the `incoming` struct. It is one-consumer: asking for a second iterator throws a `TypeError`. A frame that cannot be parsed is reported to `onRuntimeError` listeners and skipped.
1703
+ *
1704
+ * - `send(message)` validates and encodes the message with the `outgoing` struct, then sends JSON text. It sends immediately while `open`, queues while `reconnecting` when `maxOutgoingQueueSize` allows, and throws a `DOMException` named `InvalidStateError` when the session is not writable. It also throws when no `outgoing` struct is declared, the message fails the struct, or the queue is disabled or full.
1705
+ *
1706
+ * - After encoding, a value JSON cannot faithfully represent throws `TypeError` before sending or entering the queue. This includes non-finite numbers, functions, symbols, `Map`, `Set`, streams, and class instances without a JSON representation, at the root or nested. The same guard protects heartbeat messages; a heartbeat serialization failure ends the session. Struct validation may reject a value first.
1707
+ *
1708
+ * - `close(code?, reason?)` requests closure and stops the heartbeat. `code` must be `1000` or in `3000..4999`, and `reason` at most 123 UTF-8 bytes; invalid arguments throw before the state changes.
1709
+ *
1710
+ * - `closed` resolves with the logical terminal {@link WebSocketCloseInfo}.
1711
+ *
1712
+ * - `onStateChange` and `onRuntimeError` return an unsubscribe function.
1713
+ *
1714
+ * Prefer `await using ownedSession = session` for owned scope cleanup. Manual `session.close()` and `await session.closed` remain available.
1715
+ *
1716
+ * ```ts
1717
+ * async function consume(
1718
+ * session: WebSocketSession<unknown, never>,
1719
+ * ): Promise<void> {
1720
+ * await using ownedSession = session;
1721
+ * for await (const message of ownedSession.receive) console.log(message);
1722
+ * }
1723
+ * ```
1724
+ *
1725
+ * The async disposer requests native close and waits for Defjs-owned teardown, but it bounds a missing close event at one second and may reject with a `DOMException` named `TimeoutError`. It cannot prove that the physical TCP connection closed.
1726
+ *
1727
+ * @group web-socket
1728
+ */
1048
1729
  export interface WebSocketSession<TIncoming = unknown, TOutgoing = never> extends AsyncDisposable {
1049
1730
  readonly bufferedAmount: number;
1050
1731
  readonly connection: WebSocketConnectionInfo;
@@ -1057,34 +1738,83 @@ export interface WebSocketSession<TIncoming = unknown, TOutgoing = never> extend
1057
1738
  onStateChange(listener: (state: WebSocketState) => void): () => void;
1058
1739
  send(message: TOutgoing): void;
1059
1740
  }
1060
- /** Await-result tuple from opening a WebSocket via `client.execute`. */
1741
+ /**
1742
+ * Await-result tuple from opening a WebSocket.
1743
+ *
1744
+ * - On success: `[null, session, connection]`. `session.connection` moves forward on reconnect. The tuple's `connection` preserves the first successful snapshot only for a native Defjs session or a wrapper forwarding the same live `connection` object. Custom sessions and wrappers that transform `connection` instead return their current public `connection`. Sharing `closed` cannot bypass URL sanitization or metadata filtering, and Defjs does not reconstruct first-connection metadata from arbitrary transformations.
1745
+ *
1746
+ * - On failure: `[fault, undefined, connection]`. `connection` may be `undefined`, for example when the constructor failed before the socket opened.
1747
+ *
1748
+ * - Only startup failures arrive here. Once the session is returned, later errors surface through `onRuntimeError`, `receive`, and `closed`.
1749
+ *
1750
+ * @group web-socket
1751
+ */
1061
1752
  export type SocketAwaitResult<TIncoming, TOutgoing = never> = [error: null, socket: WebSocketSession<TIncoming, TOutgoing>, connection: WebSocketConnectionInfo] | [error: Fault<undefined>, socket: undefined, connection: WebSocketConnectionInfo | undefined];
1062
1753
  interface UseWebSocketBaseConfig<TIncoming = unknown, TOutgoing = unknown> {
1063
- beforeConnect?: ClientWebSocketOptions['beforeConnect'];
1754
+ beforeConnect?: ClientWebSocketOptions["beforeConnect"];
1064
1755
  heartbeat?: WebSocketHeartbeatConfig<TIncoming, TOutgoing>;
1065
1756
  protocols?: readonly string[];
1066
- reconnect?: ClientWebSocketOptions['reconnect'];
1757
+ reconnect?: ClientWebSocketOptions["reconnect"];
1067
1758
  }
1068
- /** Client configuration for WebSocket execute (heartbeat, reconnect, abort). */
1069
- export type UseWebSocketConfig<TIncoming = unknown, TOutgoing = unknown> = UseWebSocketBaseConfig<TIncoming, TOutgoing> & UseCancellationConfig;
1070
- /** Per-execute options for WebSocket commands, including an optional `AbortSignal`. */
1071
- export type WebSocketExecuteOptions<TIncoming = unknown, TOutgoing = unknown> = UseWebSocketConfig<TIncoming, TOutgoing> & {
1072
- signal?: AbortSignal;
1759
+ /**
1760
+ * WebSocket fields of {@link RequestOptions}, plus `abort`.
1761
+ *
1762
+ * `beforeConnect`, `heartbeat`, `protocols`, and `reconnect` override the client-level options for one call. `abort` covers the whole session, including after it opens. There is no startup-only deadline and no `signal` field.
1763
+ *
1764
+ * @group web-socket
1765
+ */
1766
+ export type UseWebSocketConfig<TIncoming = unknown, TOutgoing = unknown> = UseWebSocketBaseConfig<TIncoming, TOutgoing> & {
1767
+ /** Aborts the session, including after it is open. */
1768
+ abort?: AbortSignal;
1073
1769
  };
1074
- /** Executable WebSocket command produced by a `WebSocketEndpoint`. */
1075
- export interface WebSocketCommand<TInput extends AnyStruct | undefined, TIncoming extends AnyStruct, TOutgoing extends AnyStruct | undefined> extends BaseCommand<typeof WEB_SOCKET_COMMAND> {
1076
- readonly endpoint: WebSocketDefinition<TInput, TIncoming, TOutgoing>;
1077
- readonly input: EndpointInput<TInput> | undefined;
1078
- }
1079
- /** Endpoint returned by `defineWebSocket`; call it to create a `WebSocketCommand`. */
1080
- export type WebSocketEndpoint<TInput extends AnyStruct | undefined, TIncoming extends AnyStruct, TOutgoing extends AnyStruct | undefined> = EndpointCallable<TInput, WebSocketCommand<TInput, TIncoming, TOutgoing>>;
1081
- /** Reason recorded when the session is closed via `session.close()`. */
1770
+ /**
1771
+ * WebSocket fields read from {@link RequestOptions} for one call.
1772
+ *
1773
+ * See {@link UseWebSocketConfig}. `heartbeat` callbacks are not inferred from the operation; narrow `unknown` messages yourself.
1774
+ *
1775
+ * @group web-socket
1776
+ * @see {@link WebSocketHeartbeatConfig}
1777
+ */
1778
+ export type WebSocketExecuteOptions<TIncoming = unknown, TOutgoing = unknown> = UseWebSocketConfig<TIncoming, TOutgoing>;
1779
+ /**
1780
+ * Operation returned by `defineWebSocket`.
1781
+ *
1782
+ * The first argument is a {@link Client}. Further arguments are the input only. Calling it opens the session and returns the existing error-first tuple.
1783
+ *
1784
+ * @group web-socket
1785
+ */
1786
+ export type WebSocketEndpoint<Args extends unknown[], Incoming, Outgoing = never> = (client: Client, ...args: Args) => Promise<SocketAwaitResult<Incoming, Outgoing>>;
1787
+ /**
1788
+ * Close code and reason recorded when a session is closed with session.close.
1789
+ *
1790
+ * Records the `code` and `reason` passed to `session.close(code?, reason?)`. They fill `code` and `reason` of the final `{ kind: 'closed' }` close info when the peer's close event carries none. `close` throws a `DOMException` when a provided `code` is not the integer `1000` or an integer from `3000` through `4999`, and when `reason` exceeds 123 UTF-8 bytes. Omitting `code` is allowed.
1791
+ *
1792
+ * @group web-socket
1793
+ */
1082
1794
  export type ManualSocketCloseReason = {
1083
1795
  code?: number;
1084
- kind: 'manual-web-socket-close';
1796
+ kind: "manual-web-socket-close";
1085
1797
  reason?: string;
1086
1798
  };
1087
- /** Heartbeat ping/ack configuration for a live WebSocket session. */
1799
+ /**
1800
+ * Heartbeat ping and acknowledgement settings for a live WebSocket session.
1801
+ *
1802
+ * - `intervalMs` is the delay between heartbeat messages.
1803
+ *
1804
+ * - `message()` builds the payload. It is sent through the `outgoing` struct. Nothing is sent when it is omitted or returns `undefined`.
1805
+ *
1806
+ * - After struct encoding, non-JSON values fail with `TypeError` through the same serializer as `session.send`; that heartbeat failure ends the logical session without reconnecting.
1807
+ *
1808
+ * - `isAck(message)` receives each decoded incoming message. When it returns `true`, the message is treated as the acknowledgement and is not delivered to `receive`.
1809
+ *
1810
+ * - `timeoutMs` is how long to wait for an acknowledgement after a heartbeat. Missing it fails the session.
1811
+ *
1812
+ * - `intervalMs` and `timeoutMs` must be positive, finite, and at most `2_147_483_647`.
1813
+ *
1814
+ * Set it with {@link withWebSocketHeartbeat} or the `heartbeat` request option.
1815
+ *
1816
+ * @group web-socket
1817
+ */
1088
1818
  export interface WebSocketHeartbeatConfig<TIncoming = unknown, TOutgoing = unknown> {
1089
1819
  intervalMs: number;
1090
1820
  isAck?: (message: TIncoming) => boolean;
@@ -1095,99 +1825,173 @@ export interface WebSocketHeartbeatConfig<TIncoming = unknown, TOutgoing = unkno
1095
1825
  * Declare a typed WebSocket endpoint.
1096
1826
  *
1097
1827
  * Pass path, incoming (and optional outgoing) message structs, and queue limits.
1098
- * Call the returned endpoint with input to get a `WebSocketCommand` for `client.execute`.
1828
+ * Call the returned operation with a client and, when declared, the input.
1829
+ *
1830
+ * - **definition** — `path`, one `incoming` struct, and the required `maxIncomingQueueSize` (a positive safe integer). Optional `outgoing` and `input` structs, static `protocols`, `maxOutgoingQueueSize` (a non-negative safe integer), and `operation`. A declared `input` always requires an explicit `builder`. `input` only defines the parameters; the builder alone decides where each value goes. Only definitions without `input` may omit `builder`.
1831
+ *
1832
+ * A WebSocket `builder` may return `path` and `query` only; `headers` and `body` are rejected. `WebSocketDefinitionBase` and `BuilderField` are internal helper types.
1099
1833
  *
1100
1834
  * @param definition - WebSocket contract (path, structs, queue limits, optional input).
1101
- * @returns An endpoint that creates `WebSocketCommand` values from input.
1835
+ * @returns An operation `(client, ...input) => Promise<error-first tuple>`.
1102
1836
  *
1103
- * @example
1104
- * ```ts
1105
- * const useChat = defineWebSocket({
1106
- * maxIncomingQueueSize: 16,
1107
- * path: '/ws/chat',
1108
- * incoming: struct.object({ text: struct.string(), type: struct.literal('message') }),
1109
- * outgoing: struct.object({ text: struct.string(), type: struct.literal('message') }),
1110
- * })
1111
- * ```
1837
+ * @group web-socket
1838
+ * @exampleFile examples/api/define-web-socket.ts#example
1112
1839
  */
1113
- export declare function defineWebSocket<const P extends string, TInput extends AnyStruct | undefined = undefined, TIncoming extends AnyStruct = AnyStruct, TOutgoing extends AnyStruct | undefined = undefined>(definition: Omit<WebSocketDefinitionBase<TIncoming, TOutgoing>, 'path'> & {
1840
+ export declare function defineWebSocket<const P extends string, TInput extends AnyStruct | undefined = undefined, TIncoming extends AnyStruct = AnyStruct, TOutgoing extends AnyStruct | undefined = undefined>(definition: Omit<WebSocketDefinitionBase<TIncoming, TOutgoing>, "path"> & {
1114
1841
  input?: TInput;
1115
1842
  path: P;
1116
- } & BuilderField<NoInfer<TInput>, WebSocketRequestShape<NoInfer<P>>>): WebSocketEndpoint<TInput, TIncoming, TOutgoing>;
1843
+ } & BuilderField<NoInfer<TInput>, WebSocketRequestShape<NoInfer<P>>>): WebSocketEndpoint<EndpointArgs<TInput>, WebSocketIncomingData<TIncoming>, WebSocketOutgoingData<TOutgoing>>;
1117
1844
  /**
1118
- * Open a WebSocket command against the given client config.
1845
+ * The outcome of a **single connect attempt**, not the final result of the session.
1846
+ *
1847
+ * The reconnect logic uses it to decide whether to try again.
1848
+ *
1849
+ * - `closeInfo` holds the close details of that attempt: `{ cause?, code?, reason?, wasClean? }`.
1850
+ *
1851
+ * - `connection` is the connection snapshot, when the attempt got one.
1852
+ *
1853
+ * - `cause` is the error that ended the attempt, if any.
1119
1854
  *
1120
- * Prefer `client.execute(command)` in application code; this is the low-level entry used by the client.
1855
+ * - `opened` tells whether the socket reached `open` before it ended.
1121
1856
  *
1122
- * @param clientConfig - Resolved client configuration.
1123
- * @param command - WebSocket command from a `WebSocketEndpoint`.
1124
- * @param options - Optional execute options (heartbeat, reconnect, abort).
1125
- * @returns Await-result tuple of `[error, socket, connection]`.
1857
+ * The `shouldReconnect` option does not receive this object. It gets `attempt`, `cause`, `code`, `reason`, and `wasClean`, taken from it. The final result of the session is {@link WebSocketCloseInfo}.
1858
+ *
1859
+ * @group web-socket
1126
1860
  */
1127
- export declare function executeWebSocketCommand<TInput extends AnyStruct | undefined, TIncoming extends AnyStruct, TOutgoing extends AnyStruct | undefined>(clientConfig: ClientConfig, command: WebSocketCommand<TInput, TIncoming, TOutgoing>, options?: WebSocketExecuteOptions<WebSocketIncomingData<TIncoming>, WebSocketOutgoingData<TOutgoing>>): Promise<SocketAwaitResult<WebSocketIncomingData<TIncoming>, WebSocketOutgoingData<TOutgoing>>>;
1128
- /** Outcome of a single WebSocket connect attempt used by reconnect logic. */
1129
1861
  export type SocketLifecycleOutcome = {
1130
1862
  closeInfo: WebSocketCloseSnapshot;
1131
1863
  connection?: WebSocketConnectionInfo;
1132
1864
  cause?: unknown;
1133
1865
  opened: boolean;
1134
1866
  };
1135
- //#endregion
1136
- //#region src/interceptor/interceptor.d.ts
1137
1867
  /**
1138
- * Continuation that runs the rest of the HTTP interceptor chain (or the handler).
1868
+ * Continuation that runs the rest of the HTTP interceptor chain, or the handler.
1869
+ *
1870
+ * `next(req)` only goes inward: the interceptors registered after this one, then
1871
+ * the Fetch handler. It is not a new operation call from outside the chain.
1872
+ * Call `next` again for a safe
1873
+ * replay (for example one 401 refresh) from the same interceptor; outer layers
1874
+ * do not run a second time. Once the chain has settled, calling `next` rejects
1875
+ * with `HTTP interceptor next() cannot be called after the chain has settled`.
1139
1876
  *
1140
- * `next(req)` only goes **inward** — remaining interceptors registered after this one,
1141
- * then the Fetch handler. It is not a re-dispatch of `client.execute` / `axios(config)`
1142
- * from the outside of the onion. Call `next` again for a safe replay (for example one
1143
- * 401 refresh) from the same interceptor; outer layers do not run a second time.
1877
+ * @group interceptors
1144
1878
  */
1145
1879
  type HttpInterceptorNext = (req: HttpRequest) => Promise<HttpResponse<unknown>>;
1146
1880
  /**
1147
- * HTTP interceptor function: may inspect/modify the request, then call `next`.
1881
+ * HTTP interceptor function: may inspect or modify the request, then call `next`.
1882
+ *
1883
+ * Call `next(req)` or short-circuit with `makeResponse`. Wrap it with
1884
+ * `createHttpInterceptor` to register it.
1885
+ *
1886
+ * @group interceptors
1148
1887
  */
1149
1888
  type InterceptorFn = (req: HttpRequest, next: HttpInterceptorNext) => Promise<HttpResponse<unknown>>;
1150
1889
  /**
1151
- * Tagged HTTP interceptor for client option lists.
1890
+ * Tagged HTTP interceptor accepted by client interceptor options.
1891
+ *
1892
+ * Created by `createHttpInterceptor`. `kind` is the discriminant that lets
1893
+ * `withInterceptors` accept HTTP, SSE, and WebSocket interceptors in one list.
1894
+ *
1895
+ * @group interceptors
1152
1896
  */
1153
1897
  interface HttpInterceptor {
1154
- kind: 'http';
1898
+ kind: "http";
1155
1899
  fn: InterceptorFn;
1156
1900
  }
1157
1901
  /**
1158
- * Wrap an `InterceptorFn` as a tagged `HttpInterceptor`.
1902
+ * Wraps an `InterceptorFn` as a tagged `HttpInterceptor`.
1903
+ *
1904
+ * Returns `{ kind: 'http', fn }`. Call `next(req)` (optionally with a modified
1905
+ * request) or short-circuit with `makeResponse`. `next` runs only the
1906
+ * interceptors registered after this one and then the fetch handler. HTTP
1907
+ * interceptors do not run for SSE or WebSocket operations.
1908
+ *
1909
+ * A deterministic URL error from `next(req)` rejects the returned promise, so
1910
+ * `.then` or `.catch` recovery runs; if it is not recovered, the operation
1911
+ * still reports `REQ_BUILD_FAILED`.
1159
1912
  *
1160
1913
  * @param fn - HTTP interceptor function.
1161
1914
  * @returns An `HttpInterceptor` with `kind: 'http'`.
1915
+ * @group interceptors
1162
1916
  */
1163
1917
  export declare function createHttpInterceptor(fn: InterceptorFn): HttpInterceptor;
1164
1918
  /**
1165
- * Terminal SSE handler that opens an event stream for the given request.
1919
+ * Terminal `next` function an SSE interceptor receives; it opens an event stream for the request.
1920
+ *
1921
+ * `next(req)` goes inward only: through the interceptors registered after this
1922
+ * one, then the transport that opens the stream. Calling it after the chain has
1923
+ * settled rejects.
1924
+ *
1925
+ * `EventStreamHandle` extends `AsyncDisposable`. A short-circuit implementation
1926
+ * that builds its own handle must provide
1927
+ * `[Symbol.asyncDispose](): PromiseLike<void>` and connect it to the same close
1928
+ * lifecycle.
1929
+ *
1930
+ * @group interceptors
1166
1931
  */
1167
1932
  type SSEHandler = (req: HttpRequest) => Promise<EventStreamHandle<unknown>>;
1168
1933
  /**
1169
- * SSE interceptor function: may inspect/modify the request, then call `next`.
1934
+ * SSE interceptor function: may inspect, modify, or replace the request, then call `next`.
1935
+ *
1936
+ * Call `next` (an {@link SSEHandler}) to continue the chain, or return your own
1937
+ * `EventStreamHandle` to short-circuit. Wrap it with `createSSEInterceptor` to
1938
+ * register it through `withInterceptors`.
1939
+ *
1940
+ * A short-circuit handle must provide
1941
+ * `[Symbol.asyncDispose](): PromiseLike<void>` and connect it to the same close
1942
+ * lifecycle.
1943
+ *
1944
+ * @group interceptors
1170
1945
  */
1171
1946
  type SSEInterceptorFn = (req: HttpRequest, next: SSEHandler) => Promise<EventStreamHandle<unknown>>;
1172
1947
  /**
1173
- * Tagged SSE interceptor for client option lists.
1948
+ * Tagged SSE interceptor for client option lists, accepted by `withInterceptors`.
1949
+ *
1950
+ * Created by `createSSEInterceptor`. The `kind` tag lets `withInterceptors`
1951
+ * accept HTTP, SSE, and WebSocket interceptors in one list; only `kind: 'sse'`
1952
+ * interceptors run for SSE operations.
1953
+ *
1954
+ * @group interceptors
1174
1955
  */
1175
1956
  interface SSEInterceptor {
1176
- kind: 'sse';
1957
+ kind: "sse";
1177
1958
  fn: SSEInterceptorFn;
1178
1959
  }
1179
1960
  /**
1180
- * Wrap an `SSEInterceptorFn` as a tagged `SSEInterceptor`.
1961
+ * Wraps an `SSEInterceptorFn` as a tagged `SSEInterceptor`.
1962
+ *
1963
+ * Returns `{ kind: 'sse', fn }`. SSE interceptors run only for SSE operations, in
1964
+ * registration order; `next(req)` opens the stream through the remaining
1965
+ * interceptors.
1966
+ *
1967
+ * A deterministic URL error from `next(req)` rejects the returned promise, so
1968
+ * `.then` or `.catch` recovery runs; if it is not recovered, the operation
1969
+ * still reports `REQ_BUILD_FAILED`.
1970
+ *
1971
+ * `EventStreamHandle` extends `AsyncDisposable`. A structural short-circuit
1972
+ * implementation must provide `[Symbol.asyncDispose](): PromiseLike<void>` and
1973
+ * connect it to the same close lifecycle.
1181
1974
  *
1182
1975
  * @param fn - SSE interceptor function.
1183
1976
  * @returns An `SSEInterceptor` with `kind: 'sse'`.
1977
+ * @group interceptors
1184
1978
  */
1185
1979
  export declare function createSSEInterceptor(fn: SSEInterceptorFn): SSEInterceptor;
1186
1980
  /**
1187
- * Minimal WebSocket session surface used by interceptors.
1981
+ * Minimal WebSocket session surface that interceptors see.
1982
+ *
1983
+ * Structurally compatible with {@link WebSocketSession} without importing that
1984
+ * type, which avoids a circular dependency between interceptors and WebSocket.
1985
+ * Messages are typed `unknown`.
1986
+ *
1987
+ * A wrapper or short-circuit session must forward the same
1988
+ * `[Symbol.asyncDispose]()` teardown as its `close` / `closed` lifecycle;
1989
+ * returning an unrelated resolved promise is not sufficient. Requiring this
1990
+ * member is a compile-time breaking change for structural implementations. Code
1991
+ * that only accepts sessions returned by Defjs has no additional runtime call
1992
+ * requirement.
1188
1993
  *
1189
- * Structurally compatible with `WebSocketSession` without importing that type
1190
- * (avoids a circular dependency between interceptors and WebSocket).
1994
+ * @group interceptors
1191
1995
  */
1192
1996
  interface WebSocketSessionLike extends AsyncDisposable {
1193
1997
  readonly bufferedAmount: number;
@@ -1207,42 +2011,75 @@ interface WebSocketSessionLike extends AsyncDisposable {
1207
2011
  send(message: unknown): void;
1208
2012
  }
1209
2013
  /**
1210
- * Terminal WebSocket handler that opens a session for the given request.
2014
+ * Terminal `next` function a WebSocket interceptor receives; it opens a session for the request.
2015
+ *
2016
+ * `next(req)` goes inward only: through the interceptors registered after this
2017
+ * one, then the transport that opens the session. It resolves to a
2018
+ * {@link WebSocketSessionLike}. It may be called once per execute; a second
2019
+ * call, or a call after the chain has settled, rejects.
2020
+ *
2021
+ * @group interceptors
1211
2022
  */
1212
2023
  type WebSocketHandler = (req: HttpRequest) => Promise<WebSocketSessionLike>;
1213
2024
  /**
1214
- * WebSocket interceptor function: may inspect/modify the request, then call `next`.
2025
+ * WebSocket interceptor function: may inspect, modify, or replace the request, then call `next`.
2026
+ *
2027
+ * Call `next` (a {@link WebSocketHandler}) to continue the chain, or return your own
2028
+ * {@link WebSocketSessionLike} to short-circuit. Wrap it with
2029
+ * `createWebSocketInterceptor` to register it through `withInterceptors`.
2030
+ *
2031
+ * The chain runs once per operation. Reconnects happen inside the session it
2032
+ * returns and do not run it again.
2033
+ *
2034
+ * @group interceptors
1215
2035
  */
1216
2036
  type WebSocketInterceptorFn = (req: HttpRequest, next: WebSocketHandler) => Promise<WebSocketSessionLike>;
1217
2037
  /**
1218
- * Tagged WebSocket interceptor for client option lists.
2038
+ * Tagged WebSocket interceptor for client option lists, accepted by `withInterceptors`.
2039
+ *
2040
+ * Created by `createWebSocketInterceptor`. The `kind` tag lets
2041
+ * `withInterceptors` accept HTTP, SSE, and WebSocket interceptors in one list;
2042
+ * only `kind: 'web-socket'` interceptors run for WebSocket operations.
2043
+ *
2044
+ * @group interceptors
1219
2045
  */
1220
2046
  interface WebSocketInterceptor {
1221
- kind: 'web-socket';
2047
+ kind: "web-socket";
1222
2048
  fn: WebSocketInterceptorFn;
1223
2049
  }
1224
2050
  /**
1225
- * Wrap a `WebSocketInterceptorFn` as a tagged `WebSocketInterceptor`.
2051
+ * Wraps a `WebSocketInterceptorFn` as a tagged `WebSocketInterceptor`.
2052
+ *
2053
+ * Returns `{ kind: 'web-socket', fn }`. WebSocket interceptors run only for
2054
+ * WebSocket operations. `next` and the interceptor return a
2055
+ * {@link WebSocketSessionLike}, the interceptor-facing session. A wrapper or
2056
+ * short-circuit session must forward its `[Symbol.asyncDispose]()` teardown.
1226
2057
  *
1227
2058
  * @param fn - WebSocket interceptor function.
1228
2059
  * @returns A `WebSocketInterceptor` with `kind: 'web-socket'`.
2060
+ * @group interceptors
1229
2061
  */
1230
2062
  export declare function createWebSocketInterceptor(fn: WebSocketInterceptorFn): WebSocketInterceptor;
1231
2063
  /**
1232
2064
  * Any tagged interceptor accepted by client interceptor options.
2065
+ *
2066
+ * `kind` (`'http'`, `'sse'`, or `'web-socket'`) selects which operations an
2067
+ * interceptor runs for. `withInterceptors` accepts any mix; each transport runs
2068
+ * only its own kind, in registration order.
2069
+ *
2070
+ * @group interceptors
1233
2071
  */
1234
2072
  type Interceptor = HttpInterceptor | SSEInterceptor | WebSocketInterceptor;
1235
- //#endregion
1236
- //#region src/client/config.d.ts
1237
2073
  /**
1238
2074
  * Serializes an encoded query object into a query string.
1239
2075
  *
1240
- * `query` is the wire object: aliases already applied, `Date` values already ISO
1241
- * strings, `bigint` values already strings. Nested objects and arrays are preserved.
1242
- * The returned string is used verbatim and is not passed through `URLSearchParams`.
2076
+ * `query` is the wire object: aliases already applied, `Date` values already ISO strings, `bigint` values already strings. Nested objects and arrays are preserved. The string is used verbatim and is not passed through `URLSearchParams`. Interceptors see `queryParams` as `new URLSearchParams(queryString)`.
2077
+ *
2078
+ * Set it with `withQuerySerializer`. Without one, queries use `URLSearchParams`: arrays repeat keys and a nested object fails. A builder that returns `URLSearchParams` skips the serializer.
1243
2079
  *
1244
2080
  * @param query - Encoded query object.
1245
2081
  * @returns The query string without a leading `?`.
2082
+ * @group client
1246
2083
  */
1247
2084
  type QuerySerializer = (query: {
1248
2085
  [key: string]: unknown;
@@ -1250,24 +2087,52 @@ type QuerySerializer = (query: {
1250
2087
  /**
1251
2088
  * Fetch-compatible function used as an HTTP or SSE handle.
1252
2089
  *
1253
- * This is the `(input, init?) => Promise<Response>` call signature Defjs invokes,
1254
- * not `typeof fetch`, so mocks do not need host extras such as `preconnect`.
2090
+ * This is the `(input, init?) => Promise<Response>` call signature Defjs invokes, deliberately not `typeof fetch`, so a mock does not need host extras such as `preconnect`. HTTP calls the untouched platform Fetch as `(url, init)`; a replaced global Fetch or custom handle receives a `Request`. The default resolves the global on each call. SSE always passes a `Request`, including to its default handle.
2091
+ *
2092
+ * Accepted by `withHTTPHandle`, `withSSEHandle`, and `fetchHandler`.
2093
+ *
2094
+ * @group client
1255
2095
  */
1256
2096
  type FetchHandle = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
1257
2097
  /**
1258
- * Minimal WebSocket-like handle used by the client transport.
2098
+ * Minimal WebSocket instance surface used by the client transport.
2099
+ *
2100
+ * It is the DOM `WebSocket` without its event-handler properties, `binaryType`, `dispatchEvent`, and the state constants. A custom implementation therefore needs `url`, `protocol`, `extensions`, `bufferedAmount`, `send`, `close`, `addEventListener`, `removeEventListener`, and a numeric `readyState`.
2101
+ *
2102
+ * The transport sets `binaryType = 'arraybuffer'` when the property exists and ignores its absence. If the setter throws, it requests best-effort closure of the constructed handle before failing that attempt with `NET_UNREACHABLE`; the socket is not left outside cleanup. This property is an optimization, and the decoder also accepts Blob frames. This is the instance type produced by a {@link WebSocketHandleConstructor}.
1259
2103
  *
1260
- * Omits browser event-handler properties so custom implementations only need
1261
- * the send/close/state surface Defjs relies on.
2104
+ * @group client
1262
2105
  */
1263
- type WebSocketHandle = Omit<WebSocket, 'binaryType' | 'CLOSED' | 'CLOSING' | 'CONNECTING' | 'dispatchEvent' | 'onclose' | 'onerror' | 'onmessage' | 'onopen' | 'OPEN' | 'readyState'> & {
2106
+ type WebSocketHandle = Omit<WebSocket, "binaryType" | "CLOSED" | "CLOSING" | "CONNECTING" | "dispatchEvent" | "onclose" | "onerror" | "onmessage" | "onopen" | "OPEN" | "readyState"> & {
1264
2107
  readonly readyState: number;
1265
2108
  };
1266
- /** Constructor for a `WebSocketHandle`, including the `OPEN` ready-state constant. */
2109
+ /**
2110
+ * Constructor type accepted by `withWebSocketHandle`.
2111
+ *
2112
+ * A class or function that builds a {@link WebSocketHandle} and exposes the numeric `OPEN` ready-state constant. The transport compares `readyState` with `OPEN`.
2113
+ *
2114
+ * It is called as `new WebSocketImpl(url)`, or `new WebSocketImpl(url, protocols)` when subprotocols are set. The default is `globalThis.WebSocket`.
2115
+ *
2116
+ * @group client
2117
+ */
1267
2118
  type WebSocketHandleConstructor = {
1268
2119
  readonly OPEN: number;
1269
2120
  } & (new (...args: never[]) => WebSocketHandle);
1270
- /** Optional WebSocket settings applied when creating a client. */
2121
+ /**
2122
+ * Optional WebSocket settings applied when creating a client: constructor, `beforeConnect` hook, heartbeat, protocols, and reconnect policy.
2123
+ *
2124
+ * - **handle** — WebSocket constructor; defaults to `globalThis.WebSocket`.
2125
+ * - **beforeConnect** — Runs before each connect and reconnect attempt.
2126
+ * - **heartbeat** — Sends `message()` every `intervalMs`. With `timeoutMs` and `isAck`, a missing acknowledgement marks the connection unhealthy. `intervalMs` and `timeoutMs` must be positive and at most 2147483647.
2127
+ * - **protocols** — Subprotocols passed to the constructor.
2128
+ * - **reconnect** — Opt-in. `attempts` defaults to **3**; `0` disables retry, and omitting `attempts` is not unlimited. Other defaults: `delayMs` 1000, `factor` 2, `jitter` 0 (a 0-1 multiplicative factor), `maxDelayMs` 30000. `shouldReconnect` receives the close `code`, `reason`, and `wasClean` and can veto a retry.
2129
+ *
2130
+ * The retry budget applies per outage. Successful `open` resets the budget, callback attempt, and backoff exponent; the next outage starts at retry 1. `maxDelayMs` caps the final delay after jitter and rounding, also bounded by `2_147_483_647` ms. See {@link withWebSocketReconnect} for validation rules and overflow behavior.
2131
+ *
2132
+ * Set these with `withWebSocketHandle`, `withWebSocketBeforeConnect`, `withWebSocketHeartbeat`, `withWebSocketProtocols`, and `withWebSocketReconnect`. `WebSocketExecuteOptions` can override `beforeConnect`, `heartbeat`, `protocols`, and `reconnect` per call.
2133
+ *
2134
+ * @group client
2135
+ */
1271
2136
  interface ClientWebSocketOptions {
1272
2137
  /** WebSocket constructor; defaults to `globalThis.WebSocket`. */
1273
2138
  handle?: WebSocketHandleConstructor;
@@ -1291,15 +2156,15 @@ interface ClientWebSocketOptions {
1291
2156
  protocols?: readonly string[];
1292
2157
  /** Automatic reconnect policy after unexpected closes. */
1293
2158
  reconnect?: {
1294
- /** Retry budget after the first connect. Defaults to 3 when `reconnect` is set. `0` disables. Omitting this field is not unlimited. */
2159
+ /** Retries per outage; a successful open resets the budget. Non-negative safe integer; defaults to 3. `0` disables. */
1295
2160
  attempts?: number;
1296
2161
  /** Initial backoff delay in milliseconds. */
1297
2162
  delayMs?: number;
1298
- /** Multiplier applied to the delay after each failed attempt. */
2163
+ /** Multiplier applied after each failed attempt of the current outage. A successful `open` resets the exponent. */
1299
2164
  factor?: number;
1300
2165
  /** Random jitter factor applied to the delay (0–1). */
1301
2166
  jitter?: number;
1302
- /** Upper bound for computed backoff delay. */
2167
+ /** Non-negative cap on the final backoff delay, including jitter; `Infinity` removes the configured cap. */
1303
2168
  maxDelayMs?: number;
1304
2169
  /** Return `false` to skip reconnecting for this close. */
1305
2170
  shouldReconnect?: (context: {
@@ -1311,13 +2176,25 @@ interface ClientWebSocketOptions {
1311
2176
  }) => boolean;
1312
2177
  };
1313
2178
  }
1314
- /** Optional SSE settings applied when creating a client. */
2179
+ /**
2180
+ * Optional SSE settings applied when creating a client: fetch handle, invalid-event hook, and reconnect policy.
2181
+ *
2182
+ * - **handle** — Fetch implementation that opens the stream. The default resolves `globalThis.fetch` on each attempt, including replacements installed after import. Every SSE handle receives a `Request`. Missing global Fetch fails default execution with `ENV_UNSUPPORTED`; a custom handle remains usable.
2183
+ * - **onInvalidEvent** — Called when an event has no matching struct and no `default` struct (`'missing-struct'`), or its data fails validation (`'validation-failed'`). The event is skipped. An error thrown by the hook does not close the stream.
2184
+ * - **reconnect** — Opt-in. Without it the stream never reconnects. `attempts` defaults to **3** per outage; `attempts: 0` disables retry. A successful handshake resets the budget, callback `attempt`, and backoff exponent. `delayMs` is the first base delay, 1000 by default, and a server-sent `retry:` value replaces it. `factor` defaults to 1. `jitter` is a 0–1 multiplicative factor and defaults to 0. `maxDelayMs` caps the final delay after jitter and rounding; without a configured cap, the platform limit of `2_147_483_647` ms still applies. A zero base stays zero; valid-number overflow saturates at the caps. Without `shouldReconnect` only `GET`, `HEAD`, `OPTIONS`, `PUT`, and `DELETE` reconnect; with it, its result decides for every method and its context carries `method`. A `ReadableStream` request body never reconnects. Reconnect sends the latest ID as UTF-8 bytes in `Last-Event-ID`; an empty ID clears it and an ID containing NUL is ignored.
2185
+ *
2186
+ * Reconnect options are validated before interceptors or Fetch: `attempts` is a non-negative safe integer; `delayMs` must be finite and non-negative; `maxDelayMs` must be a non-negative finite number or positive `Infinity`, which removes the configured cap but not the platform limit of `2_147_483_647` ms; `factor` is finite and positive; `jitter` is finite and within 0–1. Invalid values return `REQ_OPTIONS_INVALID` when the operation runs.
2187
+ *
2188
+ * Set these with `withSSEHandle`, `withSSEOnInvalidEvent`, and `withSSEReconnect`. Omitting `withSSEReconnect` disables retry.
2189
+ *
2190
+ * @group client
2191
+ */
1315
2192
  interface ClientSSEOptions {
1316
- /** Fetch implementation used to open the event stream. */
2193
+ /** Fetch implementation used to open the event stream; defaults to resolving global `fetch` on each call. */
1317
2194
  handle?: FetchHandle;
1318
2195
  /** Called when an event has no matching struct or fails validation. */
1319
2196
  onInvalidEvent?: (context: {
1320
- reason: 'missing-struct' | 'validation-failed';
2197
+ reason: "missing-struct" | "validation-failed";
1321
2198
  message: {
1322
2199
  id: string;
1323
2200
  event: string;
@@ -1328,21 +2205,25 @@ interface ClientSSEOptions {
1328
2205
  }) => unknown;
1329
2206
  /** Automatic reconnect policy after stream failures. */
1330
2207
  reconnect?: {
1331
- /** Retry budget after the first attempt. Defaults to 3 when `reconnect` is set. `0` disables. Omitting this field is not unlimited. */
2208
+ /** Retries per outage; a successful handshake resets the budget. Non-negative safe integer; defaults to 3. `0` disables. */
1332
2209
  attempts?: number;
1333
- /** Initial backoff delay in milliseconds. */
2210
+ /** Initial backoff delay in milliseconds, finite and non-negative. Defaults to 1000. */
1334
2211
  delayMs?: number;
1335
- /** Multiplier applied to the delay after each failed attempt. */
2212
+ /** Finite positive multiplier per failed retry, reset after a successful handshake. Defaults to 1. */
1336
2213
  factor?: number;
1337
- /** Random jitter factor applied to the delay (0–1). */
2214
+ /** Finite random jitter factor applied to the delay (0–1). Defaults to 0. */
1338
2215
  jitter?: number;
1339
- /** Upper bound for computed backoff delay. */
2216
+ /** Non-negative cap on the final delay, including jitter; no configured cap when omitted or `Infinity`. */
1340
2217
  maxDelayMs?: number;
1341
- /** Return `false` to skip reconnecting for this failure. */
2218
+ /**
2219
+ * Without this callback only `GET`, `HEAD`, `OPTIONS`, `PUT` and `DELETE` reconnect. With it, the result decides
2220
+ * for every method. It is never called for a `ReadableStream` body.
2221
+ */
1342
2222
  shouldReconnect?: (context: {
1343
2223
  attempt: number;
1344
2224
  cause?: unknown;
1345
2225
  lastEventId: string;
2226
+ method: string;
1346
2227
  open?: {
1347
2228
  response: {
1348
2229
  status: number;
@@ -1354,7 +2235,13 @@ interface ClientSSEOptions {
1354
2235
  }) => boolean | Promise<boolean>;
1355
2236
  };
1356
2237
  }
1357
- /** Resolved SSE config with a required `handle`. */
2238
+ /**
2239
+ * Resolved SSE configuration with a required fetch handle.
2240
+ *
2241
+ * The type of `ClientConfig.sse`. It is {@link ClientSSEOptions} with `handle` always present; the default wrapper resolves the current global `fetch` on each attempt, rather than capturing it at import time.
2242
+ *
2243
+ * @group client
2244
+ */
1358
2245
  interface ClientSSEConfig extends ClientSSEOptions {
1359
2246
  /** Fetch implementation used to open the event stream. */
1360
2247
  handle: FetchHandle;
@@ -1363,9 +2250,9 @@ interface ClientSSEConfig extends ClientSSEOptions {
1363
2250
  interface HttpClientOptions {
1364
2251
  /** Base URL prepended to relative request paths. */
1365
2252
  endpoint: string;
1366
- /** Static default headers merged under command headers for HTTP/SSE. */
2253
+ /** Static default headers merged under headers returned by the builder for HTTP/SSE. */
1367
2254
  headers?: Headers;
1368
- /** HTTP transport overrides. */
2255
+ /** HTTP transport overrides; the default resolves global `fetch` on each call. */
1369
2256
  http?: {
1370
2257
  handle?: FetchHandle;
1371
2258
  };
@@ -1373,8 +2260,6 @@ interface HttpClientOptions {
1373
2260
  interceptors?: Interceptor[];
1374
2261
  /** Serializes encoded query objects. Omit to use `URLSearchParams`. */
1375
2262
  querySerializer?: QuerySerializer;
1376
- /** Default HTTP execute timeout in milliseconds (SSE/WebSocket ignore this). */
1377
- timeout?: number;
1378
2263
  /** XSRF cookie/header mapping and optional token provider. */
1379
2264
  xsrf?: {
1380
2265
  /** Cookie name that stores the XSRF token. */
@@ -1389,7 +2274,13 @@ interface HttpClientOptions {
1389
2274
  /** When `true`, credentials are included with requests. */
1390
2275
  withCredentials?: boolean;
1391
2276
  }
1392
- /** User-facing options for `createClient`, including SSE and WebSocket. */
2277
+ /**
2278
+ * Input-shaped client options, including SSE and WebSocket, that mirror what the option helpers configure.
2279
+ *
2280
+ * The user-facing shape of the settings before defaults are applied. `createClient` does not accept this object; configure a client with option helpers such as `withEndpoint`. {@link ClientConfig} is the resolved counterpart. The HTTP members come from the non-exported `HttpClientOptions` base and are shown inline.
2281
+ *
2282
+ * @group client
2283
+ */
1393
2284
  interface ClientOptions extends HttpClientOptions {
1394
2285
  /** SSE transport and reconnect options. */
1395
2286
  sse?: ClientSSEOptions;
@@ -1400,7 +2291,7 @@ interface ClientOptions extends HttpClientOptions {
1400
2291
  interface HttpClientConfig {
1401
2292
  /** Base URL prepended to relative request paths. */
1402
2293
  endpoint: string;
1403
- /** Static default headers merged under command headers for HTTP/SSE. */
2294
+ /** Static default headers merged under headers returned by the builder for HTTP/SSE. */
1404
2295
  headers?: Headers;
1405
2296
  /** HTTP transport with a required fetch handle. */
1406
2297
  http: {
@@ -1410,8 +2301,6 @@ interface HttpClientConfig {
1410
2301
  interceptors: Interceptor[];
1411
2302
  /** Serializes encoded query objects. Omit to use `URLSearchParams`. */
1412
2303
  querySerializer?: QuerySerializer;
1413
- /** Default HTTP execute timeout in milliseconds (SSE/WebSocket ignore this). */
1414
- timeout?: number;
1415
2304
  /** Resolved XSRF settings when enabled. */
1416
2305
  xsrf?: {
1417
2306
  /** Cookie name that stores the XSRF token. */
@@ -1426,206 +2315,529 @@ interface HttpClientConfig {
1426
2315
  /** When `true`, credentials are included with requests. */
1427
2316
  withCredentials?: boolean;
1428
2317
  }
1429
- /** Resolved client config held by instances from `createClient`. */
2318
+ /**
2319
+ * Resolved client configuration after option helpers have been applied.
2320
+ *
2321
+ * The snapshot that `createClient` builds, that every option helper mutates, and that instances from `createClient` hold. The HTTP members come from a non-exported `HttpClientConfig` base and are shown inline.
2322
+ *
2323
+ * Before any option applies, `endpoint` is `''`, `interceptors` is empty, and `http.handle` and `sse.handle` share a default wrapper that resolves `globalThis.fetch` on each call. Replacing Fetch after import affects both defaults. Core can be imported without Fetch; a default HTTP/SSE execution fails with `ENV_UNSUPPORTED` if it is still missing. Custom handles do not require a global Fetch. `webSocket.handle` is `globalThis.WebSocket`, and the other `webSocket` settings are unset. `withXSRF` fills `xsrf.cookieName` with `XSRF-TOKEN` and `xsrf.headerName` with `X-XSRF-TOKEN` when they are omitted.
2324
+ *
2325
+ * @group client
2326
+ */
1430
2327
  interface ClientConfig extends HttpClientConfig {
1431
2328
  /** Resolved SSE configuration. */
1432
2329
  sse: ClientSSEConfig;
1433
2330
  /** Resolved WebSocket configuration. */
1434
2331
  webSocket: ClientWebSocketOptions;
1435
2332
  }
1436
- //#endregion
1437
- //#region src/client/option.d.ts
1438
- /** Mutates `ClientConfig` when applied by `createClient`. */
2333
+ /**
2334
+ * Function type of the option helpers accepted by `createClient`.
2335
+ *
2336
+ * Mutates the {@link ClientConfig} that `createClient` is building. Every `with*` helper returns one. `createClient` applies the options in order, so a later option overwrites an earlier one for the same setting, except `withInterceptors`, which appends.
2337
+ *
2338
+ * @group client
2339
+ * @see {@link ClientConfig}
2340
+ */
1439
2341
  type ClientOption = (config: ClientConfig) => void;
1440
2342
  /**
1441
- * Sets the base URL prepended to relative request paths.
2343
+ * Sets the valid absolute base URL prepended to relative request paths.
2344
+ *
2345
+ * The default is an empty string, so execution requires an explicit endpoint. Its pathname is kept as a directory; its query and hash are discarded. For WebSocket operations, an `http:` base becomes `ws:` and `https:` becomes `wss:`.
1442
2346
  *
1443
- * @param endpoint - Absolute or relative base endpoint.
2347
+ * A missing, relative, or invalid endpoint, or an illegal request path, fails with `REQ_BUILD_FAILED` during building, before any interceptor or transport runs, even when an interceptor would short-circuit. HTTP, SSE, and WebSocket share this rule. The resolved request must keep the base origin and stay under its directory; C0 control characters (including tab, CR, and LF), backslashes, `?`, `#`, and directory escapes in a path are rejected. Scheme-like and protocol-relative forms are rejected even with leading spaces; a plain inner space is allowed and sent as `%20`. An interceptor's final `req.endpoint` is checked against its final `req.baseEndpoint` with the same rule, but it may deliberately replace the base with another valid absolute URL. Deterministic URL errors are never retried.
2348
+ *
2349
+ * @param endpoint - Absolute base endpoint. Missing or invalid endpoints fail request building.
1444
2350
  * @returns An option that writes `config.endpoint`.
2351
+ * @group client
1445
2352
  */
1446
2353
  export declare function withEndpoint(endpoint: string): ClientOption;
1447
2354
  /**
1448
- * Sets static default headers for HTTP/SSE requests built by this client.
2355
+ * Sets static default headers for HTTP and SSE requests built by this client.
2356
+ *
2357
+ * WebSocket connections do not use them. The `HeadersInit` is copied into a `Headers` object when the option is applied. A later call replaces the earlier headers instead of merging.
1449
2358
  *
1450
- * Builder `headers` override the same names. HMAC and
1451
- * dynamic Bearer tokens still belong in interceptors (they need the serialized body
1452
- * or a refreshed credential).
2359
+ * Headers returned by a request builder replace a default of the same name. The first value for that name replaces the default; later values in the same array append only to that replacement. A scalar or a `Headers` instance also replaces. Other default names stay.
2360
+ *
2361
+ * Per-request or computed credentials, such as a refreshed Bearer token or an HMAC signature, belong in an interceptor, because they need the serialized body or a refreshed credential.
1453
2362
  *
1454
2363
  * @param headers - `HeadersInit` bag cloned onto the client config.
1455
2364
  * @returns An option that writes `config.headers`.
2365
+ * @group client
1456
2366
  */
1457
2367
  export declare function withHeaders(headers: HeadersInit): ClientOption;
1458
- /**
1459
- * Sets the default timeout for HTTP `execute` only.
1460
- *
1461
- * Timeout is applied before the interceptor chain (`mergeAbortSignals`). Execute
1462
- * `{ timeout }` overrides this default. Execute `{ abort }` combines with the
1463
- * default (abort → `ABORTED`, timer → `TIMEOUT`). SSE and WebSocket ignore this
1464
- * option. XOR still rejects execute options that pass both `abort` and `timeout`.
1465
- *
1466
- * @param ms - Positive safe integer timeout in milliseconds.
1467
- * @returns An option that writes `config.timeout`.
1468
- */
1469
- export declare function withTimeout(ms: number): ClientOption;
1470
2368
  /**
1471
2369
  * Appends interceptors to the client interceptor chain.
1472
2370
  *
2371
+ * Calling it again appends more; it never replaces earlier ones. The first registered interceptor is the outermost layer. HTTP, SSE, and WebSocket interceptors can be mixed in one call. When an operation runs, only the interceptors of that transport run, in their registration order.
2372
+ *
1473
2373
  * @param interceptors - Interceptors applied in registration order.
1474
2374
  * @returns An option that pushes onto `config.interceptors`.
2375
+ * @group client
1475
2376
  */
1476
2377
  export declare function withInterceptors(...interceptors: Interceptor[]): ClientOption;
1477
2378
  /**
1478
- * Sets the query serializer for HTTP, SSE, and WebSocket requests.
2379
+ * Replaces query encoding for HTTP, SSE, and WebSocket requests.
1479
2380
  *
1480
- * The function receives the encoded wire object and its string is used verbatim.
1481
- * Without this option, queries use `URLSearchParams`: arrays repeat keys and a nested
1482
- * object fails. A builder that returns `URLSearchParams` skips this serializer.
2381
+ * `serializer(query)` receives the encoded wire object: aliases applied, `Date` as an ISO string, `bigint` as a string. Nested objects and arrays are kept as they are. It must return the query string without a leading `?`. The string is used verbatim.
2382
+ *
2383
+ * It is not called when the request has no query. A builder that returns `URLSearchParams` skips it. Without this option, queries use `URLSearchParams`: arrays repeat keys and a nested object fails.
1483
2384
  *
1484
2385
  * @param serializer - Function that turns an encoded query object into a string.
1485
2386
  * @returns An option that sets `config.querySerializer`.
2387
+ * @group client
1486
2388
  */
1487
2389
  export declare function withQuerySerializer(serializer: QuerySerializer): ClientOption;
1488
2390
  /**
1489
- * Overrides the Fetch-compatible function used for HTTP requests.
2391
+ * Replaces the Fetch-compatible function used for HTTP requests.
2392
+ *
2393
+ * The default is the platform `fetch`. `fetchImpl` is a {@link FetchHandle}, so a test double does not need host extras such as `fetch.preconnect`. It only affects HTTP. SSE uses {@link withSSEHandle}.
1490
2394
  *
1491
2395
  * @param fetchImpl - `(input, init?) => Promise<Response>`. Not `typeof fetch`, so
1492
2396
  * a mock does not need host extras such as `preconnect`.
1493
2397
  * @returns An option that sets `config.http.handle`.
2398
+ * @group client
1494
2399
  */
1495
2400
  export declare function withHTTPHandle(fetchImpl: FetchHandle): ClientOption;
1496
2401
  /**
1497
- * Overrides the Fetch-compatible function used for SSE connections.
2402
+ * Replaces the Fetch-compatible function used to open SSE event streams.
2403
+ *
2404
+ * The default is the platform `fetch`. `fetchImpl` is a {@link FetchHandle}. It only affects SSE. HTTP uses {@link withHTTPHandle}.
1498
2405
  *
1499
2406
  * @param fetchImpl - `(input, init?) => Promise<Response>`. Not `typeof fetch`, so
1500
2407
  * a mock does not need host extras such as `preconnect`.
1501
2408
  * @returns An option that sets `config.sse.handle`.
2409
+ * @group client
1502
2410
  */
1503
2411
  export declare function withSSEHandle(fetchImpl: FetchHandle): ClientOption;
1504
2412
  /**
1505
- * Overrides the WebSocket constructor used for socket connections.
2413
+ * Replaces the WebSocket constructor used for socket connections.
2414
+ *
2415
+ * The default is `globalThis.WebSocket`. `WebSocketImpl` is a {@link WebSocketHandleConstructor} that returns a {@link WebSocketHandle}. If no constructor is available, a WebSocket operation fails with an `ENV_UNSUPPORTED` fault.
1506
2416
  *
1507
2417
  * @param WebSocketImpl - Constructor that returns a `WebSocketHandle`.
1508
2418
  * @returns An option that sets `config.webSocket.handle`.
2419
+ * @group client
1509
2420
  */
1510
2421
  export declare function withWebSocketHandle(WebSocketImpl: WebSocketHandleConstructor): ClientOption;
1511
2422
  /**
1512
- * Runs logic before each WebSocket connect or reconnect attempt.
2423
+ * Runs a hook before the initial WebSocket connect and before every reconnect.
2424
+ *
2425
+ * The hook type is:
2426
+ *
2427
+ * ```ts
2428
+ * (context: { attempt: number; signal: AbortSignal }) => void | Promise<void>
2429
+ * ```
2430
+ *
2431
+ * - `attempt` is `0` for the first connect and grows by one for each reconnect.
2432
+ * - Pass `signal` into token refreshes and other async work so cancelling the session stops them.
2433
+ * - If the hook throws, the attempt fails with an `EXT_HOOK_FAILED` fault.
2434
+ * - A `beforeConnect` in the request options replaces this one for that call.
1513
2435
  *
1514
2436
  * @param beforeConnect - Hook receiving attempt index and abort signal.
1515
2437
  * @returns An option that sets `config.webSocket.beforeConnect`.
2438
+ * @group client
1516
2439
  */
1517
- export declare function withWebSocketBeforeConnect(beforeConnect: ClientWebSocketOptions['beforeConnect']): ClientOption;
2440
+ export declare function withWebSocketBeforeConnect(beforeConnect: ClientWebSocketOptions["beforeConnect"]): ClientOption;
1518
2441
  /**
1519
- * Configure WebSocket subprotocols for the connection handshake.
2442
+ * Sets the WebSocket subprotocols offered during the handshake.
2443
+ *
2444
+ * They are passed as the second argument to the `WebSocket` constructor only when the list is not empty, so the platform can run the RFC 6455 subprotocol negotiation. They matter only when the server requires a specific subprotocol, for example `['json']` or `['v1']`.
1520
2445
  *
1521
- * `protocols` are passed as the second argument to the `WebSocket` constructor
1522
- * and participate in the RFC 6455 subprotocol negotiation. They are only
1523
- * meaningful when the server requires a specific protocol (for example,
1524
- * `['json']` or `['v1']` to select a message encoding version).
2446
+ * Precedence, highest first: the `protocols` request option, this client option, then `protocols` on the endpoint definition.
1525
2447
  *
1526
2448
  * @param protocols - Subprotocol list for the WebSocket constructor.
1527
2449
  * @returns An option that sets `config.webSocket.protocols`.
2450
+ * @group client
1528
2451
  */
1529
2452
  export declare function withWebSocketProtocols(protocols: readonly string[]): ClientOption;
1530
2453
  /**
1531
- * Enables WebSocket heartbeat pings and optional ACK detection.
2454
+ * Enables WebSocket heartbeat messages with optional acknowledgement detection.
2455
+ *
2456
+ * Passing `undefined` clears an earlier setting. The option type is:
2457
+ *
2458
+ * ```ts
2459
+ * {
2460
+ * intervalMs: number
2461
+ * isAck?: (message: unknown) => boolean
2462
+ * message?: <T = unknown>() => T | unknown
2463
+ * timeoutMs?: number
2464
+ * }
2465
+ * ```
2466
+ *
2467
+ * - Every `intervalMs`, `message()` is called and its result is sent through the endpoint's `outgoing` struct. No message is sent when `message` is omitted or returns `undefined`.
2468
+ * - With `timeoutMs`, an acknowledgement is expected after each heartbeat. `isAck` receives the decoded incoming message; when it returns `true`, the timer is cleared and that message is **not** delivered to `receive`.
2469
+ * - If no ack arrives within `timeoutMs`, or a heartbeat cannot be sent, the session fails. This does not trigger a reconnect.
2470
+ * - Heartbeat uses the same JSON guard as `session.send`: a value JSON cannot represent after struct encoding causes `TypeError` and ends the logical session.
2471
+ * - `intervalMs` and `timeoutMs` must be positive, finite, and at most `2_147_483_647`; otherwise the operation fails with `REQ_OPTIONS_INVALID`.
2472
+ * - A `heartbeat` in the request options replaces this one for that call.
1532
2473
  *
1533
2474
  * @param options - Heartbeat interval, payload, timeout, and ACK predicate.
1534
2475
  * @returns An option that sets `config.webSocket.heartbeat`.
2476
+ * @group client
1535
2477
  */
1536
- export declare function withWebSocketHeartbeat(options: ClientWebSocketOptions['heartbeat']): ClientOption;
2478
+ export declare function withWebSocketHeartbeat(options: ClientWebSocketOptions["heartbeat"]): ClientOption;
1537
2479
  /**
1538
- * Configures automatic WebSocket reconnection after unexpected closes.
2480
+ * Configures automatic reconnection when the socket closes for any reason other than a manual `session.close()`.
2481
+ *
2482
+ * Reconnection is opt-in: without this option a close ends the session. Passing `undefined` clears an earlier setting. The option type is:
2483
+ *
2484
+ * ```ts
2485
+ * {
2486
+ * attempts?: number
2487
+ * delayMs?: number
2488
+ * factor?: number
2489
+ * jitter?: number
2490
+ * maxDelayMs?: number
2491
+ * shouldReconnect?: (context: { attempt: number; cause?: unknown; code?: number; reason?: string; wasClean?: boolean }) => boolean
2492
+ * }
2493
+ * ```
2494
+ *
2495
+ * - `attempts` is the retry budget for each outage. It defaults to `3`, and `0` disables reconnection. Successful `open` resets the budget and backoff exponent; the next outage starts at retry 1. Omitting it does not give unlimited retries within one outage. A socket that successfully opens and is immediately closed again by the server can keep reconnecting at the base delay (subject to jitter and caps), because every open resets the budget; use `shouldReconnect` to stop it.
2496
+ * - `delayMs` defaults to `1000`, `factor` to `2`, `maxDelayMs` to `30000`, and `jitter` (0 to 1) to `0`. Retry _n_ starts with `delayMs * factor^(n-1)`, capped at `maxDelayMs`; jitter and rounding follow, then the final delay is capped again at `maxDelayMs` and `2_147_483_647` ms. A zero base stays zero; valid-number overflow saturates at those caps.
2497
+ * - `shouldReconnect` can return `false` to end the session instead. Without it every close is retried while the budget lasts. It is not called for a manual `session.close()`.
2498
+ * - Reconnect options are validated before connecting: `attempts` must be a non-negative safe integer; `delayMs` must be finite and non-negative; `maxDelayMs` must be a non-negative finite number or positive `Infinity`, which removes the configured cap but not the platform limit of `2_147_483_647` ms; `factor` must be finite and positive; `jitter` must be finite and within 0–1. Invalid values make the operation fail with `REQ_OPTIONS_INVALID` before connecting. `attempts: 0` disables subsequent retries, but all explicitly supplied reconnect fields are still validated before the initial connection.
2499
+ * - A `reconnect` in the request options replaces this one for that call.
1539
2500
  *
1540
2501
  * @param options - Retry budget, backoff, and `shouldReconnect` gate.
1541
2502
  * @returns An option that sets `config.webSocket.reconnect`.
2503
+ * @group client
1542
2504
  */
1543
- export declare function withWebSocketReconnect(options: ClientWebSocketOptions['reconnect']): ClientOption;
2505
+ export declare function withWebSocketReconnect(options: ClientWebSocketOptions["reconnect"]): ClientOption;
1544
2506
  /**
1545
- * Handles SSE events that lack a matching struct or fail validation.
2507
+ * Observes SSE events that have no matching event struct or fail validation.
2508
+ *
2509
+ * The handler may be async and is awaited. The callback receives:
2510
+ *
2511
+ * ```ts
2512
+ * {
2513
+ * reason: 'missing-struct' | 'validation-failed'
2514
+ * message: { id: string; event: string; data: string }
2515
+ * cause?: unknown
2516
+ * signal: AbortSignal
2517
+ * }
2518
+ * ```
1546
2519
  *
1547
- * @param handler - Callback for invalid events; may throw or return a value.
2520
+ * - The invalid event is **always dropped**; it is never delivered to the stream. The callback only observes it.
2521
+ * - The return value is ignored. An error thrown by the callback is swallowed and does not close the stream, unless `signal` has already aborted.
2522
+ * - `cause` is set for `validation-failed`. Without a handler the invalid event is dropped silently.
2523
+ *
2524
+ * @param handler - Observer callback for invalid events.
1548
2525
  * @returns An option that sets `config.sse.onInvalidEvent`.
2526
+ * @group client
1549
2527
  */
1550
- export declare function withSSEOnInvalidEvent(handler: ClientSSEOptions['onInvalidEvent']): ClientOption;
2528
+ export declare function withSSEOnInvalidEvent(handler: ClientSSEOptions["onInvalidEvent"]): ClientOption;
1551
2529
  /**
1552
- * Configures automatic SSE reconnection after stream failures.
2530
+ * Configures automatic reconnection after an SSE stream fails.
2531
+ *
2532
+ * Reconnection is opt-in: without this option a failed stream is not retried. Passing `undefined` clears an earlier setting.
2533
+ *
2534
+ * ```ts
2535
+ * type SSEReconnect = {
2536
+ * attempts?: number;
2537
+ * delayMs?: number;
2538
+ * factor?: number;
2539
+ * jitter?: number;
2540
+ * maxDelayMs?: number;
2541
+ * shouldReconnect?: (context: {
2542
+ * attempt: number;
2543
+ * cause?: unknown;
2544
+ * lastEventId: string;
2545
+ * method: string;
2546
+ * open?: {
2547
+ * response: { status: number; statusText: string; url: string };
2548
+ * url: string;
2549
+ * };
2550
+ * }) => boolean | Promise<boolean>;
2551
+ * };
2552
+ * ```
2553
+ *
2554
+ * - `attempts` is the retry budget for each outage. It defaults to `3` when `reconnect` is set, and `0` disables retries. A successful handshake resets the budget, callback `attempt`, and backoff exponent. Omitting it does not give unlimited retries within one outage. Repeated successful opens followed immediately by retryable stream failures can keep reconnecting at the current base delay (subject to jitter and caps), because each open resets the budget; use `shouldReconnect` to stop them. Normal EOF remains terminal.
2555
+ * - Without `shouldReconnect`, only `GET`, `HEAD`, `OPTIONS`, `PUT`, and `DELETE` reconnect. With it, its result decides for every method, `POST` included; `method` in the context tells endpoints apart. Return `false` to stop for this failure. It runs before the attempts limit is checked.
2556
+ * - A request with a `ReadableStream` body never reconnects, and `shouldReconnect` is not called for it.
2557
+ * - `delayMs`, `factor`, `jitter` (0 to 1), and `maxDelayMs` shape the backoff. `factor` defaults to `1` and `jitter` to `0`. `maxDelayMs` caps the final delay after jitter and rounding. Without a configured cap, the platform timer limit of `2_147_483_647` ms still applies. A zero base stays zero; valid-number overflow saturates at the caps.
2558
+ * - `delayMs` is only the starting base delay (`1000` by default). A server `retry:` field (digits only, in milliseconds) replaces it for later attempts.
2559
+ * - The last event id is sent again as UTF-8 bytes in `Last-Event-ID` on the next attempt. An empty `id:` clears it; an ID containing NUL is ignored.
2560
+ * - Reconnect options are validated before interceptors or Fetch: `attempts` must be a non-negative safe integer; `delayMs` must be finite and non-negative; `maxDelayMs` must be a non-negative finite number or positive `Infinity`, which removes the configured cap but not the platform limit of `2_147_483_647` ms; `factor` must be finite and positive; `jitter` must be finite and within 0–1. Invalid values return `REQ_OPTIONS_INVALID` when the operation runs.
1553
2561
  *
1554
2562
  * @param options - Retry budget, backoff, and `shouldReconnect` gate.
1555
2563
  * @returns An option that sets `config.sse.reconnect`.
2564
+ * @group client
1556
2565
  */
1557
- export declare function withSSEReconnect(options: ClientSSEOptions['reconnect']): ClientOption;
2566
+ export declare function withSSEReconnect(options: ClientSSEOptions["reconnect"]): ClientOption;
1558
2567
  /**
1559
2568
  * Enables XSRF cookie-to-header token forwarding for HTTP requests.
1560
2569
  *
1561
- * Defaults to cookie `XSRF-TOKEN` and header `X-XSRF-TOKEN` when names are omitted.
2570
+ * SSE and WebSocket requests do not get the header. `cookieName` defaults to `XSRF-TOKEN` and `headerName` to `X-XSRF-TOKEN` when names are omitted.
2571
+ *
2572
+ * - The header is set only when all of these hold: the method is not `GET`, `HEAD`, `OPTIONS`, or `TRACE`; the request does not already have that header; the client has an endpoint; and, whenever `location` exists (Window or Worker), the resolved request URL has the same origin as `location.origin`. A cross-origin request does not call the provider.
2573
+ * - Without `tokenProvider`, the token is read from `document.cookie` and URL-decoded only when `document` exists. Workers and servers need a provider to supply a token.
2574
+ * - With `tokenProvider`, the cookie is never read. The provider receives the request, and its result is the token. An empty string, `null`, or `undefined` means no header.
1562
2575
  *
1563
2576
  * @param options - Cookie/header names and optional token provider.
1564
2577
  * @returns An option that sets `config.xsrf`.
2578
+ * @group client
1565
2579
  */
1566
- export declare function withXSRF(options?: HttpClientOptions['xsrf']): ClientOption;
2580
+ export declare function withXSRF(options?: HttpClientOptions["xsrf"]): ClientOption;
1567
2581
  /**
1568
- * Sets whether HTTP/SSE requests should include credentials (cookies).
2582
+ * Sets whether HTTP and SSE requests include credentials.
2583
+ *
2584
+ * When `value` is `true`, Fetch uses `credentials: 'include'`, so cookies and other credentials are sent with cross-origin requests. When it is `false` or never set, `credentials` is left unset and Fetch uses its default. The WebSocket constructor does not receive this setting.
1569
2585
  *
1570
2586
  * @param value - When `true`, credentials are sent with cross-origin requests.
1571
2587
  * @returns An option that sets `config.withCredentials`.
2588
+ * @group client
1572
2589
  */
1573
2590
  export declare function withCredentials(value: boolean): ClientOption;
1574
- //#endregion
1575
- //#region src/client/client.d.ts
2591
+ /**
2592
+ * Internal symbol key for a client's base configuration and derived request options.
2593
+ *
2594
+ * Application code calls the client and passes it to an operation. It does not read this property.
2595
+ *
2596
+ * @group client
2597
+ */
1576
2598
  declare const CLIENT: unique symbol;
1577
2599
  /**
1578
- * Defjs client: holds config and `execute`s HTTP, SSE, and WebSocket commands.
2600
+ * Flat per-call options. Each protocol reads the fields it understands and ignores the rest.
2601
+ *
2602
+ * SSE reads only `abort` from request options. SSE reconnect comes from client configuration (`withSSEReconnect`), so `client({ reconnect })` affects WebSocket only.
2603
+ *
2604
+ * `abort` cancels HTTP, SSE, and WebSocket calls. `onUploadProgress` and `onDownloadProgress` apply to HTTP. `beforeConnect`, `protocols`, `heartbeat`, and `reconnect` apply to WebSocket and replace the client setting for that call as a whole value. There is no `timeout` or `signal` field. Pass `AbortSignal.timeout(ms)` as `abort` when a call needs a deadline.
2605
+ *
2606
+ * @group client
1579
2607
  */
1580
- type Client = {
1581
- readonly [CLIENT]: ClientConfig;
1582
- /** Open an SSE command and return stream open/await results. */
1583
- execute<TInput extends AnyStruct | undefined, TEvents extends EventStructs, TError extends AnyStruct | undefined = undefined>(command: EventStreamCommand<TInput, TEvents, TError>, options?: EventStreamExecuteOptions): Promise<StreamAwaitResult<EventStreamData<TEvents>, TError>>;
1584
- /** Open a WebSocket command and return session await results. */
1585
- execute<TInput extends AnyStruct | undefined, TIncoming extends AnyStruct, TOutgoing extends AnyStruct | undefined>(command: WebSocketCommand<TInput, TIncoming, TOutgoing>, options?: WebSocketExecuteOptions<WebSocketIncomingData<TIncoming>, WebSocketOutgoingData<TOutgoing>>): Promise<SocketAwaitResult<WebSocketIncomingData<TIncoming>, WebSocketOutgoingData<TOutgoing>>>;
1586
- /** Run an HTTP command and return an await-result tuple. */
1587
- execute<TInput extends AnyStruct | undefined, TOutput extends AnyStruct | undefined, TError extends AnyStruct | undefined>(command: HttpCommand<TInput, TOutput, TError>, options?: HttpExecuteOptions): ReturnType<typeof executeHttpCommand<TInput, TOutput, TError>>;
2608
+ type RequestOptions = {
2609
+ /** Aborts the call. An already aborted signal fails the call before transport. */
2610
+ abort?: AbortSignal;
2611
+ /** Observes HTTP upload progress. */
2612
+ onUploadProgress?: HttpProgressFn;
2613
+ /** Observes HTTP download progress. */
2614
+ onDownloadProgress?: HttpProgressFn;
2615
+ /** Runs before each WebSocket connect attempt for this call. */
2616
+ beforeConnect?: ClientWebSocketOptions["beforeConnect"];
2617
+ /** WebSocket subprotocols for this call. Replaces the client list; it is not appended. */
2618
+ protocols?: readonly string[];
2619
+ /** WebSocket heartbeat for this call. Replaces the client heartbeat as a whole value. */
2620
+ heartbeat?: ClientWebSocketOptions["heartbeat"];
2621
+ /** WebSocket reconnect policy for this call. Replaces the client policy as a whole value. */
2622
+ reconnect?: ClientWebSocketOptions["reconnect"];
1588
2623
  };
2624
+ interface ClientState {
2625
+ readonly config: ClientConfig;
2626
+ readonly options: RequestOptions;
2627
+ }
2628
+ /**
2629
+ * Application client returned by `createClient`.
2630
+ *
2631
+ * Calling it returns a new client. The seven known fields are copied from the argument when `client(options)` is called, a top-level snapshot into a fresh options object overlaid on the current options. A later field wins. A field whose value is `undefined` is treated as not provided and keeps the parent's value. A derived client cannot drop a setting its parent has; to go back to no setting, derive again from the client that did not have it. Nested `heartbeat`, `protocols`, and `reconnect` values are kept by reference, not cloned. WebSocket reads them when the operation runs, copying heartbeat fields and the protocols list at that time, so mutating those objects after deriving can still affect a later call. Applications should treat them as immutable after passing them. The original client is not mutated. `client()`, `client(undefined)`, and `client({})` copy the current options. Deriving a client performs no I/O. A getter that throws does so from the call and is not returned as a fault.
2632
+ *
2633
+ * @group client
2634
+ * @see {@link createClient}
2635
+ * @see {@link RequestOptions}
2636
+ */
2637
+ interface Client {
2638
+ /** Returns a new client with `options` overlaid on this client's request options. */
2639
+ (options?: RequestOptions): Client;
2640
+ /** Internal state. Not part of the call surface. */
2641
+ readonly [CLIENT]: ClientState;
2642
+ }
1589
2643
  /**
1590
- * Create a Defjs client from option helpers.
2644
+ * Creates a Defjs client from option helpers.
2645
+ *
2646
+ * Options apply in order: a later option overwrites an earlier one for the same setting, and interceptors accumulate. Prefer creating the client inside the request boundary when interceptors close over auth or tenants. Default HTTP/SSE fetch availability is checked when an operation invokes transport.
1591
2647
  *
1592
- * Options are applied in order. Prefer creating the client inside the
1593
- * request boundary when interceptors close over auth or tenants.
2648
+ * The returned client is a function. Call it with {@link RequestOptions} to derive a client for one operation. The seven known fields are copied from that argument into a fresh options object. Nested `heartbeat`, `protocols`, and `reconnect` values are kept by reference. The base configuration is shared by every derived client and is not frozen at runtime. It is fixed by convention: nothing in the library changes it, and applications should not either. A getter that throws escapes the call; it is not converted into a fault.
1594
2649
  *
1595
2650
  * @param options - Client option helpers such as `withEndpoint`.
1596
- * @returns A client that can `execute` HTTP, SSE, and WebSocket commands.
2651
+ * @returns A callable client that carries the resolved base configuration.
2652
+ *
2653
+ * @group client
2654
+ * @see {@link Client}
2655
+ * @see {@link ClientOption}
2656
+ * @see {@link RequestOptions}
2657
+ * @exampleFile examples/api/create-client.ts#example
2658
+ */
2659
+ export declare function createClient(...options: ClientOption[]): Client;
2660
+ /**
2661
+ * HTTP fields of {@link RequestOptions}: progress callbacks and `abort`.
2662
+ *
2663
+ * `onDownloadProgress` and `onUploadProgress` are {@link HttpProgressFn}
2664
+ * callbacks. They receive an {@link HttpProgressEvent} with `loaded`, `total`,
2665
+ * and `lengthComputable`. Other request fields are ignored by HTTP.
2666
+ *
2667
+ * @group http
2668
+ */
2669
+ export type UseRequestConfig = {
2670
+ /** Aborts the call. */
2671
+ abort?: AbortSignal;
2672
+ /** Observes download progress. */
2673
+ onDownloadProgress?: HttpProgressFn;
2674
+ /** Observes upload progress. */
2675
+ onUploadProgress?: HttpProgressFn;
2676
+ };
2677
+ /**
2678
+ * HTTP fields read from {@link RequestOptions} for one call.
2679
+ *
2680
+ * Progress callbacks and `abort`. There is no `timeout` or `signal`.
2681
+ *
2682
+ * @group http
2683
+ */
2684
+ export type HttpExecuteOptions = UseRequestConfig;
2685
+ /**
2686
+ * Operation returned by `defineRequest`.
2687
+ *
2688
+ * The first argument is a {@link Client}. Further arguments are the input, and only the input: none when the definition declares no `input`, optional when the input struct accepts `undefined`, and required otherwise. Calling it starts the request and returns the error-first tuple.
2689
+ *
2690
+ * @group http
2691
+ */
2692
+ export type RequestEndpoint<Args extends unknown[], Data, ErrorData, Response extends HttpMeta> = (client: Client, ...args: Args) => Promise<[error: null, result: Data, response: Response] | [error: FaultOf<ErrorData>, result: undefined, response: undefined]>;
2693
+ type DeclaredBody<TStruct> = [TStruct] extends [undefined] ? undefined : Infer<TStruct>;
2694
+ type DeclaredResponse<TStruct> = [TStruct] extends [undefined] ? HttpMeta : DecodedResponse<Infer<TStruct>>;
2695
+ /**
2696
+ * Decoded success body for the declared `output` struct, or `undefined` when none is declared.
2697
+ *
2698
+ * @group http
2699
+ */
2700
+ export type RequestSuccessData<TOutput extends AnyStruct | undefined> = DeclaredBody<TOutput>;
2701
+ /**
2702
+ * Decoded error body for the declared `error` struct, or `undefined` when none is declared.
2703
+ *
2704
+ * @group http
2705
+ */
2706
+ export type RequestErrorData<TError extends AnyStruct | undefined> = DeclaredBody<TError>;
2707
+ type ResponseDeclaration<TOutput extends AnyStruct | undefined, TError extends AnyStruct | undefined> = ([TOutput] extends [undefined] ? {
2708
+ output?: never;
2709
+ } : {
2710
+ output: TOutput;
2711
+ }) & ([TError] extends [undefined] ? {
2712
+ error?: never;
2713
+ } : {
2714
+ error: TError;
2715
+ });
2716
+ /**
2717
+ * Contract for an HTTP endpoint: method, path, optional input, and the structs that decode its bodies.
2718
+ *
2719
+ * Declaring `input` requires an explicit `builder` to place the parsed value on
2720
+ * the request. `input` only defines the parameters; the builder alone decides
2721
+ * where each value goes. Only definitions without `input` may omit `builder`.
2722
+ * `output` declares the body for a 2xx response, `error` the body for anything
2723
+ * else. Only one of the two ever runs, and omitting one means that body is never
2724
+ * read. `ResponseDeclaration` is an internal helper type: `output` and `error`
2725
+ * are required only when the matching type parameter is a struct. There is no
2726
+ * `responseType`: the struct kind selects how a declared body is read. Pass to
2727
+ * `defineRequest` to get a typed endpoint.
2728
+ *
2729
+ * @group http
2730
+ */
2731
+ export type RequestDefinition<TInput extends AnyStruct | undefined = undefined, TOutput extends AnyStruct | undefined = undefined, TError extends AnyStruct | undefined = undefined> = ResponseDeclaration<TOutput, TError> & {
2732
+ builder?: OutboundBuilder;
2733
+ input?: TInput;
2734
+ method: string;
2735
+ operation?: string;
2736
+ path: string;
2737
+ };
2738
+ /**
2739
+ * Await-result tuple for an HTTP call: success is `[null, data, response]`, and failure is `[fault, undefined, undefined]`.
2740
+ *
2741
+ * `TData` and `TErrorData` are decoded body types. On success the third slot
2742
+ * carries `body` only when `output` was declared. On failure it is `undefined`,
2743
+ * because the fault already holds whatever response metadata existed.
2744
+ *
2745
+ * @group http
2746
+ */
2747
+ export type HttpAwaitResult<TData = undefined, TErrorData = undefined> = [error: null, result: TData, response: [TData] extends [undefined] ? HttpMeta : DecodedResponse<TData>] | [error: FaultOf<TErrorData>, result: undefined, response: undefined];
2748
+ /**
2749
+ * Declares a typed HTTP request endpoint.
2750
+ *
2751
+ * Pass method, path, an optional input struct, and the single `output` and
2752
+ * `error` structs. Call the returned operation with a client and, when declared, the input.
2753
+ *
2754
+ * The contract is `method`, `path`, an optional `input` struct, optional
2755
+ * `output` and `error` structs, and an optional `operation`. A declared `input`
2756
+ * always requires an explicit `builder`. `input` only defines the parameters;
2757
+ * the builder alone decides where each value goes. Only definitions without
2758
+ * `input` may omit `builder`. `ResponseDeclaration` and `BuilderField` are
2759
+ * internal helper types: `output` and `error` are required only when the
2760
+ * matching type parameter is a struct, and `builder` is
2761
+ * `(input: BuilderInput<TInput>) => HttpRequestShape` when `input` is declared.
2762
+ *
2763
+ * `output` is one struct for the 2xx body; `error` is one struct for every
2764
+ * non-2xx body. Neither is keyed by status. A declaration is an assertion:
2765
+ * omitting one means that body is never read.
2766
+ *
2767
+ * @param definition - Request contract (method, path, input, builder, output).
2768
+ * @returns An operation `(client, ...input) => Promise<error-first tuple>`.
2769
+ *
2770
+ * The checked example file passes the whole input object as `path`. This snippet maps individual fields into `path` and `query` instead:
1597
2771
  *
1598
2772
  * @example
1599
2773
  * ```ts
1600
- * const client = createClient(withEndpoint('https://api.example.com'))
2774
+ * const getUser = defineRequest({
2775
+ * method: 'GET',
2776
+ * path: '/users/:id',
2777
+ * input: struct.object({ id: struct.number(), verbose: struct.boolean() }),
2778
+ * builder: (input) => ({ path: { id: input.id }, query: { verbose: input.verbose } }),
2779
+ * output: struct.object({ id: struct.number(), name: struct.string() }),
2780
+ * })
1601
2781
  * ```
2782
+ * @group http
2783
+ * @exampleFile examples/api/define-request.ts#example
1602
2784
  */
1603
- export declare function createClient(...options: ClientOption[]): Client;
1604
- //#endregion
1605
- //#region src/http/transport/fetch.d.ts
2785
+ export declare function defineRequest<const M extends string, const P extends string, TInput extends AnyStruct | undefined = undefined, TOutput extends AnyStruct | undefined = undefined, TError extends AnyStruct | undefined = undefined>(definition: ResponseDeclaration<TOutput, TError> & {
2786
+ input?: TInput;
2787
+ method: M;
2788
+ operation?: string;
2789
+ path: P;
2790
+ } & BuilderField<NoInfer<TInput>, HttpRequestShape<NoInfer<P>, NoInfer<M>>>): RequestEndpoint<EndpointArgs<TInput>, RequestSuccessData<TOutput>, RequestErrorData<TError>, DeclaredResponse<TOutput>>;
1606
2791
  /**
1607
2792
  * Default HTTP transport: perform `httpRequest` with `fetch` and return an `HttpResponse`.
1608
- * Used by the client unless `withHTTPHandle` replaces it; also usable in tests and custom handlers.
2793
+ *
2794
+ * The client always uses it; `withHTTPHandle` only supplies the `fetchImpl` it
2795
+ * calls. Also usable in tests and custom handlers. `fetchImpl` defaults to the
2796
+ * global `fetch` resolved at send time. It rejects, rather than resolving with
2797
+ * a synthetic response, when no response reached the client.
1609
2798
  *
1610
2799
  * @param httpRequest - Normalized request (URL, headers, body, abort, progress hooks).
1611
2800
  * @param fetchImpl - Fetch implementation; defaults to global `fetch`. The library default handle is called as
1612
- * `(url, init)` and its body is read in one piece unless download progress is observed. Any other handle receives a
2801
+ * `(url, init)` while the global is untouched, and its body is read in one piece unless download progress is observed. Any other handle receives a
1613
2802
  * `Request` and its body is read chunk by chunk.
1614
- * @returns Parsed `HttpResponse`. Rejects when no response reached the client, or when reading the
1615
- * body failed — a transport failure is a fault, never a response.
2803
+ * @returns Parsed `HttpResponse`. Rejects when no response reached the client, when reading the body
2804
+ * failed, or when an upload observer threw (`EXT_OBSERVER_FAILED`). Cancellation still wins.
2805
+ * @group http
1616
2806
  */
1617
2807
  export declare function fetchHandler(httpRequest: HttpRequest, fetchImpl?: FetchHandle): Promise<HttpResponse<unknown>>;
1618
- //#endregion
1619
- //#region src/internal/url.d.ts
2808
+ /**
2809
+ * Resolves a relative definition path under the base endpoint's origin and directory into a URL.
2810
+ *
2811
+ * During an operation, a missing, relative, or invalid base or an illegal
2812
+ * path becomes `REQ_BUILD_FAILED` before any interceptor or transport runs,
2813
+ * including a short-circuiting interceptor, for HTTP, SSE, and WebSocket alike.
2814
+ * An interceptor's final `req.endpoint` is also validated against its final
2815
+ * `req.baseEndpoint`; deliberately replacing the base with another valid
2816
+ * absolute URL is allowed. Deterministic URL errors are never retried.
2817
+ *
2818
+ * @throws TypeError when the endpoint or path is invalid.
2819
+ * @param baseEndpoint - A valid absolute client endpoint. Its query and hash are dropped, and a trailing slash is added to its pathname so that the base path is kept: `https://api.example.com/v1` with `/users` gives `https://api.example.com/v1/users`.
2820
+ * @param path - A relative endpoint path. A single leading slash is removed. After resolution, the URL must keep the base origin and stay under its directory. Absolute or protocol-relative paths, C0 control characters (including tab, CR, and LF), backslashes, `?`, `#`, and directory escapes are rejected. Scheme-like and protocol-relative forms are rejected even with leading spaces; a plain inner space is allowed and sent as `%20`.
2821
+ * @param queryString - Query without a leading `?`. When non-empty it is set as the URL's search verbatim.
2822
+ * @returns the resolved `URL`.
2823
+ * @group http
2824
+ */
1620
2825
  export declare function createResolvedRequestUrl(baseEndpoint: string, path: string, queryString?: string): URL;
1621
- //#endregion
1622
- //#region src/interceptor/basic_auth.d.ts
1623
2826
  type BasicCredential = {
1624
2827
  username: string;
1625
2828
  password: string;
1626
2829
  };
1627
2830
  /**
1628
2831
  * Options for basic-auth interceptors, including a custom credential encoder.
2832
+ *
2833
+ * `encode` replaces only the credential encoder. The default is
2834
+ * ``btoa(`${username}:${password}`)``. The interceptor always prefixes the
2835
+ * returned string with `Basic `, so return the credential part alone: returning
2836
+ * `'Basic abc'` produces the header `Basic Basic abc`.
2837
+ *
2838
+ * Accepted by {@link basicAuthHttpInterceptor} and {@link basicAuthSSEInterceptor}.
2839
+ *
2840
+ * @group interceptors
1629
2841
  */
1630
2842
  type BasicAuthInterceptorOptions = {
1631
2843
  encode?: (credential: BasicCredential) => string;
@@ -1633,18 +2845,150 @@ type BasicAuthInterceptorOptions = {
1633
2845
  /**
1634
2846
  * HTTP interceptor that sets an `Authorization: Basic …` header from credentials.
1635
2847
  *
2848
+ * It sets `Authorization: Basic <encoded>` on a copy of the request headers,
2849
+ * replacing any existing `Authorization` header. The default encoder is
2850
+ * ``btoa(`${username}:${password}`)``, so credentials must be Latin-1. Without
2851
+ * `encode`, calling `basicAuthHttpInterceptor` throws
2852
+ * `Error: BasicAuthInterceptor is not supported in this environment` when `btoa`
2853
+ * is not available in the runtime. Use {@link basicAuthSSEInterceptor} for SSE
2854
+ * operations.
2855
+ *
1636
2856
  * @param fn - Factory returning username/password for each request.
1637
2857
  * @param options - Optional custom encoder (defaults to `btoa`).
1638
2858
  * @returns An `HttpInterceptor` that injects basic auth.
2859
+ * @group interceptors
1639
2860
  */
1640
2861
  export declare function basicAuthHttpInterceptor(fn: () => BasicCredential, options?: BasicAuthInterceptorOptions): HttpInterceptor;
1641
2862
  /**
1642
2863
  * SSE interceptor that sets an `Authorization: Basic …` header from credentials.
1643
2864
  *
2865
+ * It sets `Authorization: Basic <encoded>` on a copy of the request headers,
2866
+ * replacing any existing `Authorization` header. Encoding and the `btoa`
2867
+ * requirement are the same as for {@link basicAuthHttpInterceptor}, including
2868
+ * the synchronous throw when `btoa` is missing.
2869
+ *
1644
2870
  * @param fn - Factory returning username/password for each request.
1645
2871
  * @param options - Optional custom encoder (defaults to `btoa`).
1646
2872
  * @returns An `SSEInterceptor` that injects basic auth.
2873
+ * @group interceptors
1647
2874
  */
1648
2875
  export declare function basicAuthSSEInterceptor(fn: () => BasicCredential, options?: BasicAuthInterceptorOptions): SSEInterceptor;
1649
- //#endregion
1650
- export type { AnyStruct, BasicAuthInterceptorOptions, BuilderInput, Client, ClientConfig, ClientOption, ClientOptions, ClientSSEConfig, ClientSSEOptions, ClientWebSocketOptions, DecodedResponse, ErrorMap, EventStreamCloseInfo, EventStreamFaultCode, EventStreamHandle, EventStreamOpenInfo, EventStreamRequestShape, FetchHandle, FlattenedStructError, FormattedStructError, HttpInterceptor, HttpInterceptorNext, HttpMeta, HttpProgressEvent, HttpProgressFn, HttpRequest, HttpRequestShape, HttpResponse, HttpResponseType, Infer, Interceptor, InterceptorFn, MakeResponseOptions, ObjectStruct, ParseResult, QuerySerializer, SSEHandler, SSEInterceptor, SSEInterceptorFn, STRUCT, Struct, StructInput, StructIssue, StructLike, StructMethods, WebSocketHandle, WebSocketHandleConstructor, WebSocketHandler, WebSocketInterceptor, WebSocketInterceptorFn, WebSocketRequestShape, WebSocketSessionLike };
2876
+ /**
2877
+ * Map from SSE event names to the structs that decode their data.
2878
+ *
2879
+ * An event with no `event:` field is named `message`. An event whose name has no own entry uses the `default` struct; with neither, the event is skipped and `onInvalidEvent` is called. Declare the event name `__proto__` with a computed own key.
2880
+ *
2881
+ * A `string` event struct keeps the raw `data` text. Every other kind is JSON text: `JSON.parse`, then the struct. A bare `struct.literal` therefore needs JSON text, not an unquoted token.
2882
+ *
2883
+ * Reconnect lives on the client (`withSSEReconnect`). SSE `jitter` is a **0-1 multiplicative factor**, same as WebSocket.
2884
+ *
2885
+ * @group sse
2886
+ */
2887
+ export type EventStructs = {
2888
+ [key: string]: AnyStruct;
2889
+ };
2890
+ type EventName<TKey extends string | number> = `${TKey}`;
2891
+ type KnownEventUnion<TEvents extends EventStructs> = { [K in keyof TEvents & (number | string)]: EventName<K> extends "default" ? never : {
2892
+ data: Infer<TEvents[K]>;
2893
+ event: EventName<K>;
2894
+ id?: string;
2895
+ }; }[keyof TEvents & (number | string)];
2896
+ type DefaultEventUnion<TEvents extends EventStructs> = "default" extends keyof TEvents ? {
2897
+ data: Infer<TEvents["default"]>;
2898
+ event: string;
2899
+ id?: string;
2900
+ } : never;
2901
+ /**
2902
+ * Union of parsed SSE events inferred from an EventStructs map.
2903
+ *
2904
+ * For each declared event name except `default`, the union has `{ event: <name>; data: Infer<TEvents[name]>; id?: string }`. When a `default` struct is declared, it adds `{ event: string; data: Infer<TEvents['default']>; id?: string }` for every event name that has no struct of its own. `KnownEventUnion` and `DefaultEventUnion` are internal helper types.
2905
+ *
2906
+ * @group sse
2907
+ */
2908
+ export type EventStreamData<TEvents extends EventStructs> = TEvents extends EventStructs ? KnownEventUnion<TEvents> | DefaultEventUnion<TEvents> : never;
2909
+ interface EventStreamDefinitionBase<TEvents extends EventStructs = EventStructs> {
2910
+ events: TEvents;
2911
+ maxBufferSize: number;
2912
+ maxQueueSize: number;
2913
+ method?: string;
2914
+ operation?: string;
2915
+ path: string;
2916
+ }
2917
+ /** Makes `error` required only when an error struct is declared, so omission stays `Fault<undefined>`. */
2918
+ type EventStreamErrorField<TError extends AnyStruct | undefined> = [TError] extends [undefined] ? {
2919
+ error?: never;
2920
+ } : {
2921
+ error: TError;
2922
+ };
2923
+ /**
2924
+ * Contract that describes an SSE endpoint for defineEventStream.
2925
+ *
2926
+ * The composed members are:
2927
+ *
2928
+ * - `events`, `maxBufferSize`, and `maxQueueSize` (both limits are required positive safe integers), optional `method`, `operation`, and a required `path`.
2929
+ *
2930
+ * - `error`, required only when `TError` is a struct. It decodes the body of a failed handshake (a non-2xx response). The kind rule is the same as for HTTP: `string` is text, `blob` and `file` are blobs, `arrayBuffer` is bytes, and everything else is JSON.
2931
+ *
2932
+ * - `input` and `builder`. The request shape matches HTTP, including `body`; `GET` and `HEAD` cannot return a body. A declared `input` always requires an explicit `builder`. `input` only defines the parameters; the builder alone decides where each value goes. Only definitions without `input` may omit `builder`.
2933
+ *
2934
+ * `EventStreamDefinitionBase` and `EventStreamErrorField` are internal helper types.
2935
+ *
2936
+ * @group sse
2937
+ */
2938
+ export type EventStreamDefinition<TInput extends AnyStruct | undefined = undefined, TEvents extends EventStructs = EventStructs, TError extends AnyStruct | undefined = undefined> = EventStreamDefinitionBase<TEvents> & EventStreamErrorField<TError> & {
2939
+ builder?: OutboundBuilder;
2940
+ input?: TInput;
2941
+ };
2942
+ /**
2943
+ * Await-result tuple from opening an SSE stream.
2944
+ *
2945
+ * - On success: `[null, stream, open]`. `open` is the returned connection's first successful handshake snapshot for native Defjs handles or wrappers forwarding the same live `open` object; custom handles or wrappers overriding `open` use their current public `open` even after reconnect, while `stream.open` stays live.
2946
+ *
2947
+ * - On failure: `[fault, undefined, open]`. `open` carries the response snapshot when the failure happened after a response arrived, for example a non-2xx handshake, and is `undefined` otherwise.
2948
+ *
2949
+ * - `TError` is the declared `error` struct. It types the decoded error body on the fault.
2950
+ *
2951
+ * @group sse
2952
+ */
2953
+ export type StreamAwaitResult<TEvent, TError extends AnyStruct | undefined = undefined> = [error: null, stream: EventStreamHandle<TEvent>, open: EventStreamOpenInfo] | [error: Fault<TError>, stream: undefined, open: EventStreamOpenInfo | undefined];
2954
+ /**
2955
+ * SSE field of {@link RequestOptions}. HTTP progress and WebSocket fields are ignored.
2956
+ *
2957
+ * `abort` covers the whole stream, including after it opens. There is no startup-only deadline.
2958
+ *
2959
+ * @group sse
2960
+ */
2961
+ export type EventStreamExecuteOptions = {
2962
+ /** Aborts the stream, including after it is open. */
2963
+ abort?: AbortSignal;
2964
+ };
2965
+ /**
2966
+ * Operation returned by `defineEventStream`.
2967
+ *
2968
+ * The first argument is a {@link Client}. Further arguments are the input only. Calling it opens the stream and returns the existing error-first tuple.
2969
+ *
2970
+ * @group sse
2971
+ */
2972
+ export type EventStreamEndpoint<Args extends unknown[], Event, TError extends AnyStruct | undefined = undefined> = (client: Client, ...args: Args) => Promise<StreamAwaitResult<Event, TError>>;
2973
+ /**
2974
+ * Declare a typed Server-Sent Events endpoint.
2975
+ *
2976
+ * Pass path, event structs, and buffer/queue limits. Call the returned operation
2977
+ * with a client and, when declared, the input.
2978
+ *
2979
+ * - **definition** — `path`, an `events` map, and the required `maxBufferSize` and `maxQueueSize` limits (positive safe integers). Optional `method` (default `'GET'`), `operation`, `input`, and `error`. A declared `input` always requires an explicit `builder`. `input` only defines the parameters; the builder alone decides where each value goes. Only definitions without `input` may omit `builder`.
2980
+ *
2981
+ * `EventStreamDefinitionBase`, `EventStreamErrorField`, and `BuilderField` are internal helper types: `error` is required only when `TError` is a struct, and `builder` is `(input: BuilderInput<TInput>) => EventStreamRequestShape` when `input` is declared.
2982
+ *
2983
+ * @param definition - SSE contract (path, events, buffer limits, optional input).
2984
+ * @returns An operation `(client, ...input) => Promise<error-first tuple>`.
2985
+ *
2986
+ * @group sse
2987
+ * @exampleFile examples/api/define-event-stream.ts#example
2988
+ */
2989
+ export declare function defineEventStream<const P extends string, const M extends string = "GET", TInput extends AnyStruct | undefined = undefined, TEvents extends EventStructs = EventStructs, TError extends AnyStruct | undefined = undefined>(definition: Omit<EventStreamDefinitionBase<TEvents>, "method" | "path"> & EventStreamErrorField<TError> & {
2990
+ input?: TInput;
2991
+ method?: M;
2992
+ path: P;
2993
+ } & BuilderField<NoInfer<TInput>, EventStreamRequestShape<NoInfer<P>, NoInfer<M>>>): EventStreamEndpoint<EndpointArgs<TInput>, EventStreamData<TEvents>, TError>;
2994
+ export type { AnyStruct, BasicAuthInterceptorOptions, BuilderInput, Client, ClientConfig, ClientOption, ClientOptions, ClientSSEConfig, ClientSSEOptions, ClientWebSocketOptions, DecodedResponse, ErrorMap, EventStreamCloseInfo, EventStreamFaultCode, EventStreamHandle, EventStreamOpenInfo, EventStreamRequestShape, FetchHandle, FlattenedStructError, FormattedStructError, HttpInterceptor, HttpInterceptorNext, HttpMeta, HttpProgressEvent, HttpProgressFn, HttpRequest, HttpRequestShape, HttpResponse, HttpResponseType, Infer, Interceptor, InterceptorFn, MakeResponseOptions, ObjectStruct, ParseResult, QuerySerializer, RequestOptions, SSEHandler, SSEInterceptor, SSEInterceptorFn, STRUCT, Struct, StructInput, StructIssue, StructLike, StructMethods, WebSocketHandle, WebSocketHandleConstructor, WebSocketHandler, WebSocketInterceptor, WebSocketInterceptorFn, WebSocketRequestShape, WebSocketSessionLike };