typespec-http-zod 0.10.0 → 0.12.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 +43 -4
- package/dist/src/api.js +150 -15
- package/dist/src/lib.d.ts +10 -0
- package/dist/src/lib.js +2 -0
- package/dist/src/runtime.d.ts +22 -0
- package/package.json +1 -1
package/dist/src/api.d.ts
CHANGED
|
@@ -43,12 +43,14 @@ export { EmitterOptionsSchema, type EmitterOptions } from "./lib.js";
|
|
|
43
43
|
* independently.
|
|
44
44
|
*/
|
|
45
45
|
export { objectKey } from "./zod.js";
|
|
46
|
+
export declare function beginBanner(hint: string | undefined): void;
|
|
46
47
|
/**
|
|
47
|
-
* The
|
|
48
|
+
* The status a row registers: the first non-error status the operation declares.
|
|
48
49
|
*
|
|
49
|
-
*
|
|
50
|
-
* handler can return, so it is not something this emitter can
|
|
51
|
-
* operation carrying only those is skipped rather than guessed
|
|
50
|
+
* **"Success" here means "an outcome a handler returns", which includes a redirect.** A range
|
|
51
|
+
* (`2XX`) or a wildcard is not a status a handler can return, so it is not something this emitter can
|
|
52
|
+
* turn into a `respond()` call, and an operation carrying only those is skipped rather than guessed
|
|
53
|
+
* at.
|
|
52
54
|
*/
|
|
53
55
|
export declare function successStatusOf(operation: HttpOperation): number | undefined;
|
|
54
56
|
/**
|
|
@@ -209,6 +211,41 @@ export interface EmittedRoute {
|
|
|
209
211
|
readonly status: StatusKey;
|
|
210
212
|
readonly schema: string | undefined;
|
|
211
213
|
}[];
|
|
214
|
+
/**
|
|
215
|
+
* The headers each response status declares, as the document publishes them.
|
|
216
|
+
*
|
|
217
|
+
* **`deps.respond` could not set a header the contract promises.** A spec may declare `@header` on
|
|
218
|
+
* a response model - a `Location` on a 302, a `Link`, a correlation echo - and the document
|
|
219
|
+
* publishes it under `responses.<code>.headers`, but the arm carried a status and a body schema and
|
|
220
|
+
* nothing else. So the server could not answer faithfully, and a redirect could not be served at
|
|
221
|
+
* all. Reported as blocking by a consumer, and it is a fact the document already states.
|
|
222
|
+
*
|
|
223
|
+
* **Both names are carried, because two different things need them.** `name` is the WIRE name,
|
|
224
|
+
* which is what the response sets; `property` is the name on the returned value, which is where the
|
|
225
|
+
* value is read from. `@header("x-correlation-id") correlationId: string` is `x-correlation-id` on
|
|
226
|
+
* the wire and `correlationId` in the handler's result, and an emitter given only one of them would
|
|
227
|
+
* have to guess the other.
|
|
228
|
+
*/
|
|
229
|
+
readonly responseHeaders: readonly {
|
|
230
|
+
readonly status: StatusKey;
|
|
231
|
+
readonly headers: readonly {
|
|
232
|
+
readonly name: string;
|
|
233
|
+
readonly property: string;
|
|
234
|
+
}[];
|
|
235
|
+
}[];
|
|
236
|
+
/**
|
|
237
|
+
* The media types each response status offers.
|
|
238
|
+
*
|
|
239
|
+
* **`responseContentTypes` above carries this for the PRIMARY status only, and an arm carried
|
|
240
|
+
* none at all.** An operation whose one status offers `application/json` and `text/event-stream`
|
|
241
|
+
* emitted a single arm holding the JSON schema, and the alternative was dropped from the list
|
|
242
|
+
* entirely, so `deps.respond` could not tell what it was answering with and a non-JSON operation
|
|
243
|
+
* could not be served faithfully. Reported by two consumers independently.
|
|
244
|
+
*/
|
|
245
|
+
readonly responseMediaTypes: readonly {
|
|
246
|
+
readonly status: StatusKey;
|
|
247
|
+
readonly contentTypes: readonly string[];
|
|
248
|
+
}[];
|
|
212
249
|
/**
|
|
213
250
|
* No caller is established, so the operation receives no `ServiceContext`.
|
|
214
251
|
*
|
|
@@ -292,6 +329,8 @@ export interface ResolvedServiceOptions {
|
|
|
292
329
|
readonly sealObjectSchemas: boolean;
|
|
293
330
|
readonly keyVocabularies: readonly string[];
|
|
294
331
|
readonly runtimeModule: string;
|
|
332
|
+
/** The command that regenerates, for the banner. `undefined` keeps the generic line. */
|
|
333
|
+
readonly regenerateHint: string | undefined;
|
|
295
334
|
}
|
|
296
335
|
/** What the generated files import their runtime contract from when nothing says otherwise. */
|
|
297
336
|
export declare const DEFAULT_RUNTIME_MODULE = "typespec-http-zod/runtime";
|
package/dist/src/api.js
CHANGED
|
@@ -60,15 +60,37 @@ const PACKAGE_NAME = "typespec-http-zod";
|
|
|
60
60
|
* The package name stays, because that IS the answer to "what wrote this". The instruction becomes
|
|
61
61
|
* the one thing true for every consumer: recompile the source, do not edit the output.
|
|
62
62
|
*/
|
|
63
|
-
const GENERATED_BANNER = `// GENERATED by ${PACKAGE_NAME} from the TypeSpec service definition. DO NOT EDIT.
|
|
64
|
-
// Recompile the spec that produced it; edits here are overwritten on the next run.
|
|
65
|
-
`;
|
|
66
63
|
/**
|
|
67
|
-
*
|
|
64
|
+
* How to regenerate, for the banner. Set per emission; `undefined` keeps the generic line.
|
|
65
|
+
*
|
|
66
|
+
* Module-scoped rather than threaded through four render functions, matching how the contracts
|
|
67
|
+
* package and the stream module are carried. The emission loop awaits each service to completion
|
|
68
|
+
* before the next begins, so nothing interleaves.
|
|
69
|
+
*/
|
|
70
|
+
let regenerateHint;
|
|
71
|
+
export function beginBanner(hint) {
|
|
72
|
+
regenerateHint = hint;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The header every emitted file carries.
|
|
76
|
+
*
|
|
77
|
+
* **`DO NOT EDIT` says what not to do and not what to do instead**, and only the project knows the
|
|
78
|
+
* command that regenerates. Where it states one, the banner carries it; otherwise the generic line
|
|
79
|
+
* stands.
|
|
80
|
+
*/
|
|
81
|
+
function generatedBanner() {
|
|
82
|
+
const second = regenerateHint === undefined
|
|
83
|
+
? "// Recompile the spec that produced it; edits here are overwritten on the next run."
|
|
84
|
+
: `// Regenerate with: ${regenerateHint}`;
|
|
85
|
+
return `// GENERATED by ${PACKAGE_NAME} from the TypeSpec service definition. DO NOT EDIT.\n${second}\n`;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The status a row registers: the first non-error status the operation declares.
|
|
68
89
|
*
|
|
69
|
-
*
|
|
70
|
-
* handler can return, so it is not something this emitter can
|
|
71
|
-
* operation carrying only those is skipped rather than guessed
|
|
90
|
+
* **"Success" here means "an outcome a handler returns", which includes a redirect.** A range
|
|
91
|
+
* (`2XX`) or a wildcard is not a status a handler can return, so it is not something this emitter can
|
|
92
|
+
* turn into a `respond()` call, and an operation carrying only those is skipped rather than guessed
|
|
93
|
+
* at.
|
|
72
94
|
*/
|
|
73
95
|
export function successStatusOf(operation) {
|
|
74
96
|
return successStatusesOf(operation)[0];
|
|
@@ -85,7 +107,23 @@ export function successStatusesOf(operation) {
|
|
|
85
107
|
const codes = [];
|
|
86
108
|
for (const response of operation.responses) {
|
|
87
109
|
const code = response.statusCodes;
|
|
88
|
-
|
|
110
|
+
/**
|
|
111
|
+
* **2xx AND 3xx, because a redirect is an outcome rather than an error.**
|
|
112
|
+
*
|
|
113
|
+
* This accepted 2xx only, so an operation whose one declared response was a `302` had no status,
|
|
114
|
+
* was skipped by the `continue` in `collectRoutes`, and vanished with no diagnostic. The document
|
|
115
|
+
* declared the route and the emitted output had no trace of it, so a client generated from that
|
|
116
|
+
* document would 404 while the compile reported success. A `Location` on a 302 and an OAuth
|
|
117
|
+
* redirect are the ordinary cases.
|
|
118
|
+
*
|
|
119
|
+
* 4xx and 5xx stay out: those are error arms, collected from `@error` models separately, and a
|
|
120
|
+
* handler does not return one by returning normally.
|
|
121
|
+
*
|
|
122
|
+
* **Additive by construction.** Measured across the whole conformance corpus before the change:
|
|
123
|
+
* ZERO operations declare a response set without a 2xx, so no existing route, count or baseline
|
|
124
|
+
* moves. Only operations that were being dropped are added.
|
|
125
|
+
*/
|
|
126
|
+
if (typeof code === "number" && code >= 200 && code < 400)
|
|
89
127
|
codes.push(code);
|
|
90
128
|
}
|
|
91
129
|
return [...new Set(codes)].toSorted((a, b) => a - b);
|
|
@@ -261,6 +299,51 @@ function isSuccessKey(status) {
|
|
|
261
299
|
* Arms come out in the order OpenAPI resolves them - exact codes, then ranges, then the catch-all -
|
|
262
300
|
* so a consumer scanning for the first match applies the precedence the specification states.
|
|
263
301
|
*/
|
|
302
|
+
/**
|
|
303
|
+
* The headers each response status declares, keyed by the status the arm answers with.
|
|
304
|
+
*
|
|
305
|
+
* **Read from `@typespec/http`'s own resolution**, which separates a response's headers from its
|
|
306
|
+
* body and gives each one under its WIRE name with the declaring property behind it. Deriving either
|
|
307
|
+
* name any other way would be a second spelling of a fact the compiler already resolved.
|
|
308
|
+
*/
|
|
309
|
+
function responseHeadersOf(operation) {
|
|
310
|
+
const byStatus = new Map();
|
|
311
|
+
for (const response of operation.responses) {
|
|
312
|
+
const status = response.statusCodes;
|
|
313
|
+
if (typeof status !== "number")
|
|
314
|
+
continue;
|
|
315
|
+
const headers = [];
|
|
316
|
+
for (const content of response.responses) {
|
|
317
|
+
for (const [name, property] of Object.entries(content.headers ?? {})) {
|
|
318
|
+
if (!headers.some((existing) => existing.name === name)) {
|
|
319
|
+
headers.push({ name, property: property.name });
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (headers.length === 0)
|
|
324
|
+
continue;
|
|
325
|
+
const existing = byStatus.get(status) ?? [];
|
|
326
|
+
byStatus.set(status, [
|
|
327
|
+
...existing,
|
|
328
|
+
...headers.filter((h) => !existing.some((e) => e.name === h.name)),
|
|
329
|
+
]);
|
|
330
|
+
}
|
|
331
|
+
return [...byStatus].map(([status, headers]) => ({ status, headers }));
|
|
332
|
+
}
|
|
333
|
+
/** The media types each numeric response status offers, from the compiler's own resolution. */
|
|
334
|
+
function responseMediaTypesOf(operation) {
|
|
335
|
+
const byStatus = new Map();
|
|
336
|
+
for (const response of operation.responses) {
|
|
337
|
+
const status = response.statusCodes;
|
|
338
|
+
if (typeof status !== "number")
|
|
339
|
+
continue;
|
|
340
|
+
const types = responseContentTypesOf(operation, status);
|
|
341
|
+
if (types.length === 0)
|
|
342
|
+
continue;
|
|
343
|
+
byStatus.set(status, types);
|
|
344
|
+
}
|
|
345
|
+
return [...byStatus].map(([status, contentTypes]) => ({ status, contentTypes }));
|
|
346
|
+
}
|
|
264
347
|
function errorArmsOf(program, operation, registry) {
|
|
265
348
|
const arms = new Map();
|
|
266
349
|
for (const response of operation.responses) {
|
|
@@ -956,6 +1039,8 @@ export function collectRoutes(program, registry, service) {
|
|
|
956
1039
|
// them under the ambient request visibility declared six components under the wrong
|
|
957
1040
|
// suffix - measured: type/model/visibility gained six divergences.
|
|
958
1041
|
errorArms: withVisibility(program, Visibility.Read, () => errorArmsOf(program, operation, registry)),
|
|
1042
|
+
responseHeaders: responseHeadersOf(operation),
|
|
1043
|
+
responseMediaTypes: responseMediaTypesOf(operation),
|
|
959
1044
|
scopes: [
|
|
960
1045
|
...new Set((getAuthenticationForOperation(program, operation.operation)?.options ?? []).flatMap((option) => option.schemes.flatMap((scheme) => scheme.type === "oauth2"
|
|
961
1046
|
? scheme.flows.flatMap((flow) => flow.scopes.map((scope) => scope.value))
|
|
@@ -1046,7 +1131,7 @@ function renderSchemas(registry, externals, routeDeclarations, runtimeModule) {
|
|
|
1046
1131
|
* with no cycle emits byte-identical output to before.
|
|
1047
1132
|
*/
|
|
1048
1133
|
if (d.structural === undefined) {
|
|
1049
|
-
return `\nexport const ${d.identifier} = ${d.source};\nexport type ${d.typeName} = z.infer<typeof ${d.identifier}
|
|
1134
|
+
return `\nexport const ${d.identifier} = ${d.source};\nexport type ${d.typeName} = Exact<z.infer<typeof ${d.identifier}>>;\n`;
|
|
1050
1135
|
}
|
|
1051
1136
|
const declared = d.structural.startsWith("{")
|
|
1052
1137
|
? `export interface ${d.typeName} ${d.structural}`
|
|
@@ -1070,8 +1155,28 @@ function renderSchemas(registry, externals, routeDeclarations, runtimeModule) {
|
|
|
1070
1155
|
* inside `zValidator` or missed beside a bracket. `renderExternalImports` below reads the same
|
|
1071
1156
|
* rendered content for the same reason.
|
|
1072
1157
|
*/
|
|
1158
|
+
/**
|
|
1159
|
+
* **`Exact` narrows an inferred optional to what the wire can carry.**
|
|
1160
|
+
*
|
|
1161
|
+
* `z.infer` of `.optional()` gives `p?: T | undefined`, which under `exactOptionalPropertyTypes`
|
|
1162
|
+
* admits an explicitly-undefined property. **JSON has no `undefined`**, and the document says the
|
|
1163
|
+
* same thing by leaving the property out of `required`, so the inferred type was wider than both
|
|
1164
|
+
* the contract and the wire and a consumer had to strip it at the boundary.
|
|
1165
|
+
*
|
|
1166
|
+
* Derived from the same schema rather than hand-written beside it, so there is still one source of
|
|
1167
|
+
* truth: a mapped type cannot drift from the thing it maps.
|
|
1168
|
+
*
|
|
1169
|
+
* Emitted only where a declaration names it, like the imports beside it.
|
|
1170
|
+
*/
|
|
1171
|
+
const EXACT_HELPER = `
|
|
1172
|
+
/** An inferred type narrowed to what JSON can carry: an optional property is absent or a value. */
|
|
1173
|
+
type Exact<T> = { [K in keyof T as undefined extends T[K] ? never : K]: T[K] } & {
|
|
1174
|
+
[K in keyof T as undefined extends T[K] ? K : never]?: Exclude<T[K], undefined>;
|
|
1175
|
+
};
|
|
1176
|
+
`;
|
|
1177
|
+
const usesExact = declarations.some((d) => d.includes("Exact<"));
|
|
1073
1178
|
const usesZod = new Set([...declarations, ...routeDeclarations].join("").match(/[A-Za-z_$][\w$]*/g) ?? []).has("z");
|
|
1074
|
-
const parts = [
|
|
1179
|
+
const parts = [generatedBanner()];
|
|
1075
1180
|
if (usesZod)
|
|
1076
1181
|
parts.push('\nimport { z } from "zod";\n');
|
|
1077
1182
|
/**
|
|
@@ -1086,6 +1191,8 @@ function renderSchemas(registry, externals, routeDeclarations, runtimeModule) {
|
|
|
1086
1191
|
parts.push(`import type { ResponseArm } from ${JSON.stringify(runtimeModule)};\n`);
|
|
1087
1192
|
}
|
|
1088
1193
|
parts.push(renderExternalImports(externals, [...declarations, ...routeDeclarations].join("")));
|
|
1194
|
+
if (usesExact)
|
|
1195
|
+
parts.push(EXACT_HELPER);
|
|
1089
1196
|
parts.push(declarations.join(""));
|
|
1090
1197
|
if (routeDeclarations.length > 0)
|
|
1091
1198
|
parts.push(`\n${routeDeclarations.join("\n")}\n`);
|
|
@@ -1206,13 +1313,39 @@ function nameRouteSchemas(routes) {
|
|
|
1206
1313
|
function responseArmsOf(route, response, alternate) {
|
|
1207
1314
|
const arms = [];
|
|
1208
1315
|
const primary = response ?? "undefined";
|
|
1316
|
+
/**
|
|
1317
|
+
* The headers this status declares, rendered onto the arm that answers with it.
|
|
1318
|
+
*
|
|
1319
|
+
* Emitted only where there are some: an arm for a response declaring none must not acquire an
|
|
1320
|
+
* empty list, or every `respond` implementation has to tell "none declared" from "none carried".
|
|
1321
|
+
*/
|
|
1322
|
+
/**
|
|
1323
|
+
* The media types this status offers. Emitted only where the document names more than one, because
|
|
1324
|
+
* a single type is what a `respond` already assumes and repeating it on every arm in every service
|
|
1325
|
+
* would be noise rather than a fact.
|
|
1326
|
+
*/
|
|
1327
|
+
const mediaTypesFor = (status) => {
|
|
1328
|
+
const declared = route.responseMediaTypes.find((entry) => entry.status === status)?.contentTypes ?? [];
|
|
1329
|
+
if (declared.length < 2)
|
|
1330
|
+
return "";
|
|
1331
|
+
return `, contentTypes: [${declared.map((t) => JSON.stringify(t)).join(", ")}]`;
|
|
1332
|
+
};
|
|
1333
|
+
const headersFor = (status) => {
|
|
1334
|
+
const declared = route.responseHeaders.find((entry) => entry.status === status)?.headers ?? [];
|
|
1335
|
+
if (declared.length === 0)
|
|
1336
|
+
return "";
|
|
1337
|
+
const rendered = declared
|
|
1338
|
+
.map((h) => `{ name: ${JSON.stringify(h.name)}, property: ${JSON.stringify(h.property)} }`)
|
|
1339
|
+
.join(", ");
|
|
1340
|
+
return `, headers: [${rendered}]`;
|
|
1341
|
+
};
|
|
1209
1342
|
if (route.statusBy === undefined) {
|
|
1210
|
-
arms.push(`{ status: ${route.statusCode}, schema: ${primary} }`);
|
|
1343
|
+
arms.push(`{ status: ${route.statusCode}, schema: ${primary}${mediaTypesFor(route.statusCode)}${headersFor(route.statusCode)} }`);
|
|
1211
1344
|
}
|
|
1212
1345
|
else {
|
|
1213
1346
|
const alternateSchema = alternate ?? "undefined";
|
|
1214
1347
|
const { property, value, status } = route.statusBy;
|
|
1215
|
-
arms.push(`{ status: ${status}, schema: ${alternateSchema}, when: { property: ${JSON.stringify(property)}, value: ${JSON.stringify(value)} } }`, `{ status: ${route.statusCode}, schema: ${primary} }`);
|
|
1348
|
+
arms.push(`{ status: ${status}, schema: ${alternateSchema}${mediaTypesFor(status)}${headersFor(status)}, when: { property: ${JSON.stringify(property)}, value: ${JSON.stringify(value)} } }`, `{ status: ${route.statusCode}, schema: ${primary}${mediaTypesFor(route.statusCode)}${headersFor(route.statusCode)} }`);
|
|
1216
1349
|
}
|
|
1217
1350
|
/**
|
|
1218
1351
|
* The failure arms, which used to be dropped entirely.
|
|
@@ -1378,7 +1511,7 @@ function renderRequestTypes(entries, responses, registry) {
|
|
|
1378
1511
|
const lookup = entries
|
|
1379
1512
|
.map((entry) => `\t${entry.operationId}: ${upperFirst(entry.operationId)}Input;`)
|
|
1380
1513
|
.join("\n");
|
|
1381
|
-
return `${
|
|
1514
|
+
return `${generatedBanner()}
|
|
1382
1515
|
/**
|
|
1383
1516
|
* Flattens an intersection into a single object type.
|
|
1384
1517
|
*
|
|
@@ -1459,7 +1592,7 @@ function renderWireAssertions(schemas, types, entries, contractsPackage) {
|
|
|
1459
1592
|
if (paired.length === 0)
|
|
1460
1593
|
return undefined;
|
|
1461
1594
|
const assertions = paired.map((pair) => `export type Assert${pair.name} = MustHold<Identical<z.infer<typeof ${pair.identifier}>, Contracts.${pair.name}>>;`);
|
|
1462
|
-
return `${
|
|
1595
|
+
return `${generatedBanner()}
|
|
1463
1596
|
import type { z } from "zod";
|
|
1464
1597
|
import type * as Contracts from "${contractsPackage}";
|
|
1465
1598
|
import type {
|
|
@@ -1497,7 +1630,7 @@ function renderVocabularies(vocabularies) {
|
|
|
1497
1630
|
const entries = [...vocabularies.entries()]
|
|
1498
1631
|
.toSorted(([a], [b]) => a.localeCompare(b))
|
|
1499
1632
|
.map(([name, values]) => `\t${name}: [${values.map((v) => JSON.stringify(v)).join(", ")}],`);
|
|
1500
|
-
return `${
|
|
1633
|
+
return `${generatedBanner()}
|
|
1501
1634
|
/**
|
|
1502
1635
|
* The spec's vocabularies - every enum the surface reaches, and what each one contains.
|
|
1503
1636
|
*
|
|
@@ -1581,7 +1714,9 @@ export async function emitHttpZod(context, wrapper = {}) {
|
|
|
1581
1714
|
sealObjectSchemas: perService?.["seal-object-schemas"] ?? options["seal-object-schemas"] ?? false,
|
|
1582
1715
|
keyVocabularies: perService?.["key-vocabularies"] ?? options["key-vocabularies"] ?? [],
|
|
1583
1716
|
runtimeModule: perService?.["runtime-module"] ?? options["runtime-module"] ?? defaultRuntimeModule,
|
|
1717
|
+
regenerateHint: perService?.["regenerate-hint"] ?? options["regenerate-hint"],
|
|
1584
1718
|
};
|
|
1719
|
+
beginBanner(resolved.regenerateHint);
|
|
1585
1720
|
const registry = new SchemaRegistry(context.program);
|
|
1586
1721
|
const { result: { routes }, imports: externals, vocabularies, } = collectExternalImports(() => withContractsPackage(resolved.contractsPackage, () => withSealedObjects(resolved.sealObjectSchemas, () => {
|
|
1587
1722
|
// Declarations first: a vocabulary is a fact about the contract, so it is collected from
|
package/dist/src/lib.d.ts
CHANGED
|
@@ -78,6 +78,15 @@ export interface EmitterOptions {
|
|
|
78
78
|
* package's own tests, where the package name does not resolve.
|
|
79
79
|
*/
|
|
80
80
|
"runtime-module"?: string;
|
|
81
|
+
/**
|
|
82
|
+
* The command that regenerates these files, written into every banner.
|
|
83
|
+
*
|
|
84
|
+
* **`DO NOT EDIT` says what not to do and not what to do instead.** Only the project knows whether
|
|
85
|
+
* that is `pnpm generate`, `npm run api` or a `tsp compile` with three flags, so no emitter can
|
|
86
|
+
* supply it, and the generic line standing in its place lost the one thing a reader opening a
|
|
87
|
+
* generated file needs. Omitted, the generic line is kept.
|
|
88
|
+
*/
|
|
89
|
+
"regenerate-hint"?: string;
|
|
81
90
|
/**
|
|
82
91
|
* Per-`@service` overrides, keyed by the service namespace name.
|
|
83
92
|
*
|
|
@@ -93,6 +102,7 @@ export interface EmitterOptions {
|
|
|
93
102
|
"seal-object-schemas"?: boolean;
|
|
94
103
|
"key-vocabularies"?: string[];
|
|
95
104
|
"runtime-module"?: string;
|
|
105
|
+
"regenerate-hint"?: string;
|
|
96
106
|
}>;
|
|
97
107
|
}
|
|
98
108
|
/**
|
package/dist/src/lib.js
CHANGED
|
@@ -14,6 +14,7 @@ export const EmitterOptionsSchema = {
|
|
|
14
14
|
"seal-object-schemas": { type: "boolean", nullable: true },
|
|
15
15
|
"key-vocabularies": { type: "array", items: { type: "string" }, nullable: true },
|
|
16
16
|
"runtime-module": { type: "string", nullable: true },
|
|
17
|
+
"regenerate-hint": { type: "string", nullable: true },
|
|
17
18
|
services: {
|
|
18
19
|
type: "object",
|
|
19
20
|
nullable: true,
|
|
@@ -27,6 +28,7 @@ export const EmitterOptionsSchema = {
|
|
|
27
28
|
"seal-object-schemas": { type: "boolean", nullable: true },
|
|
28
29
|
"key-vocabularies": { type: "array", items: { type: "string" }, nullable: true },
|
|
29
30
|
"runtime-module": { type: "string", nullable: true },
|
|
31
|
+
"regenerate-hint": { type: "string", nullable: true },
|
|
30
32
|
},
|
|
31
33
|
required: [],
|
|
32
34
|
},
|
package/dist/src/runtime.d.ts
CHANGED
|
@@ -39,6 +39,28 @@ import type { ZodType } from "zod";
|
|
|
39
39
|
* arm is legitimate - so the typo produces a valid-looking arm that validates nothing.
|
|
40
40
|
*/
|
|
41
41
|
export interface ResponseArm {
|
|
42
|
+
/**
|
|
43
|
+
* The media types this response offers, where the document names more than one.
|
|
44
|
+
*
|
|
45
|
+
* Absent where it names one, which is what an application already assumes. Present, it is the set
|
|
46
|
+
* to negotiate against: `selectContentType` takes the caller's `Accept` and these.
|
|
47
|
+
*/
|
|
48
|
+
readonly contentTypes?: readonly string[];
|
|
49
|
+
/**
|
|
50
|
+
* The headers this response declares, as the document publishes them.
|
|
51
|
+
*
|
|
52
|
+
* **Two names, because two different things need them.** `name` is the WIRE name, which is what
|
|
53
|
+
* the response sets; `property` is the name on the value the handler returned, which is where the
|
|
54
|
+
* value is read from. `@header("x-correlation-id") correlationId: string` is `x-correlation-id`
|
|
55
|
+
* on the wire and `correlationId` in the result.
|
|
56
|
+
*
|
|
57
|
+
* Absent where the response declares none, so "none declared" and "none carried" are the same
|
|
58
|
+
* thing rather than two states an application has to tell apart.
|
|
59
|
+
*/
|
|
60
|
+
readonly headers?: readonly {
|
|
61
|
+
readonly name: string;
|
|
62
|
+
readonly property: string;
|
|
63
|
+
}[];
|
|
42
64
|
/**
|
|
43
65
|
* The status this arm answers with: an exact code, a `4XX`-style range, or the catch-all.
|
|
44
66
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "typespec-http-zod",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.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",
|