@ancplua/qyl-api-schema 7.3.0 → 7.4.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 +22 -0
- package/generated/zod-runtime/index.d.ts +50 -0
- package/generated/zod-runtime/index.js +262 -0
- package/package.json +19 -5
package/README.md
CHANGED
|
@@ -59,6 +59,28 @@ rather than from prose here; this line moves faster than a README is revised.
|
|
|
59
59
|
`main.tsp` is the local compile entry point and includes emitter routing. `index.tsp`
|
|
60
60
|
is the published TypeSpec entry point and contains only the client-facing contract.
|
|
61
61
|
|
|
62
|
+
## Runtime validation
|
|
63
|
+
|
|
64
|
+
`@ancplua/qyl-api-schema/zod` builds Zod validators from the bundled JSON Schema at
|
|
65
|
+
runtime. It is a translation layer over `z.fromJSONSchema` — authored in `src/zod/`
|
|
66
|
+
and compiled to `generated/zod-runtime/` — not a second hand-written schema, so a
|
|
67
|
+
contract change cannot leave a validator describing the previous shape.
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { publishedContractSchema } from "@ancplua/qyl-api-schema/zod";
|
|
71
|
+
import type { Span } from "@ancplua/qyl-api-schema/types";
|
|
72
|
+
|
|
73
|
+
const SpanSchema = publishedContractSchema<Span>("OTel.Traces.Span");
|
|
74
|
+
const span = SpanSchema.parse(await response.json());
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`zod` is an optional peer dependency (`>=4.5.0 <5`): a consumer that does not
|
|
78
|
+
install it simply never imports this subpath, and the other subpaths do not depend
|
|
79
|
+
on it. Definition names are the published `$defs` keys — the same names the OpenAPI
|
|
80
|
+
components and the generated C# and TypeScript DTOs carry — and
|
|
81
|
+
`contractDefinitionNames()` lists all of them. The type argument is not checked
|
|
82
|
+
against the schema, so pair it with the matching type from `./types`.
|
|
83
|
+
|
|
62
84
|
## Contract revision
|
|
63
85
|
|
|
64
86
|
`scripts/emit-contract-revision.mjs` stamps a deterministic revision — `sha256:` plus
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* A published contract type as it looks *before* validation: branded identity
|
|
4
|
+
* scalars are still plain strings and arrays may be readonly, because branding
|
|
5
|
+
* is what `parse` produces rather than what a caller can construct.
|
|
6
|
+
*
|
|
7
|
+
* Annotating a projection with `satisfies ContractInput<T>` moves the wire-name
|
|
8
|
+
* check from a runtime `parse` throw to a compile error, so a property spelled
|
|
9
|
+
* in an internal camelCase instead of the contract's snake_case cannot reach a
|
|
10
|
+
* response at all.
|
|
11
|
+
*/
|
|
12
|
+
export type ContractInput<TContract> = TContract extends {
|
|
13
|
+
readonly __brand: string;
|
|
14
|
+
} ? string : TContract extends readonly (infer TElement)[] ? readonly ContractInput<TElement>[] : TContract extends (...args: never[]) => unknown ? TContract : TContract extends object ? {
|
|
15
|
+
readonly [K in keyof TContract]: ContractInput<TContract[K]>;
|
|
16
|
+
} : TContract;
|
|
17
|
+
/**
|
|
18
|
+
* Build a strict runtime validator for one published contract definition.
|
|
19
|
+
*
|
|
20
|
+
* `definitionName` is a key of the published schema's `$defs` — the same name
|
|
21
|
+
* the OpenAPI component and the generated C#/TypeScript DTOs carry, for example
|
|
22
|
+
* `"OTel.Traces.Span"` or `"Common.Errors.ProblemDetails"`. Pair it with the
|
|
23
|
+
* matching type from `@ancplua/qyl-api-schema/types`; the type argument is not
|
|
24
|
+
* checked against the schema, so an unrelated one produces a validator that
|
|
25
|
+
* lies about what it returns.
|
|
26
|
+
*
|
|
27
|
+
* Results are memoized per name: building a schema walks the whole definition,
|
|
28
|
+
* and the returned validator is stateless.
|
|
29
|
+
*
|
|
30
|
+
* @throws Error if no such definition is published.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* import { publishedContractSchema } from "@ancplua/qyl-api-schema/zod";
|
|
35
|
+
* import type { Span } from "@ancplua/qyl-api-schema/types";
|
|
36
|
+
*
|
|
37
|
+
* const SpanSchema = publishedContractSchema<Span>("OTel.Traces.Span");
|
|
38
|
+
* const span = SpanSchema.parse(await response.json());
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export declare function publishedContractSchema<TContract>(definitionName: string): z.ZodType<TContract>;
|
|
42
|
+
/**
|
|
43
|
+
* Every definition name {@link publishedContractSchema} accepts, sorted.
|
|
44
|
+
*
|
|
45
|
+
* Enumerating the published surface is how a consumer proves it covers all of
|
|
46
|
+
* it — a contract definition that gains no validator is otherwise invisible.
|
|
47
|
+
*/
|
|
48
|
+
export declare function contractDefinitionNames(): readonly string[];
|
|
49
|
+
/** The published schema's `$id`, for reporting which contract a validator came from. */
|
|
50
|
+
export declare const contractJsonSchemaId: string;
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// Runtime validators for the contracts this package publishes.
|
|
2
|
+
//
|
|
3
|
+
// The JSON Schema in generated/json-schema/qyl-api-schema.json is the artifact a
|
|
4
|
+
// validator should be derived from, not a second hand-maintained copy of it: a
|
|
5
|
+
// contract change that a consumer forgets to mirror is exactly the failure this
|
|
6
|
+
// module exists to make impossible. Everything below is therefore a translation
|
|
7
|
+
// layer over `z.fromJSONSchema`, not a schema in its own right.
|
|
8
|
+
//
|
|
9
|
+
// The package's own schema is reached by self-reference through the "exports"
|
|
10
|
+
// map, so this file resolves the same artifact from source, from
|
|
11
|
+
// generated/zod-runtime, and from inside an installed node_modules copy.
|
|
12
|
+
import contractJsonSchema from "@ancplua/qyl-api-schema/json-schema" with { type: "json" };
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
/**
|
|
15
|
+
* Rewrite the published JSON Schema into the subset `z.fromJSONSchema` accepts,
|
|
16
|
+
* preserving the contract's meaning exactly. Three published constructs need it:
|
|
17
|
+
*
|
|
18
|
+
* - `unevaluatedProperties` — `z.fromJSONSchema` rejects the keyword outright.
|
|
19
|
+
* The emitted schemas only ever use it to seal an object, which
|
|
20
|
+
* `additionalProperties` expresses for the same shapes.
|
|
21
|
+
* - `allOf` inheritance — a derived object carries a single `$ref` base plus its
|
|
22
|
+
* own properties. Zod builds that as an intersection, and an intersection of a
|
|
23
|
+
* sealed base with a sealed extension accepts neither side's extra keys but
|
|
24
|
+
* also loses the base's own key set, so the derived object stops rejecting
|
|
25
|
+
* unknown keys. Flattening the base into the derivative keeps one sealed
|
|
26
|
+
* object with the full key set.
|
|
27
|
+
* - 64-bit integers — Zod's `int64` conversion adds a +/-2^53 range check. The
|
|
28
|
+
* contract has no such bound (Unix nanosecond timestamps exceed it), so the
|
|
29
|
+
* integer rule is kept as `multipleOf: 1` and the format is dropped.
|
|
30
|
+
*
|
|
31
|
+
* `format: "date-time"` is deliberately left untouched: Zod 4.5's conversion is
|
|
32
|
+
* already RFC 3339 offset-aware (it accepts `+02:00`, requires seconds, and
|
|
33
|
+
* rejects colon-less offsets). scripts/verify-zod-contracts.mjs pins that.
|
|
34
|
+
* One consequence for consumers that republish a validator through
|
|
35
|
+
* `z.toJSONSchema`: the output carries `format: "date-time"` next to the RFC
|
|
36
|
+
* 3339 pattern, so a pinned snapshot of such output changes when a consumer
|
|
37
|
+
* moves onto this module.
|
|
38
|
+
*/
|
|
39
|
+
function adaptPublishedSchemaForZod(schemaNode) {
|
|
40
|
+
if (typeof schemaNode !== "object" || schemaNode === null || Array.isArray(schemaNode)) {
|
|
41
|
+
throw new Error("Published Qyl JSON Schema root must be an object");
|
|
42
|
+
}
|
|
43
|
+
const root = schemaNode;
|
|
44
|
+
const sourceDefinitions = asSchemaRecord(root.$defs, "$defs");
|
|
45
|
+
const definitionCache = new Map();
|
|
46
|
+
const resolvingDefinitions = new Set();
|
|
47
|
+
const definition = (name) => {
|
|
48
|
+
const cached = definitionCache.get(name);
|
|
49
|
+
if (cached)
|
|
50
|
+
return cached;
|
|
51
|
+
// Inheritance is flattened by inlining the base, so a cycle would recurse
|
|
52
|
+
// forever rather than fail; a $ref cycle that is not inheritance is fine and
|
|
53
|
+
// never reaches here.
|
|
54
|
+
if (resolvingDefinitions.has(name)) {
|
|
55
|
+
throw new Error(`Published Qyl JSON Schema has an inheritance cycle at '${name}'`);
|
|
56
|
+
}
|
|
57
|
+
const source = sourceDefinitions[name];
|
|
58
|
+
if (source === undefined) {
|
|
59
|
+
throw new Error(`Published Qyl JSON Schema is missing definition '${name}'`);
|
|
60
|
+
}
|
|
61
|
+
resolvingDefinitions.add(name);
|
|
62
|
+
const adapted = adaptNode(source);
|
|
63
|
+
resolvingDefinitions.delete(name);
|
|
64
|
+
definitionCache.set(name, adapted);
|
|
65
|
+
return adapted;
|
|
66
|
+
};
|
|
67
|
+
const adaptNode = (node) => {
|
|
68
|
+
if (typeof node !== "object" || node === null || Array.isArray(node)) {
|
|
69
|
+
throw new Error("Expected a JSON Schema object node");
|
|
70
|
+
}
|
|
71
|
+
const source = node;
|
|
72
|
+
const inheritedDefinitions = inheritedObjectDefinitions(source, definition);
|
|
73
|
+
const adapted = {};
|
|
74
|
+
if (inheritedDefinitions.length > 0) {
|
|
75
|
+
let inheritedAdditionalProperties;
|
|
76
|
+
for (const inherited of inheritedDefinitions) {
|
|
77
|
+
const existingProperties = optionalSchemaRecord(adapted.properties);
|
|
78
|
+
const inheritedProperties = optionalSchemaRecord(inherited.properties);
|
|
79
|
+
const existingRequired = stringArray(adapted.required);
|
|
80
|
+
const inheritedRequired = stringArray(inherited.required);
|
|
81
|
+
Object.assign(adapted, inherited);
|
|
82
|
+
adapted.properties = { ...existingProperties, ...inheritedProperties };
|
|
83
|
+
adapted.required = [...new Set([...existingRequired, ...inheritedRequired])];
|
|
84
|
+
inheritedAdditionalProperties = inherited.additionalProperties;
|
|
85
|
+
}
|
|
86
|
+
// The derivative documents itself and seals itself; inheriting either
|
|
87
|
+
// would describe the wrong type and could reopen a sealed object.
|
|
88
|
+
delete adapted.description;
|
|
89
|
+
delete adapted.additionalProperties;
|
|
90
|
+
for (const [keyword, child] of Object.entries(source)) {
|
|
91
|
+
if (keyword === "allOf" ||
|
|
92
|
+
keyword === "properties" ||
|
|
93
|
+
keyword === "required" ||
|
|
94
|
+
keyword === "unevaluatedProperties") {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
adapted[keyword] = adaptValue(child);
|
|
98
|
+
}
|
|
99
|
+
adapted.properties = {
|
|
100
|
+
...optionalSchemaRecord(adapted.properties),
|
|
101
|
+
...adaptSchemaRecord(source.properties),
|
|
102
|
+
};
|
|
103
|
+
const required = [
|
|
104
|
+
...new Set([...stringArray(adapted.required), ...stringArray(source.required)]),
|
|
105
|
+
];
|
|
106
|
+
if (required.length > 0)
|
|
107
|
+
adapted.required = required;
|
|
108
|
+
else
|
|
109
|
+
delete adapted.required;
|
|
110
|
+
if ("unevaluatedProperties" in source) {
|
|
111
|
+
adapted.additionalProperties = adaptUnevaluatedProperties(source.unevaluatedProperties);
|
|
112
|
+
}
|
|
113
|
+
else if (inheritedAdditionalProperties !== undefined) {
|
|
114
|
+
adapted.additionalProperties = inheritedAdditionalProperties;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
for (const [keyword, child] of Object.entries(source)) {
|
|
119
|
+
if (keyword === "unevaluatedProperties")
|
|
120
|
+
continue;
|
|
121
|
+
adapted[keyword] = adaptValue(child);
|
|
122
|
+
}
|
|
123
|
+
if ("unevaluatedProperties" in source) {
|
|
124
|
+
adapted.additionalProperties = adaptUnevaluatedProperties(source.unevaluatedProperties);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (source.type === "integer" &&
|
|
128
|
+
(source.format === "int64" || source.format === "uint64")) {
|
|
129
|
+
adapted.type = "number";
|
|
130
|
+
adapted.multipleOf = 1;
|
|
131
|
+
delete adapted.format;
|
|
132
|
+
}
|
|
133
|
+
return adapted;
|
|
134
|
+
};
|
|
135
|
+
const adaptValue = (value) => {
|
|
136
|
+
if (Array.isArray(value))
|
|
137
|
+
return value.map(adaptValue);
|
|
138
|
+
if (typeof value === "object" && value !== null)
|
|
139
|
+
return adaptNode(value);
|
|
140
|
+
return value;
|
|
141
|
+
};
|
|
142
|
+
const adaptSchemaRecord = (value) => {
|
|
143
|
+
const record = optionalSchemaRecord(value);
|
|
144
|
+
return Object.fromEntries(Object.entries(record).map(([name, child]) => [name, adaptValue(child)]));
|
|
145
|
+
};
|
|
146
|
+
const adaptUnevaluatedProperties = (value) => isFalseSchema(value) ? false : adaptValue(value);
|
|
147
|
+
const rootWithoutDefinitions = { ...root };
|
|
148
|
+
delete rootWithoutDefinitions.$defs;
|
|
149
|
+
const adaptedRoot = adaptNode(rootWithoutDefinitions);
|
|
150
|
+
// Re-attached after adaptation so every `#/$defs/...` reference in an adapted
|
|
151
|
+
// node resolves against the adapted definitions rather than the source ones.
|
|
152
|
+
adaptedRoot.$defs = Object.fromEntries(Object.keys(sourceDefinitions).map((name) => [name, definition(name)]));
|
|
153
|
+
return adaptedRoot;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* The base definitions a node inherits from, or none if this is not the
|
|
157
|
+
* single-`$ref`-base object inheritance the emitter produces. Anything else —
|
|
158
|
+
* an `allOf` of inline schemas, a non-object base — is left for Zod to build as
|
|
159
|
+
* a real intersection.
|
|
160
|
+
*/
|
|
161
|
+
function inheritedObjectDefinitions(source, resolve) {
|
|
162
|
+
if (source.type !== "object" || !Array.isArray(source.allOf))
|
|
163
|
+
return [];
|
|
164
|
+
const names = source.allOf.map((entry) => definitionNameFromRef(entry));
|
|
165
|
+
if (names.some((name) => name === undefined))
|
|
166
|
+
return [];
|
|
167
|
+
const inherited = names.map((name) => resolve(name));
|
|
168
|
+
return inherited.every((definition) => definition.type === "object") ? inherited : [];
|
|
169
|
+
}
|
|
170
|
+
function definitionNameFromRef(value) {
|
|
171
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
172
|
+
return undefined;
|
|
173
|
+
const record = value;
|
|
174
|
+
if (Object.keys(record).length !== 1 || typeof record.$ref !== "string")
|
|
175
|
+
return undefined;
|
|
176
|
+
const prefix = "#/$defs/";
|
|
177
|
+
return record.$ref.startsWith(prefix) ? record.$ref.slice(prefix.length) : undefined;
|
|
178
|
+
}
|
|
179
|
+
function asSchemaRecord(value, context) {
|
|
180
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
181
|
+
throw new Error(`Published Qyl JSON Schema ${context} must be an object`);
|
|
182
|
+
}
|
|
183
|
+
return value;
|
|
184
|
+
}
|
|
185
|
+
function optionalSchemaRecord(value) {
|
|
186
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
187
|
+
? value
|
|
188
|
+
: {};
|
|
189
|
+
}
|
|
190
|
+
function stringArray(value) {
|
|
191
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string")
|
|
192
|
+
? value
|
|
193
|
+
: [];
|
|
194
|
+
}
|
|
195
|
+
/** `{ "not": {} }` — how the emitter spells "no further properties". */
|
|
196
|
+
function isFalseSchema(value) {
|
|
197
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
198
|
+
return false;
|
|
199
|
+
const record = value;
|
|
200
|
+
const not = record.not;
|
|
201
|
+
return Object.keys(record).length === 1 &&
|
|
202
|
+
typeof not === "object" &&
|
|
203
|
+
not !== null &&
|
|
204
|
+
!Array.isArray(not) &&
|
|
205
|
+
Object.keys(not).length === 0;
|
|
206
|
+
}
|
|
207
|
+
const zodCompatibleContractJsonSchema = adaptPublishedSchemaForZod(contractJsonSchema);
|
|
208
|
+
const zodCompatibleDefinitions = asSchemaRecord(zodCompatibleContractJsonSchema.$defs, "$defs");
|
|
209
|
+
const publishedContractSchemas = new Map();
|
|
210
|
+
/**
|
|
211
|
+
* Build a strict runtime validator for one published contract definition.
|
|
212
|
+
*
|
|
213
|
+
* `definitionName` is a key of the published schema's `$defs` — the same name
|
|
214
|
+
* the OpenAPI component and the generated C#/TypeScript DTOs carry, for example
|
|
215
|
+
* `"OTel.Traces.Span"` or `"Common.Errors.ProblemDetails"`. Pair it with the
|
|
216
|
+
* matching type from `@ancplua/qyl-api-schema/types`; the type argument is not
|
|
217
|
+
* checked against the schema, so an unrelated one produces a validator that
|
|
218
|
+
* lies about what it returns.
|
|
219
|
+
*
|
|
220
|
+
* Results are memoized per name: building a schema walks the whole definition,
|
|
221
|
+
* and the returned validator is stateless.
|
|
222
|
+
*
|
|
223
|
+
* @throws Error if no such definition is published.
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* ```ts
|
|
227
|
+
* import { publishedContractSchema } from "@ancplua/qyl-api-schema/zod";
|
|
228
|
+
* import type { Span } from "@ancplua/qyl-api-schema/types";
|
|
229
|
+
*
|
|
230
|
+
* const SpanSchema = publishedContractSchema<Span>("OTel.Traces.Span");
|
|
231
|
+
* const span = SpanSchema.parse(await response.json());
|
|
232
|
+
* ```
|
|
233
|
+
*/
|
|
234
|
+
export function publishedContractSchema(definitionName) {
|
|
235
|
+
const cached = publishedContractSchemas.get(definitionName);
|
|
236
|
+
if (cached)
|
|
237
|
+
return cached;
|
|
238
|
+
if (!Object.hasOwn(zodCompatibleDefinitions, definitionName)) {
|
|
239
|
+
throw new Error(`Published Qyl JSON Schema has no '${definitionName}' definition`);
|
|
240
|
+
}
|
|
241
|
+
const schema = z.fromJSONSchema({
|
|
242
|
+
$schema: zodCompatibleContractJsonSchema.$schema,
|
|
243
|
+
$defs: zodCompatibleDefinitions,
|
|
244
|
+
$ref: `#/$defs/${definitionName}`,
|
|
245
|
+
});
|
|
246
|
+
publishedContractSchemas.set(definitionName, schema);
|
|
247
|
+
return schema;
|
|
248
|
+
}
|
|
249
|
+
const definitionNames = Object.freeze(Object.keys(zodCompatibleDefinitions).sort());
|
|
250
|
+
/**
|
|
251
|
+
* Every definition name {@link publishedContractSchema} accepts, sorted.
|
|
252
|
+
*
|
|
253
|
+
* Enumerating the published surface is how a consumer proves it covers all of
|
|
254
|
+
* it — a contract definition that gains no validator is otherwise invisible.
|
|
255
|
+
*/
|
|
256
|
+
export function contractDefinitionNames() {
|
|
257
|
+
return definitionNames;
|
|
258
|
+
}
|
|
259
|
+
/** The published schema's `$id`, for reporting which contract a validator came from. */
|
|
260
|
+
export const contractJsonSchemaId = typeof contractJsonSchema.$id === "string"
|
|
261
|
+
? contractJsonSchema.$id
|
|
262
|
+
: "";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ancplua/qyl-api-schema",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.4.0",
|
|
4
4
|
"description": "TypeSpec source of truth for qyl API contracts. Emits OpenAPI, JSON Schema, Qyl.Api.Contracts DTOs, and TypeScript contract types; not an OpenTelemetry package, storage schema, or server implementation.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -46,6 +46,11 @@
|
|
|
46
46
|
},
|
|
47
47
|
"./json-schema": {
|
|
48
48
|
"default": "./generated/json-schema/qyl-api-schema.json"
|
|
49
|
+
},
|
|
50
|
+
"./zod": {
|
|
51
|
+
"types": "./generated/zod-runtime/index.d.ts",
|
|
52
|
+
"import": "./generated/zod-runtime/index.js",
|
|
53
|
+
"default": "./generated/zod-runtime/index.js"
|
|
49
54
|
}
|
|
50
55
|
},
|
|
51
56
|
"files": [
|
|
@@ -75,6 +80,8 @@
|
|
|
75
80
|
"generated/ts-runtime/api.js",
|
|
76
81
|
"generated/openapi/qyl.openapi.json",
|
|
77
82
|
"generated/json-schema/qyl-api-schema.json",
|
|
83
|
+
"generated/zod-runtime/index.d.ts",
|
|
84
|
+
"generated/zod-runtime/index.js",
|
|
78
85
|
"README.md",
|
|
79
86
|
"LICENSE"
|
|
80
87
|
],
|
|
@@ -87,17 +94,19 @@
|
|
|
87
94
|
"clean:generated": "node scripts/clean-generated.mjs",
|
|
88
95
|
"build:emitters": "tsc -p emitters/csharp && tsc -p emitters/ts-types && tsc -p emitters/qyl-lint",
|
|
89
96
|
"build:ts-runtime": "tsc -p tsconfig.contracts.json",
|
|
97
|
+
"build:zod-runtime": "tsc -p tsconfig.zod.json",
|
|
90
98
|
"build:json-schema": "node scripts/openapi-to-json-schema.mjs",
|
|
91
99
|
"emit:mcp-tool-schemas": "node scripts/emit-mcp-tool-schemas.mjs",
|
|
92
100
|
"emit:contract-revision": "node scripts/emit-contract-revision.mjs",
|
|
93
101
|
"prepare": "npm run build:emitters",
|
|
94
102
|
"prepack": "npm run compile",
|
|
95
|
-
"compile": "npm run clean:generated && npm run build:emitters && tsp compile main.tsp && npm run emit:contract-revision && npm run build:json-schema && npm run emit:mcp-tool-schemas && npm run build:ts-runtime",
|
|
103
|
+
"compile": "npm run clean:generated && npm run build:emitters && tsp compile main.tsp && npm run emit:contract-revision && npm run build:json-schema && npm run emit:mcp-tool-schemas && npm run build:ts-runtime && npm run build:zod-runtime",
|
|
96
104
|
"format": "tsp format **/*.tsp",
|
|
97
105
|
"lint": "npm run build:emitters && tsp compile main.tsp --no-emit --warn-as-error",
|
|
98
106
|
"lint:public": "npm run build:emitters && tsp compile index.tsp --no-emit --warn-as-error",
|
|
99
107
|
"verify:routes": "node scripts/verify-route-contracts.mjs",
|
|
100
108
|
"verify:contracts": "node scripts/verify-contract-fixtures.mjs",
|
|
109
|
+
"verify:zod-contracts": "npm run build:zod-runtime && node scripts/verify-zod-contracts.mjs",
|
|
101
110
|
"verify:lint-rules": "npm run build:emitters && node scripts/verify-lint-rules.mjs"
|
|
102
111
|
},
|
|
103
112
|
"peerDependencies": {
|
|
@@ -106,7 +115,8 @@
|
|
|
106
115
|
"@typespec/http": "^1.13.0",
|
|
107
116
|
"@typespec/openapi": "^1.13.0",
|
|
108
117
|
"@typespec/openapi3": "^1.13.0",
|
|
109
|
-
"@typespec/sse": ">=0.83.0 <0.86.0"
|
|
118
|
+
"@typespec/sse": ">=0.83.0 <0.86.0",
|
|
119
|
+
"zod": ">=4.5.0 <5"
|
|
110
120
|
},
|
|
111
121
|
"peerDependenciesMeta": {
|
|
112
122
|
"@typespec/compiler": {
|
|
@@ -126,13 +136,16 @@
|
|
|
126
136
|
},
|
|
127
137
|
"@typespec/sse": {
|
|
128
138
|
"optional": true
|
|
139
|
+
},
|
|
140
|
+
"zod": {
|
|
141
|
+
"optional": true
|
|
129
142
|
}
|
|
130
143
|
},
|
|
131
144
|
"devDependencies": {
|
|
132
145
|
"@ancplua/typespec-emit-csharp": "file:./emitters/csharp",
|
|
133
146
|
"@ancplua/typespec-emit-ts-types": "file:./emitters/ts-types",
|
|
134
147
|
"@ancplua/typespec-qyl-lint": "file:./emitters/qyl-lint",
|
|
135
|
-
"@types/node": "26.
|
|
148
|
+
"@types/node": "26.3.0",
|
|
136
149
|
"@typespec/compiler": "1.15.0",
|
|
137
150
|
"@typespec/events": "0.85.0",
|
|
138
151
|
"@typespec/http": "1.15.0",
|
|
@@ -140,6 +153,7 @@
|
|
|
140
153
|
"@typespec/openapi3": "1.15.0",
|
|
141
154
|
"@typespec/sse": "0.85.0",
|
|
142
155
|
"ajv": "8.20.0",
|
|
143
|
-
"typescript": "7.0.2"
|
|
156
|
+
"typescript": "7.0.2",
|
|
157
|
+
"zod": "4.5.4"
|
|
144
158
|
}
|
|
145
159
|
}
|