typed-openapi 0.1.3 → 0.1.4
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/chunk-M3WPXWOV.js +821 -0
- package/dist/chunk-TUNDL3P7.js +822 -0
- package/dist/cli.cjs +14 -7
- package/dist/cli.js +2 -2
- package/dist/index.cjs +13 -6
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/openapi-schema-to-ts.ts +11 -5
- package/src/ts-factory.ts +7 -1
|
@@ -0,0 +1,821 @@
|
|
|
1
|
+
// src/asserts.ts
|
|
2
|
+
var isPrimitiveType = (type2) => primitiveTypeList.includes(type2);
|
|
3
|
+
var primitiveTypeList = ["string", "number", "integer", "boolean", "null"];
|
|
4
|
+
|
|
5
|
+
// src/is-reference-object.ts
|
|
6
|
+
function isReferenceObject(obj) {
|
|
7
|
+
return obj != null && Object.prototype.hasOwnProperty.call(obj, "$ref");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// src/string-utils.ts
|
|
11
|
+
import { capitalize, kebabToCamel } from "pastable/server";
|
|
12
|
+
function normalizeString(text) {
|
|
13
|
+
const prefixed = prefixStringStartingWithNumberIfNeeded(text);
|
|
14
|
+
return prefixed.normalize("NFKD").trim().replace(/\s+/g, "_").replace(/-+/g, "_").replace(/[^\w\-]+/g, "_").replace(/--+/g, "-");
|
|
15
|
+
}
|
|
16
|
+
var onlyWordRegex = /^\w+$/;
|
|
17
|
+
var wrapWithQuotesIfNeeded = (str) => {
|
|
18
|
+
if (str[0] === '"' && str[str.length - 1] === '"')
|
|
19
|
+
return str;
|
|
20
|
+
if (onlyWordRegex.test(str)) {
|
|
21
|
+
return str;
|
|
22
|
+
}
|
|
23
|
+
return `"${str}"`;
|
|
24
|
+
};
|
|
25
|
+
var prefixStringStartingWithNumberIfNeeded = (str) => {
|
|
26
|
+
const firstAsNumber = Number(str[0]);
|
|
27
|
+
if (typeof firstAsNumber === "number" && !Number.isNaN(firstAsNumber)) {
|
|
28
|
+
return "_" + str;
|
|
29
|
+
}
|
|
30
|
+
return str;
|
|
31
|
+
};
|
|
32
|
+
var pathParamWithBracketsRegex = /({\w+})/g;
|
|
33
|
+
var wordPrecededByNonWordCharacter = /[^\w\-]+/g;
|
|
34
|
+
var pathToVariableName = (path) => capitalize(kebabToCamel(path).replaceAll("/", "")).replace(pathParamWithBracketsRegex, (group) => capitalize(group.slice(1, -1))).replace(wordPrecededByNonWordCharacter, "_");
|
|
35
|
+
|
|
36
|
+
// src/openapi-schema-to-ts.ts
|
|
37
|
+
var openApiSchemaToTs = ({ schema, meta: _inheritedMeta, ctx }) => {
|
|
38
|
+
const meta = {};
|
|
39
|
+
if (!schema) {
|
|
40
|
+
throw new Error("Schema is required");
|
|
41
|
+
}
|
|
42
|
+
const t = createBoxFactory(schema, ctx);
|
|
43
|
+
const getTs = () => {
|
|
44
|
+
if (isReferenceObject(schema)) {
|
|
45
|
+
const refInfo = ctx.refs.getInfosByRef(schema.$ref);
|
|
46
|
+
return t.reference(refInfo.normalized);
|
|
47
|
+
}
|
|
48
|
+
if (Array.isArray(schema.type)) {
|
|
49
|
+
if (schema.type.length === 1) {
|
|
50
|
+
return openApiSchemaToTs({ schema: { ...schema, type: schema.type[0] }, ctx, meta });
|
|
51
|
+
}
|
|
52
|
+
return t.union(schema.type.map((prop) => openApiSchemaToTs({ schema: { ...schema, type: prop }, ctx, meta })));
|
|
53
|
+
}
|
|
54
|
+
if (schema.type === "null") {
|
|
55
|
+
return t.reference("null");
|
|
56
|
+
}
|
|
57
|
+
if (schema.oneOf) {
|
|
58
|
+
if (schema.oneOf.length === 1) {
|
|
59
|
+
return openApiSchemaToTs({ schema: schema.oneOf[0], ctx, meta });
|
|
60
|
+
}
|
|
61
|
+
return t.union(schema.oneOf.map((prop) => openApiSchemaToTs({ schema: prop, ctx, meta })));
|
|
62
|
+
}
|
|
63
|
+
if (schema.anyOf) {
|
|
64
|
+
if (schema.anyOf.length === 1) {
|
|
65
|
+
return openApiSchemaToTs({ schema: schema.anyOf[0], ctx, meta });
|
|
66
|
+
}
|
|
67
|
+
const oneOf = t.union(schema.anyOf.map((prop) => openApiSchemaToTs({ schema: prop, ctx, meta })));
|
|
68
|
+
return t.union([oneOf, t.array(oneOf)]);
|
|
69
|
+
}
|
|
70
|
+
if (schema.allOf) {
|
|
71
|
+
if (schema.allOf.length === 1) {
|
|
72
|
+
return openApiSchemaToTs({ schema: schema.allOf[0], ctx, meta });
|
|
73
|
+
}
|
|
74
|
+
const types = schema.allOf.map((prop) => openApiSchemaToTs({ schema: prop, ctx, meta }));
|
|
75
|
+
return t.intersection(types);
|
|
76
|
+
}
|
|
77
|
+
const schemaType = schema.type ? schema.type.toLowerCase() : void 0;
|
|
78
|
+
if (schemaType && isPrimitiveType(schemaType)) {
|
|
79
|
+
if (schema.enum) {
|
|
80
|
+
if (schema.enum.length === 1) {
|
|
81
|
+
const value = schema.enum[0];
|
|
82
|
+
return t.literal(value === null ? "null" : `"${value}"`);
|
|
83
|
+
}
|
|
84
|
+
if (schemaType === "string") {
|
|
85
|
+
return t.union(schema.enum.map((value) => t.literal(`"${value}"`)));
|
|
86
|
+
}
|
|
87
|
+
if (schema.enum.some((e) => typeof e === "string")) {
|
|
88
|
+
return t.never();
|
|
89
|
+
}
|
|
90
|
+
return t.union(schema.enum.map((value) => t.literal(value === null ? "null" : value)));
|
|
91
|
+
}
|
|
92
|
+
if (schemaType === "string")
|
|
93
|
+
return t.string();
|
|
94
|
+
if (schemaType === "boolean")
|
|
95
|
+
return t.boolean();
|
|
96
|
+
if (schemaType === "number" || schemaType === "integer")
|
|
97
|
+
return t.number();
|
|
98
|
+
if (schemaType === "null")
|
|
99
|
+
return t.reference("null");
|
|
100
|
+
}
|
|
101
|
+
if (schemaType === "array") {
|
|
102
|
+
if (schema.items) {
|
|
103
|
+
let arrayOfType = openApiSchemaToTs({ schema: schema.items, ctx, meta });
|
|
104
|
+
if (typeof arrayOfType === "string") {
|
|
105
|
+
arrayOfType = t.reference(arrayOfType);
|
|
106
|
+
}
|
|
107
|
+
return t.array(arrayOfType);
|
|
108
|
+
}
|
|
109
|
+
return t.array(t.any());
|
|
110
|
+
}
|
|
111
|
+
if (schemaType === "object" || schema.properties || schema.additionalProperties) {
|
|
112
|
+
if (!schema.properties) {
|
|
113
|
+
return t.unknown();
|
|
114
|
+
}
|
|
115
|
+
let additionalProperties;
|
|
116
|
+
if (schema.additionalProperties) {
|
|
117
|
+
let additionalPropertiesType;
|
|
118
|
+
if (typeof schema.additionalProperties === "boolean" && schema.additionalProperties || typeof schema.additionalProperties === "object" && Object.keys(schema.additionalProperties).length === 0) {
|
|
119
|
+
additionalPropertiesType = t.any();
|
|
120
|
+
} else if (typeof schema.additionalProperties === "object") {
|
|
121
|
+
additionalPropertiesType = openApiSchemaToTs({
|
|
122
|
+
schema: schema.additionalProperties,
|
|
123
|
+
ctx,
|
|
124
|
+
meta
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
additionalProperties = t.object({ [t.string().value]: additionalPropertiesType });
|
|
128
|
+
}
|
|
129
|
+
const hasRequiredArray = schema.required && schema.required.length > 0;
|
|
130
|
+
const isPartial = !schema.required?.length;
|
|
131
|
+
const props = Object.fromEntries(
|
|
132
|
+
Object.entries(schema.properties).map(([prop, propSchema]) => {
|
|
133
|
+
let propType = openApiSchemaToTs({ schema: propSchema, ctx, meta });
|
|
134
|
+
if (typeof propType === "string") {
|
|
135
|
+
propType = t.reference(propType);
|
|
136
|
+
}
|
|
137
|
+
const isRequired = Boolean(isPartial ? true : hasRequiredArray ? schema.required?.includes(prop) : false);
|
|
138
|
+
const isOptional = !isPartial && !isRequired;
|
|
139
|
+
return [
|
|
140
|
+
`${wrapWithQuotesIfNeeded(prop)}${isOptional ? "?" : ""}`,
|
|
141
|
+
isOptional ? t.optional(propType) : propType
|
|
142
|
+
];
|
|
143
|
+
})
|
|
144
|
+
);
|
|
145
|
+
const objectType = additionalProperties ? t.intersection([t.object(props), additionalProperties]) : t.object(props);
|
|
146
|
+
return isPartial ? t.reference("Partial", [objectType]) : objectType;
|
|
147
|
+
}
|
|
148
|
+
if (!schemaType)
|
|
149
|
+
return t.unknown();
|
|
150
|
+
throw new Error(`Unsupported schema type: ${schemaType}`);
|
|
151
|
+
};
|
|
152
|
+
let output = getTs();
|
|
153
|
+
if (!isReferenceObject(schema)) {
|
|
154
|
+
if (schema.nullable) {
|
|
155
|
+
output = t.union([output, t.reference("null")]);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return output;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// src/box.ts
|
|
162
|
+
var Box = class _Box {
|
|
163
|
+
constructor(definition) {
|
|
164
|
+
this.definition = definition;
|
|
165
|
+
this.definition = definition;
|
|
166
|
+
this.type = definition.type;
|
|
167
|
+
this.value = definition.value;
|
|
168
|
+
this.params = definition.params;
|
|
169
|
+
this.schema = definition.schema;
|
|
170
|
+
this.ctx = definition.ctx;
|
|
171
|
+
}
|
|
172
|
+
type;
|
|
173
|
+
value;
|
|
174
|
+
params;
|
|
175
|
+
schema;
|
|
176
|
+
ctx;
|
|
177
|
+
toJSON() {
|
|
178
|
+
return { type: this.type, value: this.value };
|
|
179
|
+
}
|
|
180
|
+
toString() {
|
|
181
|
+
return JSON.stringify(this.toJSON(), null, 2);
|
|
182
|
+
}
|
|
183
|
+
recompute(callback) {
|
|
184
|
+
return openApiSchemaToTs({ schema: this.schema, ctx: { ...this.ctx, onBox: callback } });
|
|
185
|
+
}
|
|
186
|
+
static fromJSON(json) {
|
|
187
|
+
return new _Box(JSON.parse(json));
|
|
188
|
+
}
|
|
189
|
+
static isBox(box) {
|
|
190
|
+
return box instanceof _Box;
|
|
191
|
+
}
|
|
192
|
+
static isUnion(box) {
|
|
193
|
+
return box.type === "union";
|
|
194
|
+
}
|
|
195
|
+
static isIntersection(box) {
|
|
196
|
+
return box.type === "intersection";
|
|
197
|
+
}
|
|
198
|
+
static isArray(box) {
|
|
199
|
+
return box.type === "array";
|
|
200
|
+
}
|
|
201
|
+
static isOptional(box) {
|
|
202
|
+
return box.type === "optional";
|
|
203
|
+
}
|
|
204
|
+
static isReference(box) {
|
|
205
|
+
return box.type === "ref";
|
|
206
|
+
}
|
|
207
|
+
static isKeyword(box) {
|
|
208
|
+
return box.type === "keyword";
|
|
209
|
+
}
|
|
210
|
+
static isObject(box) {
|
|
211
|
+
return box.type === "object";
|
|
212
|
+
}
|
|
213
|
+
static isLiteral(box) {
|
|
214
|
+
return box.type === "literal";
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
// src/box-factory.ts
|
|
219
|
+
var unwrap = (param) => typeof param === "string" ? param : param.value;
|
|
220
|
+
var createFactory = (f) => f;
|
|
221
|
+
var createBoxFactory = (schema, ctx) => {
|
|
222
|
+
const f = typeof ctx.factory === "function" ? ctx.factory(schema, ctx) : ctx.factory;
|
|
223
|
+
const callback = (box2) => {
|
|
224
|
+
if (f.callback) {
|
|
225
|
+
box2 = f.callback(box2);
|
|
226
|
+
}
|
|
227
|
+
if (ctx?.onBox) {
|
|
228
|
+
box2 = ctx.onBox?.(box2);
|
|
229
|
+
}
|
|
230
|
+
return box2;
|
|
231
|
+
};
|
|
232
|
+
const box = {
|
|
233
|
+
union: (types) => callback(new Box({ ctx, schema, type: "union", params: { types }, value: f.union(types) })),
|
|
234
|
+
intersection: (types) => callback(new Box({ ctx, schema, type: "intersection", params: { types }, value: f.intersection(types) })),
|
|
235
|
+
array: (type2) => callback(new Box({ ctx, schema, type: "array", params: { type: type2 }, value: f.array(type2) })),
|
|
236
|
+
optional: (type2) => callback(new Box({ ctx, schema, type: "optional", params: { type: type2 }, value: f.optional(type2) })),
|
|
237
|
+
reference: (name, generics) => callback(
|
|
238
|
+
new Box({
|
|
239
|
+
ctx,
|
|
240
|
+
schema,
|
|
241
|
+
type: "ref",
|
|
242
|
+
params: generics ? { name, generics } : { name },
|
|
243
|
+
value: f.reference(name, generics)
|
|
244
|
+
})
|
|
245
|
+
),
|
|
246
|
+
literal: (value) => callback(new Box({ ctx, schema, type: "literal", params: {}, value: f.literal(value) })),
|
|
247
|
+
string: () => callback(new Box({ ctx, schema, type: "keyword", params: { name: "string" }, value: f.string() })),
|
|
248
|
+
number: () => callback(new Box({ ctx, schema, type: "keyword", params: { name: "number" }, value: f.number() })),
|
|
249
|
+
boolean: () => callback(new Box({ ctx, schema, type: "keyword", params: { name: "boolean" }, value: f.boolean() })),
|
|
250
|
+
unknown: () => callback(new Box({ ctx, schema, type: "keyword", params: { name: "unknown" }, value: f.unknown() })),
|
|
251
|
+
any: () => callback(new Box({ ctx, schema, type: "keyword", params: { name: "any" }, value: f.any() })),
|
|
252
|
+
never: () => callback(new Box({ ctx, schema, type: "keyword", params: { name: "never" }, value: f.never() })),
|
|
253
|
+
object: (props) => callback(new Box({ ctx, schema, type: "object", params: { props }, value: f.object(props) }))
|
|
254
|
+
};
|
|
255
|
+
return box;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// src/generator.ts
|
|
259
|
+
import { capitalize as capitalize2, groupBy } from "pastable/server";
|
|
260
|
+
|
|
261
|
+
// src/format.ts
|
|
262
|
+
import prettier from "prettier";
|
|
263
|
+
import parserTypescript from "prettier/parser-typescript";
|
|
264
|
+
function maybePretty(input, options) {
|
|
265
|
+
try {
|
|
266
|
+
return prettier.format(input, {
|
|
267
|
+
parser: "typescript",
|
|
268
|
+
plugins: [parserTypescript],
|
|
269
|
+
...options
|
|
270
|
+
});
|
|
271
|
+
} catch (err) {
|
|
272
|
+
console.warn("Failed to format code");
|
|
273
|
+
console.warn(err);
|
|
274
|
+
return input;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
var prettify = (str, options) => maybePretty(str, { printWidth: 120, trailingComma: "all", ...options });
|
|
278
|
+
|
|
279
|
+
// src/generator.ts
|
|
280
|
+
import * as Codegen from "@sinclair/typebox-codegen";
|
|
281
|
+
import { match } from "ts-pattern";
|
|
282
|
+
import { type } from "arktype";
|
|
283
|
+
var allowedRuntimes = type("'none' | 'arktype' | 'io-ts' | 'typebox' | 'valibot' | 'yup' | 'zod'");
|
|
284
|
+
var runtimeValidationGenerator = {
|
|
285
|
+
arktype: Codegen.ModelToArkType.Generate,
|
|
286
|
+
"io-ts": Codegen.ModelToIoTs.Generate,
|
|
287
|
+
typebox: Codegen.ModelToTypeBox.Generate,
|
|
288
|
+
valibot: Codegen.ModelToValibot.Generate,
|
|
289
|
+
yup: Codegen.ModelToYup.Generate,
|
|
290
|
+
zod: Codegen.ModelToZod.Generate
|
|
291
|
+
};
|
|
292
|
+
var inferByRuntime = {
|
|
293
|
+
none: (input) => input,
|
|
294
|
+
arktype: (input) => `${input}["infer"]`,
|
|
295
|
+
"io-ts": (input) => `t.TypeOf<${input}>`,
|
|
296
|
+
typebox: (input) => `Static<${input}>`,
|
|
297
|
+
valibot: (input) => `v.Output<${input}>`,
|
|
298
|
+
yup: (input) => `y.InferType<${input}>`,
|
|
299
|
+
zod: (input) => `z.infer<${input}>`
|
|
300
|
+
};
|
|
301
|
+
var methods = ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
|
|
302
|
+
var methodsRegex = new RegExp(`(?:${methods.join("|")})_`);
|
|
303
|
+
var endpointExport = new RegExp(`export (?:type|const) (?:${methodsRegex.source})`);
|
|
304
|
+
var replacerByRuntime = {
|
|
305
|
+
yup: (line) => line.replace(/y\.InferType<\s*?typeof (.*?)\s*?>/g, "typeof $1").replace(new RegExp(`(${endpointExport.source})` + new RegExp(/(.*? )(y\.object)(\()/).source, "g"), "$1$2("),
|
|
306
|
+
zod: (line) => line.replace(/z\.infer<\s*?typeof (.*?)\s*?>/g, "typeof $1").replace(new RegExp(`(${endpointExport.source})` + new RegExp(/(.*? )(z\.object)(\()/).source, "g"), "$1$2(")
|
|
307
|
+
};
|
|
308
|
+
var generateFile = (options) => {
|
|
309
|
+
const ctx = { ...options, runtime: options.runtime ?? "none" };
|
|
310
|
+
const schemaList = generateSchemaList(ctx);
|
|
311
|
+
const endpointSchemaList = generateEndpointSchemaList(ctx);
|
|
312
|
+
const apiClient = generateApiClient(ctx);
|
|
313
|
+
const transform = ctx.runtime === "none" ? (file2) => file2 : (file2) => {
|
|
314
|
+
const model = Codegen.TypeScriptToModel.Generate(file2);
|
|
315
|
+
const transformer = runtimeValidationGenerator[ctx.runtime];
|
|
316
|
+
const generated = ctx.runtime === "typebox" ? Codegen.TypeScriptToTypeBox.Generate(file2) : transformer(model);
|
|
317
|
+
let converted = "";
|
|
318
|
+
const match2 = generated.match(/(const __ENDPOINTS_START__ =)([\s\S]*?)(export type __ENDPOINTS_END__)/);
|
|
319
|
+
const content = match2?.[2];
|
|
320
|
+
if (content && ctx.runtime in replacerByRuntime) {
|
|
321
|
+
const before = generated.slice(0, generated.indexOf("export type __ENDPOINTS_START"));
|
|
322
|
+
converted = before + replacerByRuntime[ctx.runtime](
|
|
323
|
+
content.slice(content.indexOf("export"))
|
|
324
|
+
);
|
|
325
|
+
} else {
|
|
326
|
+
converted = generated;
|
|
327
|
+
}
|
|
328
|
+
return converted;
|
|
329
|
+
};
|
|
330
|
+
const file = `
|
|
331
|
+
${transform(schemaList + endpointSchemaList)}
|
|
332
|
+
${apiClient}
|
|
333
|
+
`;
|
|
334
|
+
return prettify(file);
|
|
335
|
+
};
|
|
336
|
+
var generateSchemaList = ({ refs, runtime }) => {
|
|
337
|
+
let file = `
|
|
338
|
+
${runtime === "none" ? "export namespace Schemas {" : ""}
|
|
339
|
+
// <Schemas>
|
|
340
|
+
`;
|
|
341
|
+
refs.getOrderedSchemas().forEach(([schema, infos]) => {
|
|
342
|
+
if (!infos?.name)
|
|
343
|
+
return;
|
|
344
|
+
if (infos.kind !== "schemas")
|
|
345
|
+
return;
|
|
346
|
+
file += `export type ${infos.normalized} = ${schema.value}
|
|
347
|
+
`;
|
|
348
|
+
});
|
|
349
|
+
return file + `
|
|
350
|
+
// </Schemas>
|
|
351
|
+
${runtime === "none" ? "}" : ""}
|
|
352
|
+
`;
|
|
353
|
+
};
|
|
354
|
+
var parameterObjectToString = (parameters) => {
|
|
355
|
+
if (parameters instanceof Box)
|
|
356
|
+
return parameters.value;
|
|
357
|
+
let str = "{";
|
|
358
|
+
for (const [key, box] of Object.entries(parameters)) {
|
|
359
|
+
str += `${wrapWithQuotesIfNeeded(key)}: ${box.value},
|
|
360
|
+
`;
|
|
361
|
+
}
|
|
362
|
+
return str + "}";
|
|
363
|
+
};
|
|
364
|
+
var generateEndpointSchemaList = (ctx) => {
|
|
365
|
+
let file = `
|
|
366
|
+
${ctx.runtime === "none" ? "export namespace Endpoints {" : ""}
|
|
367
|
+
// <Endpoints>
|
|
368
|
+
${ctx.runtime === "none" ? "" : "type __ENDPOINTS_START__ = {}"}
|
|
369
|
+
`;
|
|
370
|
+
ctx.endpointList.map((endpoint) => {
|
|
371
|
+
const parameters = endpoint.parameters ?? {};
|
|
372
|
+
file += `export type ${endpoint.meta.alias} = {
|
|
373
|
+
method: "${endpoint.method.toUpperCase()}",
|
|
374
|
+
path: "${endpoint.path}",
|
|
375
|
+
${endpoint.meta.hasParameters ? `parameters: {
|
|
376
|
+
${parameters.query ? `query: ${parameterObjectToString(parameters.query)},` : ""}
|
|
377
|
+
${parameters.path ? `path: ${parameterObjectToString(parameters.path)},` : ""}
|
|
378
|
+
${parameters.header ? `header: ${parameterObjectToString(parameters.header)},` : ""}
|
|
379
|
+
}` : "parameters: never,"}
|
|
380
|
+
response: ${ctx.runtime === "none" ? endpoint.response.recompute((box) => {
|
|
381
|
+
if (Box.isReference(box) && !box.params.generics) {
|
|
382
|
+
box.value = `Schemas.${box.value}`;
|
|
383
|
+
}
|
|
384
|
+
return box;
|
|
385
|
+
}).value : endpoint.response.value},
|
|
386
|
+
}
|
|
387
|
+
`;
|
|
388
|
+
});
|
|
389
|
+
return file + `
|
|
390
|
+
// </Endpoints>
|
|
391
|
+
${ctx.runtime === "none" ? "}" : ""}
|
|
392
|
+
${ctx.runtime === "none" ? "" : "type __ENDPOINTS_END__ = {}"}
|
|
393
|
+
`;
|
|
394
|
+
};
|
|
395
|
+
var generateEndpointByMethod = (ctx) => {
|
|
396
|
+
const { endpointList } = ctx;
|
|
397
|
+
const byMethods = groupBy(endpointList, "method");
|
|
398
|
+
const endpointByMethod = `
|
|
399
|
+
// <EndpointByMethod>
|
|
400
|
+
export ${ctx.runtime === "none" ? "type" : "const"} EndpointByMethod = {
|
|
401
|
+
${Object.entries(byMethods).map(([method, list]) => {
|
|
402
|
+
return `${method}: {
|
|
403
|
+
${list.map(
|
|
404
|
+
(endpoint) => `"${endpoint.path}": ${ctx.runtime === "none" ? "Endpoints." : ""}${endpoint.meta.alias}`
|
|
405
|
+
)}
|
|
406
|
+
}`;
|
|
407
|
+
}).join(",\n")}
|
|
408
|
+
}
|
|
409
|
+
${ctx.runtime === "none" ? "" : "export type EndpointByMethod = typeof EndpointByMethod;"}
|
|
410
|
+
// </EndpointByMethod>
|
|
411
|
+
`;
|
|
412
|
+
const shorthands = `
|
|
413
|
+
|
|
414
|
+
// <EndpointByMethod.Shorthands>
|
|
415
|
+
${Object.keys(byMethods).map((method) => `export type ${capitalize2(method)}Endpoints = EndpointByMethod["${method}"]`).join("\n")}
|
|
416
|
+
${endpointList.length ? `export type AllEndpoints = EndpointByMethod[keyof EndpointByMethod];` : ""}
|
|
417
|
+
// </EndpointByMethod.Shorthands>
|
|
418
|
+
`;
|
|
419
|
+
return endpointByMethod + shorthands;
|
|
420
|
+
};
|
|
421
|
+
var generateApiClient = (ctx) => {
|
|
422
|
+
const { endpointList } = ctx;
|
|
423
|
+
const byMethods = groupBy(endpointList, "method");
|
|
424
|
+
const endpointSchemaList = generateEndpointByMethod(ctx);
|
|
425
|
+
const apiClientTypes = `
|
|
426
|
+
// <ApiClientTypes>
|
|
427
|
+
export type EndpointParameters = {
|
|
428
|
+
body?: unknown;
|
|
429
|
+
query?: Record<string, unknown>;
|
|
430
|
+
header?: Record<string, unknown>;
|
|
431
|
+
path?: Record<string, unknown>;
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
export type MutationMethod = "post" | "put" | "patch" | "delete";
|
|
435
|
+
export type Method = "get" | "head" | MutationMethod;
|
|
436
|
+
|
|
437
|
+
export type DefaultEndpoint = {
|
|
438
|
+
parameters?: EndpointParameters | undefined;
|
|
439
|
+
response: unknown;
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
export type Endpoint<TConfig extends DefaultEndpoint = DefaultEndpoint> = {
|
|
443
|
+
operationId: string;
|
|
444
|
+
method: Method;
|
|
445
|
+
path: string;
|
|
446
|
+
parameters?: TConfig["parameters"];
|
|
447
|
+
meta: {
|
|
448
|
+
alias: string;
|
|
449
|
+
hasParameters: boolean;
|
|
450
|
+
areParametersRequired: boolean;
|
|
451
|
+
};
|
|
452
|
+
response: TConfig["response"];
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise<Endpoint["response"]>;
|
|
456
|
+
|
|
457
|
+
type RequiredKeys<T> = {
|
|
458
|
+
[P in keyof T]-?: undefined extends T[P] ? never : P;
|
|
459
|
+
}[keyof T];
|
|
460
|
+
|
|
461
|
+
type MaybeOptionalArg<T> = RequiredKeys<T> extends never ? [config?: T] : [config: T];
|
|
462
|
+
|
|
463
|
+
// </ApiClientTypes>
|
|
464
|
+
`;
|
|
465
|
+
const apiClient = `
|
|
466
|
+
// <ApiClient>
|
|
467
|
+
export class ApiClient {
|
|
468
|
+
baseUrl: string = "";
|
|
469
|
+
|
|
470
|
+
constructor(public fetcher: Fetcher) {}
|
|
471
|
+
|
|
472
|
+
setBaseUrl(baseUrl: string) {
|
|
473
|
+
this.baseUrl = baseUrl;
|
|
474
|
+
return this;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
${Object.entries(byMethods).map(([method, endpointByMethod]) => {
|
|
478
|
+
const capitalizedMethod = capitalize2(method);
|
|
479
|
+
const infer = inferByRuntime[ctx.runtime];
|
|
480
|
+
return endpointByMethod.length ? `// <ApiClient.${method}>
|
|
481
|
+
${method}<Path extends keyof ${capitalizedMethod}Endpoints, TEndpoint extends ${capitalizedMethod}Endpoints[Path]>(
|
|
482
|
+
path: Path,
|
|
483
|
+
...params: MaybeOptionalArg<${match(ctx.runtime).with("zod", "yup", () => infer(`TEndpoint["parameters"]`)).with("arktype", "io-ts", "typebox", "valibot", () => infer(`TEndpoint`) + `["parameters"]`).otherwise(() => `TEndpoint["parameters"]`)}>
|
|
484
|
+
): Promise<${match(ctx.runtime).with("zod", "yup", () => infer(`TEndpoint["response"]`)).with("arktype", "io-ts", "typebox", "valibot", () => infer(`TEndpoint`) + `["response"]`).otherwise(() => `TEndpoint["response"]`)}> {
|
|
485
|
+
return this.fetcher("${method}", this.baseUrl + path, params[0]);
|
|
486
|
+
}
|
|
487
|
+
// </ApiClient.${method}>
|
|
488
|
+
` : "";
|
|
489
|
+
}).join("\n")}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
export function createApiClient(fetcher: Fetcher, baseUrl?: string) {
|
|
493
|
+
return new ApiClient(fetcher).setBaseUrl(baseUrl ?? "");
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
Example usage:
|
|
499
|
+
const api = createApiClient((method, url, params) =>
|
|
500
|
+
fetch(url, { method, body: JSON.stringify(params) }).then((res) => res.json()),
|
|
501
|
+
);
|
|
502
|
+
api.get("/users").then((users) => console.log(users));
|
|
503
|
+
api.post("/users", { body: { name: "John" } }).then((user) => console.log(user));
|
|
504
|
+
api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user));
|
|
505
|
+
*/
|
|
506
|
+
|
|
507
|
+
// </ApiClient
|
|
508
|
+
`;
|
|
509
|
+
return endpointSchemaList + apiClientTypes + apiClient;
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
// src/ref-resolver.ts
|
|
513
|
+
import { get } from "pastable/server";
|
|
514
|
+
|
|
515
|
+
// src/topological-sort.ts
|
|
516
|
+
function topologicalSort(graph) {
|
|
517
|
+
const sorted = [], visited = {};
|
|
518
|
+
function visit(name, ancestors) {
|
|
519
|
+
if (!Array.isArray(ancestors))
|
|
520
|
+
ancestors = [];
|
|
521
|
+
ancestors.push(name);
|
|
522
|
+
visited[name] = true;
|
|
523
|
+
const deps = graph.get(name);
|
|
524
|
+
if (deps) {
|
|
525
|
+
deps.forEach((dep) => {
|
|
526
|
+
if (ancestors.includes(dep)) {
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
if (visited[dep])
|
|
530
|
+
return;
|
|
531
|
+
visit(dep, ancestors.slice(0));
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
if (!sorted.includes(name))
|
|
535
|
+
sorted.push(name);
|
|
536
|
+
}
|
|
537
|
+
graph.forEach((_, name) => visit(name, []));
|
|
538
|
+
return sorted;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// src/ref-resolver.ts
|
|
542
|
+
var autocorrectRef = (ref) => ref[1] === "/" ? ref : "#/" + ref.slice(1);
|
|
543
|
+
var componentsWithSchemas = ["schemas", "responses", "parameters", "requestBodies", "headers"];
|
|
544
|
+
var createRefResolver = (doc, factory2) => {
|
|
545
|
+
const nameByRef = /* @__PURE__ */ new Map();
|
|
546
|
+
const refByName = /* @__PURE__ */ new Map();
|
|
547
|
+
const byRef = /* @__PURE__ */ new Map();
|
|
548
|
+
const byNormalized = /* @__PURE__ */ new Map();
|
|
549
|
+
const boxByRef = /* @__PURE__ */ new Map();
|
|
550
|
+
const getSchemaByRef = (ref) => {
|
|
551
|
+
const correctRef = autocorrectRef(ref);
|
|
552
|
+
const split = correctRef.split("/");
|
|
553
|
+
const path = split.slice(1, -1).join("/");
|
|
554
|
+
const normalizedPath = path.replace("#/", "").replace("#", "").replaceAll("/", ".");
|
|
555
|
+
const map = get(doc, normalizedPath) ?? {};
|
|
556
|
+
const name = split[split.length - 1];
|
|
557
|
+
const normalized = normalizeString(name);
|
|
558
|
+
nameByRef.set(correctRef, normalized);
|
|
559
|
+
refByName.set(normalized, correctRef);
|
|
560
|
+
const infos = { ref: correctRef, name, normalized, kind: normalizedPath.split(".")[1] };
|
|
561
|
+
byRef.set(infos.ref, infos);
|
|
562
|
+
byNormalized.set(infos.normalized, infos);
|
|
563
|
+
const schema = map[name];
|
|
564
|
+
if (!schema) {
|
|
565
|
+
throw new Error(`Unresolved ref "${name}" not found in "${path}"`);
|
|
566
|
+
}
|
|
567
|
+
return schema;
|
|
568
|
+
};
|
|
569
|
+
const getInfosByRef = (ref) => byRef.get(autocorrectRef(ref));
|
|
570
|
+
const schemaEntries = Object.entries(doc.components ?? {}).filter(([key]) => componentsWithSchemas.includes(key));
|
|
571
|
+
schemaEntries.forEach(([key, component]) => {
|
|
572
|
+
Object.keys(component).map((name) => {
|
|
573
|
+
const ref = `#/components/${key}/${name}`;
|
|
574
|
+
getSchemaByRef(ref);
|
|
575
|
+
});
|
|
576
|
+
});
|
|
577
|
+
const directDependencies = /* @__PURE__ */ new Map();
|
|
578
|
+
schemaEntries.forEach(([key, component]) => {
|
|
579
|
+
Object.keys(component).map((name) => {
|
|
580
|
+
const ref = `#/components/${key}/${name}`;
|
|
581
|
+
const schema = getSchemaByRef(ref);
|
|
582
|
+
boxByRef.set(ref, openApiSchemaToTs({ schema, ctx: { factory: factory2, refs: { getInfosByRef } } }));
|
|
583
|
+
if (!directDependencies.has(ref)) {
|
|
584
|
+
directDependencies.set(ref, /* @__PURE__ */ new Set());
|
|
585
|
+
}
|
|
586
|
+
setSchemaDependencies(schema, directDependencies.get(ref));
|
|
587
|
+
});
|
|
588
|
+
});
|
|
589
|
+
const transitiveDependencies = getTransitiveDependencies(directDependencies);
|
|
590
|
+
return {
|
|
591
|
+
get: getSchemaByRef,
|
|
592
|
+
unwrap: (component) => {
|
|
593
|
+
return isReferenceObject(component) ? getSchemaByRef(component.$ref) : component;
|
|
594
|
+
},
|
|
595
|
+
getInfosByRef,
|
|
596
|
+
infos: byRef,
|
|
597
|
+
/**
|
|
598
|
+
* Get the schemas in the order they should be generated, depending on their dependencies
|
|
599
|
+
* so that a schema is generated before the ones that depend on it
|
|
600
|
+
*/
|
|
601
|
+
getOrderedSchemas: () => {
|
|
602
|
+
const schemaOrderedByDependencies = topologicalSort(transitiveDependencies).map((ref) => {
|
|
603
|
+
const infos = getInfosByRef(ref);
|
|
604
|
+
return [boxByRef.get(infos.ref), infos];
|
|
605
|
+
});
|
|
606
|
+
return schemaOrderedByDependencies;
|
|
607
|
+
},
|
|
608
|
+
directDependencies,
|
|
609
|
+
transitiveDependencies
|
|
610
|
+
};
|
|
611
|
+
};
|
|
612
|
+
var setSchemaDependencies = (schema, deps) => {
|
|
613
|
+
const visit = (schema2) => {
|
|
614
|
+
if (!schema2)
|
|
615
|
+
return;
|
|
616
|
+
if (isReferenceObject(schema2)) {
|
|
617
|
+
deps.add(schema2.$ref);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
if (schema2.allOf) {
|
|
621
|
+
for (const allOf of schema2.allOf) {
|
|
622
|
+
visit(allOf);
|
|
623
|
+
}
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
if (schema2.oneOf) {
|
|
627
|
+
for (const oneOf of schema2.oneOf) {
|
|
628
|
+
visit(oneOf);
|
|
629
|
+
}
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
if (schema2.anyOf) {
|
|
633
|
+
for (const anyOf of schema2.anyOf) {
|
|
634
|
+
visit(anyOf);
|
|
635
|
+
}
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
if (schema2.type === "array") {
|
|
639
|
+
if (!schema2.items)
|
|
640
|
+
return;
|
|
641
|
+
return void visit(schema2.items);
|
|
642
|
+
}
|
|
643
|
+
if (schema2.type === "object" || schema2.properties || schema2.additionalProperties) {
|
|
644
|
+
if (schema2.properties) {
|
|
645
|
+
for (const property in schema2.properties) {
|
|
646
|
+
visit(schema2.properties[property]);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
if (schema2.additionalProperties && typeof schema2.additionalProperties === "object") {
|
|
650
|
+
visit(schema2.additionalProperties);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
visit(schema);
|
|
655
|
+
};
|
|
656
|
+
var getTransitiveDependencies = (directDependencies) => {
|
|
657
|
+
const transitiveDependencies = /* @__PURE__ */ new Map();
|
|
658
|
+
const visitedsDeepRefs = /* @__PURE__ */ new Set();
|
|
659
|
+
directDependencies.forEach((deps, ref) => {
|
|
660
|
+
if (!transitiveDependencies.has(ref)) {
|
|
661
|
+
transitiveDependencies.set(ref, /* @__PURE__ */ new Set());
|
|
662
|
+
}
|
|
663
|
+
const visit = (depRef) => {
|
|
664
|
+
transitiveDependencies.get(ref).add(depRef);
|
|
665
|
+
const deps2 = directDependencies.get(depRef);
|
|
666
|
+
if (deps2 && ref !== depRef) {
|
|
667
|
+
deps2.forEach((transitive) => {
|
|
668
|
+
const key = ref + "__" + transitive;
|
|
669
|
+
if (visitedsDeepRefs.has(key))
|
|
670
|
+
return;
|
|
671
|
+
visitedsDeepRefs.add(key);
|
|
672
|
+
visit(transitive);
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
deps.forEach((dep) => visit(dep));
|
|
677
|
+
});
|
|
678
|
+
return transitiveDependencies;
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
// src/ts-factory.ts
|
|
682
|
+
var tsFactory = createFactory({
|
|
683
|
+
union: (types) => types.map(unwrap).join(" | "),
|
|
684
|
+
intersection: (types) => types.map(unwrap).join(" & "),
|
|
685
|
+
array: (type2) => `Array<${unwrap(type2)}>`,
|
|
686
|
+
optional: (type2) => `${unwrap(type2)} | undefined`,
|
|
687
|
+
reference: (name, typeArgs) => `${name}${typeArgs ? `<${typeArgs.map(unwrap).join(", ")}>` : ""}`,
|
|
688
|
+
literal: (value) => value.toString(),
|
|
689
|
+
string: () => "string",
|
|
690
|
+
number: () => "number",
|
|
691
|
+
boolean: () => "boolean",
|
|
692
|
+
unknown: () => "unknown",
|
|
693
|
+
any: () => "any",
|
|
694
|
+
never: () => "never",
|
|
695
|
+
object: (props) => {
|
|
696
|
+
const propsString = Object.entries(props).map(([prop, type2]) => `${wrapWithQuotesIfNeeded(prop)}: ${unwrap(type2)}`).join(", ");
|
|
697
|
+
return `{ ${propsString} }`;
|
|
698
|
+
}
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
// src/map-openapi-endpoints.ts
|
|
702
|
+
import { capitalize as capitalize3, pick } from "pastable/server";
|
|
703
|
+
var factory = tsFactory;
|
|
704
|
+
var mapOpenApiEndpoints = (doc) => {
|
|
705
|
+
const refs = createRefResolver(doc, factory);
|
|
706
|
+
const ctx = { refs, factory };
|
|
707
|
+
const endpointList = [];
|
|
708
|
+
Object.entries(doc.paths ?? {}).forEach(([path, pathItemObj]) => {
|
|
709
|
+
const pathItem = pick(pathItemObj, ["get", "put", "post", "delete", "options", "head", "patch", "trace"]);
|
|
710
|
+
Object.entries(pathItem).forEach(([method, operation]) => {
|
|
711
|
+
if (operation.deprecated)
|
|
712
|
+
return;
|
|
713
|
+
const endpoint = {
|
|
714
|
+
operation,
|
|
715
|
+
method,
|
|
716
|
+
path,
|
|
717
|
+
response: openApiSchemaToTs({ schema: {}, ctx }),
|
|
718
|
+
meta: {
|
|
719
|
+
alias: getAlias({ path, method, operation }),
|
|
720
|
+
areParametersRequired: false,
|
|
721
|
+
hasParameters: false
|
|
722
|
+
}
|
|
723
|
+
};
|
|
724
|
+
const lists = { query: [], path: [], header: [] };
|
|
725
|
+
const paramObjects = (operation.parameters ?? []).reduce(
|
|
726
|
+
(acc, paramOrRef) => {
|
|
727
|
+
const param = refs.unwrap(paramOrRef);
|
|
728
|
+
const schema = openApiSchemaToTs({ schema: refs.unwrap(param.schema ?? {}), ctx });
|
|
729
|
+
lists.query.push(param);
|
|
730
|
+
if (param.required)
|
|
731
|
+
endpoint.meta.areParametersRequired = true;
|
|
732
|
+
endpoint.meta.hasParameters = true;
|
|
733
|
+
if (param.in === "query")
|
|
734
|
+
acc.query[param.name] = schema;
|
|
735
|
+
if (param.in === "path")
|
|
736
|
+
acc.path[param.name] = schema;
|
|
737
|
+
if (param.in === "header")
|
|
738
|
+
acc.header[param.name] = schema;
|
|
739
|
+
return acc;
|
|
740
|
+
},
|
|
741
|
+
{ query: {}, path: {}, header: {} }
|
|
742
|
+
);
|
|
743
|
+
const params = Object.entries(paramObjects).reduce((acc, [key, value]) => {
|
|
744
|
+
if (Object.keys(value).length) {
|
|
745
|
+
acc[key] = value;
|
|
746
|
+
}
|
|
747
|
+
return acc;
|
|
748
|
+
}, {});
|
|
749
|
+
if (operation.requestBody) {
|
|
750
|
+
const requestBody = refs.unwrap(operation.requestBody ?? {});
|
|
751
|
+
const content2 = requestBody.content;
|
|
752
|
+
const matchingMediaType = Object.keys(content2).find(isAllowedParamMediaTypes);
|
|
753
|
+
if (matchingMediaType && content2[matchingMediaType]) {
|
|
754
|
+
params.body = openApiSchemaToTs({
|
|
755
|
+
schema: content2[matchingMediaType]?.schema ?? {} ?? {},
|
|
756
|
+
ctx
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
if (params) {
|
|
761
|
+
const t = createBoxFactory({}, ctx);
|
|
762
|
+
if (params.query && lists.query.length && lists.query.every((param) => !param.required)) {
|
|
763
|
+
if (!params.query)
|
|
764
|
+
params.query = {};
|
|
765
|
+
params.query = t.reference("Partial", [t.object(params.query)]);
|
|
766
|
+
}
|
|
767
|
+
if (params.path && lists.path.length && lists.path.every((param) => !param.required)) {
|
|
768
|
+
params.path = t.reference("Partial", [t.object(params.path)]);
|
|
769
|
+
}
|
|
770
|
+
if (params.header && lists.header.length && lists.header.every((param) => !param.required)) {
|
|
771
|
+
params.header = t.reference("Partial", [t.object(params.header)]);
|
|
772
|
+
}
|
|
773
|
+
endpoint.parameters = Object.keys(params).length ? params : void 0;
|
|
774
|
+
}
|
|
775
|
+
let responseObject;
|
|
776
|
+
if (operation.responses.default) {
|
|
777
|
+
responseObject = refs.unwrap(operation.responses.default);
|
|
778
|
+
} else {
|
|
779
|
+
Object.entries(operation.responses).map(([status, responseOrRef]) => {
|
|
780
|
+
const statusCode = Number(status);
|
|
781
|
+
if (statusCode >= 200 && statusCode < 300) {
|
|
782
|
+
responseObject = refs.unwrap(responseOrRef);
|
|
783
|
+
}
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
const content = responseObject?.content;
|
|
787
|
+
if (content) {
|
|
788
|
+
const matchingMediaType = Object.keys(content).find(isResponseMediaType);
|
|
789
|
+
if (matchingMediaType && content[matchingMediaType]) {
|
|
790
|
+
endpoint.response = openApiSchemaToTs({
|
|
791
|
+
schema: content[matchingMediaType]?.schema ?? {} ?? {},
|
|
792
|
+
ctx
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
endpointList.push(endpoint);
|
|
797
|
+
});
|
|
798
|
+
});
|
|
799
|
+
return { doc, refs, endpointList, factory };
|
|
800
|
+
};
|
|
801
|
+
var allowedParamMediaTypes = [
|
|
802
|
+
"application/octet-stream",
|
|
803
|
+
"multipart/form-data",
|
|
804
|
+
"application/x-www-form-urlencoded",
|
|
805
|
+
"*/*"
|
|
806
|
+
];
|
|
807
|
+
var isAllowedParamMediaTypes = (mediaType) => mediaType.includes("application/") && mediaType.includes("json") || allowedParamMediaTypes.includes(mediaType) || mediaType.includes("text/");
|
|
808
|
+
var isResponseMediaType = (mediaType) => mediaType === "application/json";
|
|
809
|
+
var getAlias = ({ path, method, operation }) => method + "_" + capitalize3(operation.operationId ?? pathToVariableName(path));
|
|
810
|
+
|
|
811
|
+
export {
|
|
812
|
+
unwrap,
|
|
813
|
+
createFactory,
|
|
814
|
+
createBoxFactory,
|
|
815
|
+
openApiSchemaToTs,
|
|
816
|
+
allowedRuntimes,
|
|
817
|
+
generateFile,
|
|
818
|
+
createRefResolver,
|
|
819
|
+
tsFactory,
|
|
820
|
+
mapOpenApiEndpoints
|
|
821
|
+
};
|