@sebspark/openapi-typegen 0.2.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -30,549 +30,812 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var src_exports = {};
32
32
  __export(src_exports, {
33
- generate: () => generate
33
+ generate: () => generate2,
34
+ generateTypescript: () => generateTypescript
34
35
  });
35
36
  module.exports = __toCommonJS(src_exports);
36
-
37
- // src/generator.ts
38
- var fastGlob = __toESM(require("fast-glob"));
39
- var import_promises = require("fs/promises");
40
37
  var import_path = require("path");
41
- var import_yaml = require("yaml");
42
-
43
- // src/shared/format.ts
44
- var prettier = require("prettier");
45
- var header = `/**
46
- * This file was auto-generated.
47
- * Do not make direct changes to the file.
48
- */
38
+ var import_change_case2 = require("change-case");
39
+ var import_promises = require("fs/promises");
40
+ var YAML = __toESM(require("yaml"));
49
41
 
50
- import type {
51
- APIServerDefinition,
52
- BaseClient,
53
- GenericRouteHandler,
54
- } from '@sebspark/openapi-core'
55
- import type { Request } from 'express'
42
+ // src/generator/common.ts
43
+ var import_change_case = require("change-case");
56
44
 
57
- type Req = Pick<Request, 'url' | 'baseUrl' | 'cookies' | 'hostname'>
58
-
59
- /* tslint:disable */
60
- /* eslint-disable */`;
61
- var formatFile = async (rows) => {
62
- const withHeader = [header, ...rows];
63
- const code = withHeader.join("\n\n");
64
- const formatted = await prettier.format(code, {
65
- parser: "typescript",
66
- singleQuote: true,
67
- semi: false
68
- });
69
- return formatted;
45
+ // src/generator/document.ts
46
+ var document = ({ title, description }) => {
47
+ if (title || description) {
48
+ const tokens = [];
49
+ tokens.push("/**");
50
+ if (title)
51
+ tokens.push(` * ${title}`);
52
+ if (description)
53
+ tokens.push(` * ${description}`);
54
+ tokens.push(" */\n");
55
+ return tokens.join("\n");
56
+ }
57
+ return "";
70
58
  };
71
-
72
- // src/shared/imports/format.ts
73
- var formatImports = (imports) => {
74
- const rows = [];
75
- const importsMap = imports.reduce(
76
- (map, it) => {
77
- if (!map[it.file])
78
- map[it.file] = /* @__PURE__ */ new Set();
79
- map[it.file].add(it.type);
80
- return map;
81
- },
82
- {}
59
+ var documentClientPath = (path, responses) => documentPath(
60
+ path,
61
+ responses,
62
+ [param("url", "string")],
63
+ [param("opts", "RequestOptions", true)]
64
+ );
65
+ var documentServerPath = (path, responses) => documentPath(path, responses);
66
+ var documentPath = (path, responses, argsBefore = [], argsAfter = []) => {
67
+ const tokens = [];
68
+ tokens.push("/**");
69
+ if (path.title)
70
+ tokens.push(` * ${path.title}`);
71
+ if (path.description)
72
+ tokens.push(` * ${path.description}`);
73
+ tokens.push(" *");
74
+ tokens.push(...argsBefore);
75
+ if (path.args)
76
+ tokens.push(...documentArgs(path.args));
77
+ tokens.push(...argsAfter);
78
+ tokens.push(` * @returns {Promise<${responses}>}`);
79
+ tokens.push(" */");
80
+ return tokens.join("\n");
81
+ };
82
+ var documentArgs = (args) => {
83
+ const tokens = [];
84
+ tokens.push(
85
+ param(
86
+ "args",
87
+ "Object",
88
+ argsOptional(args),
89
+ "The arguments for the request."
90
+ )
83
91
  );
84
- Object.entries(importsMap).forEach(([file, types]) => {
85
- rows.push(`import { ${Array.from(types).join(", ")} } from './${file}'`);
86
- });
87
- return rows;
92
+ tokens.push(...requestArgs(args.path, "params", "Path parameters"));
93
+ tokens.push(...requestArgs(args.query, "query", "Query parameters"));
94
+ tokens.push(...requestArgs(args.header, "headers", "Headers"));
95
+ tokens.push(...requestArgs(args.body, "body", "Request body"));
96
+ return tokens;
97
+ };
98
+ var buildPath = (path, property) => rxProperVariable.test(property) ? `${path}.${property}` : `${path}["${property}"]`;
99
+ var requestArgs = (args, name, title) => {
100
+ if (!args)
101
+ return [];
102
+ const tokens = [];
103
+ const type = args.extends.map((e) => e.type).join(AND) || "Object";
104
+ tokens.push(
105
+ param(
106
+ buildPath("args", name),
107
+ type,
108
+ args.optional,
109
+ `${title} for the request.`
110
+ )
111
+ );
112
+ const properties = args.properties.flatMap(
113
+ (prop) => requestProperty(buildPath("args", name), prop)
114
+ );
115
+ tokens.push(...properties);
116
+ return tokens;
117
+ };
118
+ var requestProperty = (path, property) => {
119
+ const tokens = [];
120
+ const type = property.type.map((t) => t.type).join(OR);
121
+ tokens.push(
122
+ param(
123
+ buildPath(path, property.name),
124
+ type,
125
+ property.optional,
126
+ property.title,
127
+ property.description
128
+ )
129
+ );
130
+ return tokens;
131
+ };
132
+ var param = (name, type, optional = false, title = "", description = "") => {
133
+ const tokens = [];
134
+ tokens.push(
135
+ ` * @param {${type}} ${optional ? "[" : ""}${name}${optional ? "]" : ""}`
136
+ );
137
+ if (optional || title || description) {
138
+ tokens.push(" -");
139
+ if (optional)
140
+ tokens.push(" Optional.");
141
+ if (title)
142
+ tokens.push(` ${title}`);
143
+ if (description)
144
+ tokens.push(` ${description}`);
145
+ }
146
+ return tokens.join("");
88
147
  };
89
148
 
90
- // src/shared/schema/format.ts
91
- var formatDocs = (description) => {
92
- if (description) {
93
- return `
94
- /**
95
- * ${description}
96
- */
97
- `;
98
- } else {
99
- return "";
149
+ // src/generator/common.ts
150
+ var OR = " | ";
151
+ var AND = " & ";
152
+ var generateType = (parsed) => {
153
+ let type;
154
+ switch (parsed.type) {
155
+ case "enum": {
156
+ type = generateEnum(parsed);
157
+ break;
158
+ }
159
+ case "array": {
160
+ type = generateArray(parsed);
161
+ break;
162
+ }
163
+ case "object": {
164
+ type = generateObject(parsed);
165
+ break;
166
+ }
167
+ case "Date":
168
+ case "bigint":
169
+ case "boolean":
170
+ case "null":
171
+ case "number":
172
+ case "string":
173
+ case "symbol":
174
+ case "undefined":
175
+ type = generatePrimitive(parsed);
176
+ break;
177
+ default: {
178
+ type = generateCustom(parsed);
179
+ }
100
180
  }
181
+ return type.replace(/ & \{\s*\}/g, "");
101
182
  };
102
- var formatTypeName = (name) => {
103
- return name.replace(/[^a-zA-Z0-9_$]/g, "_");
183
+ var generateProperty = (property) => {
184
+ const types = property.type.map(generateType);
185
+ return `${document(property)}${propertyName(property.name)}${property.optional ? "?" : ""}: ${types.join(OR)}`;
104
186
  };
105
- var formatParsedType = (parsedType) => {
106
- return `${formatDocs(parsedType.description)}export type ${parsedType.name} = ${parsedType.type}`;
187
+ var preamble = (type) => type.name ? `${document(type)}export type ${typeName(type.name)} = ` : "";
188
+ var typeName = (name) => {
189
+ if (rxProperVariable.test(name))
190
+ return name;
191
+ return (0, import_change_case.pascalCase)(name);
107
192
  };
108
- var formatParsedTypes = (types) => {
109
- const rows = [];
110
- Object.values(types).forEach((values) => rows.push(formatParsedType(values)));
111
- return rows;
193
+ var propertyName = (name) => {
194
+ if (rxProperVariable.test(name))
195
+ return name;
196
+ return `'${name}'`;
112
197
  };
113
- var formatProperties = (properties) => {
114
- const allProps = properties.map(({ name, required, value, description }) => {
115
- return `${formatDocs(description)}'${name}'${required ? "" : "?"}: ${value}`;
116
- }).join(`; `);
117
- return `{${allProps}}`;
198
+ var rxProperVariable = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
199
+ var extensions = (type) => type.extends.map(generateType).concat("").join(AND);
200
+ var generatePrimitive = (parsed) => `${preamble(parsed)}${parsed.type}`;
201
+ var generateCustom = (parsed) => `${preamble(parsed)}${typeName(parsed.type)}`;
202
+ var generateObject = (parsed) => {
203
+ const lines = [];
204
+ lines.push(`${preamble(parsed)}${extensions(parsed)}{`);
205
+ lines.push(...parsed.properties.map(generateProperty));
206
+ lines.push("}");
207
+ return lines.join("\n");
118
208
  };
119
-
120
- // src/shared/schema/parser.ts
121
- var parseRefString = (refString, addImport) => {
122
- const [file, rest] = refString.split("#");
123
- const typeName = formatTypeName(rest.substring(rest.lastIndexOf("/") + 1));
124
- if (file && file.length > 0) {
125
- const [fileName] = file.split(".");
126
- addImport({ file: fileName, type: typeName });
127
- }
128
- return typeName;
129
- };
130
- var parseTypes = (schemas) => {
131
- const imports = [];
132
- const types = Object.entries(schemas).map(([name, schema]) => {
133
- const schemaObj = schema;
134
- return {
135
- name: formatTypeName(name),
136
- type: generateFromSchemaObject(schemaObj, (importData) => {
137
- imports.push(importData);
138
- }),
139
- description: schemaObj.description
140
- };
141
- });
142
- return [types, imports];
143
- };
144
- var guessType = (schema) => {
145
- if (schema.type) {
146
- return schema.type;
147
- } else if (schema.enum) {
148
- return "string";
149
- } else if (schema.properties) {
150
- return "object";
151
- } else {
152
- return void 0;
209
+ var generateArray = (parsed) => {
210
+ const lines = [];
211
+ lines.push(`${preamble(parsed)}${parsed.items.type}[]`);
212
+ return lines.join("\n");
213
+ };
214
+ var generateEnum = (parsed) => {
215
+ return `${preamble(parsed)}${parsed.values.map(serializeValue).join(OR)}`;
216
+ };
217
+ var generateHeader = (header) => {
218
+ return `${preamble(header)}{ ${propertyName(header.name)}${header.optional ? "?" : ""}: ${generateType(header.type)} }`;
219
+ };
220
+ var generateResponseBody = (type) => {
221
+ const customType = type.type;
222
+ if (customType)
223
+ return typeName(customType);
224
+ const body = type;
225
+ if (!body.data && !body.headers)
226
+ return "undefined";
227
+ const tokens = [];
228
+ tokens.push(preamble(body));
229
+ tokens.push("APIResponse<");
230
+ tokens.push(body.data ? generateType(body.data) : "undefined");
231
+ if (body.headers) {
232
+ tokens.push(", ");
233
+ tokens.push(body.headers ? generateHeaders(body.headers) : "undefined");
153
234
  }
235
+ tokens.push(">");
236
+ return tokens.join("");
154
237
  };
155
- var generateFromSchemaObject = (schema, addImport) => {
156
- if ("$ref" in schema) {
157
- const { $ref } = schema;
158
- return parseRefString($ref, addImport);
238
+ var generateHeaders = (headers) => {
239
+ const tokens = [];
240
+ for (const header of headers) {
241
+ tokens.push(
242
+ `${propertyName(header.name)}${header.optional ? "?" : ""}: ${generateType(header.type)}`
243
+ );
159
244
  }
160
- const type = guessType(schema);
161
- let schemaString = "";
162
- if (type) {
163
- if (type === "object") {
164
- schemaString = generateObject(schema, addImport);
165
- } else if (type === "array") {
166
- const arrayType = generateFromSchemaObject(schema.items, addImport);
167
- if (arrayType && arrayType.length) {
168
- schemaString = `(${arrayType})[]`;
169
- } else {
170
- schemaString = "[]";
171
- }
172
- } else {
173
- switch (type) {
174
- case "integer":
175
- schemaString = "number";
176
- break;
177
- case "string": {
178
- if (schema.format === "date-time" || schema.format === "date") {
179
- schemaString = "Date";
180
- } else if (schema.enum) {
181
- schemaString = schema.enum.map((it) => `'${it}'`).join(" | ");
182
- } else {
183
- schemaString = "string";
184
- }
185
- break;
186
- }
187
- default:
188
- schemaString = type;
245
+ return `{${tokens.join(", ")}}`;
246
+ };
247
+ var serializeValue = (value) => {
248
+ if (typeof value === "string")
249
+ return `'${value}'`;
250
+ return value;
251
+ };
252
+
253
+ // src/generator/args.ts
254
+ var generateClientArgs = (args) => generateArgs(args, false);
255
+ var generateServerArgs = (args) => args ? generateArgs(args, true) : "args: Req";
256
+ var parts = ["body", "header", "path", "query"];
257
+ var generateArgs = (args, extendsReq) => {
258
+ if (args) {
259
+ const tokens = [];
260
+ for (const part of parts) {
261
+ const arg = args[part];
262
+ if (arg) {
263
+ const partName = part === "path" ? "params" : part === "header" ? "headers" : part;
264
+ tokens.push(
265
+ `${partName}${arg.optional ? "?" : ""}: ${generateType(arg)}`
266
+ );
189
267
  }
190
268
  }
269
+ if (!tokens.length)
270
+ return "";
271
+ const optional = argsOptional(args);
272
+ return `args${optional ? "?" : ""}: ${extendsReq ? "Req & " : ""}{ ${tokens.join(", ")} }, `;
191
273
  }
192
- if (schema.allOf) {
193
- const allOfString = schema.allOf.map((it) => generateFromSchemaObject(it, addImport)).join(" & ");
194
- if (allOfString.length > 0) {
195
- schemaString = schemaString + " & " + allOfString;
274
+ return "";
275
+ };
276
+ var argsOptional = (args) => (
277
+ // biome-ignore lint/style/noNonNullAssertion: <explanation>
278
+ parts.reduce((o, p) => o && (!args[p] || args[p].optional), true)
279
+ );
280
+
281
+ // src/generator/client.ts
282
+ var generateClient = (name, paths) => {
283
+ var _a;
284
+ const groupedCalls = {};
285
+ for (const path of paths) {
286
+ if (!groupedCalls[path.method]) {
287
+ groupedCalls[path.method] = [];
196
288
  }
289
+ (_a = groupedCalls[path.method]) == null ? void 0 : _a.push(generateCall(path));
197
290
  }
198
- if (schema.anyOf) {
199
- const anyOfString = schema.anyOf.map((it) => generateFromSchemaObject(it, addImport)).map((it) => `Partial<${it}>`).join(" & ");
200
- if (anyOfString.length > 0) {
201
- schemaString = schemaString + " & " + anyOfString;
202
- }
291
+ const client = [];
292
+ const methods = Object.keys(groupedCalls).map(serializeValue).join(OR);
293
+ client.push(`export type ${name}Client = Pick<BaseClient, ${methods}> & {`);
294
+ Object.entries(groupedCalls).forEach(([method, calls]) => {
295
+ client.push(`${method}: {`);
296
+ client.push(...calls);
297
+ client.push("}");
298
+ });
299
+ client.push("}");
300
+ return client.join("\n");
301
+ };
302
+ var generateCall = (path) => {
303
+ const responses = generateResponses(path);
304
+ return `${documentClientPath(path, responses)}
305
+ (
306
+ url: '${path.url}', ${generateClientArgs(path.args)}opts?: RequestOptions,
307
+ ): Promise<${responses}>`;
308
+ };
309
+ var generateResponses = (path) => Object.entries(path.responses).filter(([code]) => parseInt(code, 10) < 400).map(([, type]) => generateResponseBody(type)).join(OR);
310
+
311
+ // src/generator/server.ts
312
+ var generateServer = (name, paths) => {
313
+ const tokens = [];
314
+ tokens.push(`export type ${name}Server = APIServerDefinition & {`);
315
+ for (const [url, methods] of Object.entries(groupPathsByUrl(paths))) {
316
+ tokens.push(generatePath(url, methods));
203
317
  }
204
- if (schema.oneOf) {
205
- const oneOfString = schema.oneOf.map((it) => generateFromSchemaObject(it, addImport)).join(" | ");
206
- if (oneOfString.length > 0) {
207
- schemaString = schemaString + " & (" + oneOfString + ")";
208
- }
318
+ tokens.push("}");
319
+ return tokens.join("\n");
320
+ };
321
+ var groupPathsByUrl = (paths) => paths.reduce(
322
+ (group, path) => {
323
+ if (!group[path.url])
324
+ group[path.url] = [];
325
+ group[path.url].push(path);
326
+ return group;
327
+ },
328
+ {}
329
+ );
330
+ var generatePath = (url, methods) => `'${url}': {
331
+ ${methods.map(generateMethod).join("\n")}
332
+ }`;
333
+ var generateMethod = (path) => {
334
+ const responses = generateResponses2(path.responses);
335
+ return `${path.method}: {
336
+ ${documentServerPath(path, responses)}
337
+ handler: (${generateServerArgs(path.args)}) => Promise<${responses}>
338
+ pre?: GenericRouteHandler | GenericRouteHandler[]
339
+ }`;
340
+ };
341
+ var generateResponses2 = (responses) => Object.entries(responses).filter(([code]) => parseInt(code, 10) < 400).map(([code, response]) => generateResponse(parseInt(code, 10), response)).join(OR);
342
+ var generateResponse = (code, response) => `[${code}, ${generateResponseBody(response)}]`;
343
+
344
+ // src/generator/generator.ts
345
+ var generate = (name, doc) => `
346
+ /**
347
+ * This file was auto-generated.
348
+ * Do not make direct changes to the file.
349
+ */
350
+
351
+ import type {
352
+ APIResponse,
353
+ APIServerDefinition,
354
+ BaseClient,
355
+ GenericRouteHandler,
356
+ RequestOptions,
357
+ } from '@sebspark/openapi-core'
358
+ import type { Request } from 'express'
359
+
360
+ type Req = Pick<Request, 'url' | 'baseUrl' | 'cookies' | 'hostname'>
361
+
362
+ /* tslint:disable */
363
+ /* eslint-disable */
364
+
365
+ ${generateComponents(doc.components)}
366
+
367
+ ${generateServer(name, doc.paths)}
368
+
369
+ ${generateClient(name, doc.paths)}
370
+
371
+ `;
372
+ var generateComponents = (components) => {
373
+ const tokens = [];
374
+ for (const schema of components.schemas) {
375
+ tokens.push(generateType(schema));
209
376
  }
210
- if (!schemaString || !schemaString.length) {
211
- schemaString = "string";
377
+ for (const header of components.headers) {
378
+ tokens.push(generateHeader(header));
212
379
  }
213
- return schemaString;
214
- };
215
- var generateObject = (schema, addImport) => {
216
- const requiredFields = schema.required ?? [];
217
- const properties = Object.entries(
218
- schema.properties ?? []
219
- ).map(([name, schema2]) => ({
220
- [name]: {
221
- name,
222
- value: generateFromSchemaObject(schema2, addImport),
223
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
224
- description: schema2["description"],
225
- required: requiredFields.includes(name)
226
- }
227
- })).reduce((acc, current) => {
228
- for (const key in current) {
229
- acc[key] = current[key];
230
- }
231
- return acc;
232
- }, {});
233
- return formatProperties(Object.values(properties));
380
+ for (const param2 of components.parameters) {
381
+ tokens.push(
382
+ generateType({
383
+ type: "object",
384
+ extends: [],
385
+ name: param2.name,
386
+ properties: [
387
+ {
388
+ name: param2.parameterName,
389
+ type: [param2.type],
390
+ optional: param2.optional
391
+ }
392
+ ]
393
+ })
394
+ );
395
+ }
396
+ for (const req of components.requestBodies) {
397
+ tokens.push(generateType(req));
398
+ }
399
+ for (const res of components.responseBodies) {
400
+ tokens.push(generateResponseBody(res));
401
+ }
402
+ return tokens.join("\n\n");
234
403
  };
235
404
 
236
- // src/shared/schema/generator.ts
237
- var generateSchemas = (components) => {
238
- const [types, imports] = parseTypes(components.components.schemas);
239
- return formatFile([...formatImports(imports), ...formatParsedTypes(types)]);
405
+ // src/generator/formatter.ts
406
+ var import_prettier = require("prettier");
407
+ var options = {
408
+ parser: "typescript",
409
+ singleQuote: true,
410
+ semi: false,
411
+ trailingComma: "all"
240
412
  };
413
+ var format = async (code) => (0, import_prettier.format)(code, options);
241
414
 
242
- // src/format.ts
243
- var formatTitle = (title) => {
244
- return title.replace(/ /g, "").replace(/[^a-zA-Z0-9]/g, "_");
415
+ // src/parser/common.ts
416
+ var parseRef = (ref) => ref.substring(ref.lastIndexOf("/") + 1);
417
+ var parseEnumType = (name, schema) => ({ name, type: "enum", values: schema.enum || [] });
418
+ var findRef = (components, ref) => {
419
+ const [, , path, name] = ref.split("/");
420
+ const schemaPath = components[path];
421
+ if (!schemaPath || !schemaPath[name])
422
+ throw new Error(`Cannot find ref ${ref}`);
423
+ return schemaPath[name];
424
+ };
425
+ var parseDocumentation = (source) => {
426
+ const documented = {};
427
+ if (source.title)
428
+ documented.title = source.title;
429
+ if (source.description)
430
+ documented.description = source.description;
431
+ return documented;
245
432
  };
246
433
 
247
- // src/openapi/format.ts
248
- var import_change_case = require("change-case");
249
- var formatRoutes = (title, routes) => {
250
- const formattedTitle = formatTitle(title);
251
- const routeDefinitions = generateRouteDefinitions(routes);
252
- const server = generateServerAPI(formattedTitle, routeDefinitions);
253
- const client = generateClientAPI(formattedTitle, routeDefinitions);
254
- return server.concat(client);
255
- };
256
- var generateServerAPI = (name, routes) => {
257
- const map = generateServerType(routes);
258
- const rows = [];
259
- rows.push(
260
- `export type ${name}Server = APIServerDefinition & {
261
- ${Object.entries(map).map(
262
- ([url, methods]) => ` '${url}': {
263
- ${Object.entries(methods).map(
264
- ([method, definition]) => ` '${method}': {
265
- handler: (${serializeArgs(definition.args, true)}) => Promise<[${definition.response.code}, ${definition.response.type}]>
266
- pre?: GenericRouteHandler | GenericRouteHandler[]
267
- }`
268
- ).join("\n")}
269
- },`
270
- ).join("\n")}
271
- }
272
- `
273
- );
274
- return rows;
275
- };
276
- var generateClientAPI = (name, routes) => {
277
- const code = [];
278
- const clientHandlers = [];
279
- const verbMap = generateClientType(routes);
280
- Object.entries(verbMap).forEach(([verb, definitions]) => {
281
- const type = `${name}Client${(0, import_change_case.pascalCase)(verb)}`;
282
- clientHandlers.push(`${verb}: ${type}`);
283
- code.push(`type ${type} = {`);
284
- definitions.forEach((def) => {
285
- const args = serializeArgs(def.args);
286
- const argsString = args ? `, ${args}` : "";
287
- code.push(
288
- `(url: '${def.url}'${argsString}): Promise<${def.response.type}>`
289
- );
290
- });
291
- code.push("}");
292
- });
293
- code.push(
294
- `export type ${name}Client = Pick<BaseClient, ${Object.keys(verbMap).map((v) => `'${v}'`).join(" | ")}> & {`
295
- );
296
- code.push(clientHandlers.join("\n"));
297
- code.push("}");
298
- return code;
299
- };
300
- var generateRouteDefinitions = (routes) => routes.map((route) => ({
301
- method: route.method.toLowerCase(),
302
- response: route.response,
303
- url: route.url,
304
- args: parseArgs(route)
305
- }));
306
- var generateClientType = (routes) => routes.reduce((router, routeDefinition) => {
307
- if (!router[routeDefinition.method])
308
- router[routeDefinition.method] = [];
309
- router[routeDefinition.method].push(routeDefinition);
310
- return router;
311
- }, {});
312
- var generateServerType = (routes) => routes.reduce((router, routeDefinition) => {
313
- const route = router[routeDefinition.url] || {};
314
- route[routeDefinition.method] = routeDefinition;
315
- router[routeDefinition.url] = route;
316
- return router;
317
- }, {});
318
- var parseArgs = (route) => {
319
- const args = {};
320
- if (route.requestParams !== "never")
321
- args.params = route.requestParams;
322
- if (route.requestQuery !== "never")
323
- args.query = route.requestQuery;
324
- if (route.requestHeaders !== "never")
325
- args.headers = route.requestHeaders;
326
- if (route.requestBody !== "never")
327
- args.body = route.requestBody;
328
- if (args.headers || args.params || args.query || args.body)
329
- return args;
434
+ // src/parser/schema.ts
435
+ var parseSchemas = (schemas = {}) => Object.entries(schemas || {}).map(
436
+ ([name, schema]) => parseSchema(name, schema)
437
+ );
438
+ var parseSchema = (name, schemaOrRef) => {
439
+ const ref = schemaOrRef.$ref;
440
+ if (ref) {
441
+ return { name, type: parseRef(ref) };
442
+ }
443
+ const schema = schemaOrRef;
444
+ switch (schema.type) {
445
+ case "array":
446
+ return parseArraySchema(name, schema);
447
+ case "boolean":
448
+ case "integer":
449
+ case "number":
450
+ case "string":
451
+ return schema.enum ? parseEnumType(name, schema) : parsePropertyType(schema)[0];
452
+ default:
453
+ return parseObjectSchema(name, schema);
454
+ }
330
455
  };
331
- var optional = (params) => {
332
- const rxRequiredParams = /(?<!\?\s*)\b['"]?\w+['"]?\s*:\s*\w+/gim;
333
- const anyRequired = rxRequiredParams.test(params) || !params.includes(":");
334
- return anyRequired ? "" : "?";
456
+ var parseObjectSchema = (name, schema) => {
457
+ const type = {
458
+ name,
459
+ type: "object",
460
+ properties: [],
461
+ extends: [],
462
+ ...parseDocumentation(schema)
463
+ };
464
+ if (schema.properties) {
465
+ type.properties = Object.entries(schema.properties).map(
466
+ ([name2, property]) => parseProperty(name2, property, schema.required || [])
467
+ );
468
+ }
469
+ if (schema.allOf) {
470
+ type.extends = schema.allOf.flatMap(parsePropertyType);
471
+ }
472
+ return type;
335
473
  };
336
- var serializeArgs = (args, includeRequest = false) => {
337
- if (!args)
338
- return "";
339
- const argsString = Object.entries(args).map(([key, value]) => {
340
- return `${key}${optional(value)}: ${value}`;
341
- }).join(",");
342
- return `args${optional(argsString)}: ${includeRequest ? "Req & " : ""}{${argsString}}`;
474
+ var parseArraySchema = (name, schema) => {
475
+ if (schema.type !== "array" || !schema.items)
476
+ throw new Error("Not an array");
477
+ return {
478
+ name,
479
+ type: "array",
480
+ items: parsePropertyType(schema.items)[0],
481
+ ...parseDocumentation(schema)
482
+ };
343
483
  };
344
-
345
- // src/openapi/parser.ts
346
- var parseReferencableObjects = (name, record) => {
347
- const objects = {};
348
- if (record) {
349
- const allParameters = Object.entries(record);
350
- const referenceParameters = allParameters.filter((it) => "$ref" in it[1]).map((it) => [it[0], it[1]]);
351
- const normalParameters = allParameters.filter((it) => !("$ref" in it[1])).map((it) => [it[0], it[1]]);
352
- normalParameters.forEach((it) => {
353
- objects[`#/components/${name}/${it[0]}`] = it[1];
354
- });
355
- referenceParameters.forEach((it) => {
356
- objects[`#/components/${name}/${it[0]}`] = objects[it[1].$ref];
484
+ var parseProperty = (name, schema, required) => {
485
+ const property = {
486
+ name,
487
+ optional: !required.includes(name),
488
+ type: parsePropertyType(schema),
489
+ ...parseDocumentation(schema)
490
+ };
491
+ return property;
492
+ };
493
+ var parsePropertyType = (property) => {
494
+ const ref = property.$ref;
495
+ if (ref) {
496
+ return [{ type: parseRef(ref) }];
497
+ }
498
+ const schemaObject = property;
499
+ if (schemaObject.type) {
500
+ return (Array.isArray(schemaObject.type) ? schemaObject.type : [schemaObject.type]).map((type) => {
501
+ switch (type) {
502
+ case "array": {
503
+ return parseArraySchema(void 0, schemaObject);
504
+ }
505
+ case "object": {
506
+ return parseObjectSchema(void 0, schemaObject);
507
+ }
508
+ case "integer": {
509
+ return { type: "number", ...parseDocumentation(schemaObject) };
510
+ }
511
+ default: {
512
+ return { type, ...parseDocumentation(schemaObject) };
513
+ }
514
+ }
357
515
  });
358
516
  }
359
- return objects;
517
+ return [];
360
518
  };
361
519
 
362
- // src/openapi/paths.ts
363
- var pathGenerator = (globalParameters) => {
364
- const expressifyPath = (path) => path.replace(/{/g, ":").replace(/}/g, "");
365
- const parseResponse = (response, addImports) => {
366
- var _a, _b;
367
- const schema = (_b = (_a = response.content) == null ? void 0 : _a["application/json"]) == null ? void 0 : _b.schema;
368
- if (!schema)
369
- return "";
370
- return generateFromSchemaObject(schema, addImports);
520
+ // src/parser/headers.ts
521
+ var parseHeaders = (schemas = {}) => Object.entries(schemas || {}).map(
522
+ ([name, schema]) => parseHeader(name, schema)
523
+ );
524
+ var parseHeader = (name, schema) => {
525
+ const header = {
526
+ name,
527
+ optional: !schema.required,
528
+ // biome-ignore lint/style/noNonNullAssertion: <explanation>
529
+ type: parseSchema(void 0, schema.schema),
530
+ ...parseDocumentation(schema)
371
531
  };
372
- const getType = (param, addImport) => {
373
- if (param.type)
374
- param.type;
375
- if (param.schema) {
376
- if ("type" in param.schema)
377
- return generateFromSchemaObject(param.schema, addImport);
378
- }
379
- return "any";
532
+ return header;
533
+ };
534
+
535
+ // src/parser/parameters.ts
536
+ var parseParameters = (schemas = {}) => Object.entries(schemas || {}).map(
537
+ ([name, schema]) => parseParameter(name, schema)
538
+ );
539
+ var parseParameter = (name, schema) => {
540
+ const param2 = {
541
+ name,
542
+ in: schema.in,
543
+ parameterName: schema.name,
544
+ optional: !schema.required,
545
+ // biome-ignore lint/style/noNonNullAssertion: <explanation>
546
+ type: parseSchema(void 0, schema.schema),
547
+ ...parseDocumentation(schema)
380
548
  };
381
- const propName = (name) => {
382
- if (name.indexOf("-") === -1)
383
- return name;
384
- return `'${name}'`;
549
+ return param2;
550
+ };
551
+
552
+ // src/parser/args.ts
553
+ var parseArgs = (path, components) => {
554
+ var _a;
555
+ if (!((_a = path.parameters) == null ? void 0 : _a.length) && !path.requestBody)
556
+ return void 0;
557
+ const args = {
558
+ ...parseParameters2(path.parameters, components),
559
+ ...parseRequestBody(path.requestBody, components)
385
560
  };
386
- const generateProps = (params, filter, addImport) => {
387
- const props = params.map((it) => {
388
- if ("$ref" in it) {
389
- return globalParameters[it.$ref];
390
- } else {
391
- return it;
561
+ return args;
562
+ };
563
+ var createArgs = (initializer = {}) => ({
564
+ type: "object",
565
+ extends: [],
566
+ properties: [],
567
+ optional: true,
568
+ ...initializer
569
+ });
570
+ var parseParameters2 = (parameters = [], components = {}) => {
571
+ const args = {};
572
+ for (const p of parameters) {
573
+ const ref = p.$ref;
574
+ if (ref) {
575
+ const part = ref.split("/")[2];
576
+ switch (part) {
577
+ case "parameters": {
578
+ const param2 = findRef(components, ref);
579
+ const arg = args[param2.in] || createArgs({ ...parseDocumentation(param2) });
580
+ arg.optional = arg.optional && !param2.required;
581
+ arg.extends.push({ type: parseRef(ref) });
582
+ args[param2.in] = arg;
583
+ break;
584
+ }
585
+ case "headers": {
586
+ const header = findRef(components, ref);
587
+ const arg = args.header || createArgs();
588
+ const name = parseRef(ref);
589
+ arg.properties.push({
590
+ name,
591
+ optional: !header.required,
592
+ // biome-ignore lint/style/noNonNullAssertion: <explanation>
593
+ type: [{ type: parseSchema(void 0, header.schema).type }],
594
+ ...parseDocumentation(header.schema || {})
595
+ });
596
+ args.header = arg;
597
+ break;
598
+ }
392
599
  }
393
- }).filter((p) => p.in === filter).map(
394
- (p) => `${propName(p.name)}${p.required ? "" : "?"}: ${getType(
395
- p,
396
- addImport
397
- )}`
398
- );
399
- if (props.length)
400
- return `{${props.join(", ")}}`;
401
- return "never";
402
- };
403
- const generateBody = (body, addImport) => {
404
- var _a, _b;
405
- if ((_a = body == null ? void 0 : body.content) == null ? void 0 : _a["application/json"]) {
406
- const content = (_b = body.content["application/json"]) == null ? void 0 : _b.schema;
407
- return generateFromSchemaObject(content, addImport);
600
+ } else {
601
+ const param2 = p;
602
+ const arg = args[param2.in] || createArgs({ ...parseDocumentation(param2) });
603
+ arg.properties.push({
604
+ name: param2.name,
605
+ optional: !param2.required,
606
+ type: [parseSchema(void 0, param2.schema)]
607
+ });
608
+ arg.optional = arg.optional && !param2.required;
609
+ args[param2.in] = arg;
408
610
  }
409
- return "never";
410
- };
411
- const generateResponses = (responses, addImport, errors = false) => {
412
- const responseTypes = Object.entries(responses).map(([strCode, response]) => {
413
- const code = parseInt(strCode, 10);
414
- if (code >= 400 !== errors)
415
- return;
416
- if ("$ref" in response) {
417
- return {
418
- code,
419
- type: parseRefString(response.$ref, addImport)
420
- };
611
+ }
612
+ return args;
613
+ };
614
+ var parseRequestBody = (requestBody, components = {}) => {
615
+ const args = {};
616
+ if (!requestBody)
617
+ return args;
618
+ const ref = requestBody.$ref;
619
+ if (ref) {
620
+ const refBody = findRef(components, ref);
621
+ args.body = createArgs({
622
+ optional: !refBody.required,
623
+ extends: [{ type: parseRef(ref) }]
624
+ });
625
+ } else {
626
+ const body = requestBody;
627
+ const bodyArgs = args.body || createArgs({ optional: !body.required, ...parseDocumentation(body) });
628
+ if (body.content["application/json"]) {
629
+ const schema = body.content["application/json"].schema;
630
+ if (schema) {
631
+ const parsed = parseSchema(void 0, schema);
632
+ if (parsed.type === "object") {
633
+ args.body = {
634
+ ...parsed,
635
+ optional: !body.required
636
+ };
637
+ } else if (parsed.type) {
638
+ args.body = createArgs({
639
+ optional: !body.required,
640
+ extends: [parsed],
641
+ ...parseDocumentation(body)
642
+ });
643
+ }
421
644
  }
422
- return {
423
- code,
424
- type: parseResponse(response, addImport) || "void"
425
- };
426
- }).filter((r) => r);
427
- return responseTypes;
428
- };
429
- const generateRoutes = (path, item, addImport) => {
430
- const verbs = ["get", "post", "put", "patch", "delete"];
431
- const routes = verbs.map((verb) => {
432
- const operation = item[verb];
433
- if (!operation)
434
- return void 0;
435
- const route = {
436
- url: expressifyPath(path),
437
- method: verb,
438
- requestBody: generateBody(
439
- operation.requestBody,
440
- addImport
441
- ),
442
- requestParams: generateProps(
443
- operation.parameters || [],
444
- "path",
445
- addImport
446
- ),
447
- requestQuery: generateProps(
448
- operation.parameters || [],
449
- "query",
450
- addImport
451
- ),
452
- requestHeaders: generateProps(
453
- operation.parameters || [],
454
- "header",
455
- addImport
456
- ),
457
- response: generateResponses(operation.responses, addImport)[0],
458
- errorResponses: generateResponses(
459
- operation.responses,
460
- addImport,
461
- true
462
- )
463
- };
464
- return route;
465
- }).filter((r) => r);
466
- return routes;
467
- };
468
- const generatePaths = (paths) => {
469
- const imports = [];
470
- const routes = Object.entries(paths).flatMap(
471
- ([path, item]) => generateRoutes(path, item, (imp) => imports.push(imp))
472
- );
473
- return [routes, imports];
474
- };
475
- return {
476
- generate: generatePaths
477
- };
645
+ }
646
+ if (bodyArgs.extends.length || bodyArgs.properties.length) {
647
+ args.body = bodyArgs;
648
+ }
649
+ }
650
+ return args;
478
651
  };
479
652
 
480
- // src/openapi/generator.ts
481
- var generateData = (schema) => {
482
- var _a, _b, _c;
483
- const [types, importsFromTypes] = ((_a = schema.components) == null ? void 0 : _a.schemas) ? parseTypes(schema.components.schemas) : [[], []];
484
- const parameters = parseReferencableObjects(
485
- "parameters",
486
- ((_b = schema.components) == null ? void 0 : _b.parameters) || {}
487
- );
488
- const [paths, importsFromPaths] = pathGenerator(parameters).generate(
489
- schema.paths || {}
490
- );
653
+ // src/parser/responseBodies.ts
654
+ var parseResponseBodies = (responses = {}) => {
655
+ const bodies = [];
656
+ for (const [name, b] of Object.entries(responses)) {
657
+ const body = parseResponseBody(name, b);
658
+ bodies.push(body);
659
+ }
660
+ return bodies;
661
+ };
662
+ var parseResponseBody = (name, response) => {
663
+ var _a, _b;
664
+ const ref = response.$ref;
665
+ if (ref)
666
+ return { type: parseRef(ref) };
667
+ const responseObject = response;
668
+ const body = {};
669
+ if (name)
670
+ body.name = name;
671
+ if (responseObject.description)
672
+ body.description = responseObject.description;
673
+ if ((_b = (_a = responseObject.content) == null ? void 0 : _a["application/json"]) == null ? void 0 : _b.schema) {
674
+ const schema = responseObject.content["application/json"].schema;
675
+ body.data = parseSchema(void 0, schema);
676
+ }
677
+ if (responseObject.headers) {
678
+ body.headers = [];
679
+ for (const [headerName, header] of Object.entries(responseObject.headers)) {
680
+ const ref2 = header.$ref;
681
+ if (ref2)
682
+ body.headers.push({
683
+ name: headerName,
684
+ optional: false,
685
+ type: { type: parseRef(ref2) },
686
+ ...parseDocumentation(header)
687
+ });
688
+ else
689
+ body.headers.push(parseHeader(headerName, header));
690
+ }
691
+ }
692
+ return body;
693
+ };
694
+
695
+ // src/parser/paths.ts
696
+ var parsePaths = (doc) => Object.entries(doc.paths || {}).flatMap(
697
+ ([name, path]) => parsePath(name, path, doc.components)
698
+ );
699
+ var parsePath = (url, path, components) => {
700
+ const paths = [];
701
+ const methods = ["delete", "get", "patch", "post", "put"];
702
+ for (const method of methods) {
703
+ if (path[method]) {
704
+ paths.push(
705
+ parseMethod(url, method, path[method], components)
706
+ );
707
+ }
708
+ }
709
+ return paths;
710
+ };
711
+ var parseMethod = (url, method, operation, components) => {
491
712
  return {
492
- types,
493
- paths,
494
- imports: importsFromPaths.concat(importsFromTypes),
495
- title: ((_c = schema.info) == null ? void 0 : _c.title) ?? ""
713
+ method,
714
+ url: parseUrl(url),
715
+ responses: parseResponses(operation.responses),
716
+ args: parseArgs(operation, components),
717
+ ...parseDocumentation(operation)
496
718
  };
497
719
  };
498
- var generateFormattedString = async ({
499
- title,
500
- imports,
501
- paths,
502
- types
503
- }) => {
504
- const formattedImports = formatImports(imports);
505
- const formattedTypes = formatParsedTypes(types);
506
- const formattedRoutes = formatRoutes(title, paths);
507
- const formatted = await formatFile([
508
- ...formattedImports,
509
- ...formattedTypes,
510
- ...formattedRoutes
511
- ]);
512
- return formatted;
513
- };
514
- var generateOpenApi = async (schema) => {
515
- const data = generateData(schema);
516
- const formatted = await generateFormattedString(data);
517
- return formatted;
720
+ var parseUrl = (url) => url.replace(/{([^}]+)}/g, ":$1");
721
+ var parseResponses = (responses) => {
722
+ return Object.assign(
723
+ {},
724
+ ...Object.entries(responses).map(([code, response]) => {
725
+ return {
726
+ [parseInt(code, 10)]: parseResponseBody(void 0, response)
727
+ };
728
+ })
729
+ );
518
730
  };
519
731
 
520
- // src/generator.ts
521
- var getSchemas = async (input) => {
522
- const schemas = {
523
- openApi: {},
524
- // asyncApi: {},
525
- sharedTypes: {}
526
- };
527
- const files = fastGlob.sync(input, { globstar: true, dot: true });
528
- for (const file of files) {
529
- const { name } = (0, import_path.parse)(file);
530
- const content = await (0, import_promises.readFile)(file, "utf-8");
531
- const parsed = file.endsWith(".yaml") || file.endsWith(".yml") ? (0, import_yaml.parse)(content) : JSON.parse(content);
532
- if (parsed["asyncapi"]) {
533
- } else if (parsed["openapi"]) {
534
- schemas.openApi[name] = parsed;
535
- } else if (parsed["components"]) {
536
- schemas.sharedTypes[name] = parsed;
732
+ // src/parser/requestBodies.ts
733
+ var parseRequestBodies = (requestBodies = {}) => {
734
+ const definitions = [];
735
+ for (const [name, requestBody] of Object.entries(requestBodies)) {
736
+ if (requestBody.content["application/json"].schema) {
737
+ definitions.push(
738
+ parseSchema(name, requestBody.content["application/json"].schema)
739
+ );
537
740
  }
538
741
  }
539
- return schemas;
540
- };
541
- var generate = async ({
542
- input,
543
- output
544
- }) => {
545
- if (!input)
546
- throw new Error("You need to supply at least one schema");
547
- const schemas = await getSchemas(input);
548
- const generatedOpenApi = await Promise.all(
549
- Object.entries(schemas.openApi).map(async ([name, schema]) => ({
550
- name,
551
- schema: await generateOpenApi(schema)
552
- }))
553
- );
554
- const generatedSharedTypes = await Promise.all(
555
- Object.entries(schemas.sharedTypes).map(async ([name, schema]) => ({
556
- name,
557
- schema: await generateSchemas(schema)
558
- }))
559
- );
742
+ return definitions;
743
+ };
744
+
745
+ // src/parser/index.ts
746
+ var parseDocument = (schema) => ({
747
+ paths: parsePaths(schema),
748
+ components: parseComponents(schema.components)
749
+ });
750
+ var parseComponents = (components = {}) => ({
751
+ schemas: parseSchemas(components.schemas),
752
+ headers: parseHeaders(components.headers),
753
+ parameters: parseParameters(components.parameters),
754
+ requestBodies: parseRequestBodies(components.requestBodies),
755
+ responseBodies: parseResponseBodies(components.responses)
756
+ });
757
+
758
+ // src/index.ts
759
+ var generateTypescript = async (name, doc) => {
760
+ const parsed = parseDocument(doc);
761
+ const generated = generate(name, parsed);
762
+ const formatted = await format(generated);
763
+ return formatted;
764
+ };
765
+ var generate2 = async (input, output) => {
766
+ const docs = await readDocs(input);
767
+ const generated = await generateDocs(docs);
560
768
  if (!output)
561
- return generatedOpenApi.concat(generatedSharedTypes).map(
562
- ({ name, schema }) => `/**
563
- * ${name}
564
- */
565
- ${schema}
566
- `
567
- ).join("\n");
568
- await (0, import_promises.mkdir)(output, { recursive: true });
569
- for (const { name, schema } of generatedOpenApi.concat(generatedSharedTypes)) {
570
- const path = (0, import_path.join)(output, `${name}.ts`);
571
- await (0, import_promises.writeFile)(path, schema, "utf-8");
769
+ return generated.map((d) => d.ts).join("\n\n");
770
+ await saveDocs(output, generated);
771
+ };
772
+ var readDocs = async (input) => {
773
+ const path = (0, import_path.resolve)(input);
774
+ const stats = await (0, import_promises.stat)(path);
775
+ const filePaths = [];
776
+ if (stats.isFile())
777
+ filePaths.push(path);
778
+ if (stats.isDirectory()) {
779
+ const files = await (0, import_promises.readdir)(path);
780
+ filePaths.push(...files.map((f) => (0, import_path.resolve)(path, f)));
572
781
  }
573
- return;
782
+ const readFiles = [];
783
+ for (const p of filePaths) {
784
+ const { name, ext } = (0, import_path.parse)(p);
785
+ let doc;
786
+ switch (ext) {
787
+ case ".json": {
788
+ console.log(`Reading ${p}`);
789
+ const txt = await (0, import_promises.readFile)(p, "utf8");
790
+ doc = JSON.parse(txt);
791
+ break;
792
+ }
793
+ case ".yml":
794
+ case ".yaml": {
795
+ console.log(`Reading ${p}`);
796
+ const txt = await (0, import_promises.readFile)(p, "utf8");
797
+ doc = YAML.parse(txt);
798
+ break;
799
+ }
800
+ default:
801
+ continue;
802
+ }
803
+ readFiles.push({
804
+ doc,
805
+ name: formatName(name)
806
+ });
807
+ }
808
+ return readFiles;
809
+ };
810
+ var generateDocs = async (files) => {
811
+ const generated = [];
812
+ for (const doc of files) {
813
+ console.log(`Generating ${doc.name}`);
814
+ const ts = await generateTypescript(doc.name, doc.doc);
815
+ generated.push({
816
+ ...doc,
817
+ ts
818
+ });
819
+ }
820
+ return generated;
821
+ };
822
+ var saveDocs = async (output, docs) => {
823
+ const stats = await (0, import_promises.stat)(output);
824
+ const dir = stats.isDirectory() ? output : (0, import_path.parse)(output).dir;
825
+ await (0, import_promises.mkdir)(dir, { recursive: true });
826
+ for (const doc of docs) {
827
+ const path = (0, import_path.resolve)(dir, `${doc.name}.ts`);
828
+ console.log(`Writing ${path}`);
829
+ await (0, import_promises.writeFile)(path, doc.ts, "utf8");
830
+ }
831
+ };
832
+ var formatName = (name) => {
833
+ if (name[0].toUpperCase() === name[0])
834
+ return name;
835
+ return (0, import_change_case2.pascalCase)(name);
574
836
  };
575
837
  // Annotate the CommonJS export names for ESM import in node:
576
838
  0 && (module.exports = {
577
- generate
839
+ generate,
840
+ generateTypescript
578
841
  });