@defjs/core 0.4.0-beta.0 → 0.4.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. package/README.md +10 -47
  2. package/index.d.ts +487 -435
  3. package/index.js +5078 -4580
  4. package/index.min.js +44 -2
  5. package/package.json +2 -1
package/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  //#region src/internal/http_request.d.ts
2
2
  /**
3
3
  * How the HTTP response body should be parsed before struct validation.
4
- * Defaults to `'json'` when an output shape is declared.
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.
5
6
  */
6
7
  type HttpResponseType = 'arraybuffer' | 'blob' | 'json' | 'text';
7
8
  /**
@@ -20,9 +21,9 @@ type HttpProgressFn = (event: HttpProgressEvent) => Promise<void> | void;
20
21
  /**
21
22
  * Normalized outgoing HTTP request used by handlers, interceptors, and `fetchHandler`.
22
23
  *
23
- * After `struct.json(...)` (or another body codec) runs during command build, `body` is
24
- * already the wire value — for JSON that means a **JSON text string**. Sign HMAC / digests
25
- * against that string (or other final bytes), not the original Struct input object.
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
26
+ * original struct input.
26
27
  */
27
28
  interface HttpRequest {
28
29
  abort?: AbortSignal;
@@ -39,7 +40,18 @@ interface HttpRequest {
39
40
  operation?: string;
40
41
  queryParams?: URLSearchParams;
41
42
  queryString?: string;
42
- responseType?: HttpResponseType;
43
+ /**
44
+ * Representation for a 2xx body, derived from the endpoint `output` struct kind.
45
+ * Set only when `output` is declared. Interceptors that short-circuit with `makeResponse`
46
+ * read this to match the transport. Must not be changed. `undefined` leaves the 2xx body unread.
47
+ */
48
+ readonly responseType?: HttpResponseType;
49
+ /**
50
+ * Representation for a non-2xx body, derived from the endpoint `error` struct kind.
51
+ * Set only when `error` is declared. Must not be changed. `undefined` leaves the non-2xx body
52
+ * unread and does not fall back to `responseType`.
53
+ */
54
+ readonly errorResponseType?: HttpResponseType;
43
55
  timeout?: number;
44
56
  uploadProgress?: HttpProgressFn;
45
57
  withCredentials?: boolean;
@@ -55,8 +67,8 @@ interface HttpRequest {
55
67
  //#region src/internal/http_response.d.ts
56
68
  declare const HTTP_RESPONSE: unique symbol;
57
69
  /**
58
- * Parsed HTTP response wrapper returned by handlers and `executeHttpCommand`.
59
- * `ok` is true for status codes in the 2xx range; `error` may hold transport or parse failures.
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.
60
72
  */
61
73
  type HttpResponse<R> = {
62
74
  readonly [HTTP_RESPONSE]: true;
@@ -64,9 +76,30 @@ type HttpResponse<R> = {
64
76
  readonly status: number;
65
77
  readonly statusText: string;
66
78
  readonly headers: Headers;
67
- readonly body: R | null;
68
- readonly error?: unknown;
79
+ /** Absent when no body was present. JSON `null` stays `null`. */
80
+ readonly body: R | null | undefined;
81
+ readonly ok: boolean;
82
+ };
83
+ /**
84
+ * Response metadata, available whenever a response reached the client.
85
+ *
86
+ * Carries no body: a body only exists once a declared struct decoded it.
87
+ */
88
+ type HttpMeta = {
89
+ readonly headers: Headers;
69
90
  readonly ok: boolean;
91
+ readonly status: number;
92
+ readonly statusText: string;
93
+ readonly url: string;
94
+ };
95
+ /**
96
+ * Response whose body decoded successfully against a declared struct.
97
+ *
98
+ * Reaching this type is the proof that decoding happened; a failed decode is reported
99
+ * as a fault carrying `HttpMeta` alone.
100
+ */
101
+ type DecodedResponse<TBody> = HttpMeta & {
102
+ readonly body: TBody;
70
103
  };
71
104
  /**
72
105
  * Fields accepted by `makeResponse` when building a synthetic `HttpResponse` (for example in interceptors).
@@ -77,18 +110,23 @@ type MakeResponseOptions<R> = {
77
110
  url?: string;
78
111
  headers?: Headers;
79
112
  body?: R | null;
80
- error?: unknown;
81
113
  request?: HttpRequest;
82
114
  };
83
115
  /**
84
116
  * Build an `HttpResponse` value without performing a network call.
85
117
  * Useful in interceptors that short-circuit `next`, and in tests.
86
118
  *
87
- * @param options - Status, headers, body, optional request to copy headers/url from, and optional error; defaults yield status `0`.
119
+ * @param options - Status, headers, body, and an optional request to copy headers/url from; defaults yield status `0`.
88
120
  * @returns An `HttpResponse` with `ok` derived from the status code.
89
121
  */
90
122
  export declare function makeResponse<R>(options?: MakeResponseOptions<R>): HttpResponse<R>;
91
123
  //#endregion
124
+ //#region src/error/cause.d.ts
125
+ /** Shared cause/message sentinel for aborted requests. */
126
+ export declare const ERR_ABORTED: Error;
127
+ /** Shared cause/message sentinel for timed-out requests. */
128
+ export declare const ERR_TIMEOUT: Error;
129
+ //#endregion
92
130
  //#region src/struct/errors.d.ts
93
131
  /**
94
132
  * Optional callback that rewrites a `StructIssue` message before it is stored.
@@ -131,6 +169,15 @@ export declare class StructError extends Error {
131
169
  prettify(): string;
132
170
  }
133
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
134
181
  //#region src/struct/types.d.ts
135
182
  type Path = Array<number | string>;
136
183
  /**
@@ -164,7 +211,7 @@ type OptionalOutputStruct = {
164
211
  */
165
212
  interface StructIssue {
166
213
  /** Machine-readable failure category. */
167
- code: 'custom' | 'invalid_enum' | 'invalid_literal' | 'invalid_type' | 'invalid_union' | 'missing_key';
214
+ code: 'custom' | 'invalid_enum' | 'invalid_intersection' | 'invalid_literal' | 'invalid_type' | 'invalid_union' | 'missing_key';
168
215
  /** Human-readable description of the expected type or value. */
169
216
  expected: string;
170
217
  /** Final message after optional `ErrorMap` rewriting. */
@@ -204,7 +251,7 @@ interface StructMethods<I, O, OO extends boolean> {
204
251
  * @param name - Wire / protocol field name.
205
252
  * @returns The same struct branded with the alias.
206
253
  */
207
- alias(name: string): Struct<I, O, OO>;
254
+ alias(name: string): this;
208
255
  /**
209
256
  * Allow `null` in addition to the current input and output types.
210
257
  *
@@ -237,6 +284,20 @@ interface StructMethods<I, O, OO extends boolean> {
237
284
  */
238
285
  type Struct<Input = unknown, Output = Input, OptionalOut extends boolean = false> = StructMethods<Input, Output, OptionalOut> & StructLike<Input, Output, OptionalOut>;
239
286
  type PresentValue = NonNullable<unknown>;
287
+ declare const carriedStruct: unique symbol;
288
+ /** Keeps the struct that a nullish modifier wrapped, so builders still see its fields. */
289
+ type CarriedStruct<T> = {
290
+ readonly [carriedStruct]: T;
291
+ };
292
+ declare const unionStructOptions: unique symbol;
293
+ declare const intersectionStructOptions: unique symbol;
294
+ declare const discriminatedUnionOptions: unique symbol;
295
+ interface PreservedStruct<T, I, O, OO extends boolean> extends Struct<I, O, OO>, CarriedStruct<T> {
296
+ null(): PreservedStruct<T, I | null, O | null, OO>;
297
+ nullable(): PreservedStruct<T, I | null, O | null, OO>;
298
+ nullish(): PreservedStruct<T, I | null | undefined, O | null | undefined, true>;
299
+ optional(): PreservedStruct<T, I | undefined, O | undefined, true>;
300
+ }
240
301
  /**
241
302
  * Broad struct constraint for endpoint and client generics.
242
303
  *
@@ -291,6 +352,10 @@ interface ArrayStructTypes<S extends StructLike<unknown, unknown, boolean>> exte
291
352
  }
292
353
  interface ArrayStruct<S extends StructLike<unknown, unknown, boolean>> extends StructMethods<ArrayInput<S>, ArrayOutput<S>, false>, StructLike<ArrayInput<S>, ArrayOutput<S>, false> {
293
354
  readonly _struct: ArrayStructTypes<S>;
355
+ null(): PreservedStruct<ArrayStruct<S>, ArrayInput<S> | null, ArrayOutput<S> | null, false>;
356
+ nullable(): PreservedStruct<ArrayStruct<S>, ArrayInput<S> | null, ArrayOutput<S> | null, false>;
357
+ nullish(): PreservedStruct<ArrayStruct<S>, ArrayInput<S> | null | undefined, ArrayOutput<S> | null | undefined, true>;
358
+ optional(): PreservedStruct<ArrayStruct<S>, ArrayInput<S> | undefined, ArrayOutput<S> | undefined, true>;
294
359
  }
295
360
  interface ObjectStructTypes<T extends ObjectShape> extends StructTypes<ObjectInput<T>, ObjectOutput<T>, false> {
296
361
  input: ObjectInput<T>;
@@ -305,123 +370,202 @@ interface ObjectStructTypes<T extends ObjectShape> extends StructTypes<ObjectInp
305
370
  */
306
371
  interface ObjectStruct<T extends ObjectShape> extends StructMethods<ObjectInput<T>, ObjectOutput<T>, false>, StructLike<ObjectInput<T>, ObjectOutput<T>, false> {
307
372
  readonly _struct: ObjectStructTypes<T>;
373
+ null(): PreservedStruct<ObjectStruct<T>, ObjectInput<T> | null, ObjectOutput<T> | null, false>;
374
+ nullable(): PreservedStruct<ObjectStruct<T>, ObjectInput<T> | null, ObjectOutput<T> | null, false>;
375
+ nullish(): PreservedStruct<ObjectStruct<T>, ObjectInput<T> | null | undefined, ObjectOutput<T> | null | undefined, true>;
376
+ optional(): PreservedStruct<ObjectStruct<T>, ObjectInput<T> | undefined, ObjectOutput<T> | undefined, true>;
308
377
  }
309
- type RequestBodyCodec = 'arrayBuffer' | 'blob' | 'formData' | 'json' | 'text' | 'urlencoded';
310
- interface RequestBodyStructTypes<C extends RequestBodyCodec, S extends StructLike<unknown, unknown, boolean>> extends StructTypes<Exclude<StructInput<S>, undefined>, Exclude<StructOutput<S>, undefined>, false> {
311
- codec: C;
312
- input: Exclude<StructInput<S>, undefined>;
313
- optionalOut: undefined;
314
- output: Exclude<StructOutput<S>, undefined>;
315
- }
316
- interface RequestBodyStruct<C extends RequestBodyCodec, S extends StructLike<unknown, unknown, boolean>> extends StructMethods<Exclude<StructInput<S>, undefined>, Exclude<StructOutput<S>, undefined>, false>, StructLike<Exclude<StructInput<S>, undefined>, Exclude<StructOutput<S>, undefined>, false> {
317
- readonly _struct: RequestBodyStructTypes<C, S>;
318
- }
319
- type RequestBinaryBodyStruct = Struct<ArrayBuffer, ArrayBuffer> | Struct<Blob, Blob>;
320
- type RequestBodyShapeStruct = RequestBinaryBodyStruct | RequestBodyStruct<RequestBodyCodec, StructLike<unknown, unknown, boolean>>;
321
- type RequestShape = {
322
- body?: RequestBodyShapeStruct;
323
- headers?: ObjectStruct<ObjectShape>;
324
- path?: ObjectStruct<ObjectShape>;
325
- query?: ObjectStruct<ObjectShape>;
378
+ declare const recordValueStruct: unique symbol;
379
+ declare const tupleItemStructs: unique symbol;
380
+ type RecordInput<S extends StructLike<unknown, unknown, boolean>> = {
381
+ [key: string]: StructInput<S>;
382
+ };
383
+ type RecordOutput<S extends StructLike<unknown, unknown, boolean>> = {
384
+ [key: string]: FieldOutput<S>;
326
385
  };
327
- type RequestObjectSectionInput<TKey extends 'headers' | 'path' | 'query', TSection> = TSection extends ObjectStruct<ObjectShape> ? {} extends StructInput<TSection> ? { [K in TKey]?: StructInput<TSection>; } : { [K in TKey]: StructInput<TSection>; } : {};
328
- type RequestInput<T extends RequestShape> = Simplify<RequestObjectSectionInput<'path', T['path']> & RequestObjectSectionInput<'query', T['query']> & RequestObjectSectionInput<'headers', T['headers']> & (T['body'] extends StructLike<unknown, unknown, boolean> ? {
329
- body: StructInput<T['body']>;
330
- } : {})>;
331
- type RequestOutput<T extends RequestShape> = Simplify<(T['path'] extends ObjectStruct<ObjectShape> ? {
332
- path: StructOutput<T['path']>;
333
- } : {}) & (T['query'] extends ObjectStruct<ObjectShape> ? {
334
- query: StructOutput<T['query']>;
335
- } : {}) & (T['headers'] extends ObjectStruct<ObjectShape> ? {
336
- headers: StructOutput<T['headers']>;
337
- } : {}) & (T['body'] extends StructLike<unknown, unknown, boolean> ? {
338
- body: StructOutput<T['body']>;
339
- } : {})>;
340
- interface RequestStructTypes<T extends RequestShape> extends StructTypes<RequestInput<T>, RequestOutput<T>, false> {
341
- input: RequestInput<T>;
342
- optionalOut: undefined;
343
- output: RequestOutput<T>;
344
- }
345
386
  /**
346
- * Request-section struct produced by `struct.request({ path, query, headers, body })`.
347
- *
348
- * Groups path, query, headers, and body contracts used by HTTP/SSE/WebSocket builders.
387
+ * Value struct carried by `struct.record`.
388
+ * Tagged parsing stores this struct on the record object itself. `null` and `undefined` are not tagged.
349
389
  */
350
- interface RequestStruct<T extends RequestShape> extends StructMethods<RequestInput<T>, RequestOutput<T>, false>, StructLike<RequestInput<T>, RequestOutput<T>, false> {
351
- readonly _struct: RequestStructTypes<T>;
390
+ interface RecordStruct<S extends StructLike<unknown, unknown, boolean>> extends Struct<RecordInput<S>, RecordOutput<S>> {
391
+ readonly [recordValueStruct]: S;
392
+ null(): PreservedStruct<RecordStruct<S>, RecordInput<S> | null, RecordOutput<S> | null, false>;
393
+ nullable(): PreservedStruct<RecordStruct<S>, RecordInput<S> | null, RecordOutput<S> | null, false>;
394
+ nullish(): PreservedStruct<RecordStruct<S>, RecordInput<S> | null | undefined, RecordOutput<S> | null | undefined, true>;
395
+ optional(): PreservedStruct<RecordStruct<S>, RecordInput<S> | undefined, RecordOutput<S> | undefined, true>;
352
396
  }
353
- type RecordStruct<S extends StructLike<unknown, unknown, boolean>> = Struct<{
354
- [key: string]: StructInput<S>;
355
- }, {
356
- [key: string]: FieldOutput<S>;
357
- }>;
358
- type TupleStruct<T extends readonly StructLike<unknown, unknown, boolean>[]> = Struct<TupleInput<T>, TupleOutput<T>>;
359
- type UnionStruct<T extends readonly StructLike<unknown, unknown, boolean>[]> = Struct<UnionInput<T>, UnionOutput<T>>;
360
- type DiscriminatedUnionStruct<TOptions extends readonly ObjectStruct<ObjectShape>[]> = Struct<StructInput<TOptions[number]>, StructOutput<TOptions[number]>>;
361
- //#endregion
362
- //#region src/internal/request_values.d.ts
363
- interface RequestBodyOptions {
364
- readonly contentType?: string | null;
397
+ /** Item structs carried by `struct.tuple`, so builders tag each position. The tuple itself is not tagged. */
398
+ interface TupleStruct<T extends readonly StructLike<unknown, unknown, boolean>[]> extends Struct<TupleInput<T>, TupleOutput<T>> {
399
+ readonly [tupleItemStructs]: T;
400
+ null(): PreservedStruct<TupleStruct<T>, TupleInput<T> | null, TupleOutput<T> | null, false>;
401
+ nullable(): PreservedStruct<TupleStruct<T>, TupleInput<T> | null, TupleOutput<T> | null, false>;
402
+ nullish(): PreservedStruct<TupleStruct<T>, TupleInput<T> | null | undefined, TupleOutput<T> | null | undefined, true>;
403
+ optional(): PreservedStruct<TupleStruct<T>, TupleInput<T> | undefined, TupleOutput<T> | undefined, true>;
404
+ }
405
+ interface UnionStruct<T extends readonly StructLike<unknown, unknown, boolean>[]> extends Struct<UnionInput<T>, UnionOutput<T>> {
406
+ readonly [unionStructOptions]: T;
407
+ null(): PreservedStruct<UnionStruct<T>, UnionInput<T> | null, UnionOutput<T> | null, false>;
408
+ nullable(): PreservedStruct<UnionStruct<T>, UnionInput<T> | null, UnionOutput<T> | null, false>;
409
+ nullish(): PreservedStruct<UnionStruct<T>, UnionInput<T> | null | undefined, UnionOutput<T> | null | undefined, true>;
410
+ optional(): PreservedStruct<UnionStruct<T>, UnionInput<T> | undefined, UnionOutput<T> | undefined, true>;
411
+ }
412
+ interface DiscriminatedUnionStruct<TOptions extends readonly ObjectStruct<ObjectShape>[]> extends Struct<StructInput<TOptions[number]>, StructOutput<TOptions[number]>> {
413
+ readonly [discriminatedUnionOptions]: TOptions;
414
+ null(): PreservedStruct<DiscriminatedUnionStruct<TOptions>, StructInput<TOptions[number]> | null, StructOutput<TOptions[number]> | null, false>;
415
+ nullable(): PreservedStruct<DiscriminatedUnionStruct<TOptions>, StructInput<TOptions[number]> | null, StructOutput<TOptions[number]> | null, false>;
416
+ nullish(): PreservedStruct<DiscriminatedUnionStruct<TOptions>, StructInput<TOptions[number]> | null | undefined, StructOutput<TOptions[number]> | null | undefined, true>;
417
+ optional(): PreservedStruct<DiscriminatedUnionStruct<TOptions>, StructInput<TOptions[number]> | undefined, StructOutput<TOptions[number]> | undefined, true>;
418
+ }
419
+ interface IntersectionStruct<T extends readonly StructLike<unknown, unknown, boolean>[]> extends Struct<IntersectionInput<T>, IntersectionOutput<T>> {
420
+ readonly [intersectionStructOptions]: T;
421
+ null(): PreservedStruct<IntersectionStruct<T>, IntersectionInput<T> | null, IntersectionOutput<T> | null, false>;
422
+ nullable(): PreservedStruct<IntersectionStruct<T>, IntersectionInput<T> | null, IntersectionOutput<T> | null, false>;
423
+ nullish(): PreservedStruct<IntersectionStruct<T>, IntersectionInput<T> | null | undefined, IntersectionOutput<T> | null | undefined, true>;
424
+ optional(): PreservedStruct<IntersectionStruct<T>, IntersectionInput<T> | undefined, IntersectionOutput<T> | undefined, true>;
365
425
  }
366
426
  //#endregion
367
- //#region src/internal/request_builder.d.ts
368
- type RequestTransport = 'http' | 'sse' | 'webSocket';
369
- declare const BOUND_SOURCE: unique symbol;
370
- declare const ARRAY_PROJECTION: unique symbol;
371
- type BuildBoundRef<TOutput = unknown> = {
372
- readonly [BOUND_SOURCE]: {
373
- readonly output: TOutput;
374
- };
375
- };
376
- type BuildArrayProjection<TItem> = {
377
- readonly [ARRAY_PROJECTION]: never;
378
- readonly item: TItem;
379
- };
380
- type BuildArray<TItem, TOutput extends readonly unknown[]> = BuildBoundRef<TOutput> & {
381
- map<TProjection>(callback: (item: TItem) => TProjection): BuildArrayProjection<TProjection>;
382
- };
383
- type BuildInput<T> = T extends {
384
- readonly _struct: {
385
- readonly output: infer TOutput;
386
- };
387
- } ? BuildOutput<TOutput> : BuildBoundRef;
388
- type BuildOutput<TOutput> = TOutput extends readonly (infer TItem)[] ? BuildArray<BuildOutput<TItem>, TOutput> : TOutput extends {
389
- [key: string]: unknown;
390
- } ? BuildBoundRef & { readonly [K in keyof TOutput]: BuildOutput<TOutput[K]>; } : BuildBoundRef<TOutput>;
391
- type BuildFlatScalar = bigint | boolean | Date | null | number | string | undefined;
392
- type BuildFlatValue = BuildFlatScalar | readonly BuildFlatScalar[];
393
- type BuildFormDataValue = BuildFlatValue | Blob | File | readonly (Blob | BuildFlatScalar | File)[];
394
- type BuildJsonProjection = BuildArrayProjection<unknown> | BuildBoundRef | undefined | readonly BuildJsonProjection[] | {
395
- readonly [key: string]: BuildJsonProjection;
427
+ //#region src/error/types.d.ts
428
+ /**
429
+ * Every failure Defjs reports, as one closed set of codes.
430
+ *
431
+ * The prefix before the first `_` is the class; there is no separate `kind` field.
432
+ * Read the prefix for coarse triage and the whole code for an exact case:
433
+ *
434
+ * | Class | Meaning |
435
+ * | ------ | -------------------------------------------------------------- |
436
+ * | `HTTP` | The peer answered with a non-2xx status. |
437
+ * | `REQ` | The request never left: input, execute options, or build. |
438
+ * | `NET` | No usable response: cancel, timeout, connect, truncated body. |
439
+ * | `RES` | A response arrived but could not be read as declared. |
440
+ * | `EXT` | Code you attached to the pipeline threw. |
441
+ * | `CAP` | A declared buffer or queue limit was exceeded. |
442
+ * | `ENV` | The host runtime lacks a required capability. |
443
+ *
444
+ * The set is closed on purpose: it keeps `switch (fault.code)` exhaustive. Extensions
445
+ * report their own detail through `cause`, not through a new code.
446
+ */
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';
448
+ /**
449
+ * The class of a fault code: the segment before its first `_`.
450
+ *
451
+ * @typeParam C - A `FaultCode`, or a union of them.
452
+ */
453
+ 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'>;
458
+ type ErrorBody<TErr> = [TErr] extends [undefined] ? undefined : Infer<TErr>;
459
+ /**
460
+ * `HttpStatusFault` expressed by its decoded body type instead of the struct that produced it.
461
+ *
462
+ * Factories work in decoded values, so they use this form; endpoint types use
463
+ * `HttpStatusFault` and let `Infer` bridge the two.
464
+ */
465
+ export type HttpStatusFaultOf<TData> = Error & {
466
+ code: 'HTTP_STATUS';
467
+ data: TData;
468
+ response: [TData] extends [undefined] ? HttpMeta : DecodedResponse<TData>;
469
+ status: number;
396
470
  };
397
- type BuildRecordProjection = {
398
- readonly [key: string]: BuildBoundRef<BuildFlatValue> | undefined;
471
+ /**
472
+ * The peer answered with a non-2xx status and the declared `error` struct decoded its body.
473
+ *
474
+ * Omitting `error` skips decoding, which leaves `data` as `undefined` and `response`
475
+ * without a `body` field.
476
+ */
477
+ export type HttpStatusFault<TErr extends AnyStruct | undefined = undefined> = HttpStatusFaultOf<ErrorBody<TErr>>;
478
+ /**
479
+ * A response arrived but could not be read as declared.
480
+ *
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
483
+ * parser failure for an unreadable representation).
484
+ */
485
+ export type DecodeFault = Error & {
486
+ cause: unknown;
487
+ code: DecodeFaultCode;
488
+ response: HttpMeta;
489
+ status: number;
399
490
  };
400
- type BuildFormDataProjection = {
401
- readonly [key: string]: BuildBoundRef<BuildFormDataValue> | undefined;
491
+ /**
492
+ * A failure that may have happened before any response existed.
493
+ *
494
+ * `response` is present only where the transport already had metadata to report,
495
+ * such as a body that truncated mid-download.
496
+ */
497
+ export type PreflightFault = Error & {
498
+ cause?: unknown;
499
+ code: PreflightFaultCode;
500
+ response?: HttpMeta;
402
501
  };
403
- interface RequestBuilder {
404
- addFormData(projection: BuildFormDataProjection): void;
405
- addFormUrlEncoded(projection: BuildRecordProjection, options?: RequestBodyOptions): void;
406
- addHeaders(projection: BuildRecordProjection): void;
407
- setArrayBuffer(value: BuildBoundRef<ArrayBuffer>, options?: RequestBodyOptions): void;
408
- setBlob(value: BuildBoundRef<Blob>, options?: RequestBodyOptions): void;
409
- setFormData(projection: BuildFormDataProjection): void;
410
- setFormUrlEncoded(projection: BuildRecordProjection, options?: RequestBodyOptions): void;
411
- setHeaders(projection: BuildRecordProjection): void;
412
- setHtml(value: BuildBoundRef<string>, options?: RequestBodyOptions): void;
413
- setJson(projection: BuildJsonProjection, options?: RequestBodyOptions): void;
414
- setPathParams(projection: BuildRecordProjection): void;
415
- setQueryParams(projection: BuildRecordProjection): void;
416
- setText(value: BuildBoundRef<string>, options?: RequestBodyOptions): void;
417
- }
418
- interface WebSocketRequestBuildContext {
419
- setPathParams(projection: BuildRecordProjection): void;
420
- setQueryParams(projection: BuildRecordProjection): void;
421
- }
422
- type RequestBuildInput<TInput extends AnyStruct | undefined> = [TInput] extends [undefined] ? unknown : TInput extends AnyStruct ? BuildInput<TInput> : never;
423
- type RequestBuildContext<TTransport extends RequestTransport = 'http'> = TTransport extends 'webSocket' ? WebSocketRequestBuildContext : RequestBuilder;
424
- type RequestBuildHandler<TInput extends AnyStruct | undefined, TTransport extends RequestTransport = 'http'> = (request: RequestBuildContext<TTransport>, input: RequestBuildInput<TInput>) => void;
502
+ /**
503
+ * Any failure returned by `client.execute`, expressed by its decoded error-body type.
504
+ *
505
+ * @typeParam TData - The decoded `error` body, or `undefined` when no error struct was declared.
506
+ */
507
+ export type FaultOf<TData = undefined> = DecodeFault | HttpStatusFaultOf<TData> | PreflightFault;
508
+ /**
509
+ * Any failure returned by `client.execute`, discriminated by `code`.
510
+ *
511
+ * @typeParam TErr - The endpoint's declared `error` struct, or `undefined` when it declares none.
512
+ */
513
+ export type Fault<TErr extends AnyStruct | undefined = undefined> = FaultOf<ErrorBody<TErr>>;
514
+ /**
515
+ * Any fault at all, whatever its decoded error-body type.
516
+ *
517
+ * Use it for handlers that classify faults without caring which endpoint produced them.
518
+ * Prefer `Fault<typeof yourErrorStruct>` where the body type matters.
519
+ */
520
+ export type AnyFault = DecodeFault | HttpStatusFaultOf<undefined> | HttpStatusFaultOf<unknown> | PreflightFault;
521
+ //#endregion
522
+ //#region src/error/factory.d.ts
523
+ /**
524
+ * Build the fault for a non-2xx response whose body decoded against the declared `error` struct.
525
+ *
526
+ * @param response - The response, carrying the decoded body.
527
+ * @returns An `HTTP_STATUS` fault whose `data` is that body.
528
+ */
529
+ export declare function createHttpStatusFault<TData>(response: DecodedResponse<TData>): HttpStatusFaultOf<TData>;
530
+ /**
531
+ * Build the fault for a non-2xx response that was never decoded, because the endpoint
532
+ * declared no `error` struct.
533
+ *
534
+ * @param response - Metadata of the response; no body was read.
535
+ * @returns An `HTTP_STATUS` fault whose `data` is `undefined`.
536
+ */
537
+ export declare function createUndecodedHttpStatusFault(response: HttpMeta): HttpStatusFaultOf<undefined>;
538
+ /**
539
+ * Build the fault for a response that arrived but could not be read as declared.
540
+ *
541
+ * Carries metadata only: a body is a decoded value, and decoding is what failed.
542
+ *
543
+ * @param code - Which read step failed.
544
+ * @param cause - Underlying parser or struct failure.
545
+ * @param response - Metadata of the response that could not be read.
546
+ * @returns A decode fault with no body.
547
+ */
548
+ export declare function createDecodeFault(code: DecodeFaultCode, cause: unknown, response: HttpMeta): DecodeFault;
549
+ /**
550
+ * Build a fault for a failure that may have happened before any response existed.
551
+ *
552
+ * @param code - The specific failure.
553
+ * @param cause - Underlying value, when there is one; the code becomes the message otherwise.
554
+ * @param response - Metadata, only where the transport already had some to report.
555
+ * @returns A preflight fault.
556
+ */
557
+ export declare function createPreflightFault(code: PreflightFaultCode, cause?: unknown, response?: HttpMeta): PreflightFault;
558
+ /**
559
+ * Build a `NET_*` fault, choosing the code from the shape of `cause`.
560
+ *
561
+ * Recognizes the shared abort/timeout sentinels and the platform's `AbortError`/`TimeoutError`;
562
+ * anything else is reported as unreachable.
563
+ *
564
+ * @param cause - Underlying cancellation, timeout, or transport failure.
565
+ * @param response - Metadata, only where the transport already had some to report.
566
+ * @returns A `NET_ABORTED`, `NET_TIMEOUT`, or `NET_UNREACHABLE` fault.
567
+ */
568
+ export declare function createNetworkFault(cause: unknown, response?: HttpMeta): PreflightFault;
425
569
  //#endregion
426
570
  //#region src/struct/constructors.d.ts
427
571
  declare function createStringStruct(): StringStruct;
@@ -433,13 +577,8 @@ declare function createUnknownStruct(): UnknownStruct;
433
577
  declare function createLiteralStruct<const T extends LiteralValue>(value: T): Struct<T, T>;
434
578
  declare function createArrayStruct<S extends StructLike<unknown, unknown, boolean>>(item: S): ArrayStruct<S>;
435
579
  declare function createObjectStruct<T extends ObjectShape>(shape: T): ObjectStruct<T>;
436
- declare function createRequestStruct<const T extends RequestShape>(shape: T): RequestStruct<T>;
437
- declare function createJsonBodyStruct<S extends StructLike<unknown, unknown, boolean>>(struct: S, options?: {
438
- contentType?: string | null;
439
- }): RequestBodyStruct<'json', S>;
440
- declare function createUrlencodedBodyStruct<T extends ObjectShape>(shape: T): RequestBodyStruct<'urlencoded', ObjectStruct<T>>;
441
- declare function createFormDataBodyStruct<T extends ObjectShape>(shape: T): RequestBodyStruct<'formData', ObjectStruct<T>>;
442
- declare function createTextBodyStruct(): RequestBodyStruct<'text', StringStruct>;
580
+ declare function createUrlencodedBodyStruct<T extends ObjectShape>(shape: T): ObjectStruct<T>;
581
+ declare function createFormDataBodyStruct<T extends ObjectShape>(shape: T): ObjectStruct<T>;
443
582
  declare function createRecordStruct<S extends StructLike<unknown, unknown, boolean>>(value: S): RecordStruct<S>;
444
583
  declare function createTupleStruct<const T extends readonly [StructLike<unknown, unknown, boolean>, ...StructLike<unknown, unknown, boolean>[]]>(items: T): TupleStruct<T>;
445
584
  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;
@@ -448,7 +587,7 @@ declare function createDiscriminatedUnionStruct<const TDiscriminator extends str
448
587
  declare function createBlobStruct(): Struct<Blob, Blob>;
449
588
  declare function createBigIntStruct(): Struct<bigint | string, bigint>;
450
589
  declare function createDateStruct(): Struct<Date | number | string, Date>;
451
- declare function createIntersectionStruct<const T extends readonly [StructLike<unknown, unknown, boolean>, ...StructLike<unknown, unknown, boolean>[]]>(...structs: T): Struct<IntersectionInput<T>, IntersectionOutput<T>>;
590
+ declare function createIntersectionStruct<const T extends readonly [StructLike<unknown, unknown, boolean>, ...StructLike<unknown, unknown, boolean>[]]>(...structs: T): IntersectionStruct<T>;
452
591
  declare function createFileStruct(): Struct<File, File>;
453
592
  declare function createArrayBufferStruct(): Struct<ArrayBuffer, ArrayBuffer>;
454
593
  //#endregion
@@ -471,8 +610,8 @@ declare function structOr<const T extends readonly [StructLike, ...StructLike[]]
471
610
  * Facade for building protocol-boundary structs and parsing values.
472
611
  *
473
612
  * Prefer this entry point over internal constructors. Compose shapes with
474
- * `struct.object` / `struct.request`, then parse with `struct.parse` or let
475
- * HTTP/SSE/WebSocket endpoints apply the same contracts at the wire boundary.
613
+ * `struct.object`, then parse with `struct.parse` or let HTTP/SSE/WebSocket
614
+ * endpoints apply the same contracts at the wire boundary.
476
615
  *
477
616
  * @example
478
617
  * ```ts
@@ -481,8 +620,12 @@ declare function structOr<const T extends readonly [StructLike, ...StructLike[]]
481
620
  * displayName: struct.string().alias('display_name'),
482
621
  * })
483
622
  *
484
- * const getUserInput = struct.request({
485
- * path: struct.object({ id: struct.number() }),
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,
486
629
  * })
487
630
  *
488
631
  * const [error, user] = struct.parse(User, { id: 1, displayName: 'Ada' })
@@ -501,7 +644,6 @@ export declare const struct: {
501
644
  readonly file: typeof createFileStruct;
502
645
  readonly formData: typeof createFormDataBodyStruct;
503
646
  readonly intersection: typeof createIntersectionStruct;
504
- readonly json: typeof createJsonBodyStruct;
505
647
  readonly literal: typeof createLiteralStruct;
506
648
  readonly null: typeof createNullStruct;
507
649
  readonly number: typeof createNumberStruct;
@@ -509,30 +651,12 @@ export declare const struct: {
509
651
  readonly or: typeof structOr;
510
652
  readonly parse: typeof parseStructTuple;
511
653
  readonly record: typeof createRecordStruct;
512
- readonly request: typeof createRequestStruct;
513
654
  readonly string: typeof createStringStruct;
514
- readonly text: typeof createTextBodyStruct;
515
655
  readonly tuple: typeof createTupleStruct;
516
656
  readonly unknown: typeof createUnknownStruct;
517
657
  readonly urlencoded: typeof createUrlencodedBodyStruct;
518
658
  };
519
659
  //#endregion
520
- //#region src/http/request.d.ts
521
- /**
522
- * One status-to-body mapping used in the array form of `RequestOutputShape`.
523
- * `status` may be a single code or a list of codes that share the same body struct.
524
- */
525
- type ResponseGroupItem<S extends number = number, B extends AnyStruct = AnyStruct> = {
526
- body: B;
527
- status: S | readonly S[];
528
- };
529
- /**
530
- * Declared HTTP response shapes keyed by status code, as a map or a list of `ResponseGroupItem`s.
531
- */
532
- type RequestOutputShape = {
533
- [key: number]: AnyStruct;
534
- } | readonly ResponseGroupItem[];
535
- //#endregion
536
660
  //#region src/sse/transport/parser.d.ts
537
661
  /**
538
662
  * Portions of this file are adapted from Azure/fetch-event-source.
@@ -550,20 +674,28 @@ interface EventStreamOpenInfo {
550
674
  response: HttpResponse<unknown>;
551
675
  url: string;
552
676
  }
553
- /** Fatal or recoverable error codes reported when an SSE stream closes with `code: 'error'`. */
554
- type EventStreamErrorCode = 'INVALID_RESPONSE' | 'MESSAGE_PROCESSING_FAILED' | 'PARSER_LIMIT_EXCEEDED' | 'QUEUE_OVERFLOW' | 'TIMEOUT' | 'TRANSPORT_ERROR';
677
+ /**
678
+ * Fault codes an event stream can end with, drawn from the one shared `FaultCode` namespace.
679
+ *
680
+ * SSE used to carry a parallel set with no mapping to the request error model.
681
+ */
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'>;
555
683
  interface EventStreamCloseInfoBase {
556
684
  reason?: string;
557
685
  cause?: unknown;
558
686
  }
559
- /** How an SSE stream ended: clean EOF, abort, or an error with `errorCode`. */
687
+ /**
688
+ * How an SSE stream ended: clean EOF, abort, or an error carrying its fault `code`.
689
+ *
690
+ * The discriminant is `kind` so that `code` can mean the same thing it means on a fault.
691
+ */
560
692
  type EventStreamCloseInfo = (EventStreamCloseInfoBase & {
561
- code: 'eof';
693
+ kind: 'eof';
562
694
  }) | (EventStreamCloseInfoBase & {
563
- code: 'aborted';
695
+ kind: 'aborted';
564
696
  }) | (EventStreamCloseInfoBase & {
565
- code: 'error';
566
- errorCode: EventStreamErrorCode;
697
+ code: EventStreamFaultCode;
698
+ kind: 'error';
567
699
  });
568
700
  /** Open SSE stream: iterate events, inspect open metadata, and close early. */
569
701
  interface EventStreamHandle<TEvent = EventStreamMessage> extends AsyncIterable<TEvent>, AsyncDisposable {
@@ -573,86 +705,6 @@ interface EventStreamHandle<TEvent = EventStreamMessage> extends AsyncIterable<T
573
705
  [Symbol.asyncDispose](): PromiseLike<void>;
574
706
  }
575
707
  //#endregion
576
- //#region src/error/types.d.ts
577
- /**
578
- * Error for an HTTP response whose status is treated as failure.
579
- */
580
- export interface HttpStatusError<TErrorData = unknown, TStatus extends number = number> extends Error {
581
- code: 'HTTP_STATUS';
582
- data: TErrorData;
583
- kind: 'http';
584
- message: string;
585
- response: HttpResponse<unknown>;
586
- status: TStatus;
587
- }
588
- /**
589
- * Error for transport-level failures such as abort, timeout, or network issues.
590
- */
591
- export interface TransportError extends Error {
592
- cause?: unknown;
593
- code: 'ABORTED' | 'NETWORK_ERROR' | 'TIMEOUT';
594
- kind: 'transport';
595
- message: string;
596
- }
597
- /**
598
- * Error for definition-time validation or undeclared response status failures.
599
- */
600
- export type DefinitionError = (Error & {
601
- cause?: unknown;
602
- code: 'REQUEST_VALIDATION_FAILED' | 'RESPONSE_VALIDATION_FAILED' | 'INTERCEPTOR_FAILED';
603
- kind: 'definition';
604
- response?: HttpResponse<unknown>;
605
- }) | (Error & {
606
- cause?: unknown;
607
- code: 'UNDECLARED_STATUS';
608
- kind: 'definition';
609
- response: HttpResponse<unknown>;
610
- status: number;
611
- });
612
- /**
613
- * Union of HTTP status, transport, and definition errors from request execution.
614
- *
615
- * @typeParam TErrorData - Declared error body type for `HttpStatusError` variants.
616
- */
617
- export type RequestError<TErrorData = unknown> = HttpStatusError<TErrorData> | TransportError | DefinitionError;
618
- //#endregion
619
- //#region src/error/factory.d.ts
620
- /** Shared cause/message sentinel for aborted requests. */
621
- export declare const ERR_ABORTED: Error;
622
- /** Shared cause/message sentinel for timed-out requests. */
623
- export declare const ERR_TIMEOUT: Error;
624
- /**
625
- * Build a `TransportError` from an abort, timeout, or other network cause.
626
- *
627
- * @param cause - Underlying abort/timeout/error value.
628
- * @returns A normalized `TransportError`.
629
- */
630
- export declare function createTransportError(cause: unknown): TransportError;
631
- /**
632
- * Build a `DefinitionError` for request/response validation or undeclared status.
633
- *
634
- * @param code - Definition error code.
635
- * @param cause - Underlying validation or status failure.
636
- * @param response - HTTP response; required for `UNDECLARED_STATUS` (status is taken from it).
637
- * @returns A normalized `DefinitionError`.
638
- */
639
- export declare function createDefinitionError(code: 'UNDECLARED_STATUS', cause: unknown, response: HttpResponse<unknown>): Extract<DefinitionError, {
640
- code: 'UNDECLARED_STATUS';
641
- }>;
642
- export declare function createDefinitionError(code: Exclude<DefinitionError['code'], 'UNDECLARED_STATUS'>, cause: unknown, response?: HttpResponse<unknown>): Extract<DefinitionError, {
643
- code: Exclude<DefinitionError['code'], 'UNDECLARED_STATUS'>;
644
- }>;
645
- /**
646
- * Build an `HttpStatusError` for a non-success HTTP status.
647
- *
648
- * @param status - HTTP status code.
649
- * @param message - Human-readable error message.
650
- * @param response - Full HTTP response.
651
- * @param data - Optional parsed error body.
652
- * @returns A normalized `HttpStatusError`.
653
- */
654
- export declare function createHttpStatusError<TErrorData = unknown, TStatus extends number = number>(status: TStatus, message: string, response: HttpResponse<unknown>, data?: TErrorData): HttpStatusError<TErrorData, TStatus>;
655
- //#endregion
656
708
  //#region src/internal/abort.d.ts
657
709
  type UseCancellationConfig = {
658
710
  abort?: AbortSignal;
@@ -667,7 +719,67 @@ type EndpointInput<TInput extends AnyStruct | undefined> = TInput extends AnyStr
667
719
  //#endregion
668
720
  //#region src/internal/endpoint_command.d.ts
669
721
  type IsEndpointInputOptional<TInput extends AnyStruct | undefined> = [TInput] extends [undefined] ? true : undefined extends EndpointInput<TInput> ? true : false;
670
- type EndpointCommandBuilder<TInput extends AnyStruct | undefined, TCommand> = IsEndpointInputOptional<TInput> extends true ? (input?: EndpointInput<TInput>) => TCommand : (input: EndpointInput<TInput>) => TCommand;
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
725
+ /** Object produced by endpoint input parsing. The symbol points at the struct that produced the node. */
726
+ type Typed = {
727
+ readonly [STRUCT]: AnyStruct;
728
+ };
729
+ type Scalar = string | number | bigint | boolean | Date;
730
+ type FlatValue = Scalar | readonly Scalar[] | undefined;
731
+ type FlatRecord = {
732
+ readonly [key: string]: FlatValue;
733
+ };
734
+ type QueryValue = Scalar | readonly QueryValue[] | {
735
+ readonly [key: string]: QueryValue;
736
+ } | undefined;
737
+ type QueryRecord = {
738
+ readonly [key: string]: QueryValue;
739
+ };
740
+ type PathParams<P extends string> = P extends `${string}:${infer Name}/${infer Rest}` ? Name | PathParams<`/${Rest}`> : P extends `${string}:${infer Name}` ? Name : never;
741
+ type PathValue<P extends string> = [PathParams<P>] extends [never] ? never : ({ readonly [K in PathParams<P>]: Scalar; } & {
742
+ readonly [STRUCT]?: never;
743
+ }) | (Typed & {
744
+ readonly [key: string]: Scalar;
745
+ });
746
+ type HttpRequestShape<P extends string, M extends string> = {
747
+ body?: Uppercase<M> extends 'GET' | 'HEAD' ? never : unknown;
748
+ headers?: HeadersInit | FlatRecord;
749
+ path?: PathValue<P>;
750
+ query?: URLSearchParams | QueryRecord;
751
+ };
752
+ type WebSocketRequestShape<P extends string> = {
753
+ body?: never;
754
+ headers?: never;
755
+ path?: PathValue<P>;
756
+ query?: URLSearchParams | QueryRecord;
757
+ };
758
+ type EventStreamRequestShape<P extends string, M extends string> = HttpRequestShape<P, M>;
759
+ 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;
761
+ /**
762
+ * Parsed endpoint input seen by a builder.
763
+ *
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.
768
+ */
769
+ 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
+ [key: string]: BuilderInput<Value>;
771
+ } : 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
+ type BuilderField<TInput, TShape> = [TInput] extends [undefined] ? {
773
+ builder?: () => TShape;
774
+ } : BuilderInput<TInput> extends TShape ? {
775
+ builder?: (input: BuilderInput<TInput>) => TShape;
776
+ } : {
777
+ builder: (input: BuilderInput<TInput>) => TShape;
778
+ };
779
+ //#endregion
780
+ //#region src/internal/outbound_request.d.ts
781
+ /** Accepts any concrete builder. `never` keeps the parameter contravariant. */
782
+ type OutboundBuilder = (input: never) => unknown;
671
783
  //#endregion
672
784
  //#region src/http/http.d.ts
673
785
  /**
@@ -679,11 +791,11 @@ export type UseRequestConfig = {
679
791
  onUploadProgress?: HttpProgressFn;
680
792
  } & UseCancellationConfig;
681
793
  /**
682
- * Executable HTTP command produced by a `defineRequest` builder.
794
+ * Executable HTTP command produced by a `defineRequest` endpoint.
683
795
  * Carries the request definition and optional typed input for `client.execute`.
684
796
  */
685
- export interface HttpCommand<TInput extends AnyStruct | undefined, TOutput extends RequestOutputShape | undefined> extends BaseCommand<typeof HTTP_COMMAND> {
686
- readonly definition: RequestDefinition<TInput, TOutput>;
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>;
687
799
  readonly input: EndpointInput<TInput> | undefined;
688
800
  }
689
801
  /**
@@ -695,100 +807,84 @@ export type HttpExecuteOptions = UseRequestConfig & {
695
807
  signal?: AbortSignal;
696
808
  };
697
809
  /**
698
- * Builder returned by `defineRequest`; call with input to create an `HttpCommand`.
810
+ * Endpoint returned by `defineRequest`; call with input to create an `HttpCommand`.
699
811
  */
700
- export type RequestCommandBuilder<TInput extends AnyStruct | undefined, TOutput extends RequestOutputShape | undefined> = EndpointCommandBuilder<TInput, HttpCommand<TInput, TOutput>>;
701
- type ResponsePairForStatus<TBody extends AnyStruct, TStatus> = TStatus extends readonly (infer U extends number)[] ? U extends number ? {
702
- body: TBody;
703
- status: U;
704
- } : never : TStatus extends number ? {
705
- body: TBody;
706
- status: TStatus;
707
- } : never;
708
- type ResponsePair<TOutput extends RequestOutputShape | undefined> = NonNullable<TOutput> extends readonly (infer TItem)[] ? TItem extends {
709
- body: infer TBody extends AnyStruct;
710
- status: infer TStatus;
711
- } ? ResponsePairForStatus<TBody, TStatus> : never : { [K in keyof NonNullable<TOutput>]: NonNullable<TOutput>[K] extends (infer TBody extends AnyStruct) ? K extends number ? {
712
- body: TBody;
713
- status: K;
714
- } : K extends `${infer TStatus extends number}` ? {
715
- body: TBody;
716
- status: TStatus;
717
- } : never : never; }[keyof NonNullable<TOutput>];
718
- type HttpStatusErrorByOutput<TOutput extends RequestOutputShape | undefined> = [TOutput] extends [undefined] ? HttpStatusError<undefined> : ResponsePair<TOutput> extends (infer TPair) ? TPair extends {
719
- body: infer TBody extends AnyStruct;
720
- status: infer TStatus extends number;
721
- } ? `${TStatus}` extends `2${string}` ? never : HttpStatusError<Infer<TBody>, TStatus> : never : never;
722
- type RequestErrorByOutput<TOutput extends RequestOutputShape | undefined> = HttpStatusErrorByOutput<TOutput> | TransportError | DefinitionError;
723
- type ResponseBodyByStatus<TOutput extends RequestOutputShape | undefined, TOk extends boolean> = ResponsePair<TOutput> extends (infer TPair) ? TPair extends {
724
- body: infer TBody extends AnyStruct;
725
- status: infer TStatus extends number;
726
- } ? `${TStatus}` extends `2${string}` ? TOk extends true ? TBody : never : TOk extends true ? never : TBody : never : never;
727
- type InferResponseBodyByStatus<TOutput extends RequestOutputShape | undefined, TOk extends boolean> = [TOutput] extends [undefined] ? undefined : [ResponseBodyByStatus<TOutput, TOk>] extends [never] ? unknown : Infer<ResponseBodyByStatus<TOutput, TOk>>;
728
- /** Inferred success body type for 2xx statuses declared in `TOutput`. */
729
- export type RequestSuccessData<TOutput extends RequestOutputShape | undefined> = InferResponseBodyByStatus<TOutput, true>;
730
- /** Inferred error-body type for non-2xx statuses declared in `TOutput`. */
731
- export type RequestErrorData<TOutput extends RequestOutputShape | undefined> = InferResponseBodyByStatus<TOutput, false>;
732
- type ResponseDeclaration<TOutput extends RequestOutputShape | undefined> = [TOutput] extends [undefined] ? {
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] ? {
733
820
  output?: never;
734
- responseType?: never;
735
821
  } : {
736
822
  output: TOutput;
737
- responseType?: HttpResponseType;
738
- };
823
+ }) & ([TError] extends [undefined] ? {
824
+ error?: never;
825
+ } : {
826
+ error: TError;
827
+ });
739
828
  /**
740
- * Contract for an HTTP endpoint: method, path, optional input struct, and status-keyed outputs.
741
- * Pass to `defineRequest` to get a typed command builder.
829
+ * Contract for an HTTP endpoint: method, path, optional input struct, and the structs that
830
+ * decode its bodies.
831
+ *
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.
742
835
  */
743
- export type RequestDefinition<TInput extends AnyStruct | undefined = undefined, TOutput extends RequestOutputShape | undefined = undefined> = ResponseDeclaration<TOutput> & {
744
- operation?: string;
745
- } & ({
746
- method: string;
747
- path: string;
748
- build?: never;
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;
749
838
  input?: TInput;
750
- } | (TInput extends AnyStruct ? {
751
839
  method: string;
840
+ operation?: string;
752
841
  path: string;
753
- build: RequestBuildHandler<TInput>;
754
- input: TInput;
755
- } : never));
842
+ };
756
843
  /**
757
- * Await-result tuple for an HTTP call: success `[null, body, response]` or failure `[error, undefined, response?]`.
844
+ * Await-result tuple for an HTTP call.
845
+ *
846
+ * Success is `[null, data, response]`; failure is `[fault, undefined, undefined]` because the
847
+ * fault carries whatever response metadata existed.
758
848
  */
759
- export type HttpAwaitResult<TSuccess = unknown, TErrorData = unknown> = [error: null, result: TSuccess, response: HttpResponse<TSuccess>] | [error: RequestError<TErrorData>, result: undefined, response: HttpResponse<unknown> | undefined];
760
- type HttpExecuteAwaitResult<TOutput extends RequestOutputShape | undefined> = [error: null, result: RequestSuccessData<TOutput>, response: HttpResponse<RequestSuccessData<TOutput>>] | [error: RequestErrorByOutput<TOutput>, result: undefined, response: HttpResponse<unknown> | undefined];
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];
761
851
  /**
762
- * Declare a typed HTTP request command builder.
852
+ * Declare a typed HTTP request endpoint.
763
853
  *
764
- * Pass method, path, optional input struct, and status-keyed output structs.
765
- * Call the returned builder with input to get an `HttpCommand` for `client.execute`.
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`.
766
856
  *
767
- * @param definition - Request contract (method, path, input, output).
768
- * @returns A builder that creates `HttpCommand` values from input.
857
+ * @param definition - Request contract (method, path, input, builder, output).
858
+ * @returns An endpoint that creates `HttpCommand` values from input.
769
859
  *
770
860
  * @example
771
861
  * ```ts
772
862
  * const getUser = defineRequest({
773
863
  * method: 'GET',
774
864
  * path: '/users/:id',
775
- * input: struct.request({ path: struct.object({ id: struct.number() }) }),
776
- * output: [{ status: 200, body: struct.object({ id: struct.number(), name: struct.string() }) }],
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() }),
777
869
  * })
778
870
  * ```
779
871
  */
780
- export declare function defineRequest<TInput extends AnyStruct, const TOutput extends RequestOutputShape | undefined = undefined>(definition: RequestDefinition<TInput, TOutput>): RequestCommandBuilder<TInput, TOutput>;
781
- export declare function defineRequest<TInput extends AnyStruct | undefined = undefined, const TOutput extends RequestOutputShape | undefined = undefined>(definition: RequestDefinition<TInput, TOutput>): RequestCommandBuilder<TInput, TOutput>;
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>;
782
878
  /**
783
879
  * Run an `HttpCommand` against client config: validate input, send the request, parse the response.
784
880
  * Prefer `client.execute(command)` in application code; this is the underlying implementation.
785
881
  *
786
882
  * @param clientConfig - Resolved HTTP client configuration (endpoint, interceptors, handler).
787
- * @param command - Command from a `defineRequest` builder.
883
+ * @param command - Command from a `defineRequest` endpoint.
788
884
  * @param options - Per-request progress and cancellation options.
789
885
  * @returns An await-result tuple of success body or typed request error.
790
886
  */
791
- export declare function executeHttpCommand<TInput extends AnyStruct | undefined, TOutput extends RequestOutputShape | undefined>(clientConfig: HttpClientConfig, command: HttpCommand<TInput, TOutput>, options?: HttpExecuteOptions): Promise<HttpExecuteAwaitResult<TOutput>>;
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>>;
792
888
  //#endregion
793
889
  //#region src/sse/sse.d.ts
794
890
  /** Map of SSE event names to payload structs. Declare `__proto__` with a computed own key. */
@@ -814,44 +910,43 @@ interface EventStreamDefinitionBase<TEvents extends EventStructs = EventStructs>
814
910
  maxQueueSize: number;
815
911
  method?: string;
816
912
  operation?: string;
817
- /** Optional non-2xx handshake bodies; fills `error.data` when the open status is declared. */
818
- output?: RequestOutputShape;
819
913
  path: string;
820
914
  }
821
- type EventStreamDefinitionWithoutBuild<TInput extends AnyStruct | undefined = undefined, TEvents extends EventStructs = EventStructs> = EventStreamDefinitionBase<TEvents> & {
822
- build?: never;
823
- input?: TInput;
824
- };
825
- type EventStreamDefinitionWithBuild<TInput extends AnyStruct, TEvents extends EventStructs = EventStructs> = EventStreamDefinitionBase<TEvents> & {
826
- build: RequestBuildHandler<TInput, 'sse'>;
827
- input: TInput;
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;
828
920
  };
829
921
  /** Contract describing an SSE endpoint: path, events, and buffer/queue limits. */
830
- export type EventStreamDefinition<TInput extends AnyStruct | undefined = undefined, TEvents extends EventStructs = EventStructs> = EventStreamDefinitionWithoutBuild<TInput, TEvents> | (TInput extends AnyStruct ? EventStreamDefinitionWithBuild<TInput, TEvents> : never);
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
+ };
831
926
  /** Await-result tuple from opening an SSE stream via `client.execute`. */
832
- export type StreamAwaitResult<TEvent> = [error: null, stream: EventStreamHandle<TEvent>, open: EventStreamOpenInfo] | [error: RequestError<unknown>, stream: undefined, open: EventStreamOpenInfo | undefined];
833
- /** Executable SSE command produced by an `EventStreamCommandBuilder`. */
834
- export interface EventStreamCommand<TInput extends AnyStruct | undefined, TEvents extends EventStructs> extends BaseCommand<typeof EVENT_STREAM_COMMAND> {
835
- readonly endpoint: EventStreamEndpoint<TInput, TEvents>;
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>;
836
931
  readonly input: EndpointInput<TInput> | undefined;
837
932
  }
838
933
  /** Per-execute options for SSE commands (abort, timeout). */
839
934
  export type EventStreamExecuteOptions = UseCancellationConfig & {
840
935
  signal?: AbortSignal;
841
936
  };
842
- /** Builder function that creates `EventStreamCommand` values from endpoint input. */
843
- export type EventStreamCommandBuilder<TInput extends AnyStruct | undefined, TEvents extends EventStructs> = EndpointCommandBuilder<TInput, EventStreamCommand<TInput, TEvents>>;
844
- type EventStreamEndpoint<TInput extends AnyStruct | undefined = undefined, TEvents extends EventStructs = EventStructs> = EventStreamDefinition<TInput, TEvents> & {
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> & {
845
940
  readonly method: string;
846
941
  };
847
942
  /**
848
- * Declare a typed Server-Sent Events command builder.
943
+ * Declare a typed Server-Sent Events endpoint.
849
944
  *
850
- * Pass path, event structs, and buffer/queue limits. Call the returned builder
945
+ * Pass path, event structs, and buffer/queue limits. Call the returned endpoint
851
946
  * with input to get an `EventStreamCommand` for `client.execute`.
852
947
  *
853
948
  * @param definition - SSE contract (path, events, buffer limits, optional input).
854
- * @returns A builder that creates `EventStreamCommand` values from input.
949
+ * @returns An endpoint that creates `EventStreamCommand` values from input.
855
950
  *
856
951
  * @example
857
952
  * ```ts
@@ -863,27 +958,34 @@ type EventStreamEndpoint<TInput extends AnyStruct | undefined = undefined, TEven
863
958
  * })
864
959
  * ```
865
960
  */
866
- export declare function defineEventStream<TInput extends AnyStruct, TEvents extends EventStructs = EventStructs>(definition: EventStreamDefinitionWithBuild<TInput, TEvents>): EventStreamCommandBuilder<TInput, TEvents>;
867
- export declare function defineEventStream<TInput extends AnyStruct | undefined = undefined, TEvents extends EventStructs = EventStructs>(definition: EventStreamDefinitionWithoutBuild<TInput, TEvents>): EventStreamCommandBuilder<TInput, TEvents>;
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>;
868
966
  /**
869
967
  * Open an SSE command against the given client config.
870
968
  *
871
969
  * Prefer `client.execute(command)` in application code; this is the low-level entry used by the client.
872
970
  *
873
971
  * @param clientConfig - Resolved client configuration.
874
- * @param command - SSE command from an `EventStreamCommandBuilder`.
972
+ * @param command - SSE command from an `EventStreamEndpoint`.
875
973
  * @param options - Optional execute options (abort, timeout).
876
974
  * @returns Await-result tuple of `[error, stream, open]`.
877
975
  */
878
- export declare function executeEventStreamCommand<TInput extends AnyStruct | undefined, TEvents extends EventStructs>(clientConfig: ClientConfig, command: EventStreamCommand<TInput, TEvents>, options?: EventStreamExecuteOptions): Promise<StreamAwaitResult<EventStreamData<TEvents>>>;
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>>;
879
977
  //#endregion
880
978
  //#region src/client/command.d.ts
881
979
  declare const COMMAND_TYPE: unique symbol;
882
980
  declare const HTTP_COMMAND: unique symbol;
883
981
  declare const EVENT_STREAM_COMMAND: unique symbol;
884
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>;
885
986
  type CommandType = typeof HTTP_COMMAND | typeof EVENT_STREAM_COMMAND | typeof WEB_SOCKET_COMMAND;
886
987
  interface BaseCommand<TCommandType extends CommandType> {
988
+ readonly [COMMAND_EXECUTE]: CommandExecutor;
887
989
  readonly [COMMAND_TYPE]: TCommandType;
888
990
  }
889
991
  //#endregion
@@ -898,67 +1000,24 @@ interface WebSocketCloseSnapshot {
898
1000
  //#region src/web_socket/web_socket.d.ts
899
1001
  /** Lifecycle state of a managed WebSocket session. */
900
1002
  export type WebSocketState = 'aborted' | 'closed' | 'closing' | 'connecting' | 'error' | 'idle' | 'open' | 'reconnecting';
901
- /** Map of WebSocket message type names to payload structs. */
902
- export type SocketStructs = {
903
- [key: string]: AnyStruct;
904
- };
905
- export interface NormalizedWebSocketIncoming {
906
- data: unknown;
907
- type: string;
908
- }
909
- export type NormalizedWebSocketOutgoing = boolean | null | number | string | readonly unknown[] | {
910
- readonly [key: string]: unknown;
911
- };
912
- export type WebSocketIncomingNormalizer = (decoded: unknown) => NormalizedWebSocketIncoming | undefined;
913
- export type WebSocketOutgoingNormalizer = (type: string, encodedPayload: unknown) => NormalizedWebSocketOutgoing;
914
- type SimplifySocket<T> = { [K in keyof T]: T[K]; } & {};
915
- type NormalizeSocketMessage<TKey extends string, TPayload> = TPayload extends {
916
- [key: string]: unknown;
917
- } ? SimplifySocket<{
918
- type: TKey;
919
- } & TPayload> : {
920
- data: TPayload;
921
- type: TKey;
922
- };
923
- type SocketSendMessage<TKey extends string, TPayload> = TPayload extends {
924
- [key: string]: unknown;
925
- } ? SimplifySocket<{
926
- type: TKey;
927
- } & TPayload> | {
928
- data: TPayload;
929
- type: TKey;
930
- } : {
931
- data: TPayload;
932
- type: TKey;
933
- };
934
- type KnownIncomingSocketUnion<TIncoming extends SocketStructs> = { [K in keyof TIncoming & string as K extends 'default' ? never : K]: NormalizeSocketMessage<K, Infer<TIncoming[K]>>; } extends (infer O) ? O[keyof O] : never;
935
- type KnownOutgoingSocketUnion<TOutgoing extends SocketStructs> = { [K in keyof TOutgoing & string as K extends 'default' ? never : K]: SocketSendMessage<K, EndpointInput<TOutgoing[K]>>; } extends (infer O) ? O[keyof O] : never;
936
- type DefaultIncomingSocketUnion<TIncoming extends SocketStructs> = 'default' extends keyof TIncoming ? NormalizeSocketMessage<string, Infer<TIncoming['default']>> : never;
937
- /** Incoming message shape inferred from an incoming `SocketStructs` map. */
938
- export type WebSocketIncomingData<TIncoming extends SocketStructs> = [KnownIncomingSocketUnion<TIncoming> | DefaultIncomingSocketUnion<TIncoming>] extends [never] ? never : KnownIncomingSocketUnion<TIncoming> | DefaultIncomingSocketUnion<TIncoming>;
939
- /** Outgoing message shape inferred from an outgoing `SocketStructs` map. */
940
- export type WebSocketOutgoingData<TOutgoing extends SocketStructs | undefined> = TOutgoing extends SocketStructs ? [KnownOutgoingSocketUnion<TOutgoing>] extends [never] ? never : KnownOutgoingSocketUnion<TOutgoing> : never;
941
- interface WebSocketDefinitionBase<TIncoming extends SocketStructs = SocketStructs, TOutgoing extends SocketStructs | undefined = undefined> {
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
+ export type WebSocketOutgoingData<TOutgoing extends AnyStruct | undefined> = TOutgoing extends AnyStruct ? StructInput<TOutgoing> : never;
1007
+ interface WebSocketDefinitionBase<TIncoming extends AnyStruct = AnyStruct, TOutgoing extends AnyStruct | undefined = undefined> {
942
1008
  incoming: TIncoming;
943
1009
  maxIncomingQueueSize: number;
944
1010
  maxOutgoingQueueSize?: number;
945
- normalizeIncoming?: WebSocketIncomingNormalizer;
946
- normalizeOutgoing?: WebSocketOutgoingNormalizer;
947
1011
  operation?: string;
948
1012
  outgoing?: TOutgoing;
949
1013
  path: string;
950
1014
  protocols?: readonly string[];
951
1015
  }
952
- type WebSocketDefinitionWithoutBuild<TInput extends AnyStruct | undefined = undefined, TIncoming extends SocketStructs = SocketStructs, TOutgoing extends SocketStructs | undefined = undefined> = WebSocketDefinitionBase<TIncoming, TOutgoing> & {
953
- build?: never;
1016
+ /** Contract describing a WebSocket endpoint: path, message structs, and queue limits. */
1017
+ export type WebSocketDefinition<TInput extends AnyStruct | undefined = undefined, TIncoming extends AnyStruct = AnyStruct, TOutgoing extends AnyStruct | undefined = undefined> = WebSocketDefinitionBase<TIncoming, TOutgoing> & {
1018
+ builder?: OutboundBuilder;
954
1019
  input?: TInput;
955
1020
  };
956
- type WebSocketDefinitionWithBuild<TInput extends AnyStruct, TIncoming extends SocketStructs = SocketStructs, TOutgoing extends SocketStructs | undefined = undefined> = WebSocketDefinitionBase<TIncoming, TOutgoing> & {
957
- build: RequestBuildHandler<TInput, 'webSocket'>;
958
- input: TInput;
959
- };
960
- /** Contract describing a WebSocket endpoint: path, message structs, and queue limits. */
961
- export type WebSocketDefinition<TInput extends AnyStruct | undefined = undefined, TIncoming extends SocketStructs = SocketStructs, TOutgoing extends SocketStructs | undefined = undefined> = WebSocketDefinitionWithoutBuild<TInput, TIncoming, TOutgoing> | (TInput extends AnyStruct ? WebSocketDefinitionWithBuild<TInput, TIncoming, TOutgoing> : never);
962
1021
  /** Connection metadata for the current WebSocket generation. */
963
1022
  export interface WebSocketConnectionInfo {
964
1023
  extensions?: string;
@@ -999,7 +1058,7 @@ export interface WebSocketSession<TIncoming = unknown, TOutgoing = never> extend
999
1058
  send(message: TOutgoing): void;
1000
1059
  }
1001
1060
  /** Await-result tuple from opening a WebSocket via `client.execute`. */
1002
- export type SocketAwaitResult<TIncoming, TOutgoing = never> = [error: null, socket: WebSocketSession<TIncoming, TOutgoing>, connection: WebSocketConnectionInfo] | [error: RequestError<unknown>, socket: undefined, connection: WebSocketConnectionInfo | undefined];
1061
+ export type SocketAwaitResult<TIncoming, TOutgoing = never> = [error: null, socket: WebSocketSession<TIncoming, TOutgoing>, connection: WebSocketConnectionInfo] | [error: Fault<undefined>, socket: undefined, connection: WebSocketConnectionInfo | undefined];
1003
1062
  interface UseWebSocketBaseConfig<TIncoming = unknown, TOutgoing = unknown> {
1004
1063
  beforeConnect?: ClientWebSocketOptions['beforeConnect'];
1005
1064
  heartbeat?: WebSocketHeartbeatConfig<TIncoming, TOutgoing>;
@@ -1012,14 +1071,13 @@ export type UseWebSocketConfig<TIncoming = unknown, TOutgoing = unknown> = UseWe
1012
1071
  export type WebSocketExecuteOptions<TIncoming = unknown, TOutgoing = unknown> = UseWebSocketConfig<TIncoming, TOutgoing> & {
1013
1072
  signal?: AbortSignal;
1014
1073
  };
1015
- /** Executable WebSocket command produced by a `WebSocketCommandBuilder`. */
1016
- export interface WebSocketCommand<TInput extends AnyStruct | undefined, TIncoming extends SocketStructs, TOutgoing extends SocketStructs | undefined> extends BaseCommand<typeof WEB_SOCKET_COMMAND> {
1017
- readonly endpoint: WebSocketEndpoint<TInput, TIncoming, TOutgoing>;
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>;
1018
1077
  readonly input: EndpointInput<TInput> | undefined;
1019
1078
  }
1020
- /** Builder function that creates `WebSocketCommand` values from endpoint input. */
1021
- export type WebSocketCommandBuilder<TInput extends AnyStruct | undefined, TIncoming extends SocketStructs, TOutgoing extends SocketStructs | undefined> = EndpointCommandBuilder<TInput, WebSocketCommand<TInput, TIncoming, TOutgoing>>;
1022
- type WebSocketEndpoint<TInput extends AnyStruct | undefined = undefined, TIncoming extends SocketStructs = SocketStructs, TOutgoing extends SocketStructs | undefined = undefined> = WebSocketDefinition<TInput, TIncoming, TOutgoing>;
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>>;
1023
1081
  /** Reason recorded when the session is closed via `session.close()`. */
1024
1082
  export type ManualSocketCloseReason = {
1025
1083
  code?: number;
@@ -1030,41 +1088,43 @@ export type ManualSocketCloseReason = {
1030
1088
  export interface WebSocketHeartbeatConfig<TIncoming = unknown, TOutgoing = unknown> {
1031
1089
  intervalMs: number;
1032
1090
  isAck?: (message: TIncoming) => boolean;
1033
- message?: <T = TOutgoing>() => T | unknown;
1091
+ message?: () => TOutgoing;
1034
1092
  timeoutMs?: number;
1035
1093
  }
1036
1094
  /**
1037
- * Declare a typed WebSocket command builder.
1095
+ * Declare a typed WebSocket endpoint.
1038
1096
  *
1039
1097
  * Pass path, incoming (and optional outgoing) message structs, and queue limits.
1040
- * Call the returned builder with input to get a `WebSocketCommand` for `client.execute`.
1098
+ * Call the returned endpoint with input to get a `WebSocketCommand` for `client.execute`.
1041
1099
  *
1042
1100
  * @param definition - WebSocket contract (path, structs, queue limits, optional input).
1043
- * @returns A builder that creates `WebSocketCommand` values from input.
1101
+ * @returns An endpoint that creates `WebSocketCommand` values from input.
1044
1102
  *
1045
1103
  * @example
1046
1104
  * ```ts
1047
1105
  * const useChat = defineWebSocket({
1048
1106
  * maxIncomingQueueSize: 16,
1049
1107
  * path: '/ws/chat',
1050
- * incoming: { message: struct.object({ text: struct.string() }) },
1051
- * outgoing: { message: struct.object({ text: struct.string() }) },
1108
+ * incoming: struct.object({ text: struct.string(), type: struct.literal('message') }),
1109
+ * outgoing: struct.object({ text: struct.string(), type: struct.literal('message') }),
1052
1110
  * })
1053
1111
  * ```
1054
1112
  */
1055
- export declare function defineWebSocket<TInput extends AnyStruct, TIncoming extends SocketStructs = SocketStructs, TOutgoing extends SocketStructs | undefined = undefined>(definition: WebSocketDefinitionWithBuild<TInput, TIncoming, TOutgoing>): WebSocketCommandBuilder<TInput, TIncoming, TOutgoing>;
1056
- export declare function defineWebSocket<TInput extends AnyStruct | undefined = undefined, TIncoming extends SocketStructs = SocketStructs, TOutgoing extends SocketStructs | undefined = undefined>(definition: WebSocketDefinitionWithoutBuild<TInput, TIncoming, TOutgoing>): WebSocketCommandBuilder<TInput, TIncoming, TOutgoing>;
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'> & {
1114
+ input?: TInput;
1115
+ path: P;
1116
+ } & BuilderField<NoInfer<TInput>, WebSocketRequestShape<NoInfer<P>>>): WebSocketEndpoint<TInput, TIncoming, TOutgoing>;
1057
1117
  /**
1058
1118
  * Open a WebSocket command against the given client config.
1059
1119
  *
1060
1120
  * Prefer `client.execute(command)` in application code; this is the low-level entry used by the client.
1061
1121
  *
1062
1122
  * @param clientConfig - Resolved client configuration.
1063
- * @param command - WebSocket command from a `WebSocketCommandBuilder`.
1123
+ * @param command - WebSocket command from a `WebSocketEndpoint`.
1064
1124
  * @param options - Optional execute options (heartbeat, reconnect, abort).
1065
1125
  * @returns Await-result tuple of `[error, socket, connection]`.
1066
1126
  */
1067
- export declare function executeWebSocketCommand<TInput extends AnyStruct | undefined, TIncoming extends SocketStructs, TOutgoing extends SocketStructs | undefined>(clientConfig: ClientConfig, command: WebSocketCommand<TInput, TIncoming, TOutgoing>, options?: WebSocketExecuteOptions<WebSocketIncomingData<TIncoming>, WebSocketOutgoingData<TOutgoing>>): Promise<SocketAwaitResult<WebSocketIncomingData<TIncoming>, WebSocketOutgoingData<TOutgoing>>>;
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>>>;
1068
1128
  /** Outcome of a single WebSocket connect attempt used by reconnect logic. */
1069
1129
  export type SocketLifecycleOutcome = {
1070
1130
  closeInfo: WebSocketCloseSnapshot;
@@ -1175,13 +1235,16 @@ type Interceptor = HttpInterceptor | SSEInterceptor | WebSocketInterceptor;
1175
1235
  //#endregion
1176
1236
  //#region src/client/config.d.ts
1177
1237
  /**
1178
- * Serializes query parameters for the request URL.
1238
+ * Serializes an encoded query object into a query string.
1239
+ *
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`.
1179
1243
  *
1180
- * @param params - Normalized `URLSearchParams` built from the request.
1181
- * @param rawParams - Optional original param object before normalization.
1182
- * @returns The query string (without a leading `?`).
1244
+ * @param query - Encoded query object.
1245
+ * @returns The query string without a leading `?`.
1183
1246
  */
1184
- type QueryParamsSerializer = (params: URLSearchParams, rawParams?: {
1247
+ type QuerySerializer = (query: {
1185
1248
  [key: string]: unknown;
1186
1249
  }) => string;
1187
1250
  /**
@@ -1247,14 +1310,6 @@ interface ClientWebSocketOptions {
1247
1310
  wasClean?: boolean;
1248
1311
  }) => boolean;
1249
1312
  };
1250
- /** Called when an incoming message has no matching struct (unknown `type`). Struct/JSON failures still use `onRuntimeError`. */
1251
- onInvalidEvent?: (context: {
1252
- reason: 'missing-struct';
1253
- message: {
1254
- type: string;
1255
- data: unknown;
1256
- };
1257
- }) => unknown;
1258
1313
  }
1259
1314
  /** Optional SSE settings applied when creating a client. */
1260
1315
  interface ClientSSEOptions {
@@ -1316,8 +1371,8 @@ interface HttpClientOptions {
1316
1371
  };
1317
1372
  /** Interceptors applied in registration order. */
1318
1373
  interceptors?: Interceptor[];
1319
- /** Custom query-string serializer. */
1320
- queryParamsSerializer?: QueryParamsSerializer;
1374
+ /** Serializes encoded query objects. Omit to use `URLSearchParams`. */
1375
+ querySerializer?: QuerySerializer;
1321
1376
  /** Default HTTP execute timeout in milliseconds (SSE/WebSocket ignore this). */
1322
1377
  timeout?: number;
1323
1378
  /** XSRF cookie/header mapping and optional token provider. */
@@ -1353,8 +1408,8 @@ interface HttpClientConfig {
1353
1408
  };
1354
1409
  /** Interceptors applied in registration order. */
1355
1410
  interceptors: Interceptor[];
1356
- /** Query-string serializer used for requests. */
1357
- queryParamsSerializer: QueryParamsSerializer;
1411
+ /** Serializes encoded query objects. Omit to use `URLSearchParams`. */
1412
+ querySerializer?: QuerySerializer;
1358
1413
  /** Default HTTP execute timeout in milliseconds (SSE/WebSocket ignore this). */
1359
1414
  timeout?: number;
1360
1415
  /** Resolved XSRF settings when enabled. */
@@ -1392,7 +1447,7 @@ export declare function withEndpoint(endpoint: string): ClientOption;
1392
1447
  /**
1393
1448
  * Sets static default headers for HTTP/SSE requests built by this client.
1394
1449
  *
1395
- * Command `struct.request({ headers })` values override the same names. HMAC and
1450
+ * Builder `headers` override the same names. HMAC and
1396
1451
  * dynamic Bearer tokens still belong in interceptors (they need the serialized body
1397
1452
  * or a refreshed credential).
1398
1453
  *
@@ -1420,12 +1475,16 @@ export declare function withTimeout(ms: number): ClientOption;
1420
1475
  */
1421
1476
  export declare function withInterceptors(...interceptors: Interceptor[]): ClientOption;
1422
1477
  /**
1423
- * Replaces the serializer used when encoding query parameters.
1478
+ * Sets the query serializer for HTTP, SSE, and WebSocket requests.
1479
+ *
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.
1424
1483
  *
1425
- * @param serializer - Function that turns params into a query string.
1426
- * @returns An option that sets `config.queryParamsSerializer`.
1484
+ * @param serializer - Function that turns an encoded query object into a string.
1485
+ * @returns An option that sets `config.querySerializer`.
1427
1486
  */
1428
- export declare function withQueryParamsSerializer(serializer: QueryParamsSerializer): ClientOption;
1487
+ export declare function withQuerySerializer(serializer: QuerySerializer): ClientOption;
1429
1488
  /**
1430
1489
  * Overrides the Fetch-compatible function used for HTTP requests.
1431
1490
  *
@@ -1482,16 +1541,6 @@ export declare function withWebSocketHeartbeat(options: ClientWebSocketOptions['
1482
1541
  * @returns An option that sets `config.webSocket.reconnect`.
1483
1542
  */
1484
1543
  export declare function withWebSocketReconnect(options: ClientWebSocketOptions['reconnect']): ClientOption;
1485
- /**
1486
- * Handles WebSocket messages whose `type` has no matching incoming struct.
1487
- *
1488
- * JSON/Struct decode failures still go to `session.onRuntimeError`. Unknown types
1489
- * do not tear down the session.
1490
- *
1491
- * @param handler - Observer callback; failures are isolated.
1492
- * @returns An option that sets `config.webSocket.onInvalidEvent`.
1493
- */
1494
- export declare function withWebSocketOnInvalidEvent(handler: ClientWebSocketOptions['onInvalidEvent']): ClientOption;
1495
1544
  /**
1496
1545
  * Handles SSE events that lack a matching struct or fail validation.
1497
1546
  *
@@ -1531,11 +1580,11 @@ declare const CLIENT: unique symbol;
1531
1580
  type Client = {
1532
1581
  readonly [CLIENT]: ClientConfig;
1533
1582
  /** Open an SSE command and return stream open/await results. */
1534
- execute<TInput extends AnyStruct | undefined, TEvents extends EventStructs>(command: EventStreamCommand<TInput, TEvents>, options?: EventStreamExecuteOptions): Promise<StreamAwaitResult<EventStreamData<TEvents>>>;
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>>;
1535
1584
  /** Open a WebSocket command and return session await results. */
1536
- execute<TInput extends AnyStruct | undefined, TIncoming extends SocketStructs, TOutgoing extends SocketStructs | undefined>(command: WebSocketCommand<TInput, TIncoming, TOutgoing>, options?: WebSocketExecuteOptions<WebSocketIncomingData<TIncoming>, WebSocketOutgoingData<TOutgoing>>): Promise<SocketAwaitResult<WebSocketIncomingData<TIncoming>, WebSocketOutgoingData<TOutgoing>>>;
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>>>;
1537
1586
  /** Run an HTTP command and return an await-result tuple. */
1538
- execute<TInput extends AnyStruct | undefined, TOutput extends RequestOutputShape | undefined>(command: HttpCommand<TInput, TOutput>, options?: HttpExecuteOptions): ReturnType<typeof executeHttpCommand<TInput, TOutput>>;
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>>;
1539
1588
  };
1540
1589
  /**
1541
1590
  * Create a Defjs client from option helpers.
@@ -1559,8 +1608,11 @@ export declare function createClient(...options: ClientOption[]): Client;
1559
1608
  * Used by the client unless `withHTTPHandle` replaces it; also usable in tests and custom handlers.
1560
1609
  *
1561
1610
  * @param httpRequest - Normalized request (URL, headers, body, abort, progress hooks).
1562
- * @param fetchImpl - Fetch implementation; defaults to global `fetch`.
1563
- * @returns Parsed `HttpResponse`, or a status-0 response when the network/abort fails.
1611
+ * @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
1613
+ * `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.
1564
1616
  */
1565
1617
  export declare function fetchHandler(httpRequest: HttpRequest, fetchImpl?: FetchHandle): Promise<HttpResponse<unknown>>;
1566
1618
  //#endregion
@@ -1595,4 +1647,4 @@ export declare function basicAuthHttpInterceptor(fn: () => BasicCredential, opti
1595
1647
  */
1596
1648
  export declare function basicAuthSSEInterceptor(fn: () => BasicCredential, options?: BasicAuthInterceptorOptions): SSEInterceptor;
1597
1649
  //#endregion
1598
- export type { AnyStruct, BasicAuthInterceptorOptions, Client, ClientConfig, ClientOption, ClientOptions, ClientSSEConfig, ClientSSEOptions, ClientWebSocketOptions, ErrorMap, EventStreamCloseInfo, EventStreamErrorCode, EventStreamHandle, EventStreamOpenInfo, FetchHandle, FlattenedStructError, FormattedStructError, HttpInterceptor, HttpInterceptorNext, HttpProgressEvent, HttpProgressFn, HttpRequest, HttpResponse, HttpResponseType, Infer, Interceptor, InterceptorFn, MakeResponseOptions, ObjectStruct, ParseResult, QueryParamsSerializer, RequestOutputShape, RequestStruct, ResponseGroupItem, SSEHandler, SSEInterceptor, SSEInterceptorFn, Struct, StructInput, StructIssue, StructLike, StructMethods, WebSocketHandle, WebSocketHandleConstructor, WebSocketHandler, WebSocketInterceptor, WebSocketInterceptorFn, WebSocketSessionLike };
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 };