typespec-http-zod 0.26.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -177,12 +177,12 @@ then a range such as `4XX`, then `default`.
177
177
 
178
178
  ## What it emits
179
179
 
180
- | file | contents | emitted when |
181
- | ---------------------- | --------------------------------------------------------------------------------------------------------- | ----------------------------- |
182
- | `schemas.gen.ts` | component schemas, per-operation path, query, header, body and response validators, and the response arms | always |
183
- | `requests.gen.ts` | framework-free TypeScript types, with no imports | `contracts-output-dir` is set |
184
- | `vocabularies.gen.ts` | each enum's members as a runtime tuple | `contracts-output-dir` is set |
185
- | `wire-contract.gen.ts` | assertions pairing the emitted Zod against those types | that and `contracts-package` |
180
+ | file | contents | emitted when |
181
+ | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- |
182
+ | `schemas.gen.ts` | component schemas, per-operation path, query, header, body and response validators, and the response arms | always |
183
+ | `requests.gen.ts` | framework-free TypeScript types, with no imports: the models, and every operation's input, success bodies and result (`WireInputs`, `WireOutputs`, `WireResults`) | `contracts-output-dir` is set |
184
+ | `vocabularies.gen.ts` | each enum's members as a runtime tuple | `contracts-output-dir` is set |
185
+ | `wire-contract.gen.ts` | assertions pairing the emitted Zod against those types | that and `contracts-package` |
186
186
 
187
187
  `wire-contract.gen.ts` is not imported by any other file. The file contains type-level assertions
188
188
  pairing each generated validator with its contract type, so a mismatch between the two is a compile
package/dist/src/api.d.ts CHANGED
@@ -1,8 +1,11 @@
1
1
  import { type EmitContext, type Program, type Type } from "@typespec/compiler";
2
2
  import { type HttpOperation, type HttpService } from "@typespec/http";
3
3
  import { SchemaRegistry } from "./registry.js";
4
+ import { type StatusKey } from "./results.js";
4
5
  import { type EmittedPathSegment } from "./uri-template.js";
5
6
  export type { EmittedPathSegment } from "./uri-template.js";
7
+ export { CONTENTLESS_STATUS_CODES, STATUS_CODES } from "./statuses.js";
8
+ export { type BodyServing, isJsonMediaType, isMediaRange, isTextualMediaType, mediaTypeEssenceOf, mediaTypeTypeOf, type ResponseFacts, type ResultMember, resultMembersOf, type StatusKey, statusCodesOf, } from "./results.js";
6
9
  /**
7
10
  * `typespec-http-zod` - Zod validators and framework-free contract types from a TypeSpec HTTP
8
11
  * service, agreeing with the OpenAPI document `@typespec/openapi3` publishes from the same source.
@@ -64,13 +67,6 @@ export declare function successStatusOf(operation: HttpOperation): number | unde
64
67
  * asserts the two stay in step.
65
68
  */
66
69
  export declare function successStatusesOf(operation: HttpOperation): number[];
67
- /**
68
- * A status as the DOCUMENT keys it: an exact code, a `4XX` bucket, or the catch-all.
69
- *
70
- * `"default"` is kept as itself rather than mapped to a number. The document means "any status not
71
- * listed above", and inventing one would be answering a question the contract left open.
72
- */
73
- export type StatusKey = number | "default" | `${1 | 2 | 3 | 4 | 5}XX`;
74
70
  /**
75
71
  * A header one response declares, as a handler has to supply it.
76
72
  *
@@ -83,6 +79,15 @@ export interface EmittedResponseHeader {
83
79
  readonly name: string;
84
80
  /** The TypeScript type of the value, so a server emitter can put it in a signature. */
85
81
  readonly type: string;
82
+ /**
83
+ * The Zod expression for the value, so a server can check what it is about to send.
84
+ *
85
+ * The TYPE above constrains what a handler may return at compile time; this constrains what
86
+ * actually goes on the wire, which is the same split `schema` and the contract types already make
87
+ * for a body. A value that crossed a boundary the type system cannot see into - a service binding,
88
+ * a cast - is exactly what it catches.
89
+ */
90
+ readonly schema: string;
86
91
  /** The document's `required: false`. */
87
92
  readonly optional: boolean;
88
93
  }
@@ -298,6 +303,8 @@ export interface EmittedRoute {
298
303
  * request. It read as "the parameters" and was the field an adopter reached for first.
299
304
  */
300
305
  readonly pathSchema: string | undefined;
306
+ /** Cookie parameters, as one object schema, or `undefined` where the operation declares none. */
307
+ readonly cookieSchema: string | undefined;
301
308
  readonly querySchema: string | undefined;
302
309
  /**
303
310
  * The query OBJECT itself, which differs from `querySchema` only where a form-exploded record or
@@ -317,10 +324,29 @@ export interface EmittedRoute {
317
324
  /**
318
325
  * The input property that receives the UNPARSED body, when the operation declares one.
319
326
  *
320
- * A `bytes` body means the bytes themselves are the contract: where a signature covers exactly
321
- * what arrived, parsing and re-serialising would verify a different string than the sender signed.
327
+ * Three bodies are handed over unparsed. A `bytes` body, because the bytes themselves are the
328
+ * contract: where a signature covers exactly what arrived, parsing and re-serialising would verify
329
+ * a different string than the sender signed. An `Http.File` body, whose contents are the body. And
330
+ * a body declared only under media types nothing can parse into it, such as a model as
331
+ * `application/xml`, which is the text that arrived, or as `application/octet-stream`, which is
332
+ * the bytes. {@link rawBodyBinary} says which are bytes.
322
333
  */
323
334
  readonly rawBodyProperty: string | undefined;
335
+ /**
336
+ * Whether the raw body is BYTES - handed over as the unread stream - rather than the text a
337
+ * signature covers. A `bytes` body is bytes under a binary media type and text under a JSON one;
338
+ * an `Http.File` body is bytes whatever its media type, which is why a server cannot read this off
339
+ * {@link requestContentTypes}: a file declared `application/json` looks like a JSON body there.
340
+ * `false` where there is no raw body.
341
+ */
342
+ readonly rawBodyBinary: boolean;
343
+ /**
344
+ * **The raw body is declared with a schema nothing checks it against**: it is declared only under
345
+ * media types nothing can parse into it, such as a model as `application/xml`. A `bytes` or
346
+ * `Http.File` body declares nothing to check, so it is not this. The request-side twin of
347
+ * {@link ResultMember.unvalidated}.
348
+ */
349
+ readonly rawBodyUnvalidated: boolean;
324
350
  /**
325
351
  * The input property a PARSED body arrives under, where it must not be spread into the parameters.
326
352
  *
@@ -416,6 +442,22 @@ export interface RouteSchemaNames {
416
442
  */
417
443
  readonly queryFields: string | undefined;
418
444
  readonly header: string | undefined;
445
+ /**
446
+ * **The `accept` the negotiation selects this operation by**, on a route several operations
447
+ * share. `header` leaves it out there, because it chooses the operation rather than constraining
448
+ * one, and the operation's input still declares it: the one value that reaches this operation is
449
+ * its own literal, so a handler receives exactly that. `undefined` wherever `header` validates
450
+ * `accept` itself, or the operation declares no literal one.
451
+ */
452
+ readonly selectedAccept: {
453
+ readonly name: string;
454
+ readonly value: string;
455
+ } | undefined;
456
+ /**
457
+ * Cookie parameters. **Optional on the interface, so a server emitter that does not read it yet
458
+ * still compiles** - this arrived after the other four and two emitters consume this type.
459
+ */
460
+ readonly cookie?: string | undefined;
419
461
  readonly body: string | undefined;
420
462
  /**
421
463
  * The identifier each response's body is declared under, parallel to `EmittedRoute.responses` and
@@ -428,6 +470,11 @@ export interface RouteSchemaNames {
428
470
  readonly arms: readonly {
429
471
  readonly status: StatusKey;
430
472
  readonly schema: string | undefined;
473
+ /**
474
+ * The identifier holding this status's declared response headers, where it declares any.
475
+ * **Optional on the interface**, so a server emitter that does not check them yet still builds.
476
+ */
477
+ readonly headers?: string | undefined;
431
478
  }[];
432
479
  /**
433
480
  * The `readonly ResponseArm[]` const - what the operation may answer with, and with which body.
@@ -454,6 +501,13 @@ export interface EmittedService {
454
501
  readonly routes: readonly EmittedRoute[];
455
502
  /** The identifiers every per-operation validator was declared under, keyed by operation id. */
456
503
  readonly schemaNames: ReadonlyMap<string, RouteSchemaNames>;
504
+ /**
505
+ * **The operation each route was collected from, keyed by operation id**, so a wrapping emitter
506
+ * reading its doc comment, tags or diagnostic target reads the right one. Verb and path do not
507
+ * identify an operation: `@sharedRoute` puts several on one, and a lookup by them answers the
508
+ * same operation for each - its description became every member's, and its tags selected them.
509
+ */
510
+ readonly operations: ReadonlyMap<string, HttpOperation>;
457
511
  /** Where this service's artefacts were written - a server emitter writes its own beside them. */
458
512
  readonly outputDir: string;
459
513
  /** Already resolved through the per-service overrides, so a caller re-reads nothing. */