@temporary-name/server 0.0.1-alpha.0cb08cf84b740850e7d8dcd1d8b87f78af26071b
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/LICENSE +21 -0
- package/README.md +31 -0
- package/dist/adapters/aws-lambda/index.d.mts +26 -0
- package/dist/adapters/aws-lambda/index.d.ts +26 -0
- package/dist/adapters/aws-lambda/index.mjs +30 -0
- package/dist/adapters/fastify/index.d.mts +23 -0
- package/dist/adapters/fastify/index.d.ts +23 -0
- package/dist/adapters/fastify/index.mjs +30 -0
- package/dist/adapters/fetch/index.d.mts +28 -0
- package/dist/adapters/fetch/index.d.ts +28 -0
- package/dist/adapters/fetch/index.mjs +32 -0
- package/dist/adapters/node/index.d.mts +26 -0
- package/dist/adapters/node/index.d.ts +26 -0
- package/dist/adapters/node/index.mjs +30 -0
- package/dist/handler/index.d.mts +22 -0
- package/dist/handler/index.d.ts +22 -0
- package/dist/handler/index.mjs +8 -0
- package/dist/helpers/index.d.mts +149 -0
- package/dist/helpers/index.d.ts +149 -0
- package/dist/helpers/index.mjs +142 -0
- package/dist/index.d.mts +395 -0
- package/dist/index.d.ts +395 -0
- package/dist/index.mjs +831 -0
- package/dist/openapi/index.d.mts +167 -0
- package/dist/openapi/index.d.ts +167 -0
- package/dist/openapi/index.mjs +15 -0
- package/dist/shared/server.BOkueCuE.d.ts +39 -0
- package/dist/shared/server.BTVDgKdb.d.mts +9552 -0
- package/dist/shared/server.BTVDgKdb.d.ts +9552 -0
- package/dist/shared/server.C1RJffw4.mjs +30 -0
- package/dist/shared/server.CWiZN27E.d.mts +39 -0
- package/dist/shared/server.CjPiuQYH.d.mts +51 -0
- package/dist/shared/server.CjPiuQYH.d.ts +51 -0
- package/dist/shared/server.D7jtfDRy.mjs +288 -0
- package/dist/shared/server.JwVIEHL6.mjs +386 -0
- package/dist/shared/server.RQr7F4gX.mjs +1292 -0
- package/package.json +87 -0
|
@@ -0,0 +1,1292 @@
|
|
|
1
|
+
import { isAsyncIteratorObject, toArray, isObject, stringifyJSON, findDeepMatches, clone, assertNever, fallbackContractConfig } from '@temporary-name/shared';
|
|
2
|
+
import * as z from '@temporary-name/zod';
|
|
3
|
+
import { custom, safeParseAsync } from '@temporary-name/zod';
|
|
4
|
+
import { V as ValidationError, s as standardizeHTTPPath, u as unlazyResources, g as getDynamicParams } from './server.D7jtfDRy.mjs';
|
|
5
|
+
import { mapEventIterator } from '@temporary-name/standard-server';
|
|
6
|
+
import { JSONSchemaFormat, JSONSchemaContentEncoding } from '@temporary-name/server/openapi';
|
|
7
|
+
import { registry, globalRegistry } from 'zod/v4/core';
|
|
8
|
+
import { TypeName } from '@temporary-name/interop/json-schema-typed/draft-2020-12';
|
|
9
|
+
|
|
10
|
+
const EVENT_ITERATOR_DETAILS_SYMBOL = Symbol("KRUSTY_EVENT_ITERATOR_DETAILS");
|
|
11
|
+
function eventIterator(yields, returns) {
|
|
12
|
+
const schema = custom(
|
|
13
|
+
(iterator) => isAsyncIteratorObject(iterator)
|
|
14
|
+
).transform((iterator) => {
|
|
15
|
+
const mapped = mapEventIterator(iterator, {
|
|
16
|
+
async value(value, done) {
|
|
17
|
+
const schema2 = done ? returns : yields;
|
|
18
|
+
if (!schema2) {
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
const result = await safeParseAsync(schema2, value);
|
|
22
|
+
if (result.success) {
|
|
23
|
+
return result.data;
|
|
24
|
+
} else {
|
|
25
|
+
throw new Error("Event iterator validation failed", {
|
|
26
|
+
cause: new ValidationError({
|
|
27
|
+
issues: result.error.issues,
|
|
28
|
+
data: value
|
|
29
|
+
})
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
error: async (error) => error
|
|
34
|
+
});
|
|
35
|
+
return mapped;
|
|
36
|
+
});
|
|
37
|
+
schema[EVENT_ITERATOR_DETAILS_SYMBOL] = {
|
|
38
|
+
yields,
|
|
39
|
+
returns
|
|
40
|
+
};
|
|
41
|
+
return schema;
|
|
42
|
+
}
|
|
43
|
+
function getEventIteratorSchemaDetails(schema) {
|
|
44
|
+
if (schema === void 0) {
|
|
45
|
+
return void 0;
|
|
46
|
+
}
|
|
47
|
+
return schema[EVENT_ITERATOR_DETAILS_SYMBOL];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
var JsonSchemaXNativeType = /* @__PURE__ */ ((JsonSchemaXNativeType2) => {
|
|
51
|
+
JsonSchemaXNativeType2["BigInt"] = "bigint";
|
|
52
|
+
JsonSchemaXNativeType2["RegExp"] = "regexp";
|
|
53
|
+
JsonSchemaXNativeType2["Date"] = "date";
|
|
54
|
+
JsonSchemaXNativeType2["Url"] = "url";
|
|
55
|
+
JsonSchemaXNativeType2["Set"] = "set";
|
|
56
|
+
JsonSchemaXNativeType2["Map"] = "map";
|
|
57
|
+
return JsonSchemaXNativeType2;
|
|
58
|
+
})(JsonSchemaXNativeType || {});
|
|
59
|
+
|
|
60
|
+
const JSON_SCHEMA_REGISTRY = registry();
|
|
61
|
+
const JSON_SCHEMA_INPUT_REGISTRY = registry();
|
|
62
|
+
const JSON_SCHEMA_OUTPUT_REGISTRY = registry();
|
|
63
|
+
|
|
64
|
+
class ZodToJsonSchemaConverter {
|
|
65
|
+
maxLazyDepth;
|
|
66
|
+
maxStructureDepth;
|
|
67
|
+
anyJsonSchema;
|
|
68
|
+
unsupportedJsonSchema;
|
|
69
|
+
undefinedJsonSchema;
|
|
70
|
+
constructor(options = {}) {
|
|
71
|
+
this.maxLazyDepth = options.maxLazyDepth ?? 2;
|
|
72
|
+
this.maxStructureDepth = options.maxStructureDepth ?? 10;
|
|
73
|
+
this.anyJsonSchema = options.anyJsonSchema ?? {};
|
|
74
|
+
this.unsupportedJsonSchema = options.unsupportedJsonSchema ?? { not: {} };
|
|
75
|
+
this.undefinedJsonSchema = options.undefinedJsonSchema ?? { not: {} };
|
|
76
|
+
}
|
|
77
|
+
convert = (schema, options) => {
|
|
78
|
+
return this.#convert(schema, options, 0, 0);
|
|
79
|
+
};
|
|
80
|
+
#convert(schema, options, lazyDepth, structureDepth, isHandledCustomJSONSchema = false) {
|
|
81
|
+
if (structureDepth > this.maxStructureDepth) {
|
|
82
|
+
return [false, this.anyJsonSchema];
|
|
83
|
+
}
|
|
84
|
+
if (!options.minStructureDepthForRef || options.minStructureDepthForRef <= structureDepth) {
|
|
85
|
+
const components = toArray(options.components);
|
|
86
|
+
for (const component of components) {
|
|
87
|
+
if (component.schema === schema && component.allowedStrategies.includes(options.strategy)) {
|
|
88
|
+
return [component.required, { $ref: component.ref }];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (!isHandledCustomJSONSchema) {
|
|
93
|
+
const customJSONSchema = this.#getCustomJsonSchema(schema, options);
|
|
94
|
+
if (customJSONSchema) {
|
|
95
|
+
const [required, json] = this.#convert(schema, options, lazyDepth, structureDepth, true);
|
|
96
|
+
return [required, { ...json, ...customJSONSchema }];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
switch (schema._zod.def.type) {
|
|
100
|
+
case "string": {
|
|
101
|
+
const string = schema;
|
|
102
|
+
const json = { type: "string" };
|
|
103
|
+
const { minimum, maximum, format, patterns, contentEncoding } = string._zod.bag;
|
|
104
|
+
if (typeof minimum === "number") {
|
|
105
|
+
json.minLength = minimum;
|
|
106
|
+
}
|
|
107
|
+
if (typeof maximum === "number") {
|
|
108
|
+
json.maxLength = maximum;
|
|
109
|
+
}
|
|
110
|
+
if (typeof contentEncoding === "string") {
|
|
111
|
+
json.contentEncoding = this.#handleContentEncoding(contentEncoding);
|
|
112
|
+
}
|
|
113
|
+
if (typeof format === "string" && format !== "regex" && json.contentEncoding === void 0) {
|
|
114
|
+
json.format = this.#handleStringFormat(format);
|
|
115
|
+
}
|
|
116
|
+
if (patterns instanceof Set && json.contentEncoding === void 0 && json.format === void 0) {
|
|
117
|
+
for (const pattern of patterns) {
|
|
118
|
+
if (json.pattern === void 0) {
|
|
119
|
+
json.pattern = pattern.source;
|
|
120
|
+
} else {
|
|
121
|
+
json.allOf ??= [];
|
|
122
|
+
json.allOf.push({ pattern: pattern.source });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (format === "jwt" && json.contentEncoding === void 0 && json.format === void 0 && json.pattern === void 0) {
|
|
127
|
+
json.pattern = /^[\w-]+\.[\w-]+\.[\w-]+$/.source;
|
|
128
|
+
}
|
|
129
|
+
return [true, json];
|
|
130
|
+
}
|
|
131
|
+
case "number": {
|
|
132
|
+
const number = schema;
|
|
133
|
+
const json = { type: "number" };
|
|
134
|
+
const { minimum, maximum, format, multipleOf, exclusiveMaximum, exclusiveMinimum } = number._zod.bag;
|
|
135
|
+
if (typeof format === "string" && format?.includes("int")) {
|
|
136
|
+
json.type = "integer";
|
|
137
|
+
}
|
|
138
|
+
if (typeof minimum === "number") {
|
|
139
|
+
json.minimum = minimum;
|
|
140
|
+
}
|
|
141
|
+
if (typeof maximum === "number") {
|
|
142
|
+
json.maximum = maximum;
|
|
143
|
+
}
|
|
144
|
+
if (typeof exclusiveMinimum === "number") {
|
|
145
|
+
json.exclusiveMinimum = exclusiveMinimum;
|
|
146
|
+
}
|
|
147
|
+
if (typeof exclusiveMaximum === "number") {
|
|
148
|
+
json.exclusiveMaximum = exclusiveMaximum;
|
|
149
|
+
}
|
|
150
|
+
if (typeof multipleOf === "number") {
|
|
151
|
+
json.multipleOf = multipleOf;
|
|
152
|
+
}
|
|
153
|
+
return [true, json];
|
|
154
|
+
}
|
|
155
|
+
case "boolean": {
|
|
156
|
+
return [true, { type: "boolean" }];
|
|
157
|
+
}
|
|
158
|
+
case "bigint": {
|
|
159
|
+
return [
|
|
160
|
+
true,
|
|
161
|
+
{
|
|
162
|
+
type: "string",
|
|
163
|
+
pattern: "^-?[0-9]+$",
|
|
164
|
+
"x-native-type": JsonSchemaXNativeType.BigInt
|
|
165
|
+
}
|
|
166
|
+
];
|
|
167
|
+
}
|
|
168
|
+
case "date": {
|
|
169
|
+
return [
|
|
170
|
+
true,
|
|
171
|
+
{
|
|
172
|
+
type: "string",
|
|
173
|
+
format: JSONSchemaFormat.DateTime,
|
|
174
|
+
"x-native-type": JsonSchemaXNativeType.Date
|
|
175
|
+
}
|
|
176
|
+
];
|
|
177
|
+
}
|
|
178
|
+
case "null": {
|
|
179
|
+
return [true, { type: "null" }];
|
|
180
|
+
}
|
|
181
|
+
case "undefined":
|
|
182
|
+
case "void": {
|
|
183
|
+
return [false, this.undefinedJsonSchema];
|
|
184
|
+
}
|
|
185
|
+
case "any": {
|
|
186
|
+
return [false, this.anyJsonSchema];
|
|
187
|
+
}
|
|
188
|
+
case "unknown": {
|
|
189
|
+
return [false, this.anyJsonSchema];
|
|
190
|
+
}
|
|
191
|
+
case "never": {
|
|
192
|
+
return [true, this.unsupportedJsonSchema];
|
|
193
|
+
}
|
|
194
|
+
case "array": {
|
|
195
|
+
const array = schema;
|
|
196
|
+
const json = { type: "array" };
|
|
197
|
+
const { minimum, maximum } = array._zod.bag;
|
|
198
|
+
if (typeof minimum === "number") {
|
|
199
|
+
json.minItems = minimum;
|
|
200
|
+
}
|
|
201
|
+
if (typeof maximum === "number") {
|
|
202
|
+
json.maxItems = maximum;
|
|
203
|
+
}
|
|
204
|
+
json.items = this.#handleArrayItemJsonSchema(
|
|
205
|
+
this.#convert(array._zod.def.element, options, lazyDepth, structureDepth + 1),
|
|
206
|
+
options
|
|
207
|
+
);
|
|
208
|
+
return [true, json];
|
|
209
|
+
}
|
|
210
|
+
case "object": {
|
|
211
|
+
const object = schema;
|
|
212
|
+
const json = { type: "object" };
|
|
213
|
+
let required = true;
|
|
214
|
+
for (const [key, value] of Object.entries(object._zod.def.shape)) {
|
|
215
|
+
const [itemRequired, itemJson] = this.#convert(value, options, lazyDepth, structureDepth + 1);
|
|
216
|
+
json.properties ??= {};
|
|
217
|
+
json.properties[key] = itemJson;
|
|
218
|
+
if (itemRequired) {
|
|
219
|
+
json.required ??= [];
|
|
220
|
+
json.required.push(key);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const { catchall } = object._zod.def;
|
|
224
|
+
if (!catchall || catchall._zod.def.type === "never") {
|
|
225
|
+
json.additionalProperties = false;
|
|
226
|
+
} else if (catchall) {
|
|
227
|
+
const [_, addJson] = this.#convert(catchall, options, lazyDepth, structureDepth + 1);
|
|
228
|
+
json.additionalProperties = addJson;
|
|
229
|
+
}
|
|
230
|
+
if (options.strategy === "input" && structureDepth === 0 && (!json.properties || Object.keys(json.properties).length === 0)) {
|
|
231
|
+
required = false;
|
|
232
|
+
}
|
|
233
|
+
return [required, json];
|
|
234
|
+
}
|
|
235
|
+
case "union": {
|
|
236
|
+
const union = schema;
|
|
237
|
+
const anyOf = [];
|
|
238
|
+
let required = true;
|
|
239
|
+
for (const item of union._zod.def.options) {
|
|
240
|
+
const [itemRequired, itemJson] = this.#convert(item, options, lazyDepth, structureDepth);
|
|
241
|
+
if (!itemRequired) {
|
|
242
|
+
required = false;
|
|
243
|
+
}
|
|
244
|
+
if (options.strategy === "input") {
|
|
245
|
+
if (itemJson !== this.undefinedJsonSchema && itemJson !== this.unsupportedJsonSchema) {
|
|
246
|
+
anyOf.push(itemJson);
|
|
247
|
+
}
|
|
248
|
+
} else {
|
|
249
|
+
if (itemJson !== this.undefinedJsonSchema) {
|
|
250
|
+
anyOf.push(itemJson);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return [required, anyOf.length === 1 ? anyOf[0] : { anyOf }];
|
|
255
|
+
}
|
|
256
|
+
case "intersection": {
|
|
257
|
+
const intersection = schema;
|
|
258
|
+
const json = { allOf: [] };
|
|
259
|
+
let required = false;
|
|
260
|
+
for (const item of [intersection._zod.def.left, intersection._zod.def.right]) {
|
|
261
|
+
const [itemRequired, itemJson] = this.#convert(item, options, lazyDepth, structureDepth);
|
|
262
|
+
json.allOf.push(itemJson);
|
|
263
|
+
if (itemRequired) {
|
|
264
|
+
required = true;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return [required, json];
|
|
268
|
+
}
|
|
269
|
+
case "tuple": {
|
|
270
|
+
const tuple = schema;
|
|
271
|
+
const json = { type: "array", prefixItems: [] };
|
|
272
|
+
for (const item of tuple._zod.def.items) {
|
|
273
|
+
json.prefixItems.push(
|
|
274
|
+
this.#handleArrayItemJsonSchema(
|
|
275
|
+
this.#convert(item, options, lazyDepth, structureDepth + 1),
|
|
276
|
+
options
|
|
277
|
+
)
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
if (tuple._zod.def.rest) {
|
|
281
|
+
json.items = this.#handleArrayItemJsonSchema(
|
|
282
|
+
this.#convert(tuple._zod.def.rest, options, lazyDepth, structureDepth + 1),
|
|
283
|
+
options
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
const { minimum, maximum } = tuple._zod.bag;
|
|
287
|
+
if (typeof minimum === "number") {
|
|
288
|
+
json.minItems = minimum;
|
|
289
|
+
}
|
|
290
|
+
if (typeof maximum === "number") {
|
|
291
|
+
json.maxItems = maximum;
|
|
292
|
+
}
|
|
293
|
+
return [true, json];
|
|
294
|
+
}
|
|
295
|
+
case "record": {
|
|
296
|
+
const record = schema;
|
|
297
|
+
const json = { type: "object" };
|
|
298
|
+
json.propertyNames = this.#convert(
|
|
299
|
+
record._zod.def.keyType,
|
|
300
|
+
options,
|
|
301
|
+
lazyDepth,
|
|
302
|
+
structureDepth + 1
|
|
303
|
+
)[1];
|
|
304
|
+
json.additionalProperties = this.#convert(
|
|
305
|
+
record._zod.def.valueType,
|
|
306
|
+
options,
|
|
307
|
+
lazyDepth,
|
|
308
|
+
structureDepth + 1
|
|
309
|
+
)[1];
|
|
310
|
+
return [true, json];
|
|
311
|
+
}
|
|
312
|
+
case "map": {
|
|
313
|
+
const map = schema;
|
|
314
|
+
return [
|
|
315
|
+
true,
|
|
316
|
+
{
|
|
317
|
+
type: "array",
|
|
318
|
+
items: {
|
|
319
|
+
type: "array",
|
|
320
|
+
prefixItems: [
|
|
321
|
+
this.#handleArrayItemJsonSchema(
|
|
322
|
+
this.#convert(map._zod.def.keyType, options, lazyDepth, structureDepth + 1),
|
|
323
|
+
options
|
|
324
|
+
),
|
|
325
|
+
this.#handleArrayItemJsonSchema(
|
|
326
|
+
this.#convert(map._zod.def.valueType, options, lazyDepth, structureDepth + 1),
|
|
327
|
+
options
|
|
328
|
+
)
|
|
329
|
+
],
|
|
330
|
+
maxItems: 2,
|
|
331
|
+
minItems: 2
|
|
332
|
+
},
|
|
333
|
+
"x-native-type": JsonSchemaXNativeType.Map
|
|
334
|
+
}
|
|
335
|
+
];
|
|
336
|
+
}
|
|
337
|
+
case "set": {
|
|
338
|
+
const set = schema;
|
|
339
|
+
return [
|
|
340
|
+
true,
|
|
341
|
+
{
|
|
342
|
+
type: "array",
|
|
343
|
+
uniqueItems: true,
|
|
344
|
+
items: this.#handleArrayItemJsonSchema(
|
|
345
|
+
this.#convert(set._zod.def.valueType, options, lazyDepth, structureDepth + 1),
|
|
346
|
+
options
|
|
347
|
+
),
|
|
348
|
+
"x-native-type": JsonSchemaXNativeType.Set
|
|
349
|
+
}
|
|
350
|
+
];
|
|
351
|
+
}
|
|
352
|
+
case "enum": {
|
|
353
|
+
const enum_ = schema;
|
|
354
|
+
return [true, { enum: Object.values(enum_._zod.def.entries) }];
|
|
355
|
+
}
|
|
356
|
+
case "literal": {
|
|
357
|
+
const literal = schema;
|
|
358
|
+
let required = true;
|
|
359
|
+
const values = /* @__PURE__ */ new Set();
|
|
360
|
+
for (const value of literal._zod.def.values) {
|
|
361
|
+
if (value === void 0) {
|
|
362
|
+
required = false;
|
|
363
|
+
} else {
|
|
364
|
+
values.add(typeof value === "bigint" ? value.toString() : value);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
const json = values.size === 0 ? this.undefinedJsonSchema : values.size === 1 ? { const: values.values().next().value } : { enum: Array.from(values) };
|
|
368
|
+
return [required, json];
|
|
369
|
+
}
|
|
370
|
+
case "file": {
|
|
371
|
+
const file = schema;
|
|
372
|
+
const oneOf = [];
|
|
373
|
+
const { mime } = file._zod.bag;
|
|
374
|
+
if (mime === void 0 || Array.isArray(mime) && mime.every((m) => typeof m === "string")) {
|
|
375
|
+
for (const type of mime ?? ["*/*"]) {
|
|
376
|
+
oneOf.push({
|
|
377
|
+
type: "string",
|
|
378
|
+
contentMediaType: type
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return [true, oneOf.length === 1 ? oneOf[0] : { anyOf: oneOf }];
|
|
383
|
+
}
|
|
384
|
+
case "transform": {
|
|
385
|
+
return [false, this.anyJsonSchema];
|
|
386
|
+
}
|
|
387
|
+
case "nullable": {
|
|
388
|
+
const nullable = schema;
|
|
389
|
+
const [required, json] = this.#convert(
|
|
390
|
+
nullable._zod.def.innerType,
|
|
391
|
+
options,
|
|
392
|
+
lazyDepth,
|
|
393
|
+
structureDepth
|
|
394
|
+
);
|
|
395
|
+
return [required, { anyOf: [json, { type: "null" }] }];
|
|
396
|
+
}
|
|
397
|
+
case "nonoptional": {
|
|
398
|
+
const nonoptional = schema;
|
|
399
|
+
const [, json] = this.#convert(nonoptional._zod.def.innerType, options, lazyDepth, structureDepth);
|
|
400
|
+
return [true, json];
|
|
401
|
+
}
|
|
402
|
+
case "success": {
|
|
403
|
+
return [true, { type: "boolean" }];
|
|
404
|
+
}
|
|
405
|
+
case "default":
|
|
406
|
+
case "prefault": {
|
|
407
|
+
const default_ = schema;
|
|
408
|
+
const [, json] = this.#convert(default_._zod.def.innerType, options, lazyDepth, structureDepth);
|
|
409
|
+
return [
|
|
410
|
+
false,
|
|
411
|
+
{
|
|
412
|
+
...json,
|
|
413
|
+
default: default_._zod.def.defaultValue
|
|
414
|
+
}
|
|
415
|
+
];
|
|
416
|
+
}
|
|
417
|
+
case "catch": {
|
|
418
|
+
const catch_ = schema;
|
|
419
|
+
return this.#convert(catch_._zod.def.innerType, options, lazyDepth, structureDepth);
|
|
420
|
+
}
|
|
421
|
+
case "nan": {
|
|
422
|
+
return [true, options.strategy === "input" ? this.unsupportedJsonSchema : { type: "null" }];
|
|
423
|
+
}
|
|
424
|
+
case "pipe": {
|
|
425
|
+
const pipe = schema;
|
|
426
|
+
return this.#convert(
|
|
427
|
+
options.strategy === "input" ? pipe._zod.def.in : pipe._zod.def.out,
|
|
428
|
+
options,
|
|
429
|
+
lazyDepth,
|
|
430
|
+
structureDepth
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
case "readonly": {
|
|
434
|
+
const readonly_ = schema;
|
|
435
|
+
const [required, json] = this.#convert(
|
|
436
|
+
readonly_._zod.def.innerType,
|
|
437
|
+
options,
|
|
438
|
+
lazyDepth,
|
|
439
|
+
structureDepth
|
|
440
|
+
);
|
|
441
|
+
return [required, { ...json, readOnly: true }];
|
|
442
|
+
}
|
|
443
|
+
case "template_literal": {
|
|
444
|
+
const templateLiteral = schema;
|
|
445
|
+
return [
|
|
446
|
+
true,
|
|
447
|
+
{
|
|
448
|
+
type: "string",
|
|
449
|
+
pattern: templateLiteral._zod.pattern.source
|
|
450
|
+
}
|
|
451
|
+
];
|
|
452
|
+
}
|
|
453
|
+
case "optional": {
|
|
454
|
+
const optional = schema;
|
|
455
|
+
const [, json] = this.#convert(optional._zod.def.innerType, options, lazyDepth, structureDepth);
|
|
456
|
+
return [false, json];
|
|
457
|
+
}
|
|
458
|
+
case "lazy": {
|
|
459
|
+
const lazy = schema;
|
|
460
|
+
const currentLazyDepth = lazyDepth + 1;
|
|
461
|
+
if (currentLazyDepth > this.maxLazyDepth) {
|
|
462
|
+
return [false, this.anyJsonSchema];
|
|
463
|
+
}
|
|
464
|
+
return this.#convert(lazy._zod.def.getter(), options, currentLazyDepth, structureDepth);
|
|
465
|
+
}
|
|
466
|
+
default: {
|
|
467
|
+
schema._zod.def.type;
|
|
468
|
+
return [true, this.unsupportedJsonSchema];
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
#getCustomJsonSchema(schema, options) {
|
|
473
|
+
if (options.strategy === "input" && JSON_SCHEMA_INPUT_REGISTRY.has(schema)) {
|
|
474
|
+
return JSON_SCHEMA_INPUT_REGISTRY.get(schema);
|
|
475
|
+
}
|
|
476
|
+
if (options.strategy === "output" && JSON_SCHEMA_OUTPUT_REGISTRY.has(schema)) {
|
|
477
|
+
return JSON_SCHEMA_OUTPUT_REGISTRY.get(schema);
|
|
478
|
+
}
|
|
479
|
+
if (JSON_SCHEMA_REGISTRY.has(schema)) {
|
|
480
|
+
return JSON_SCHEMA_REGISTRY.get(schema);
|
|
481
|
+
}
|
|
482
|
+
const global = globalRegistry.get(schema);
|
|
483
|
+
if (global) {
|
|
484
|
+
return {
|
|
485
|
+
title: global.title,
|
|
486
|
+
description: global.description,
|
|
487
|
+
examples: Array.isArray(global.examples) ? global.examples : void 0
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
#handleArrayItemJsonSchema([required, schema], options) {
|
|
492
|
+
if (required || options.strategy === "input" || schema.default !== void 0) {
|
|
493
|
+
return schema;
|
|
494
|
+
}
|
|
495
|
+
if (schema === this.undefinedJsonSchema) {
|
|
496
|
+
return { type: "null" };
|
|
497
|
+
}
|
|
498
|
+
return {
|
|
499
|
+
anyOf: [
|
|
500
|
+
// schema can contain { type: 'null' } so we should use anyOf instead of oneOf
|
|
501
|
+
schema,
|
|
502
|
+
{ type: "null" }
|
|
503
|
+
]
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
#handleStringFormat(format) {
|
|
507
|
+
if (format === "guid") {
|
|
508
|
+
return JSONSchemaFormat.UUID;
|
|
509
|
+
}
|
|
510
|
+
if (format === "url") {
|
|
511
|
+
return JSONSchemaFormat.URI;
|
|
512
|
+
}
|
|
513
|
+
if (format === "datetime") {
|
|
514
|
+
return JSONSchemaFormat.DateTime;
|
|
515
|
+
}
|
|
516
|
+
return Object.values(JSONSchemaFormat).includes(format) ? format : void 0;
|
|
517
|
+
}
|
|
518
|
+
#handleContentEncoding(contentEncoding) {
|
|
519
|
+
return Object.values(JSONSchemaContentEncoding).includes(contentEncoding) ? contentEncoding : void 0;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const OPERATION_EXTENDER_SYMBOL = Symbol("KRUSTY_OPERATION_EXTENDER");
|
|
524
|
+
function customOpenAPIOperation(o, extend) {
|
|
525
|
+
return new Proxy(o, {
|
|
526
|
+
get(target, prop, receiver) {
|
|
527
|
+
if (prop === OPERATION_EXTENDER_SYMBOL) {
|
|
528
|
+
return extend;
|
|
529
|
+
}
|
|
530
|
+
return Reflect.get(target, prop, receiver);
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
function getCustomOpenAPIOperation(o) {
|
|
535
|
+
return o[OPERATION_EXTENDER_SYMBOL];
|
|
536
|
+
}
|
|
537
|
+
function applyCustomOpenAPIOperation(operation, contract) {
|
|
538
|
+
const operationCustoms = [];
|
|
539
|
+
for (const middleware of contract["~def"].middlewares) {
|
|
540
|
+
const maybeExtender = getCustomOpenAPIOperation(middleware);
|
|
541
|
+
if (maybeExtender) {
|
|
542
|
+
operationCustoms.push(maybeExtender);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
let currentOperation = operation;
|
|
546
|
+
for (const custom of operationCustoms) {
|
|
547
|
+
if (typeof custom === "function") {
|
|
548
|
+
currentOperation = custom(currentOperation, contract);
|
|
549
|
+
} else {
|
|
550
|
+
currentOperation = {
|
|
551
|
+
...currentOperation,
|
|
552
|
+
...custom
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
return currentOperation;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const LOGIC_KEYWORDS = [
|
|
560
|
+
"$dynamicRef",
|
|
561
|
+
"$ref",
|
|
562
|
+
"additionalItems",
|
|
563
|
+
"additionalProperties",
|
|
564
|
+
"allOf",
|
|
565
|
+
"anyOf",
|
|
566
|
+
"const",
|
|
567
|
+
"contains",
|
|
568
|
+
"contentEncoding",
|
|
569
|
+
"contentMediaType",
|
|
570
|
+
"contentSchema",
|
|
571
|
+
"dependencies",
|
|
572
|
+
"dependentRequired",
|
|
573
|
+
"dependentSchemas",
|
|
574
|
+
"else",
|
|
575
|
+
"enum",
|
|
576
|
+
"exclusiveMaximum",
|
|
577
|
+
"exclusiveMinimum",
|
|
578
|
+
"format",
|
|
579
|
+
"if",
|
|
580
|
+
"items",
|
|
581
|
+
"maxContains",
|
|
582
|
+
"maximum",
|
|
583
|
+
"maxItems",
|
|
584
|
+
"maxLength",
|
|
585
|
+
"maxProperties",
|
|
586
|
+
"minContains",
|
|
587
|
+
"minimum",
|
|
588
|
+
"minItems",
|
|
589
|
+
"minLength",
|
|
590
|
+
"minProperties",
|
|
591
|
+
"multipleOf",
|
|
592
|
+
"not",
|
|
593
|
+
"oneOf",
|
|
594
|
+
"pattern",
|
|
595
|
+
"patternProperties",
|
|
596
|
+
"prefixItems",
|
|
597
|
+
"properties",
|
|
598
|
+
"propertyNames",
|
|
599
|
+
"required",
|
|
600
|
+
"then",
|
|
601
|
+
"type",
|
|
602
|
+
"unevaluatedItems",
|
|
603
|
+
"unevaluatedProperties",
|
|
604
|
+
"uniqueItems"
|
|
605
|
+
];
|
|
606
|
+
|
|
607
|
+
function isFileSchema(schema) {
|
|
608
|
+
return isObject(schema) && schema.type === "string" && typeof schema.contentMediaType === "string";
|
|
609
|
+
}
|
|
610
|
+
function isObjectSchema(schema) {
|
|
611
|
+
return isObject(schema) && schema.type === "object";
|
|
612
|
+
}
|
|
613
|
+
function isAnySchema(schema) {
|
|
614
|
+
if (schema === true) {
|
|
615
|
+
return true;
|
|
616
|
+
}
|
|
617
|
+
if (Object.keys(schema).every((k) => !LOGIC_KEYWORDS.includes(k))) {
|
|
618
|
+
return true;
|
|
619
|
+
}
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
function separateObjectSchema(schema, separatedProperties) {
|
|
623
|
+
if (Object.keys(schema).some(
|
|
624
|
+
(k) => !["type", "properties", "required", "additionalProperties"].includes(k) && LOGIC_KEYWORDS.includes(k) && schema[k] !== void 0
|
|
625
|
+
)) {
|
|
626
|
+
return [{ type: "object" }, schema];
|
|
627
|
+
}
|
|
628
|
+
const matched = { ...schema };
|
|
629
|
+
const rest = { ...schema };
|
|
630
|
+
matched.properties = separatedProperties.reduce((acc, key) => {
|
|
631
|
+
const keySchema = schema.properties?.[key] ?? schema.additionalProperties;
|
|
632
|
+
if (keySchema !== void 0) {
|
|
633
|
+
acc[key] = keySchema;
|
|
634
|
+
}
|
|
635
|
+
return acc;
|
|
636
|
+
}, {});
|
|
637
|
+
matched.required = schema.required?.filter((key) => separatedProperties.includes(key));
|
|
638
|
+
matched.examples = schema.examples?.map((example) => {
|
|
639
|
+
if (!isObject(example)) {
|
|
640
|
+
return example;
|
|
641
|
+
}
|
|
642
|
+
return Object.entries(example).reduce(
|
|
643
|
+
(acc, [key, value]) => {
|
|
644
|
+
if (separatedProperties.includes(key)) {
|
|
645
|
+
acc[key] = value;
|
|
646
|
+
}
|
|
647
|
+
return acc;
|
|
648
|
+
},
|
|
649
|
+
{}
|
|
650
|
+
);
|
|
651
|
+
});
|
|
652
|
+
rest.properties = schema.properties && Object.entries(schema.properties).filter(([key]) => !separatedProperties.includes(key)).reduce(
|
|
653
|
+
(acc, [key, value]) => {
|
|
654
|
+
acc[key] = value;
|
|
655
|
+
return acc;
|
|
656
|
+
},
|
|
657
|
+
{}
|
|
658
|
+
);
|
|
659
|
+
rest.required = schema.required?.filter((key) => !separatedProperties.includes(key));
|
|
660
|
+
rest.examples = schema.examples?.map((example) => {
|
|
661
|
+
if (!isObject(example)) {
|
|
662
|
+
return example;
|
|
663
|
+
}
|
|
664
|
+
return Object.entries(example).reduce(
|
|
665
|
+
(acc, [key, value]) => {
|
|
666
|
+
if (!separatedProperties.includes(key)) {
|
|
667
|
+
acc[key] = value;
|
|
668
|
+
}
|
|
669
|
+
return acc;
|
|
670
|
+
},
|
|
671
|
+
{}
|
|
672
|
+
);
|
|
673
|
+
});
|
|
674
|
+
return [matched, rest];
|
|
675
|
+
}
|
|
676
|
+
function filterSchemaBranches(schema, check, matches = []) {
|
|
677
|
+
if (check(schema)) {
|
|
678
|
+
matches.push(schema);
|
|
679
|
+
return [matches, void 0];
|
|
680
|
+
}
|
|
681
|
+
if (isObject(schema)) {
|
|
682
|
+
for (const keyword of ["anyOf", "oneOf"]) {
|
|
683
|
+
if (schema[keyword] && Object.keys(schema).every((k) => k === keyword || !LOGIC_KEYWORDS.includes(k))) {
|
|
684
|
+
const rest = schema[keyword].map((s) => filterSchemaBranches(s, check, matches)[1]).filter((v) => !!v);
|
|
685
|
+
if (rest.length === 1 && typeof rest[0] === "object") {
|
|
686
|
+
return [matches, { ...schema, [keyword]: void 0, ...rest[0] }];
|
|
687
|
+
}
|
|
688
|
+
return [matches, { ...schema, [keyword]: rest }];
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
return [matches, schema];
|
|
693
|
+
}
|
|
694
|
+
function applySchemaOptionality(required, schema) {
|
|
695
|
+
if (required) {
|
|
696
|
+
return schema;
|
|
697
|
+
}
|
|
698
|
+
return {
|
|
699
|
+
anyOf: [schema, { not: {} }]
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
function expandUnionSchema(schema) {
|
|
703
|
+
if (typeof schema === "object") {
|
|
704
|
+
for (const keyword of ["anyOf", "oneOf"]) {
|
|
705
|
+
if (schema[keyword] && Object.keys(schema).every((k) => k === keyword || !LOGIC_KEYWORDS.includes(k))) {
|
|
706
|
+
return schema[keyword].flatMap((s) => expandUnionSchema(s));
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return [schema];
|
|
711
|
+
}
|
|
712
|
+
function expandArrayableSchema(schema) {
|
|
713
|
+
const schemas = expandUnionSchema(schema);
|
|
714
|
+
if (schemas.length !== 2) {
|
|
715
|
+
return void 0;
|
|
716
|
+
}
|
|
717
|
+
const arraySchema = schemas.find(
|
|
718
|
+
(s) => typeof s === "object" && s.type === "array" && Object.keys(s).filter((k) => LOGIC_KEYWORDS.includes(k)).every((k) => k === "type" || k === "items")
|
|
719
|
+
);
|
|
720
|
+
if (arraySchema === void 0) {
|
|
721
|
+
return void 0;
|
|
722
|
+
}
|
|
723
|
+
const items1 = arraySchema.items;
|
|
724
|
+
const items2 = schemas.find((s) => s !== arraySchema);
|
|
725
|
+
if (stringifyJSON(items1) !== stringifyJSON(items2)) {
|
|
726
|
+
return void 0;
|
|
727
|
+
}
|
|
728
|
+
return [items2, arraySchema];
|
|
729
|
+
}
|
|
730
|
+
const PRIMITIVE_SCHEMA_TYPES = /* @__PURE__ */ new Set([
|
|
731
|
+
TypeName.String,
|
|
732
|
+
TypeName.Number,
|
|
733
|
+
TypeName.Integer,
|
|
734
|
+
TypeName.Boolean,
|
|
735
|
+
TypeName.Null
|
|
736
|
+
]);
|
|
737
|
+
function isPrimitiveSchema(schema) {
|
|
738
|
+
return expandUnionSchema(schema).every((s) => {
|
|
739
|
+
if (typeof s === "boolean") {
|
|
740
|
+
return false;
|
|
741
|
+
}
|
|
742
|
+
if (typeof s.type === "string" && PRIMITIVE_SCHEMA_TYPES.has(s.type)) {
|
|
743
|
+
return true;
|
|
744
|
+
}
|
|
745
|
+
if (s.const !== void 0) {
|
|
746
|
+
return true;
|
|
747
|
+
}
|
|
748
|
+
return false;
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
function toOpenAPIPath(path) {
|
|
753
|
+
return standardizeHTTPPath(path).replace(/\/\{\+([^}]+)\}/g, "/{$1}");
|
|
754
|
+
}
|
|
755
|
+
function toOpenAPIMethod(method) {
|
|
756
|
+
return method.toLocaleLowerCase();
|
|
757
|
+
}
|
|
758
|
+
function toOpenAPIContent(schema) {
|
|
759
|
+
const content = {};
|
|
760
|
+
const [matches, restSchema] = filterSchemaBranches(schema, isFileSchema);
|
|
761
|
+
for (const file of matches) {
|
|
762
|
+
content[file.contentMediaType] = {
|
|
763
|
+
schema: toOpenAPISchema(file)
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
if (restSchema !== void 0) {
|
|
767
|
+
content["application/json"] = {
|
|
768
|
+
schema: toOpenAPISchema(restSchema)
|
|
769
|
+
};
|
|
770
|
+
const isStillHasFileSchema = findDeepMatches((v) => isObject(v) && isFileSchema(v), restSchema).values.length > 0;
|
|
771
|
+
if (isStillHasFileSchema) {
|
|
772
|
+
content["multipart/form-data"] = {
|
|
773
|
+
schema: toOpenAPISchema(restSchema)
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
return content;
|
|
778
|
+
}
|
|
779
|
+
function toOpenAPIEventIteratorContent([yieldsRequired, yieldsSchema], [returnsRequired, returnsSchema]) {
|
|
780
|
+
return {
|
|
781
|
+
"text/event-stream": {
|
|
782
|
+
schema: toOpenAPISchema({
|
|
783
|
+
oneOf: [
|
|
784
|
+
{
|
|
785
|
+
type: "object",
|
|
786
|
+
properties: {
|
|
787
|
+
event: { const: "message" },
|
|
788
|
+
data: yieldsSchema,
|
|
789
|
+
id: { type: "string" },
|
|
790
|
+
retry: { type: "number" }
|
|
791
|
+
},
|
|
792
|
+
required: yieldsRequired ? ["event", "data"] : ["event"]
|
|
793
|
+
},
|
|
794
|
+
{
|
|
795
|
+
type: "object",
|
|
796
|
+
properties: {
|
|
797
|
+
event: { const: "done" },
|
|
798
|
+
data: returnsSchema,
|
|
799
|
+
id: { type: "string" },
|
|
800
|
+
retry: { type: "number" }
|
|
801
|
+
},
|
|
802
|
+
required: returnsRequired ? ["event", "data"] : ["event"]
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
type: "object",
|
|
806
|
+
properties: {
|
|
807
|
+
event: { const: "error" },
|
|
808
|
+
data: {},
|
|
809
|
+
id: { type: "string" },
|
|
810
|
+
retry: { type: "number" }
|
|
811
|
+
},
|
|
812
|
+
required: ["event"]
|
|
813
|
+
}
|
|
814
|
+
]
|
|
815
|
+
})
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
function toOpenAPIParameters(schema, parameterIn) {
|
|
820
|
+
const parameters = [];
|
|
821
|
+
for (const key in schema.properties) {
|
|
822
|
+
const keySchema = schema.properties[key];
|
|
823
|
+
let isDeepObjectStyle = true;
|
|
824
|
+
if (parameterIn !== "query") {
|
|
825
|
+
isDeepObjectStyle = false;
|
|
826
|
+
} else if (isPrimitiveSchema(keySchema)) {
|
|
827
|
+
isDeepObjectStyle = false;
|
|
828
|
+
} else {
|
|
829
|
+
const [item] = expandArrayableSchema(keySchema) ?? [];
|
|
830
|
+
if (item !== void 0 && isPrimitiveSchema(item)) {
|
|
831
|
+
isDeepObjectStyle = false;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
parameters.push({
|
|
835
|
+
name: key,
|
|
836
|
+
in: parameterIn,
|
|
837
|
+
required: schema.required?.includes(key),
|
|
838
|
+
schema: toOpenAPISchema(keySchema),
|
|
839
|
+
style: isDeepObjectStyle ? "deepObject" : void 0,
|
|
840
|
+
explode: isDeepObjectStyle ? true : void 0,
|
|
841
|
+
allowEmptyValue: parameterIn === "query" ? true : void 0,
|
|
842
|
+
allowReserved: parameterIn === "query" ? true : void 0
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
return parameters;
|
|
846
|
+
}
|
|
847
|
+
function checkParamsSchema(schema, params) {
|
|
848
|
+
const properties = Object.keys(schema.properties ?? {});
|
|
849
|
+
const required = schema.required ?? [];
|
|
850
|
+
if (properties.length !== params.length || properties.some((v) => !params.includes(v))) {
|
|
851
|
+
return false;
|
|
852
|
+
}
|
|
853
|
+
if (required.length !== params.length || required.some((v) => !params.includes(v))) {
|
|
854
|
+
return false;
|
|
855
|
+
}
|
|
856
|
+
return true;
|
|
857
|
+
}
|
|
858
|
+
function toOpenAPISchema(schema) {
|
|
859
|
+
if (schema === true) {
|
|
860
|
+
return {};
|
|
861
|
+
} else if (schema === false) {
|
|
862
|
+
return { not: {} };
|
|
863
|
+
}
|
|
864
|
+
if (schema.type === "object" && !schema.properties && schema.additionalProperties === false) {
|
|
865
|
+
return {
|
|
866
|
+
...schema,
|
|
867
|
+
"x-stainless-empty-object": true
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
return schema;
|
|
871
|
+
}
|
|
872
|
+
const OPENAPI_JSON_SCHEMA_REF_PREFIX = "#/components/schemas/";
|
|
873
|
+
function resolveOpenAPIJsonSchemaRef(doc, schema) {
|
|
874
|
+
if (typeof schema !== "object" || !schema.$ref?.startsWith(OPENAPI_JSON_SCHEMA_REF_PREFIX)) {
|
|
875
|
+
return schema;
|
|
876
|
+
}
|
|
877
|
+
const name = schema.$ref.slice(OPENAPI_JSON_SCHEMA_REF_PREFIX.length);
|
|
878
|
+
const resolved = doc.components?.schemas?.[name];
|
|
879
|
+
return resolved ?? schema;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
class OpenAPIGeneratorError extends Error {
|
|
883
|
+
}
|
|
884
|
+
async function generateOpenApiSpec(resources, options = {}) {
|
|
885
|
+
const converter = typeof options.schemaConverter === "function" ? options.schemaConverter : new ZodToJsonSchemaConverter(options.schemaConverter ?? {}).convert;
|
|
886
|
+
const doc = {
|
|
887
|
+
...clone(options.spec),
|
|
888
|
+
info: options.spec?.info ?? { title: "API Reference", version: "0.0.0" },
|
|
889
|
+
openapi: "3.1.1"
|
|
890
|
+
};
|
|
891
|
+
const contractsById = /* @__PURE__ */ new Map();
|
|
892
|
+
const apiResources = await unlazyResources(resources);
|
|
893
|
+
for (const apiResource of apiResources) {
|
|
894
|
+
for (const [methodName, contract] of Object.entries(apiResource.methods)) {
|
|
895
|
+
const operationId = `${apiResource.name}.${methodName}`;
|
|
896
|
+
if (contractsById.has(operationId)) {
|
|
897
|
+
throw new Error(`Multiple ApiResources have the same name: "${operationId}"`);
|
|
898
|
+
}
|
|
899
|
+
contractsById.set(operationId, contract);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
const combinedErrorMap = {};
|
|
903
|
+
for (const contract of contractsById.values()) {
|
|
904
|
+
const { errorMap } = contract["~def"];
|
|
905
|
+
for (const key in errorMap) {
|
|
906
|
+
if (!errorMap[key]) {
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
if (key in combinedErrorMap && combinedErrorMap[key] !== errorMap[key]) {
|
|
910
|
+
throw new OpenAPIGeneratorError(
|
|
911
|
+
`Conflicting error map entries for key "${key}". Please ensure that all error maps have unique keys or identical values for shared keys.`
|
|
912
|
+
);
|
|
913
|
+
}
|
|
914
|
+
combinedErrorMap[key] = errorMap[key];
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
const { baseSchemaConvertOptions } = await resolveCommonSchemas(doc, options.commonSchemas, converter);
|
|
918
|
+
const errorRefMap = await generateErrorComponents(
|
|
919
|
+
combinedErrorMap,
|
|
920
|
+
doc,
|
|
921
|
+
baseSchemaConvertOptions,
|
|
922
|
+
converter
|
|
923
|
+
);
|
|
924
|
+
const namesByAuthConfig = getNamesByAuthConfig(contractsById.values());
|
|
925
|
+
if (namesByAuthConfig.size > 0) {
|
|
926
|
+
doc.components ??= {};
|
|
927
|
+
const schemes = doc.components.securitySchemes ??= {};
|
|
928
|
+
for (const [authConfig, name] of namesByAuthConfig) {
|
|
929
|
+
schemes[name] ??= authConfigToSecurityScheme(authConfig);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
const errors = [];
|
|
933
|
+
for (const [operationId, contract] of contractsById) {
|
|
934
|
+
const def = contract["~def"];
|
|
935
|
+
const path = def.methodConfig.path;
|
|
936
|
+
try {
|
|
937
|
+
const method = toOpenAPIMethod(def.methodConfig.method);
|
|
938
|
+
const httpPath = toOpenAPIPath(path);
|
|
939
|
+
let operationObjectRef;
|
|
940
|
+
if (def.methodConfig.spec !== void 0 && typeof def.methodConfig.spec !== "function") {
|
|
941
|
+
operationObjectRef = def.methodConfig.spec;
|
|
942
|
+
} else {
|
|
943
|
+
operationObjectRef = {
|
|
944
|
+
operationId,
|
|
945
|
+
summary: def.methodConfig.summary,
|
|
946
|
+
description: def.methodConfig.description,
|
|
947
|
+
deprecated: def.methodConfig.deprecated,
|
|
948
|
+
tags: def.methodConfig.tags?.map((tag) => tag)
|
|
949
|
+
};
|
|
950
|
+
const security = def.authConfigs.map(
|
|
951
|
+
(authConfig) => authConfig.type === "none" ? {} : { [namesByAuthConfig.get(authConfig)]: [] }
|
|
952
|
+
);
|
|
953
|
+
if (security.length > 0) {
|
|
954
|
+
operationObjectRef.security = security;
|
|
955
|
+
}
|
|
956
|
+
await handleRequest(doc, operationObjectRef, def, baseSchemaConvertOptions, converter);
|
|
957
|
+
await handleSuccessResponse(doc, operationObjectRef, def, baseSchemaConvertOptions, converter);
|
|
958
|
+
handleErrorResponse(operationObjectRef, def, errorRefMap);
|
|
959
|
+
}
|
|
960
|
+
if (typeof def.methodConfig.spec === "function") {
|
|
961
|
+
operationObjectRef = def.methodConfig.spec(operationObjectRef);
|
|
962
|
+
}
|
|
963
|
+
doc.paths ??= {};
|
|
964
|
+
doc.paths[httpPath] ??= {};
|
|
965
|
+
doc.paths[httpPath][method] = applyCustomOpenAPIOperation(operationObjectRef, contract);
|
|
966
|
+
} catch (e) {
|
|
967
|
+
if (!(e instanceof OpenAPIGeneratorError)) {
|
|
968
|
+
throw e;
|
|
969
|
+
}
|
|
970
|
+
errors.push(
|
|
971
|
+
`[OpenAPIGenerator] Error occurred while generating OpenAPI for method at path: ${path}
|
|
972
|
+
${e.message}`
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
if (errors.length) {
|
|
977
|
+
throw new OpenAPIGeneratorError(
|
|
978
|
+
`Some error occurred during OpenAPI generation:
|
|
979
|
+
|
|
980
|
+
${errors.join("\n\n")}`
|
|
981
|
+
);
|
|
982
|
+
}
|
|
983
|
+
return doc;
|
|
984
|
+
}
|
|
985
|
+
async function resolveCommonSchemas(doc, commonSchemas, converter) {
|
|
986
|
+
const baseSchemaConvertOptions = {};
|
|
987
|
+
if (commonSchemas) {
|
|
988
|
+
baseSchemaConvertOptions.components = [];
|
|
989
|
+
for (const key in commonSchemas) {
|
|
990
|
+
const options = commonSchemas[key];
|
|
991
|
+
if (options.schema === void 0) {
|
|
992
|
+
continue;
|
|
993
|
+
}
|
|
994
|
+
const { schema, strategy = "input" } = options;
|
|
995
|
+
const [required, json] = await converter(schema, { strategy });
|
|
996
|
+
const allowedStrategies = [strategy];
|
|
997
|
+
if (strategy === "input") {
|
|
998
|
+
const [outputRequired, outputJson] = await converter(schema, { strategy: "output" });
|
|
999
|
+
if (outputRequired === required && stringifyJSON(outputJson) === stringifyJSON(json)) {
|
|
1000
|
+
allowedStrategies.push("output");
|
|
1001
|
+
}
|
|
1002
|
+
} else if (strategy === "output") {
|
|
1003
|
+
const [inputRequired, inputJson] = await converter(schema, { strategy: "input" });
|
|
1004
|
+
if (inputRequired === required && stringifyJSON(inputJson) === stringifyJSON(json)) {
|
|
1005
|
+
allowedStrategies.push("input");
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
baseSchemaConvertOptions.components.push({
|
|
1009
|
+
schema,
|
|
1010
|
+
required,
|
|
1011
|
+
ref: `#/components/schemas/${key}`,
|
|
1012
|
+
allowedStrategies
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
doc.components ??= {};
|
|
1016
|
+
doc.components.schemas ??= {};
|
|
1017
|
+
for (const key in commonSchemas) {
|
|
1018
|
+
const options = commonSchemas[key];
|
|
1019
|
+
if (options.schema === void 0) {
|
|
1020
|
+
continue;
|
|
1021
|
+
}
|
|
1022
|
+
const { schema, strategy = "input" } = options;
|
|
1023
|
+
const [, json] = await converter(schema, {
|
|
1024
|
+
...baseSchemaConvertOptions,
|
|
1025
|
+
strategy,
|
|
1026
|
+
minStructureDepthForRef: 1
|
|
1027
|
+
// not allow use $ref for root schemas
|
|
1028
|
+
});
|
|
1029
|
+
doc.components.schemas[key] = toOpenAPISchema(json);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
return { baseSchemaConvertOptions };
|
|
1033
|
+
}
|
|
1034
|
+
async function handleRequest(doc, ref, def, baseSchemaConvertOptions, converter) {
|
|
1035
|
+
const method = def.methodConfig.method;
|
|
1036
|
+
const dynamicParams = getDynamicParams(def.methodConfig.path)?.map((v) => v.name);
|
|
1037
|
+
const [_pathRequired, pathSchema] = await converter(def.schemas.pathSchema, {
|
|
1038
|
+
...baseSchemaConvertOptions,
|
|
1039
|
+
strategy: "input",
|
|
1040
|
+
minStructureDepthForRef: 1
|
|
1041
|
+
});
|
|
1042
|
+
if (dynamicParams?.length) {
|
|
1043
|
+
const error = new OpenAPIGeneratorError(
|
|
1044
|
+
// TODO: fix this error
|
|
1045
|
+
'When input structure is "compact", and path has dynamic params, input schema must be an object with all dynamic params as required.'
|
|
1046
|
+
);
|
|
1047
|
+
if (!isObjectSchema(pathSchema)) {
|
|
1048
|
+
throw error;
|
|
1049
|
+
}
|
|
1050
|
+
if (!checkParamsSchema(pathSchema, dynamicParams)) {
|
|
1051
|
+
throw error;
|
|
1052
|
+
}
|
|
1053
|
+
ref.parameters ??= [];
|
|
1054
|
+
ref.parameters.push(...toOpenAPIParameters(pathSchema, "path"));
|
|
1055
|
+
} else {
|
|
1056
|
+
const error = new OpenAPIGeneratorError("Path placeholders do not match the path schema");
|
|
1057
|
+
if (!isObjectSchema(pathSchema)) {
|
|
1058
|
+
throw error;
|
|
1059
|
+
}
|
|
1060
|
+
if (!checkParamsSchema(pathSchema, [])) {
|
|
1061
|
+
throw error;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
const [_queryRequired, querySchema] = await converter(def.schemas.querySchema, {
|
|
1065
|
+
...baseSchemaConvertOptions,
|
|
1066
|
+
strategy: "input",
|
|
1067
|
+
minStructureDepthForRef: 0
|
|
1068
|
+
});
|
|
1069
|
+
if (!isAnySchema(querySchema)) {
|
|
1070
|
+
const resolvedSchema = resolveOpenAPIJsonSchemaRef(doc, querySchema);
|
|
1071
|
+
if (!isObjectSchema(resolvedSchema)) {
|
|
1072
|
+
throw new OpenAPIGeneratorError("Query param schema must satisfy: object | any | unknown");
|
|
1073
|
+
}
|
|
1074
|
+
ref.parameters ??= [];
|
|
1075
|
+
ref.parameters.push(...toOpenAPIParameters(resolvedSchema, "query"));
|
|
1076
|
+
}
|
|
1077
|
+
const [_headersRequired, headersSchema] = await converter(def.schemas.headersSchema, {
|
|
1078
|
+
...baseSchemaConvertOptions,
|
|
1079
|
+
strategy: "input",
|
|
1080
|
+
minStructureDepthForRef: 0
|
|
1081
|
+
});
|
|
1082
|
+
if (!isAnySchema(headersSchema)) {
|
|
1083
|
+
const resolvedSchema = resolveOpenAPIJsonSchemaRef(doc, headersSchema);
|
|
1084
|
+
if (!isObjectSchema(resolvedSchema)) {
|
|
1085
|
+
throw new OpenAPIGeneratorError("Header schema must satisfy: object | any | unknown");
|
|
1086
|
+
}
|
|
1087
|
+
ref.parameters ??= [];
|
|
1088
|
+
ref.parameters.push(...toOpenAPIParameters(resolvedSchema, "header"));
|
|
1089
|
+
}
|
|
1090
|
+
if (method !== "GET") {
|
|
1091
|
+
const details = getEventIteratorSchemaDetails(def.schemas.bodySchema);
|
|
1092
|
+
if (details) {
|
|
1093
|
+
ref.requestBody = {
|
|
1094
|
+
required: true,
|
|
1095
|
+
content: toOpenAPIEventIteratorContent(
|
|
1096
|
+
await converter(details.yields, { ...baseSchemaConvertOptions, strategy: "input" }),
|
|
1097
|
+
await converter(details.returns, { ...baseSchemaConvertOptions, strategy: "input" })
|
|
1098
|
+
)
|
|
1099
|
+
};
|
|
1100
|
+
} else {
|
|
1101
|
+
const [bodyRequired, bodySchema] = await converter(def.schemas.bodySchema, {
|
|
1102
|
+
...baseSchemaConvertOptions,
|
|
1103
|
+
strategy: "input",
|
|
1104
|
+
minStructureDepthForRef: 0
|
|
1105
|
+
});
|
|
1106
|
+
if (isAnySchema(bodySchema)) {
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
ref.requestBody = {
|
|
1110
|
+
required: bodyRequired,
|
|
1111
|
+
content: toOpenAPIContent(bodySchema)
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
async function handleSuccessResponse(doc, ref, def, baseSchemaConvertOptions, converter) {
|
|
1117
|
+
const responseSchema = def.schemas.responseSchema;
|
|
1118
|
+
const status = fallbackContractConfig("defaultSuccessStatus", def.methodConfig.successStatus);
|
|
1119
|
+
const description = fallbackContractConfig(
|
|
1120
|
+
"defaultSuccessDescription",
|
|
1121
|
+
def.methodConfig?.successDescription
|
|
1122
|
+
);
|
|
1123
|
+
const eventIteratorSchemaDetails = getEventIteratorSchemaDetails(responseSchema);
|
|
1124
|
+
if (eventIteratorSchemaDetails) {
|
|
1125
|
+
ref.responses ??= {};
|
|
1126
|
+
ref.responses[status] = {
|
|
1127
|
+
description,
|
|
1128
|
+
content: toOpenAPIEventIteratorContent(
|
|
1129
|
+
await converter(eventIteratorSchemaDetails.yields, {
|
|
1130
|
+
...baseSchemaConvertOptions,
|
|
1131
|
+
strategy: "output"
|
|
1132
|
+
}),
|
|
1133
|
+
await converter(eventIteratorSchemaDetails.returns, {
|
|
1134
|
+
...baseSchemaConvertOptions,
|
|
1135
|
+
strategy: "output"
|
|
1136
|
+
})
|
|
1137
|
+
)
|
|
1138
|
+
};
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
const [required, json] = await converter(responseSchema, {
|
|
1142
|
+
...baseSchemaConvertOptions,
|
|
1143
|
+
strategy: "output",
|
|
1144
|
+
minStructureDepthForRef: 0
|
|
1145
|
+
});
|
|
1146
|
+
ref.responses ??= {};
|
|
1147
|
+
ref.responses[status] = {
|
|
1148
|
+
description
|
|
1149
|
+
};
|
|
1150
|
+
ref.responses[status].content = toOpenAPIContent(applySchemaOptionality(required, json));
|
|
1151
|
+
}
|
|
1152
|
+
function getNamesByAuthConfig(contracts) {
|
|
1153
|
+
const namesByAuthConfig = /* @__PURE__ */ new Map();
|
|
1154
|
+
const authConfigNames = /* @__PURE__ */ new Set();
|
|
1155
|
+
for (const contract of contracts) {
|
|
1156
|
+
for (const authConfig of contract["~def"].authConfigs) {
|
|
1157
|
+
if (authConfig.type === "none" || namesByAuthConfig.has(authConfig)) {
|
|
1158
|
+
continue;
|
|
1159
|
+
}
|
|
1160
|
+
const oasNameBase = authConfig.oasName ?? `${authConfig.type}Auth`;
|
|
1161
|
+
let oasName = oasNameBase;
|
|
1162
|
+
for (let i = 2; authConfigNames.has(oasName); i++) {
|
|
1163
|
+
oasName = `${oasNameBase}${i}`;
|
|
1164
|
+
}
|
|
1165
|
+
namesByAuthConfig.set(authConfig, oasName);
|
|
1166
|
+
authConfigNames.add(oasName);
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
return namesByAuthConfig;
|
|
1170
|
+
}
|
|
1171
|
+
function authConfigToSecurityScheme(authConfig) {
|
|
1172
|
+
switch (authConfig.type) {
|
|
1173
|
+
case "basic":
|
|
1174
|
+
return {
|
|
1175
|
+
type: "http",
|
|
1176
|
+
scheme: "basic",
|
|
1177
|
+
description: authConfig.oasDescription
|
|
1178
|
+
};
|
|
1179
|
+
case "bearer":
|
|
1180
|
+
return {
|
|
1181
|
+
type: "http",
|
|
1182
|
+
scheme: "bearer",
|
|
1183
|
+
description: authConfig.oasDescription,
|
|
1184
|
+
bearerFormat: authConfig.oasBearerFormat
|
|
1185
|
+
};
|
|
1186
|
+
case "header":
|
|
1187
|
+
return {
|
|
1188
|
+
type: "apiKey",
|
|
1189
|
+
in: "header",
|
|
1190
|
+
name: authConfig.name,
|
|
1191
|
+
description: authConfig.oasDescription
|
|
1192
|
+
};
|
|
1193
|
+
case "query":
|
|
1194
|
+
return {
|
|
1195
|
+
type: "apiKey",
|
|
1196
|
+
in: "query",
|
|
1197
|
+
name: authConfig.name,
|
|
1198
|
+
description: authConfig.oasDescription
|
|
1199
|
+
};
|
|
1200
|
+
case "cookie":
|
|
1201
|
+
return {
|
|
1202
|
+
type: "apiKey",
|
|
1203
|
+
in: "cookie",
|
|
1204
|
+
name: authConfig.name,
|
|
1205
|
+
description: authConfig.oasDescription
|
|
1206
|
+
};
|
|
1207
|
+
default:
|
|
1208
|
+
assertNever(authConfig, `Unsupported auth config type: ${authConfig.type}`);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
async function generateErrorComponents(combinedErrorMap, doc, baseSchemaConvertOptions, converter) {
|
|
1212
|
+
const refMap = /* @__PURE__ */ new Map();
|
|
1213
|
+
const schemas = {};
|
|
1214
|
+
for (const mapKey in combinedErrorMap) {
|
|
1215
|
+
const errorClass = combinedErrorMap[mapKey];
|
|
1216
|
+
if (!errorClass) {
|
|
1217
|
+
continue;
|
|
1218
|
+
}
|
|
1219
|
+
const extraZod = z.object(errorClass.extraSchema).strict();
|
|
1220
|
+
const [, extraSchema] = await converter(extraZod, {
|
|
1221
|
+
...baseSchemaConvertOptions,
|
|
1222
|
+
strategy: "output"
|
|
1223
|
+
});
|
|
1224
|
+
if (!isObjectSchema(extraSchema)) {
|
|
1225
|
+
throw new OpenAPIGeneratorError(
|
|
1226
|
+
`Error response extra schema must be an object, but got: ${stringifyJSON(extraSchema)}`
|
|
1227
|
+
);
|
|
1228
|
+
}
|
|
1229
|
+
const schema = {
|
|
1230
|
+
type: "object",
|
|
1231
|
+
properties: {
|
|
1232
|
+
type: { const: errorClass.type },
|
|
1233
|
+
message: { type: "string", default: errorClass.defaultMessage },
|
|
1234
|
+
...extraSchema.properties
|
|
1235
|
+
},
|
|
1236
|
+
required: ["type", "message", ...extraSchema.required ?? []]
|
|
1237
|
+
};
|
|
1238
|
+
schemas[mapKey] = toOpenAPISchema(schema);
|
|
1239
|
+
const ref = `#/components/schemas/${mapKey}`;
|
|
1240
|
+
refMap.set(mapKey, ref);
|
|
1241
|
+
}
|
|
1242
|
+
if (refMap.size > 0) {
|
|
1243
|
+
if (doc.components?.schemas?.Error) {
|
|
1244
|
+
throw new OpenAPIGeneratorError(
|
|
1245
|
+
'"Error" is a reserved name for models, please choose a different name.'
|
|
1246
|
+
);
|
|
1247
|
+
}
|
|
1248
|
+
schemas.Error = {
|
|
1249
|
+
oneOf: [...refMap.values()].map((ref) => ({ $ref: ref }))
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
if (Object.keys(schemas).length > 0) {
|
|
1253
|
+
doc.components ??= {};
|
|
1254
|
+
doc.components.schemas ??= {};
|
|
1255
|
+
for (const key in doc.components.schemas) {
|
|
1256
|
+
if (Object.prototype.hasOwnProperty.call(schemas, key)) {
|
|
1257
|
+
throw new OpenAPIGeneratorError(
|
|
1258
|
+
`Model name "${key}" conflicts with an error class of the same name. Please rename one of them.`
|
|
1259
|
+
);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
Object.assign(doc.components.schemas, schemas);
|
|
1263
|
+
}
|
|
1264
|
+
return refMap;
|
|
1265
|
+
}
|
|
1266
|
+
function handleErrorResponse(ref, def, errorRefMap) {
|
|
1267
|
+
const errorMap = def.errorMap;
|
|
1268
|
+
const errorResponsesByStatus = {};
|
|
1269
|
+
for (const mapKey in errorMap) {
|
|
1270
|
+
const errorClass = errorMap[mapKey];
|
|
1271
|
+
const errorRef = errorRefMap.get(mapKey);
|
|
1272
|
+
if (!errorClass || !errorRef) {
|
|
1273
|
+
continue;
|
|
1274
|
+
}
|
|
1275
|
+
errorResponsesByStatus[errorClass.status] ??= { status: errorClass.status, refs: [] };
|
|
1276
|
+
errorResponsesByStatus[errorClass.status].refs.push({ $ref: errorRef });
|
|
1277
|
+
}
|
|
1278
|
+
ref.responses ??= {};
|
|
1279
|
+
for (const statusString in errorResponsesByStatus) {
|
|
1280
|
+
const errorResponse = errorResponsesByStatus[statusString];
|
|
1281
|
+
ref.responses[statusString] = {
|
|
1282
|
+
description: statusString,
|
|
1283
|
+
content: toOpenAPIContent(
|
|
1284
|
+
errorResponse.refs.length === 1 ? errorResponse.refs[0] : {
|
|
1285
|
+
oneOf: errorResponse.refs
|
|
1286
|
+
}
|
|
1287
|
+
)
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
export { JSON_SCHEMA_OUTPUT_REGISTRY as A, JsonSchemaXNativeType as J, LOGIC_KEYWORDS as L, ZodToJsonSchemaConverter as Z, applyCustomOpenAPIOperation as a, generateOpenApiSpec as b, customOpenAPIOperation as c, getNamesByAuthConfig as d, toOpenAPIMethod as e, toOpenAPIContent as f, getCustomOpenAPIOperation as g, toOpenAPIEventIteratorContent as h, toOpenAPIParameters as i, checkParamsSchema as j, toOpenAPISchema as k, isFileSchema as l, isObjectSchema as m, isAnySchema as n, filterSchemaBranches as o, applySchemaOptionality as p, expandUnionSchema as q, resolveOpenAPIJsonSchemaRef as r, separateObjectSchema as s, toOpenAPIPath as t, expandArrayableSchema as u, isPrimitiveSchema as v, eventIterator as w, getEventIteratorSchemaDetails as x, JSON_SCHEMA_REGISTRY as y, JSON_SCHEMA_INPUT_REGISTRY as z };
|