typespec-http-zod 0.32.0 → 0.33.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/dist/src/api.d.ts +37 -0
- package/dist/src/api.js +1 -0
- package/dist/src/registry.d.ts +10 -0
- package/dist/src/registry.js +49 -0
- package/package.json +1 -1
package/dist/src/api.d.ts
CHANGED
|
@@ -575,6 +575,43 @@ export interface EmittedService {
|
|
|
575
575
|
readonly routes: readonly EmittedRoute[];
|
|
576
576
|
/** The identifiers every per-operation validator was declared under, keyed by operation id. */
|
|
577
577
|
readonly schemaNames: ReadonlyMap<string, RouteSchemaNames>;
|
|
578
|
+
/**
|
|
579
|
+
* **The identifier each declared type's validator is exported under in `schemas.gen.ts`**, keyed
|
|
580
|
+
* by the type: a model, union or enum the document publishes as a component of its own. A scalar
|
|
581
|
+
* is validated inline wherever it is used and has no entry.
|
|
582
|
+
*
|
|
583
|
+
* **Keyed by the types this service was emitted from**, which are the ones `operations` holds: a
|
|
584
|
+
* walk from an operation's response bodies, through each base's `derivedModels`, reaches the keys
|
|
585
|
+
* themselves. For a versioned service those are the types projected to its latest version, not
|
|
586
|
+
* the types as declared, which hold every version at once.
|
|
587
|
+
*
|
|
588
|
+
* **A body is keyed by the model it is, its effective payload type.** Where visibility reshapes a
|
|
589
|
+
* model, `@typespec/http` hands its body over as a copy - `Widget` returned beside a create-only
|
|
590
|
+
* property is `{ ...Widget }` - which is not a key and has no `derivedModels`. openapi3 resolves it
|
|
591
|
+
* to `Widget` with `getEffectivePayloadType`, and so does a lookup:
|
|
592
|
+
* `createMetadataInfo(program, { canonicalVisibility: Visibility.Read })
|
|
593
|
+
* .getEffectivePayloadType(body.type, Visibility.Read)`.
|
|
594
|
+
*
|
|
595
|
+
* **The declaration under the type's own component name**, which is the one a response reads
|
|
596
|
+
* wherever a response reads the type: `Widget` -> `widgetSchema`, beside a create view
|
|
597
|
+
* `widgetCreateSchema` that is not in this map. `@friendlyName` names it and a reserved word takes
|
|
598
|
+
* the module's `_`, as the module declares it: `@friendlyName("Canine") model Hound` ->
|
|
599
|
+
* `canineSchema`, `` model `default` `` -> `default_Schema`. A multipart body's model is here,
|
|
600
|
+
* validated as the form the document publishes. A type the document publishes only under views of
|
|
601
|
+
* it, sent in create and update bodies and never returned, has none.
|
|
602
|
+
*
|
|
603
|
+
* **Every subtype of a discriminated base is here**, nested bases included and whether or not an
|
|
604
|
+
* operation names it, so a wrapping emitter can tell which subtype a value is by the subtype's
|
|
605
|
+
* own validator: the base's parsed output does not say which arm of its dispatch matched.
|
|
606
|
+
*
|
|
607
|
+
* **A declaration that validates nothing has no entry** - `duplicate-declaration`,
|
|
608
|
+
* `split-visibility` and every other refusal of a whole type - so a lookup answers `undefined`
|
|
609
|
+
* rather than a validator that refuses every value.
|
|
610
|
+
*
|
|
611
|
+
* Published so that an emitter building on this one imports a declared type's validator by name
|
|
612
|
+
* rather than agreeing with this package about how the name is spelled.
|
|
613
|
+
*/
|
|
614
|
+
readonly typeSchemaNames: ReadonlyMap<Type, string>;
|
|
578
615
|
/**
|
|
579
616
|
* **The operation each route was collected from, keyed by operation id**, so a wrapping emitter
|
|
580
617
|
* reading its doc comment, tags or diagnostic target reads the right one. Verb and path do not
|
package/dist/src/api.js
CHANGED
package/dist/src/registry.d.ts
CHANGED
|
@@ -119,6 +119,16 @@ export declare class SchemaRegistry {
|
|
|
119
119
|
declareNamed(name: string, build: () => string, site: Type): string;
|
|
120
120
|
/** Every declaration, dependencies first. */
|
|
121
121
|
declarations(): readonly Declaration[];
|
|
122
|
+
/**
|
|
123
|
+
* **The identifier each type is declared under by its own component name** - see
|
|
124
|
+
* `EmittedService.typeSchemaNames`. That is the declaration a response reads, wherever one does:
|
|
125
|
+
* `Read` is never suffixed, so a view of the type (`WidgetCreate`) and a 3.0 request's reading of
|
|
126
|
+
* it are other declarations, and neither is the one a wrapping emitter resolves a value by. A
|
|
127
|
+
* multipart body is its model's, which the document publishes under the model's name as the form it
|
|
128
|
+
* is; an envelope is named for its union and variant, and is no type's own. A declaration that
|
|
129
|
+
* validates nothing is left out rather than published as a validator.
|
|
130
|
+
*/
|
|
131
|
+
typeIdentifiers(): ReadonlyMap<Type, string>;
|
|
122
132
|
/** The names of the declarations that validate nothing, refused here or in the walk beneath. */
|
|
123
133
|
refusedNames(): ReadonlySet<string>;
|
|
124
134
|
}
|
package/dist/src/registry.js
CHANGED
|
@@ -495,6 +495,8 @@ export class SchemaRegistry {
|
|
|
495
495
|
#withheld = new Set();
|
|
496
496
|
/** Every type name a refused identifier stood for: `Foo` and `foo` are both `fooSchema`. */
|
|
497
497
|
#refusedTypeNames = new Set();
|
|
498
|
+
/** Each type declared by another type's declaration, the two rendered alike under one name. */
|
|
499
|
+
#alike = [];
|
|
498
500
|
/**
|
|
499
501
|
* Inlined types currently being walked, by identity, against the number of declarations that were
|
|
500
502
|
* in flight when each was entered.
|
|
@@ -632,6 +634,9 @@ export class SchemaRegistry {
|
|
|
632
634
|
*/
|
|
633
635
|
if (twin !== undefined && twin.source === declaration.source) {
|
|
634
636
|
this.#declarations.set(key, twin);
|
|
637
|
+
// Another type of the name, rendered alike, is validated by it too - see `typeIdentifiers`.
|
|
638
|
+
if (twin.type !== type)
|
|
639
|
+
this.#alike.push({ type, identifier });
|
|
635
640
|
return twin.identifier;
|
|
636
641
|
}
|
|
637
642
|
if (twin !== undefined) {
|
|
@@ -1057,6 +1062,50 @@ export class SchemaRegistry {
|
|
|
1057
1062
|
declarations() {
|
|
1058
1063
|
return this.#order;
|
|
1059
1064
|
}
|
|
1065
|
+
/**
|
|
1066
|
+
* **The identifier each type is declared under by its own component name** - see
|
|
1067
|
+
* `EmittedService.typeSchemaNames`. That is the declaration a response reads, wherever one does:
|
|
1068
|
+
* `Read` is never suffixed, so a view of the type (`WidgetCreate`) and a 3.0 request's reading of
|
|
1069
|
+
* it are other declarations, and neither is the one a wrapping emitter resolves a value by. A
|
|
1070
|
+
* multipart body is its model's, which the document publishes under the model's name as the form it
|
|
1071
|
+
* is; an envelope is named for its union and variant, and is no type's own. A declaration that
|
|
1072
|
+
* validates nothing is left out rather than published as a validator.
|
|
1073
|
+
*/
|
|
1074
|
+
typeIdentifiers() {
|
|
1075
|
+
const found = new Map();
|
|
1076
|
+
const consider = (type, declaration) => {
|
|
1077
|
+
if (this.#validatesNothing(declaration))
|
|
1078
|
+
return;
|
|
1079
|
+
// A view, and a 3.0 request's reading, are named with a suffix - see `nameAt`.
|
|
1080
|
+
const bare = declaredNameOf(this.#program, type);
|
|
1081
|
+
if (bare === undefined || declaration.typeName !== safeDeclarationName(bare))
|
|
1082
|
+
return;
|
|
1083
|
+
found.set(type, declaration.identifier);
|
|
1084
|
+
};
|
|
1085
|
+
for (const declaration of this.#order) {
|
|
1086
|
+
const { type, synthetic } = declaration;
|
|
1087
|
+
if (type !== undefined)
|
|
1088
|
+
consider(type, declaration);
|
|
1089
|
+
else if (synthetic?.kind === "multipart")
|
|
1090
|
+
consider(synthetic.site, declaration);
|
|
1091
|
+
}
|
|
1092
|
+
// By identifier, because the declaration a type shares can be rendered again since.
|
|
1093
|
+
for (const { type, identifier } of this.#alike) {
|
|
1094
|
+
const declaration = this.#order.find((each) => each.identifier === identifier);
|
|
1095
|
+
if (declaration !== undefined)
|
|
1096
|
+
consider(type, declaration);
|
|
1097
|
+
}
|
|
1098
|
+
return found;
|
|
1099
|
+
}
|
|
1100
|
+
/**
|
|
1101
|
+
* **Rendered as `z.never()`, and described where it has a doc comment**, so its source is not always
|
|
1102
|
+
* `UNREPRESENTABLE` itself: a model split by visibility keeps its description. A declaration refused
|
|
1103
|
+
* as two shapes under one name is replaced by a bare `UNREPRESENTABLE`, so it is one of these too.
|
|
1104
|
+
*/
|
|
1105
|
+
#validatesNothing(declaration) {
|
|
1106
|
+
return (declaration.source === UNREPRESENTABLE ||
|
|
1107
|
+
declaration.source.startsWith(`${UNREPRESENTABLE}.describe(`));
|
|
1108
|
+
}
|
|
1060
1109
|
/** The names of the declarations that validate nothing, refused here or in the walk beneath. */
|
|
1061
1110
|
refusedNames() {
|
|
1062
1111
|
return new Set([
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "typespec-http-zod",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.0",
|
|
4
4
|
"description": "TypeSpec emitter and library: turn an HTTP service definition into Zod validators and framework-free contract types that agree, keyword for keyword, with the OpenAPI document @typespec/openapi3 publishes from the same source.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"codegen",
|