@sebspark/openapi-typegen 5.0.5 → 5.0.7

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/index.mjs ADDED
@@ -0,0 +1,893 @@
1
+ import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
2
+ import { parse, resolve } from "node:path";
3
+ import { constantCase, pascalCase } from "change-case";
4
+ import * as YAML from "yaml";
5
+ import { format } from "prettier";
6
+
7
+ //#region src/generator/formatter.ts
8
+ const options = {
9
+ parser: "typescript",
10
+ singleQuote: true,
11
+ semi: false,
12
+ trailingComma: "all"
13
+ };
14
+ const format$1 = async (code) => format(code, options);
15
+
16
+ //#endregion
17
+ //#region src/generator/document.ts
18
+ const document = ({ title, description }) => {
19
+ if (title || description) {
20
+ const tokens = [];
21
+ tokens.push("/**");
22
+ if (title) tokens.push(` * ${title}`);
23
+ if (description) tokens.push(` * ${description}`);
24
+ tokens.push(" */\n");
25
+ return tokens.join("\n");
26
+ }
27
+ return "";
28
+ };
29
+ const documentClientPath = (path, responses) => documentPath(path, responses, [param("url", "string")], [param("opts", "RequestOptions", true)]);
30
+ const documentServerPath = (path, responses) => documentPath(path, responses);
31
+ const documentPath = (path, responses, argsBefore = [], argsAfter = []) => {
32
+ const tokens = [];
33
+ tokens.push("/**");
34
+ if (path.title) tokens.push(` * ${path.title}`);
35
+ if (path.description) tokens.push(` * ${path.description}`);
36
+ tokens.push(" *");
37
+ tokens.push(...argsBefore);
38
+ if (path.args) tokens.push(...documentArgs(path.args));
39
+ tokens.push(...argsAfter);
40
+ tokens.push(` * @returns {Promise<${responses}>}`);
41
+ tokens.push(" */");
42
+ return tokens.join("\n");
43
+ };
44
+ const documentArgs = (args) => {
45
+ const tokens = [];
46
+ tokens.push(param("args", "Object", argsOptional(args), "The arguments for the request."));
47
+ tokens.push(...requestArgs(args.path, "params", "Path parameters"));
48
+ tokens.push(...requestArgs(args.query, "query", "Query parameters"));
49
+ tokens.push(...requestArgs(args.header, "headers", "Headers"));
50
+ tokens.push(...requestArgs(args.body, "body", "Request body"));
51
+ return tokens;
52
+ };
53
+ const buildPath = (path, property) => rxProperVariable.test(property) ? `${path}.${property}` : `${path}["${property}"]`;
54
+ const requestArgs = (args, name, title) => {
55
+ if (!args) return [];
56
+ const tokens = [];
57
+ const type = (args.allOf || []).map((e) => e.type).join(AND) || "Object";
58
+ tokens.push(param(buildPath("args", name), type, args.optional, `${title} for the request.`));
59
+ const properties = args.properties.flatMap((prop) => requestProperty(buildPath("args", name), prop));
60
+ tokens.push(...properties);
61
+ return tokens;
62
+ };
63
+ const requestProperty = (path, property) => {
64
+ const tokens = [];
65
+ const type = property.type.map((t) => t.type).join(OR);
66
+ tokens.push(param(buildPath(path, property.name), type, property.optional, property.title, property.description));
67
+ return tokens;
68
+ };
69
+ const param = (name, type, optional = false, title = "", description = "") => {
70
+ const tokens = [];
71
+ tokens.push(` * @param {${type}} ${optional ? "[" : ""}${name}${optional ? "]" : ""}`);
72
+ if (optional || title || description) {
73
+ tokens.push(" -");
74
+ if (optional) tokens.push(" Optional.");
75
+ if (title) tokens.push(` ${title}`);
76
+ if (description) tokens.push(` ${description}`);
77
+ }
78
+ return tokens.join("");
79
+ };
80
+
81
+ //#endregion
82
+ //#region src/generator/common.ts
83
+ const OR = " | ";
84
+ const AND = " & ";
85
+ const generateType = (parsed) => {
86
+ let type;
87
+ switch (parsed.type) {
88
+ case "enum":
89
+ type = generateEnum(parsed);
90
+ break;
91
+ case "array":
92
+ type = generateArray(parsed);
93
+ break;
94
+ case "object":
95
+ type = generateObject(parsed);
96
+ break;
97
+ case "record":
98
+ type = generateRecord(parsed);
99
+ break;
100
+ case "unknown":
101
+ type = generateUnknown(parsed);
102
+ break;
103
+ case "Date":
104
+ case "bigint":
105
+ case "boolean":
106
+ case "null":
107
+ case "number":
108
+ case "string":
109
+ case "symbol":
110
+ case "undefined":
111
+ type = generatePrimitive(parsed);
112
+ break;
113
+ default: type = generateCustom(parsed);
114
+ }
115
+ return type.replace(/ [&|] \{\s*\}/g, "");
116
+ };
117
+ const generateProperty = (property) => {
118
+ const types = property.type.map(generateType);
119
+ return `${document(property)}${propertyName(property.name)}${property.optional ? "?" : ""}: ${types.join(OR) || "unknown"}`;
120
+ };
121
+ const preamble = (type) => type.name ? `${document(type)}export type ${typeName(type.name)} = ` : "";
122
+ const rxProperVariable = /^[a-zA-Z_<>$][a-zA-Z0-9_<>$]*$/;
123
+ const isValidName = (name) => {
124
+ const namingConventionRegex = /^([A-Z_]\w*)([a-z_]\w*)(<([a-z_]\w*(,\s*)?)+>)?$/;
125
+ const hasCapitalLetterRegex = /[A-Z]/;
126
+ if (!namingConventionRegex.test(name)) return false;
127
+ if (!hasCapitalLetterRegex.test(name)) return false;
128
+ if (name[0] !== name[0].toUpperCase() && !name.includes("_")) return false;
129
+ return true;
130
+ };
131
+ const typeName = (name) => {
132
+ if (isValidName(name)) return name;
133
+ if (name.includes("<")) return name.replace(/<([^>]+)>/, (_match, genericContent) => `<${typeName(genericContent)}>`);
134
+ const domainStyleTransformed = name.split(".").map((part, index, array) => {
135
+ if (index === array.length - 1) return pascalCase(part);
136
+ return part;
137
+ }).join("_");
138
+ const prefixedIfNumberStart = domainStyleTransformed.match(/^\d/) ? `_${domainStyleTransformed}` : domainStyleTransformed;
139
+ const finalName = prefixedIfNumberStart.includes("_") ? prefixedIfNumberStart : pascalCase(prefixedIfNumberStart);
140
+ if (finalName.includes("_")) {
141
+ const lastUnderscoreIndex = finalName.lastIndexOf("_");
142
+ if (lastUnderscoreIndex !== -1 && lastUnderscoreIndex < finalName.length - 1) return finalName.substring(0, lastUnderscoreIndex + 1) + finalName.charAt(lastUnderscoreIndex + 1).toUpperCase() + finalName.slice(lastUnderscoreIndex + 2);
143
+ return finalName;
144
+ }
145
+ return finalName.charAt(0).toUpperCase() + finalName.slice(1);
146
+ };
147
+ const propertyName = (name) => {
148
+ if (rxProperVariable.test(name.replace(/\./g, "_"))) return name.replace(/\./g, "_");
149
+ return `'${name.replace(/\./g, "_")}'`;
150
+ };
151
+ const extensions = (type) => (type.allOf || []).map(generateType).concat("").join(AND) + (type.oneOf || []).map(generateType).concat("").join(OR);
152
+ const generatePrimitive = (parsed) => `${preamble(parsed)}${parsed.type}`;
153
+ const generateCustom = (parsed) => `${preamble(parsed)}${typeName(parsed.type)}`;
154
+ const generateUnknown = (parsed) => `${preamble(parsed)}unknown`;
155
+ const generateObject = (parsed) => {
156
+ const lines = [];
157
+ lines.push(`${preamble(parsed)}${extensions(parsed)}{`);
158
+ lines.push(...parsed.properties.map(generateProperty));
159
+ lines.push("}");
160
+ if (parsed.discriminator && parsed.name) lines.push(generateDiscriminator(parsed.discriminator, parsed.name));
161
+ return lines.join("\n");
162
+ };
163
+ const generateRecord = (parsed) => {
164
+ return `Record<string, ${parsed.items.type === "undefined" ? "unknown" : generateType(parsed.items)}>`;
165
+ };
166
+ const generateDiscriminator = (discriminator, name) => {
167
+ const lines = [""];
168
+ lines.push(`export type ${name}Discriminator = {`);
169
+ for (const [key, type] of Object.entries(discriminator.mapping)) lines.push(`${key}: ${type.type}`);
170
+ lines.push("}");
171
+ return lines.join("\n");
172
+ };
173
+ const generateArray = (parsed) => {
174
+ const lines = [];
175
+ let items = generateType(parsed.items);
176
+ if (parsed.items.type === "enum" || "oneOf" in parsed.items) items = `(${items})`;
177
+ lines.push(`${preamble(parsed)}${items}[]`);
178
+ return lines.join("\n");
179
+ };
180
+ const generateEnum = (parsed) => {
181
+ if (parsed.name) {
182
+ const values = parsed.values.map(serializeValue).join(", ");
183
+ const valuesName = constantCase(`${parsed.name}_VALUES`);
184
+ return [`export const ${valuesName} = [${values}] as const`, `${preamble(parsed)}typeof ${valuesName}[number]`].join("\n");
185
+ }
186
+ return `${preamble(parsed)}${parsed.values.map(serializeValue).join(OR)}`;
187
+ };
188
+ const generateHeader = (header) => {
189
+ return `${preamble(header)}{ ${propertyName(header.name)}${header.optional ? "?" : ""}: ${generateType(header.type)} }`;
190
+ };
191
+ const generateResponseBody = (type, optional = true) => {
192
+ const customType = type.type;
193
+ if (customType) return typeName(customType);
194
+ const body = type;
195
+ if (!body.data && !body.headers) return "undefined";
196
+ const tokens = [];
197
+ tokens.push(preamble(body));
198
+ tokens.push("APIResponse<");
199
+ tokens.push(body.data ? generateType(serialized(body.data, optional)) : "undefined");
200
+ if (body.headers) {
201
+ tokens.push(", ");
202
+ tokens.push(body.headers ? generateHeaders(body.headers) : "undefined");
203
+ }
204
+ tokens.push(">");
205
+ return tokens.join("");
206
+ };
207
+ const serialized = (orig, optional = true) => {
208
+ switch (orig.type) {
209
+ case "bigint":
210
+ case "boolean":
211
+ case "enum":
212
+ case "null":
213
+ case "number":
214
+ case "string":
215
+ case "symbol":
216
+ case "undefined": return orig;
217
+ case "Date": return {
218
+ ...orig,
219
+ type: "string"
220
+ };
221
+ case "array": return {
222
+ ...orig,
223
+ items: serialized(orig.items, optional)
224
+ };
225
+ case "object": return orig;
226
+ default: {
227
+ const wrapper = optional ? "PartiallySerialized" : "Serialized";
228
+ return {
229
+ ...orig,
230
+ type: `${wrapper}<${typeName(orig.type)}>`
231
+ };
232
+ }
233
+ }
234
+ };
235
+ const generateHeaders = (headers) => {
236
+ const tokens = [];
237
+ for (const header of headers) tokens.push(`${propertyName(header.name)}${header.optional ? "?" : ""}: ${generateType(header.type)}`);
238
+ return `{${tokens.join(", ")}}`;
239
+ };
240
+ const serializeValue = (value) => {
241
+ if (typeof value === "string") return `'${value}'`;
242
+ return value;
243
+ };
244
+
245
+ //#endregion
246
+ //#region src/generator/args.ts
247
+ const generateClientArgs = (args) => generateArgs(args, false);
248
+ const generateServerArgs = (args) => args ? generateArgs(args, true) : "args: Req";
249
+ const parts = [
250
+ "body",
251
+ "header",
252
+ "path",
253
+ "query"
254
+ ];
255
+ const generateArgs = (args, isServer) => {
256
+ if (args) {
257
+ const tokens = [];
258
+ for (const part of parts) {
259
+ const arg = args[part];
260
+ if (arg) {
261
+ const partName = part === "path" ? "params" : part === "header" ? "headers" : part;
262
+ if (partName === "query" && isServer) tokens.push(`${partName}${arg.optional ? "?" : ""}: QueryParams<${generateType(arg)}>`);
263
+ else tokens.push(`${partName}${arg.optional ? "?" : ""}: ${wrapArgs(generateType(arg), isServer && part === "header")}`);
264
+ }
265
+ }
266
+ if (!tokens.length) return "";
267
+ return `args${argsOptional(args) ? "?" : ""}: ${isServer ? "Req & " : ""}{ ${tokens.join(", ")} }, `;
268
+ }
269
+ return "";
270
+ };
271
+ const wrapArgs = (args, wrap) => {
272
+ if (!wrap) return args;
273
+ return `LowerCaseHeaders<${args}>`;
274
+ };
275
+ const argsOptional = (args) => parts.reduce((o, p) => o && (!args[p] || args[p].optional), true);
276
+
277
+ //#endregion
278
+ //#region src/generator/client.ts
279
+ const generateClient = (name, paths) => {
280
+ const groupedCalls = {};
281
+ for (const path of paths) {
282
+ if (!groupedCalls[path.method]) groupedCalls[path.method] = [];
283
+ groupedCalls[path.method]?.push(generateCall(path));
284
+ }
285
+ const client = [];
286
+ const methods = Object.keys(groupedCalls).map(serializeValue).join(OR);
287
+ client.push(`export type ${name}Client = Pick<BaseClient, ${methods}> & {`);
288
+ Object.entries(groupedCalls).forEach(([method, calls]) => {
289
+ client.push(`${method}: {`);
290
+ client.push(...calls);
291
+ client.push("}");
292
+ });
293
+ client.push("}");
294
+ return client.join("\n");
295
+ };
296
+ const generateCall = (path) => {
297
+ const responses = generateResponses$1(path);
298
+ return `${documentClientPath(path, responses)}
299
+ (
300
+ url: '${path.url}', ${generateClientArgs(path.args)}opts?: RequestOptions,
301
+ ): Promise<${responses}>`;
302
+ };
303
+ const generateResponses$1 = (path) => Object.entries(path.responses).filter(([code]) => Number.parseInt(code, 10) < 400).map(([, type]) => generateResponseBody(type, false)).join(OR);
304
+
305
+ //#endregion
306
+ //#region src/generator/server.ts
307
+ const generateServer = (name, paths) => {
308
+ const tokens = [];
309
+ tokens.push(`export type ${name}ServerPaths = {`);
310
+ for (const [url, methods] of Object.entries(groupPathsByUrl(paths))) tokens.push(generatePath(url, methods));
311
+ tokens.push("}");
312
+ tokens.push("\n");
313
+ tokens.push(`export type ${name}Server = APIServerDefinition & ${name}ServerPaths`);
314
+ return tokens.join("\n");
315
+ };
316
+ const groupPathsByUrl = (paths) => paths.reduce((group, path) => {
317
+ if (!group[path.url]) group[path.url] = [];
318
+ group[path.url].push(path);
319
+ return group;
320
+ }, {});
321
+ const generatePath = (url, methods) => `'${url}': {
322
+ ${methods.map(generateMethod).join("\n")}
323
+ }`;
324
+ const generateMethod = (path) => {
325
+ const responses = generateResponses(path.responses);
326
+ return `${path.method}: {
327
+ ${documentServerPath(path, responses)}
328
+ handler: (${generateServerArgs(path.args)}) => Promise<${responses}>
329
+ pre?: GenericRouteHandler | GenericRouteHandler[]
330
+ }`;
331
+ };
332
+ const generateResponses = (responses) => Object.entries(responses).filter(([code]) => Number.parseInt(code, 10) < 500).map(([code, response]) => generateResponse(Number.parseInt(code, 10), response)).join(OR);
333
+ const generateResponse = (code, response) => `[${code}, ${generateResponseBody(response)}]`;
334
+
335
+ //#endregion
336
+ //#region src/generator/generator.ts
337
+ const generate$1 = (name, doc) => `
338
+ /**
339
+ * This file was auto-generated.
340
+ * Do not make direct changes to the file.
341
+ */
342
+
343
+ import type {
344
+ APIResponse,
345
+ APIServerDefinition,
346
+ BaseClient,
347
+ ExpressRequest,
348
+ GenericRouteHandler,
349
+ LowerCaseHeaders,
350
+ PartiallySerialized,
351
+ QueryParams,
352
+ RequestOptions,
353
+ Serialized,
354
+ } from '@sebspark/openapi-core'
355
+
356
+ type Req = Pick<ExpressRequest, 'url' | 'baseUrl' | 'cookies' | 'hostname'>
357
+
358
+ /* tslint:disable */
359
+ /* eslint-disable */
360
+
361
+ ${generateComponents(doc.components)}
362
+
363
+ ${doc.paths.length ? generateServer(name, doc.paths) : ""}
364
+
365
+ ${doc.paths.length ? generateClient(name, doc.paths) : ""}
366
+
367
+ `;
368
+ const generateComponents = (components) => {
369
+ const tokens = [];
370
+ for (const schema of components.schemas) tokens.push(generateType(schema));
371
+ for (const header of components.headers) tokens.push(generateHeader(header));
372
+ for (const param$1 of components.parameters) tokens.push(generateType({
373
+ type: "object",
374
+ name: param$1.name,
375
+ properties: [{
376
+ name: param$1.parameterName,
377
+ type: [param$1.type],
378
+ optional: param$1.optional
379
+ }]
380
+ }));
381
+ for (const req of components.requestBodies) tokens.push(generateType(req));
382
+ for (const res of components.responseBodies) tokens.push(generateResponseBody(res));
383
+ for (const param$1 of components.securitySchemes) tokens.push(generateType({
384
+ type: "object",
385
+ name: param$1.name,
386
+ properties: [{
387
+ name: param$1.parameterName,
388
+ type: [param$1.type],
389
+ optional: param$1.optional
390
+ }]
391
+ }));
392
+ return tokens.join("\n\n");
393
+ };
394
+
395
+ //#endregion
396
+ //#region src/parser/common.ts
397
+ const parseRef = (ref) => ref.substring(ref.lastIndexOf("/") + 1);
398
+ const parseEnumType = (name, schema) => ({
399
+ name,
400
+ type: "enum",
401
+ values: schema.enum || []
402
+ });
403
+ const findRef = (components, ref) => {
404
+ const [, , path, name] = ref.split("/");
405
+ const schemaPath = components[path];
406
+ if (!schemaPath || !schemaPath[name]) throw new Error(`Cannot find ref ${ref}`);
407
+ return schemaPath[name];
408
+ };
409
+ const parseDocumentation = (source) => {
410
+ const documented = {};
411
+ if (source.title) documented.title = source.title;
412
+ if (source.description) documented.description = source.description;
413
+ return documented;
414
+ };
415
+
416
+ //#endregion
417
+ //#region src/parser/schema.ts
418
+ const parseSchemas = (schemas = {}) => Object.entries(schemas || {}).map(([name, schema]) => parseSchema(name, schema));
419
+ const marshall = (type, format$2) => {
420
+ if (type === "integer") return "number";
421
+ if (type === "string" && (format$2 === "date" || format$2 === "date-time")) return "Date";
422
+ return type;
423
+ };
424
+ const parseSchema = (name, schemaOrRef, generateDocs$1 = true) => {
425
+ const ref = schemaOrRef.$ref;
426
+ if (ref) return {
427
+ name,
428
+ type: parseRef(ref)
429
+ };
430
+ const schema = schemaOrRef;
431
+ switch (schema.type) {
432
+ case "array": return parseArraySchema(name, schema);
433
+ case "boolean":
434
+ case "integer":
435
+ case "number":
436
+ case "string": return schema.enum ? parseEnumType(name, schema) : name ? {
437
+ name,
438
+ type: marshall(schema.type, schema.format)
439
+ } : parsePropertyType(schema, generateDocs$1)[0];
440
+ default: return parseObjectSchema(name, schema);
441
+ }
442
+ };
443
+ const parseObjectSchema = (name, schema) => {
444
+ const type = {
445
+ name,
446
+ type: "object",
447
+ properties: [],
448
+ ...parseDocumentation(schema)
449
+ };
450
+ if (schema.properties) type.properties = Object.entries(schema.properties).map(([name$1, property]) => parseProperty(name$1, property, schema.required || []));
451
+ if (schema.allOf) type.allOf = schema.allOf.flatMap((s) => parsePropertyType(s));
452
+ if (schema.oneOf) type.oneOf = schema.oneOf.flatMap((s) => parsePropertyType(s));
453
+ if (schema.anyOf) type.oneOf = schema.anyOf.flatMap((s) => parsePropertyType(s));
454
+ if (schema.discriminator?.mapping) {
455
+ const mapping = {};
456
+ for (const [prop, ref] of Object.entries(schema.discriminator.mapping)) mapping[prop] = { type: parseRef(ref) };
457
+ type.discriminator = {
458
+ propertyName: schema.discriminator.propertyName,
459
+ mapping
460
+ };
461
+ }
462
+ if (schema.additionalProperties) {
463
+ const record = parseAdditionalProperties(schema.additionalProperties);
464
+ if (!type.allOf) type.allOf = [];
465
+ type.allOf.push(record);
466
+ }
467
+ return type;
468
+ };
469
+ const parseAdditionalProperties = (schema) => {
470
+ let items;
471
+ if (schema === true) items = { type: "undefined" };
472
+ else items = parseSchema(void 0, schema);
473
+ return {
474
+ type: "record",
475
+ items
476
+ };
477
+ };
478
+ const parseArraySchema = (name, schema) => {
479
+ if (schema.type !== "array") throw new Error("Not an array");
480
+ return {
481
+ name,
482
+ type: "array",
483
+ items: schema.items ? parseSchema(void 0, schema.items, false) : { type: "unknown" },
484
+ ...parseDocumentation(schema)
485
+ };
486
+ };
487
+ const parseProperty = (name, schema, required) => {
488
+ return {
489
+ name,
490
+ optional: !required.includes(name),
491
+ type: parsePropertyType(schema),
492
+ ...parseDocumentation(schema)
493
+ };
494
+ };
495
+ const parsePropertyType = (property, generateDocs$1 = true) => {
496
+ const ref = property.$ref;
497
+ if (ref) return [{ type: parseRef(ref) }];
498
+ const schemaObject = property;
499
+ const docs = generateDocs$1 ? parseDocumentation(schemaObject) : {};
500
+ if (schemaObject.enum) return [{
501
+ type: "enum",
502
+ values: schemaObject.enum,
503
+ ...docs
504
+ }];
505
+ if (schemaObject.type) return (Array.isArray(schemaObject.type) ? schemaObject.type : [schemaObject.type]).map((type) => {
506
+ switch (type) {
507
+ case "array": return parseArraySchema(void 0, schemaObject);
508
+ case "object": return parseObjectSchema(void 0, schemaObject);
509
+ default: return {
510
+ type: marshall(type, schemaObject.format),
511
+ ...docs
512
+ };
513
+ }
514
+ });
515
+ if (schemaObject.allOf) {
516
+ const types = [];
517
+ for (const allOf of schemaObject.allOf) {
518
+ const type = parseSchema(void 0, allOf);
519
+ delete type.name;
520
+ types.push(type);
521
+ }
522
+ return types;
523
+ }
524
+ return [];
525
+ };
526
+
527
+ //#endregion
528
+ //#region src/parser/headers.ts
529
+ const parseHeaders = (schemas = {}) => Object.entries(schemas || {}).map(([name, schema]) => parseHeader(name, schema));
530
+ const parseHeader = (name, schema) => {
531
+ return {
532
+ name,
533
+ optional: !schema.required,
534
+ type: parseSchema(void 0, schema.schema),
535
+ ...parseDocumentation(schema)
536
+ };
537
+ };
538
+
539
+ //#endregion
540
+ //#region src/parser/parameters.ts
541
+ const parseParameters = (schemas = {}) => Object.entries(schemas || {}).map(([name, schema]) => parseParameter(name, schema));
542
+ const parseParameter = (name, schema) => {
543
+ return {
544
+ name,
545
+ in: schema.in,
546
+ parameterName: schema.name,
547
+ optional: !schema.required,
548
+ type: parseSchema(void 0, schema.schema),
549
+ ...parseDocumentation(schema)
550
+ };
551
+ };
552
+
553
+ //#endregion
554
+ //#region src/parser/args.ts
555
+ const parseArgs = (path, components) => {
556
+ if (!path.parameters?.length && !path.security?.length && !path.requestBody) return void 0;
557
+ return joinArgs([
558
+ parseParameters$1(path.parameters, components),
559
+ parseSecurity(path.security, components),
560
+ parseRequestBody(path.requestBody, components)
561
+ ]);
562
+ };
563
+ const createArgs = (initializer = {}) => ({
564
+ type: "object",
565
+ properties: [],
566
+ optional: true,
567
+ ...initializer
568
+ });
569
+ const joinArgs = (args) => {
570
+ const reqArg = {};
571
+ for (const arg of args) for (const [prop, val] of Object.entries(arg)) {
572
+ const key = prop;
573
+ if (reqArg[key]) reqArg[key] = joinArg(reqArg[key], val);
574
+ else reqArg[key] = val;
575
+ }
576
+ return reqArg;
577
+ };
578
+ const joinArg = (arg1, arg2) => {
579
+ const arg = {
580
+ type: "object",
581
+ optional: arg1.optional && arg2.optional,
582
+ properties: arg1.properties.concat(arg2.properties)
583
+ };
584
+ if (arg1.allOf || arg2.allOf) arg.allOf = (arg1.allOf || []).concat(arg2.allOf || []);
585
+ if (arg1.anyOf || arg2.anyOf) arg.anyOf = (arg1.anyOf || []).concat(arg2.anyOf || []);
586
+ if (arg1.oneOf || arg2.oneOf) arg.oneOf = (arg1.oneOf || []).concat(arg2.oneOf || []);
587
+ if (arg1.description || arg2.description) arg.description = arg1.description || arg2.description;
588
+ if (arg1.title || arg2.title) arg.title = arg1.title || arg2.title;
589
+ return arg;
590
+ };
591
+ const parseSecurity = (security = [], components = {}) => {
592
+ const args = {};
593
+ for (const secReq of security) for (const [name] of Object.entries(secReq)) {
594
+ const param$1 = findRef(components, `#/components/securitySchemes/${name}`);
595
+ const arg = args.header || createArgs({ ...parseDocumentation(param$1) });
596
+ arg.optional = false;
597
+ if (!arg.allOf) arg.allOf = [];
598
+ arg.allOf.push({ type: parseRef(name) });
599
+ args.header = arg;
600
+ }
601
+ return args;
602
+ };
603
+ const parseParameters$1 = (parameters = [], components = {}) => {
604
+ const args = {};
605
+ for (const p of parameters) {
606
+ const ref = p.$ref;
607
+ if (ref) switch (ref.split("/")[2]) {
608
+ case "parameters": {
609
+ const param$1 = findRef(components, ref);
610
+ const arg = args[param$1.in] || createArgs({ ...parseDocumentation(param$1) });
611
+ arg.optional = arg.optional && !param$1.required;
612
+ if (!arg.allOf) arg.allOf = [];
613
+ arg.allOf.push({ type: parseRef(ref) });
614
+ args[param$1.in] = arg;
615
+ break;
616
+ }
617
+ case "headers": {
618
+ const header = findRef(components, ref);
619
+ const arg = args.header || createArgs();
620
+ const name = parseRef(ref);
621
+ arg.properties.push({
622
+ name,
623
+ optional: !header.required,
624
+ type: [{ type: parseSchema(void 0, header.schema).type }],
625
+ ...parseDocumentation(header.schema || {})
626
+ });
627
+ args.header = arg;
628
+ break;
629
+ }
630
+ }
631
+ else {
632
+ const param$1 = p;
633
+ const arg = args[param$1.in] || createArgs({ ...parseDocumentation(param$1) });
634
+ arg.properties.push({
635
+ name: param$1.name,
636
+ optional: !param$1.required,
637
+ type: [parseSchema(void 0, param$1.schema)]
638
+ });
639
+ arg.optional = arg.optional && !param$1.required;
640
+ args[param$1.in] = arg;
641
+ }
642
+ }
643
+ return args;
644
+ };
645
+ const parseRequestBody = (requestBody, components = {}) => {
646
+ const args = {};
647
+ if (!requestBody) return args;
648
+ const ref = requestBody.$ref;
649
+ if (ref) args.body = createArgs({
650
+ optional: !findRef(components, ref).required,
651
+ allOf: [{ type: parseRef(ref) }]
652
+ });
653
+ else {
654
+ const body = requestBody;
655
+ const bodyArgs = args.body || createArgs({
656
+ optional: !body.required,
657
+ ...parseDocumentation(body)
658
+ });
659
+ if (body.content["application/json"]) {
660
+ const schema = body.content["application/json"].schema;
661
+ if (schema) {
662
+ const parsed = parseSchema(void 0, schema);
663
+ if (parsed.type === "object") args.body = {
664
+ ...parsed,
665
+ optional: !body.required
666
+ };
667
+ else if (parsed.type) args.body = createArgs({
668
+ optional: !body.required,
669
+ allOf: [parsed],
670
+ ...parseDocumentation(body)
671
+ });
672
+ }
673
+ }
674
+ if (bodyArgs.allOf?.length || bodyArgs.oneOf?.length || bodyArgs.properties.length) args.body = bodyArgs;
675
+ }
676
+ return args;
677
+ };
678
+
679
+ //#endregion
680
+ //#region src/parser/responseBodies.ts
681
+ const parseResponseBodies = (responses = {}) => {
682
+ const bodies = [];
683
+ for (const [name, b] of Object.entries(responses)) {
684
+ const body = parseResponseBody(name, b);
685
+ bodies.push(body);
686
+ }
687
+ return bodies;
688
+ };
689
+ const parseResponseBody = (name, response) => {
690
+ const ref = response.$ref;
691
+ if (ref) return { type: parseRef(ref) };
692
+ const responseObject = response;
693
+ const body = {};
694
+ if (name) body.name = name;
695
+ if (responseObject.description) body.description = responseObject.description;
696
+ if (responseObject.content?.["application/json"]?.schema) {
697
+ const schema = responseObject.content["application/json"].schema;
698
+ body.data = parseSchema(void 0, schema);
699
+ }
700
+ if (responseObject.headers) {
701
+ body.headers = [];
702
+ for (const [headerName, header] of Object.entries(responseObject.headers)) {
703
+ const ref$1 = header.$ref;
704
+ if (ref$1) body.headers.push({
705
+ name: headerName,
706
+ optional: false,
707
+ type: { type: parseRef(ref$1) },
708
+ ...parseDocumentation(header)
709
+ });
710
+ else body.headers.push(parseHeader(headerName, header));
711
+ }
712
+ }
713
+ return body;
714
+ };
715
+
716
+ //#endregion
717
+ //#region src/parser/paths.ts
718
+ const parsePaths = (doc) => Object.entries(doc.paths || {}).flatMap(([name, path]) => parsePath(name, path, doc.components));
719
+ const parsePath = (url, path, components) => {
720
+ const paths = [];
721
+ for (const method of [
722
+ "delete",
723
+ "get",
724
+ "patch",
725
+ "post",
726
+ "put"
727
+ ]) if (path[method]) paths.push(parseMethod(url, method, path[method], components));
728
+ return paths;
729
+ };
730
+ const parseMethod = (url, method, operation, components) => {
731
+ return {
732
+ method,
733
+ url: parseUrl(url),
734
+ responses: parseResponses(operation.responses),
735
+ args: parseArgs(operation, components),
736
+ ...parseDocumentation(operation)
737
+ };
738
+ };
739
+ const parseUrl = (url) => url.replace(/{([^}]+)}/g, ":$1");
740
+ const parseResponses = (responses) => {
741
+ return Object.assign({}, ...Object.entries(responses).map(([code, response]) => {
742
+ return { [Number.parseInt(code, 10)]: parseResponseBody(void 0, response) };
743
+ }));
744
+ };
745
+
746
+ //#endregion
747
+ //#region src/parser/requestBodies.ts
748
+ const parseRequestBodies = (requestBodies = {}) => {
749
+ const definitions = [];
750
+ for (const [name, requestBody] of Object.entries(requestBodies)) if (requestBody.content["application/json"].schema) definitions.push(parseSchema(name, requestBody.content["application/json"].schema));
751
+ return definitions;
752
+ };
753
+
754
+ //#endregion
755
+ //#region src/parser/securitySchemes.ts
756
+ const parseSecuritySchemes = (schemes = {}) => {
757
+ const parameters = [];
758
+ for (const [name, scheme] of Object.entries(schemes)) parameters.push(parseSecurityScheme(name, scheme));
759
+ return parameters;
760
+ };
761
+ const parseSecurityScheme = (name, scheme) => {
762
+ switch (scheme.type) {
763
+ case "apiKey": return parseApiKey(name, scheme);
764
+ case "http": return parseHttpSecurity(name, scheme);
765
+ case "oauth2": return parseOAuth(name, scheme);
766
+ case "openIdConnect": return parseOpenIdConnect(name, scheme);
767
+ }
768
+ throw new Error(`Unknown security scheme '${scheme.type}'`);
769
+ };
770
+ const parseApiKey = (name, scheme) => {
771
+ const _in = scheme.in || "header";
772
+ return {
773
+ name,
774
+ parameterName: scheme.name,
775
+ in: _in,
776
+ optional: false,
777
+ type: { type: "string" }
778
+ };
779
+ };
780
+ const parseHttpSecurity = (name, _scheme) => ({
781
+ name,
782
+ in: "header",
783
+ parameterName: "Authorization",
784
+ optional: false,
785
+ type: { type: "string" }
786
+ });
787
+ const parseOAuth = (name, _scheme) => ({
788
+ name,
789
+ in: "header",
790
+ parameterName: "Authorization",
791
+ optional: false,
792
+ type: { type: "string" }
793
+ });
794
+ const parseOpenIdConnect = (name, _scheme) => ({
795
+ name,
796
+ in: "header",
797
+ parameterName: "Authorization",
798
+ optional: false,
799
+ type: { type: "string" }
800
+ });
801
+
802
+ //#endregion
803
+ //#region src/parser/index.ts
804
+ const parseDocument = (schema) => ({
805
+ paths: parsePaths(schema),
806
+ components: parseComponents(schema.components)
807
+ });
808
+ const parseComponents = (components = {}) => ({
809
+ schemas: parseSchemas(components.schemas),
810
+ headers: parseHeaders(components.headers),
811
+ parameters: parseParameters(components.parameters),
812
+ requestBodies: parseRequestBodies(components.requestBodies),
813
+ responseBodies: parseResponseBodies(components.responses),
814
+ securitySchemes: parseSecuritySchemes(components.securitySchemes)
815
+ });
816
+
817
+ //#endregion
818
+ //#region src/index.ts
819
+ const generateTypescript = async (name, doc) => {
820
+ return await format$1(generate$1(name, parseDocument(doc)));
821
+ };
822
+ const generate = async (input, output) => {
823
+ const generated = await generateDocs(await readDocs(input));
824
+ if (!output) return generated.map((d) => d.ts).join("\n\n");
825
+ await saveDocs(output, generated);
826
+ };
827
+ const readDocs = async (input) => {
828
+ const path = resolve(input);
829
+ const stats = await stat(path);
830
+ const filePaths = [];
831
+ if (stats.isFile()) filePaths.push(path);
832
+ if (stats.isDirectory()) {
833
+ const files = await readdir(path);
834
+ filePaths.push(...files.map((f) => resolve(path, f)));
835
+ }
836
+ const readFiles = [];
837
+ for (const p of filePaths) {
838
+ const { name, ext } = parse(p);
839
+ let doc;
840
+ switch (ext) {
841
+ case ".json": {
842
+ console.log(`Reading ${p}`);
843
+ const txt = await readFile(p, "utf8");
844
+ doc = JSON.parse(txt);
845
+ break;
846
+ }
847
+ case ".yml":
848
+ case ".yaml": {
849
+ console.log(`Reading ${p}`);
850
+ const txt = await readFile(p, "utf8");
851
+ doc = YAML.parse(txt);
852
+ break;
853
+ }
854
+ default: continue;
855
+ }
856
+ readFiles.push({
857
+ doc,
858
+ name
859
+ });
860
+ }
861
+ return readFiles;
862
+ };
863
+ const generateDocs = async (files) => {
864
+ const generated = [];
865
+ for (const doc of files) {
866
+ console.log(`Generating ${doc.name}`);
867
+ const ts = await generateTypescript(classname(doc.name), doc.doc);
868
+ generated.push({
869
+ ...doc,
870
+ ts
871
+ });
872
+ }
873
+ return generated;
874
+ };
875
+ const saveDocs = async (output, docs) => {
876
+ const dir = (await stat(output)).isDirectory() ? output : parse(output).dir;
877
+ await mkdir(dir, { recursive: true });
878
+ for (const doc of docs) {
879
+ const path = resolve(dir, `${filename(doc.name)}.ts`);
880
+ console.log(`Writing ${path}`);
881
+ await writeFile(path, doc.ts, "utf8");
882
+ }
883
+ };
884
+ const classname = (name) => {
885
+ return pascalCase(name.replace(/\d+/g, ""));
886
+ };
887
+ const filename = (name) => {
888
+ return name.replace(/\./g, "_");
889
+ };
890
+
891
+ //#endregion
892
+ export { classname, filename, generate, generateTypescript };
893
+ //# sourceMappingURL=index.mjs.map