@spec2ts/openapi-client 3.1.3 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,15 +8,16 @@
8
8
 
9
9
  ## Features
10
10
 
11
- * **AST-based:** Unlike other code generators `@spec2ts/openapi-client` does not use templates to generate code but uses TypeScript's built-in API to generate and pretty-print an abstract syntax tree.
12
- * **Tree-shakeable:** Individually exported types allows you to bundle only the ones you actually use.
13
- * **YAML or JSON:** Use YAML or JSON for your OpenAPI v3 specification.
14
- * **External references:** Resolves automatically external references and bundle or import them in generated files.
15
- * **Implementation agnostic:** Use generated types in any projet or framework.
11
+ - **AST-based:** Unlike other code generators `@spec2ts/openapi-client` does not use templates to generate code but uses TypeScript's built-in API to generate and pretty-print an abstract syntax tree.
12
+ - **Tree-shakeable:** Individually exported types allows you to bundle only the ones you actually use.
13
+ - **YAML or JSON:** Use YAML or JSON for your OpenAPI v3 specification.
14
+ - **External references:** Resolves automatically external references and bundle or import them in generated files.
15
+ - **Implementation agnostic:** Use generated types in any projet or framework.
16
16
 
17
17
  ## Installation
18
18
 
19
19
  Install in your project:
20
+
20
21
  ```bash
21
22
  npm install @spec2ts/openapi-client
22
23
  ```
@@ -65,9 +66,11 @@ async function generate(path: string): Promise<string> {
65
66
  ## Compatibility Matrix
66
67
 
67
68
  | TypeScript version | spec2ts version |
68
- |--------------------|-----------------|
69
- | v3.x.x | v1 |
70
- | v4.x.x | v2 |
69
+ | ------------------ | --------------- |
70
+ | v3.x.x | v1 |
71
+ | v4.x.x | v2 |
72
+ | v5.x.x | v3 |
73
+ | v6.x.x | v4 |
71
74
 
72
75
  ## License
73
76
 
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ import { i as usage, n as describe, r as handler, t as builder } from "../command-C4XUi5ys.mjs";
3
+ import yargs from "yargs";
4
+ import { hideBin } from "yargs/helpers";
5
+ //#region src/bin/oapi2tsclient.ts
6
+ yargs(hideBin(process.argv)).command(usage, describe, builder, handler).help("help", "Show help usage").demandCommand().argv;
7
+ //#endregion
8
+ export {};
@@ -0,0 +1,24 @@
1
+ import { t as OApiGeneratorOptions } from "../openapi-generator-B6yW0z4d.mjs";
2
+ import { Argv } from "yargs";
3
+ //#region src/cli/command.d.ts
4
+ interface BuildClientFromOpenApiOptions extends OApiGeneratorOptions {
5
+ input: string | string[];
6
+ output?: string;
7
+ banner?: string;
8
+ importFetchVersion?: string;
9
+ importFormDataVersion?: string;
10
+ packageName?: string;
11
+ packageVersion?: string;
12
+ packageAuthor?: string;
13
+ packageLicense?: string;
14
+ packagePrivate?: boolean;
15
+ packageBuildTarget?: string;
16
+ packageBuildModule?: string;
17
+ packageBuildModuleResolution?: string;
18
+ }
19
+ declare const usage = "$0 <input..>";
20
+ declare const describe = "Generate TypeScript HTTP client from OpenAPI specification";
21
+ declare function builder(argv: Argv): Argv<BuildClientFromOpenApiOptions>;
22
+ declare function handler(options: BuildClientFromOpenApiOptions): Promise<void>;
23
+ //#endregion
24
+ export { BuildClientFromOpenApiOptions, builder, describe, handler, usage };
@@ -0,0 +1,2 @@
1
+ import { i as usage, n as describe, r as handler, t as builder } from "../command-C4XUi5ys.mjs";
2
+ export { builder, describe, handler, usage };
@@ -0,0 +1,143 @@
1
+ import { n as generateClientFromFile } from "./openapi-generator-IB_huQ-a.mjs";
2
+ import { cli, printer } from "@spec2ts/core";
3
+ import path from "node:path";
4
+ import fs from "node:fs/promises";
5
+ //#region src/cli/command.ts
6
+ const usage = "$0 <input..>";
7
+ const describe = "Generate TypeScript HTTP client from OpenAPI specification";
8
+ function builder(argv) {
9
+ return argv.positional("input", {
10
+ array: true,
11
+ type: "string",
12
+ describe: "Path to OpenAPI Specification(s) to convert to TypeScript HTTP client",
13
+ demandOption: true
14
+ }).option("output", {
15
+ type: "string",
16
+ alias: "o",
17
+ describe: "Output file for generated client"
18
+ }).option("cwd", {
19
+ type: "string",
20
+ alias: "c",
21
+ describe: "Root directory for resolving $refs"
22
+ }).option("baseUrl", {
23
+ type: "string",
24
+ describe: "Base url of the server"
25
+ }).option("prefix", {
26
+ type: "string",
27
+ describe: "Only generate paths with this prefix"
28
+ }).option("avoidAny", {
29
+ type: "boolean",
30
+ describe: "Avoid the `any` type and use `unknown` instead"
31
+ }).option("enableDate", {
32
+ choices: [
33
+ true,
34
+ "strict",
35
+ "lax"
36
+ ],
37
+ describe: "Build `Date` for format `date` and `date-time`"
38
+ }).option("inlineRequired", {
39
+ type: "boolean",
40
+ describe: "Create a method argument for each required parameter"
41
+ }).option("importFetch", {
42
+ choices: [
43
+ "node-fetch",
44
+ "cross-fetch",
45
+ "isomorphic-fetch"
46
+ ],
47
+ describe: "Use a custom fetch implementation"
48
+ }).option("typesPath", {
49
+ type: "string",
50
+ describe: "Generate client types in external file relative to the output file"
51
+ }).option("importFetchVersion", {
52
+ type: "string",
53
+ describe: "Use a custom fetch implementation version"
54
+ }).option("importFormDataVersion", {
55
+ type: "string",
56
+ describe: "Use a custom form-data implementation version"
57
+ }).option("packageName", {
58
+ type: "string",
59
+ describe: "Generate a package.json with given name"
60
+ }).option("packageVersion", {
61
+ type: "string",
62
+ describe: "Set the version of the package.json"
63
+ }).option("packageAuthor", {
64
+ type: "string",
65
+ describe: "Set the author of the package.json"
66
+ }).option("packageLicense", {
67
+ type: "string",
68
+ describe: "Set the license of the package.json"
69
+ }).option("packagePrivate", {
70
+ type: "boolean",
71
+ describe: "Set the package.json private"
72
+ }).option("packageBuildTarget", {
73
+ type: "string",
74
+ describe: "Set the TypeScript build target"
75
+ }).option("packageBuildModule", {
76
+ type: "string",
77
+ describe: "Set the TypeScript build module"
78
+ }).option("packageBuildModuleResolution", {
79
+ type: "string",
80
+ describe: "Set the TypeScript build module resolution"
81
+ }).option("banner", {
82
+ type: "string",
83
+ alias: "b",
84
+ describe: "Comment prepended to the top of each generated file"
85
+ });
86
+ }
87
+ async function handler(options) {
88
+ const files = await cli.findFiles(options.input);
89
+ for (const file of files) {
90
+ const output = options.output || cli.getOutputPath(file, options);
91
+ await cli.mkdirp(output);
92
+ if (options.typesPath) {
93
+ if (!options.typesPath.startsWith(".")) options.typesPath = "./" + options.typesPath;
94
+ const res = await generateClientFromFile(file, options);
95
+ await printFile(res.client, output, options);
96
+ const outputTypes = path.resolve(path.dirname(output), options.typesPath + ".ts");
97
+ await printFile(res.types, outputTypes, options);
98
+ } else await printFile(await generateClientFromFile(file, options), output, options);
99
+ if (options.packageName) await generatePackage(output, options);
100
+ }
101
+ }
102
+ async function printFile(file, output, options) {
103
+ const content = printer.printFile(file);
104
+ await cli.mkdirp(output);
105
+ await cli.writeFile(output, (options.banner || defaultBanner()) + "\n\n" + content);
106
+ }
107
+ function defaultBanner() {
108
+ return `/**
109
+ * DO NOT MODIFY
110
+ * Generated using @spec2ts/openapi-client.
111
+ * See https://www.npmjs.com/package/@spec2ts/openapi-client
112
+ */
113
+
114
+ /* eslint-disable */`;
115
+ }
116
+ async function generatePackage(output, options) {
117
+ const outputDir = path.dirname(output);
118
+ const main = path.relative(outputDir, output);
119
+ const pkg = {
120
+ name: options.packageName,
121
+ version: options.packageVersion || "1.0.0",
122
+ description: "OpenAPI v3 client for " + options.packageName,
123
+ author: options.packageAuthor || "@spec2ts/openapi-client",
124
+ license: options.packageLicense || "UNLICENSED",
125
+ main: main.replace(/\.ts$/, ".js"),
126
+ files: ["*.js", "*.d.ts"],
127
+ scripts: {
128
+ build: `tsc ${main} --strict --target ${options.packageBuildTarget || "ES2018"} --module ${options.packageBuildModule || "node16"} --moduleResolution ${options.packageBuildModuleResolution || "node16"} --skipLibCheck`,
129
+ prepublishOnly: "npm run build"
130
+ },
131
+ dependencies: {},
132
+ devDependencies: { typescript: "^6.0.0" }
133
+ };
134
+ if (options.importFetch) {
135
+ pkg.dependencies[options.importFetch] = options.importFetchVersion || "*";
136
+ pkg.dependencies["form-data"] = options.importFormDataVersion || "*";
137
+ pkg.devDependencies["@types/node"] = "*";
138
+ }
139
+ if (options.packagePrivate) pkg.private = options.packagePrivate;
140
+ await fs.writeFile(path.join(outputDir, "package.json"), JSON.stringify(pkg, null, 2), "utf8");
141
+ }
142
+ //#endregion
143
+ export { usage as i, describe as n, handler as r, builder as t };
@@ -0,0 +1,2 @@
1
+ import { i as generateClientFromFile, n as SeparatedClientResult, r as generateClient, t as OApiGeneratorOptions } from "./openapi-generator-B6yW0z4d.mjs";
2
+ export { OApiGeneratorOptions, SeparatedClientResult, generateClient, generateClientFromFile };
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import { n as generateClientFromFile, t as generateClient } from "./openapi-generator-IB_huQ-a.mjs";
2
+ export { generateClient, generateClientFromFile };
@@ -0,0 +1,25 @@
1
+ import { ParserOptions } from "@spec2ts/jsonschema";
2
+ import ts from "typescript";
3
+ import { OpenAPIObject } from "openapi3-ts/oas31";
4
+ //#region src/lib/openapi-generator.d.ts
5
+ interface OApiGeneratorOptions extends ParserOptions {
6
+ inlineRequired?: boolean;
7
+ importFetch?: "node-fetch" | "cross-fetch" | "isomorphic-fetch";
8
+ typesPath?: string;
9
+ baseUrl?: string;
10
+ prefix?: string;
11
+ }
12
+ declare function generateClientFromFile(file: string, options: OApiGeneratorOptions & {
13
+ typesPath: string;
14
+ }): Promise<SeparatedClientResult>;
15
+ declare function generateClientFromFile(file: string, options?: OApiGeneratorOptions): Promise<ts.SourceFile>;
16
+ declare function generateClient(spec: OpenAPIObject, options: OApiGeneratorOptions & {
17
+ typesPath: string;
18
+ }): Promise<SeparatedClientResult>;
19
+ declare function generateClient(spec: OpenAPIObject, options?: OApiGeneratorOptions): Promise<ts.SourceFile>;
20
+ interface SeparatedClientResult {
21
+ client: ts.SourceFile;
22
+ types: ts.SourceFile;
23
+ }
24
+ //#endregion
25
+ export { generateClientFromFile as i, SeparatedClientResult as n, generateClient as r, OApiGeneratorOptions as t };
@@ -0,0 +1,414 @@
1
+ import $RefParser from "@apidevtools/json-schema-ref-parser";
2
+ import * as core from "@spec2ts/core";
3
+ import { createContext, getTypeFromSchema, isReference, resolveReference } from "@spec2ts/jsonschema";
4
+ import { getOperationName, getResponseName, parseReference } from "@spec2ts/openapi";
5
+ import path from "node:path";
6
+ import ts from "typescript";
7
+ //#region src/lib/util.ts
8
+ function camelCase(str) {
9
+ const words = str.match(/[A-Z\xC0-\xD6\xD8-\xDE_$]?[a-z\xDF-\xF6\xF8-\xFF_$]+|[A-Z\xC0-\xD6\xD8-\xDE_$]+(?![a-z\xDF-\xF6\xF8-\xFF_$])|\d+/g);
10
+ if (!words) return "";
11
+ let result = "";
12
+ const len = words.length;
13
+ for (let i = 0; i < len; i++) {
14
+ let tmp = words[i].toLowerCase();
15
+ if (i !== 0) tmp = tmp[0].toUpperCase() + tmp.substr(1);
16
+ result += tmp;
17
+ }
18
+ return result;
19
+ }
20
+ //#endregion
21
+ //#region src/lib/core-parser.ts
22
+ const methods = [
23
+ "GET",
24
+ "PUT",
25
+ "POST",
26
+ "DELETE",
27
+ "OPTIONS",
28
+ "HEAD",
29
+ "PATCH",
30
+ "TRACE"
31
+ ];
32
+ const contentTypes = {
33
+ "*/*": "json",
34
+ "application/json": "json",
35
+ "application/x-www-form-urlencoded": "form",
36
+ "multipart/form-data": "multipart"
37
+ };
38
+ function parseOperation(path, item, method, operation, context) {
39
+ const result = {
40
+ name: getOperationVar(method, path, operation.operationId, context),
41
+ paramsVars: {},
42
+ args: [],
43
+ query: [],
44
+ header: [],
45
+ response: core.keywordType.void
46
+ };
47
+ parseArgs(result, item, operation, context);
48
+ parseResponses(result, path, method, operation, context);
49
+ return result;
50
+ }
51
+ function parseArgs(result, item, operation, context) {
52
+ parseParameters(result, item, operation, context);
53
+ if (operation.requestBody) parseRequestBody(result, operation.requestBody, context);
54
+ result.args.push(core.createParameter("options", {
55
+ type: ts.factory.createTypeReferenceNode("RequestOptions", void 0),
56
+ questionToken: true
57
+ }));
58
+ }
59
+ function parseResponses(result, path, method, operation, context) {
60
+ const operationName = getOperationName(method, path, operation.operationId, context);
61
+ if (operation.responses) {
62
+ result.response = getTypeFromResponses(operationName, operation.responses, context);
63
+ result.responseJSON = isJSONResponse(operation.responses, context);
64
+ result.responseVoid = result.response === core.keywordType.void;
65
+ } else {
66
+ result.response = core.keywordType.unknown;
67
+ result.responseJSON = false;
68
+ result.responseVoid = false;
69
+ }
70
+ }
71
+ function parseParameters(result, item, operation, context) {
72
+ const parameters = fixDeepObjects([...resolveReferences(item.parameters, context), ...resolveReferences(operation.parameters, context)]);
73
+ result.query = parameters.filter((p) => p.in === "query");
74
+ result.header = parameters.filter((p) => p.in === "header");
75
+ const argNames = result.paramsVars = createParametersNames(parameters);
76
+ let objectBindingParams = parameters;
77
+ if (context.options.inlineRequired) {
78
+ const [required, optional] = splitParameters(parameters);
79
+ objectBindingParams = optional;
80
+ required.forEach((p) => {
81
+ result.args.push(core.createParameter(argNames[p.name], { type: getTypeFromSchema(p.schema, context) }));
82
+ });
83
+ }
84
+ if (!objectBindingParams.length) return;
85
+ result.args.push(core.createParameter(core.createObjectBinding(objectBindingParams.map(({ name }) => ({ name: argNames[name] }))), {
86
+ initializer: objectBindingParams.some((p) => p.required) ? void 0 : ts.factory.createObjectLiteralExpression(),
87
+ type: ts.factory.createTypeLiteralNode(objectBindingParams.map((p) => core.createPropertySignature({
88
+ name: argNames[p.name],
89
+ questionToken: !p.required,
90
+ type: getTypeFromSchema(p.schema, context)
91
+ })))
92
+ }));
93
+ }
94
+ function parseRequestBody(result, requestBody, context) {
95
+ const [schema, mode] = getSchemaFromContent(resolveReference(requestBody, context).content);
96
+ const type = getTypeFromSchema(schema, context);
97
+ const bodyVar = result.bodyVar = camelCase(type.name || getReferenceName(schema) || "body");
98
+ result.bodyMode = mode;
99
+ result.args.push(core.createParameter(bodyVar, { type }));
100
+ }
101
+ function createParametersNames(parameters) {
102
+ const argNames = {};
103
+ parameters.forEach(({ name }) => {
104
+ const stripped = camelCase(name.replace(/.+\./, ""));
105
+ argNames[name] = stripped in argNames ? camelCase(name) : stripped;
106
+ });
107
+ return argNames;
108
+ }
109
+ function splitParameters(parameters) {
110
+ const required = [];
111
+ const optional = [];
112
+ parameters.forEach((p) => {
113
+ if (p.required) required.push(p);
114
+ else optional.push(p);
115
+ });
116
+ return [required, optional];
117
+ }
118
+ function getTypeFromResponses(operationName, res, context) {
119
+ const codes = Object.keys(res);
120
+ const types = [];
121
+ codes.forEach((code) => {
122
+ const isOK = isOKResponse(code, codes.length);
123
+ const type = getTypeFromResponse(res[code], context);
124
+ if (!type) console.log(res[code]);
125
+ if (ts.isTypeReferenceNode(type) || core.isKeywordTypeNode(type)) {
126
+ if (isOK) types.push(type);
127
+ } else {
128
+ const name = getResponseName(operationName, code, context);
129
+ context.aliases.push(core.createTypeOrInterfaceDeclaration({
130
+ modifiers: [core.modifier.export],
131
+ name,
132
+ type
133
+ }));
134
+ if (isOK) types.push(ts.factory.createTypeReferenceNode(name, void 0));
135
+ }
136
+ });
137
+ if (types.length === 1) return types[0];
138
+ return ts.factory.createUnionTypeNode(types);
139
+ }
140
+ function isJSONResponse(responses, context) {
141
+ const codes = Object.keys(responses);
142
+ const resCode = codes.find((code) => isOKResponse(code, codes.length));
143
+ if (!resCode) return false;
144
+ const response = resolveReference(responses[resCode], context);
145
+ return !!response?.content?.["application/json"] || !!response?.content?.["*/*"];
146
+ }
147
+ function isOKResponse(code, codesCount) {
148
+ return codesCount === 1 || parseInt(code, 10) < 400;
149
+ }
150
+ function isMethod(method) {
151
+ return methods.includes(method);
152
+ }
153
+ function getTypeFromResponse(res, context) {
154
+ res = resolveReference(res, context);
155
+ if (!res?.content) return core.keywordType.void;
156
+ return getTypeFromSchema(getSchemaFromContent(res.content)[0], context);
157
+ }
158
+ function getSchemaFromContent(content) {
159
+ const res = Object.entries(contentTypes).find(([t]) => t in content);
160
+ let schema;
161
+ let mode;
162
+ if (res) {
163
+ const [contentType, contentMode] = res;
164
+ mode = contentMode;
165
+ schema = content?.[contentType]?.schema;
166
+ }
167
+ return [schema || { type: "string" }, mode];
168
+ }
169
+ function resolveReferences(array, context) {
170
+ return array?.map((ref) => resolveReference(ref, context)) ?? [];
171
+ }
172
+ function getReferenceName(obj) {
173
+ if (isReference(obj)) return camelCase(obj.$ref.split("/").slice(-1)[0]);
174
+ }
175
+ /**
176
+ * Despite its name, OpenApi's `deepObject` serialization does not support
177
+ * deeply nested objects. As a workaround we detect parameters that contain
178
+ * square brackets and merge them into a single object.
179
+ */
180
+ function fixDeepObjects(params) {
181
+ const res = [];
182
+ const merged = {};
183
+ params.forEach((p) => {
184
+ const m = /^(.+?)\[(.*?)\]/.exec(p.name);
185
+ if (!m) {
186
+ res.push(p);
187
+ return;
188
+ }
189
+ const [, name, prop] = m;
190
+ let obj = merged[name];
191
+ if (!obj) {
192
+ obj = merged[name] = {
193
+ name,
194
+ in: p.in,
195
+ style: "deepObject",
196
+ schema: {
197
+ type: "object",
198
+ properties: {}
199
+ }
200
+ };
201
+ res.push(obj);
202
+ }
203
+ obj.schema.properties[prop] = p.schema;
204
+ });
205
+ return res;
206
+ }
207
+ function getOperationVar(verb, path, operationId, context) {
208
+ const id = getOperationVarId(operationId);
209
+ if (id) return id;
210
+ return getPathVar(`${verb} ${path}`, context);
211
+ }
212
+ function getPathVar(path, context) {
213
+ path = path.replace(/\{(.+?)\}/, "by $1").replace(/\{(.+?)\}/g, "and $1");
214
+ let name = camelCase(path);
215
+ const count = context.names[name] = (context.names[name] || 0) + 1;
216
+ if (count > 1) name += count;
217
+ return name;
218
+ }
219
+ function getOperationVarId(id) {
220
+ if (!id) return;
221
+ if (id.match(/[^\w\s]/)) return;
222
+ id = camelCase(id);
223
+ if (core.isValidIdentifier(id)) return id;
224
+ }
225
+ //#endregion
226
+ //#region src/lib/server-parser.ts
227
+ function parseServers(servers) {
228
+ const props = servers.map((server, i) => [serverName(server, i), generateServerExpression(server)]);
229
+ return core.createObjectLiteral(props);
230
+ }
231
+ function defaultBaseUrl(servers) {
232
+ return ts.factory.createStringLiteral(defaultUrl(servers[0]));
233
+ }
234
+ function serverName(server, index) {
235
+ return server.description ? camelCase(server.description.replace(/\W+/, " ")) : `server${index + 1}`;
236
+ }
237
+ function generateServerExpression(server) {
238
+ return server.variables ? createServerFunction(server.url, server.variables) : ts.factory.createStringLiteral(server.url);
239
+ }
240
+ function createServerFunction(template, vars) {
241
+ const params = [core.createParameter(core.createObjectBinding(Object.entries(vars || {}).map(([name, value]) => {
242
+ return {
243
+ name,
244
+ initializer: createLiteral(value.default)
245
+ };
246
+ })), { type: ts.factory.createTypeLiteralNode(Object.entries(vars || {}).map(([name, value]) => {
247
+ return core.createPropertySignature({
248
+ name,
249
+ type: value.enum ? ts.factory.createUnionTypeNode(createUnion(value.enum)) : ts.factory.createUnionTypeNode([
250
+ core.keywordType.string,
251
+ core.keywordType.number,
252
+ core.keywordType.boolean
253
+ ])
254
+ });
255
+ })) })];
256
+ return core.createArrowFunction(params, createTemplate(template));
257
+ }
258
+ function createUnion(strs) {
259
+ return strs.map((e) => ts.factory.createLiteralTypeNode(createLiteral(e)));
260
+ }
261
+ function createTemplate(url) {
262
+ const tokens = url.split(/{([\s\S]+?)}/g);
263
+ const spans = [];
264
+ const len = tokens.length;
265
+ for (let i = 1; i < len; i += 2) {
266
+ const template_factory = i === len - 2 ? ts.factory.createTemplateTail.bind(ts.factory) : ts.factory.createTemplateMiddle.bind(ts.factory);
267
+ spans.push(ts.factory.createTemplateSpan(ts.factory.createIdentifier(tokens[i]), template_factory(tokens[i + 1])));
268
+ }
269
+ return ts.factory.createTemplateExpression(ts.factory.createTemplateHead(tokens[0]), spans);
270
+ }
271
+ function createLiteral(v) {
272
+ switch (typeof v) {
273
+ case "string": return ts.factory.createStringLiteral(v);
274
+ case "boolean": return v ? ts.factory.createTrue() : ts.factory.createFalse();
275
+ case "number": return ts.factory.createNumericLiteral(String(v));
276
+ }
277
+ }
278
+ function defaultUrl(server) {
279
+ if (!server) return "/";
280
+ const { url, variables } = server;
281
+ if (!variables) return url;
282
+ return url.replace(/\{(.+?)\}/g, (m, name) => variables[name] ? String(variables[name].default) : m);
283
+ }
284
+ //#endregion
285
+ //#region src/lib/core-generator.ts
286
+ function generateServers(file, { servers }, context) {
287
+ servers = servers || [];
288
+ if (context.options.baseUrl) servers = [{ url: context.options.baseUrl }];
289
+ const serversConst = core.findFirstVariableStatement(file.statements, "servers");
290
+ const defaultsConst = core.findFirstVariableStatement(file.statements, "defaults");
291
+ if (!serversConst || !defaultsConst) throw new Error("Invalid template: missing servers or defaults const");
292
+ file = core.replaceSourceFileStatement(file, serversConst, core.updateVariableStatementValue(serversConst, "servers", parseServers(servers)));
293
+ file = core.replaceSourceFileStatement(file, defaultsConst, core.updateVariableStatementPropertyValue(defaultsConst, "defaults", "baseUrl", defaultBaseUrl(servers)));
294
+ return file;
295
+ }
296
+ function generateDefaults(file, context) {
297
+ if (context.options.importFetch) {
298
+ const defaultsConst = core.findFirstVariableStatement(file.statements, "defaults");
299
+ if (!defaultsConst) throw new Error("Invalid template: missing defaults const");
300
+ file = core.prependSourceFileStatements(file, core.createDefaultImportDeclaration({
301
+ moduleSpecifier: context.options.importFetch,
302
+ name: "fetch",
303
+ bindings: ["RequestInit", "Headers"]
304
+ }), core.createNamespaceImportDeclaration({
305
+ moduleSpecifier: "form-data",
306
+ name: "FormData"
307
+ }));
308
+ file = core.replaceSourceFileStatement(file, defaultsConst, core.updateVariableStatementPropertyValue(defaultsConst, "defaults", "fetch", core.toExpression("fetch")));
309
+ }
310
+ return file;
311
+ }
312
+ function generateFunctions(file, spec, context) {
313
+ const paths = Object.fromEntries(Object.entries(spec.paths ?? {}).filter(([path]) => !context.options.prefix || path.startsWith(context.options.prefix)));
314
+ const functions = Object.entries(paths).map(([path, pathSpec]) => {
315
+ const item = resolveReference(pathSpec, context);
316
+ return Object.entries(item).filter(([verb]) => isMethod(verb.toUpperCase())).map(([verb, entry]) => generateFunction(path, item, verb.toUpperCase(), entry, context));
317
+ }).flat();
318
+ if (context.options.typesPath && context.typesFile) {
319
+ context.typesFile = core.updateSourceFileStatements(context.typesFile, context.aliases);
320
+ file = core.updateSourceFileStatements(file, [
321
+ core.createNamedImportDeclaration({
322
+ isTypeOnly: true,
323
+ moduleSpecifier: context.options.typesPath,
324
+ bindings: context.aliases.map((a) => a.name.text)
325
+ }),
326
+ ...file.statements,
327
+ ...functions
328
+ ]);
329
+ } else file = core.appendSourceFileStatements(file, ...context.aliases, ...functions);
330
+ return file;
331
+ }
332
+ function generateFunction(path, item, method, operation, context) {
333
+ const { name, query, header, paramsVars, args, bodyMode, bodyVar, response, responseVoid, responseJSON } = parseOperation(path, item, method, operation, context);
334
+ const url = generateUrl(path, generateQs(query, paramsVars));
335
+ const init = [ts.factory.createSpreadAssignment(ts.factory.createIdentifier("options"))];
336
+ if (method !== "GET") init.push(ts.factory.createPropertyAssignment("method", ts.factory.createStringLiteral(method)));
337
+ if (bodyVar) init.push(core.createPropertyAssignment("body", ts.factory.createIdentifier(bodyVar)));
338
+ if (header.length) init.push(ts.factory.createPropertyAssignment("headers", ts.factory.createObjectLiteralExpression([ts.factory.createSpreadAssignment(ts.factory.createPropertyAccessChain(ts.factory.createIdentifier("options"), core.questionDotToken, ts.factory.createIdentifier("headers"))), ...header.map(({ name }) => core.createPropertyAssignment(name, ts.factory.createIdentifier(paramsVars[name])))], true)));
339
+ const fetchArgs = [url];
340
+ if (init.length) {
341
+ const initObj = ts.factory.createObjectLiteralExpression(init, true);
342
+ fetchArgs.push(bodyMode ? callFunction("http", bodyMode, [initObj]) : initObj);
343
+ }
344
+ return core.addComment(core.createFunctionDeclaration(name, {
345
+ modifiers: [core.modifier.export, core.modifier.async],
346
+ type: ts.factory.createTypeReferenceNode("Promise", [ts.factory.createTypeReferenceNode("ApiResponse", [response])])
347
+ }, args, core.block(ts.factory.createReturnStatement(ts.factory.createAwaitExpression(callFunction("http", responseJSON ? "fetchJson" : responseVoid ? "fetchVoid" : "fetch", fetchArgs))))), operation.summary || operation.description);
348
+ }
349
+ function generateQs(parameters, paramsVars) {
350
+ if (!parameters.length) return;
351
+ const paramsByFormatter = groupByFormatter(parameters);
352
+ return callFunction("QS", "query", Object.entries(paramsByFormatter).map(([format, params]) => {
353
+ return callFunction("QS", format, [core.createObjectLiteral(params.map((p) => [p.name, paramsVars[p.name]]))]);
354
+ }));
355
+ }
356
+ function generateUrl(path, qs) {
357
+ const spans = [];
358
+ const head = path.replace(/(.*?)\{(.+?)\}(.*?)(?=\{|$)/g, (_, head, name, literal) => {
359
+ const expression = camelCase(name);
360
+ spans.push({
361
+ expression: ts.factory.createIdentifier(expression),
362
+ literal
363
+ });
364
+ return head;
365
+ });
366
+ if (qs) spans.push({
367
+ expression: qs,
368
+ literal: ""
369
+ });
370
+ return core.createTemplateString(head, spans);
371
+ }
372
+ function callFunction(ns, name, args) {
373
+ return core.createCall(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(ns), name), { args });
374
+ }
375
+ function groupByFormatter(parameters) {
376
+ const res = {};
377
+ parameters.forEach((param) => {
378
+ const formatter = getFormatter(param);
379
+ res[formatter] = res[formatter] || [];
380
+ res[formatter].push(param);
381
+ });
382
+ return res;
383
+ }
384
+ function getFormatter({ style, explode }) {
385
+ if (style === "spaceDelimited") return "space";
386
+ if (style === "pipeDelimited") return "pipe";
387
+ if (style === "deepObject") return "deep";
388
+ if (style === "form") return explode === false ? "form" : "explode";
389
+ return explode ? "explode" : "form";
390
+ }
391
+ //#endregion
392
+ //#region src/lib/openapi-generator.ts
393
+ async function generateClientFromFile(file, options = {}) {
394
+ return generateClient(await $RefParser.parse(file), {
395
+ cwd: path.resolve(path.dirname(file)) + "/",
396
+ ...options
397
+ });
398
+ }
399
+ async function generateClient(spec, options = {}) {
400
+ if (!options.parseReference) options.parseReference = parseReference;
401
+ const context = await createContext(spec, options);
402
+ let file = await core.createSourceFileFromFile(import.meta.dirname + "/templates/_client.tpl.ts");
403
+ if (context.options.typesPath) context.typesFile = ts.createSourceFile("types.ts", "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
404
+ file = generateServers(file, spec, context);
405
+ file = generateDefaults(file, context);
406
+ file = generateFunctions(file, spec, context);
407
+ if (context.options.typesPath) return {
408
+ client: file,
409
+ types: context.typesFile
410
+ };
411
+ return file;
412
+ }
413
+ //#endregion
414
+ export { generateClientFromFile as n, generateClient as t };