@technicity/openapi-sdk-generator 2.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/generate.d.ts +46 -0
- package/dist/generate.js +763 -0
- package/dist/generate.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/openapi-sdk-generator.d.ts +2 -0
- package/dist/openapi-sdk-generator.js +56 -0
- package/dist/openapi-sdk-generator.js.map +1 -0
- package/package.json +33 -0
- package/publish.sh +8 -0
- package/src/generate.ts +954 -0
- package/src/index.ts +1 -0
- package/src/openapi-sdk-generator.ts +67 -0
- package/tsconfig.json +12 -0
package/dist/generate.js
ADDED
|
@@ -0,0 +1,763 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
const child_process = require("child_process");
|
|
7
|
+
const fetch = require("node-fetch");
|
|
8
|
+
const json_schema_to_typescript_1 = require("json-schema-to-typescript");
|
|
9
|
+
const SwaggerParser = require("@apidevtools/swagger-parser");
|
|
10
|
+
const Ajv = require("ajv");
|
|
11
|
+
const toJsonSchema = require("@openapi-contrib/openapi-schema-to-json-schema");
|
|
12
|
+
const fse = require("fs-extra");
|
|
13
|
+
const traverse = require("json-schema-traverse");
|
|
14
|
+
const { json2ts } = require("json-ts");
|
|
15
|
+
const prettier = require("prettier");
|
|
16
|
+
const prependFile = require("prepend-file");
|
|
17
|
+
exports.schema = {
|
|
18
|
+
type: "object",
|
|
19
|
+
properties: {
|
|
20
|
+
def: {
|
|
21
|
+
type: "string",
|
|
22
|
+
description: "Path or URL to OpenAPI definition",
|
|
23
|
+
},
|
|
24
|
+
outdir: {
|
|
25
|
+
type: "string",
|
|
26
|
+
description: "Directory in which to generate files",
|
|
27
|
+
},
|
|
28
|
+
preProcessDef: {
|
|
29
|
+
type: "string",
|
|
30
|
+
description: "String for JS function to run on OpenAPI def before prior to validation and de-referencing",
|
|
31
|
+
},
|
|
32
|
+
cacheType: {
|
|
33
|
+
type: "string",
|
|
34
|
+
enum: ["memory", "none"],
|
|
35
|
+
default: "memory",
|
|
36
|
+
description: "'memory' | 'none'",
|
|
37
|
+
},
|
|
38
|
+
throwOnNotOkayRes: {
|
|
39
|
+
type: "boolean",
|
|
40
|
+
default: false,
|
|
41
|
+
description: "Whether a SDK method should throw when the response status code is not okay",
|
|
42
|
+
},
|
|
43
|
+
packageName: {
|
|
44
|
+
type: "string",
|
|
45
|
+
},
|
|
46
|
+
packageAuthor: {
|
|
47
|
+
type: "string",
|
|
48
|
+
},
|
|
49
|
+
packageDescription: {
|
|
50
|
+
type: "string",
|
|
51
|
+
},
|
|
52
|
+
module: {
|
|
53
|
+
type: "string",
|
|
54
|
+
enum: ["CommonJS", "ES2020"],
|
|
55
|
+
default: "CommonJS",
|
|
56
|
+
description: "module type for generated SDK",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
additionalProperties: false,
|
|
60
|
+
required: ["def", "outdir"],
|
|
61
|
+
};
|
|
62
|
+
const ajv = new Ajv({ allErrors: true, useDefaults: true, coerceTypes: true });
|
|
63
|
+
async function generate(opts) {
|
|
64
|
+
const valid = ajv.validate(exports.schema, opts);
|
|
65
|
+
if (!valid) {
|
|
66
|
+
console.log(ajv.errors);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
const isUrl = opts.def.startsWith("http://") || opts.def.startsWith("https://");
|
|
70
|
+
if (!isUrl && !fs.existsSync(path.resolve(opts.def))) {
|
|
71
|
+
throw new Error("Invalid path.");
|
|
72
|
+
}
|
|
73
|
+
let doc = isUrl
|
|
74
|
+
? await fetch(opts.def, {}).then((res) => res.json())
|
|
75
|
+
: JSON.parse(fs.readFileSync(path.resolve(opts.def), { encoding: "utf8" }));
|
|
76
|
+
if (opts.preProcessDef) {
|
|
77
|
+
const fn = Function("doc", opts.preProcessDef);
|
|
78
|
+
doc = fn(doc);
|
|
79
|
+
}
|
|
80
|
+
// https://apitools.dev/swagger-parser/docs/options.html
|
|
81
|
+
const api = await SwaggerParser.validate(doc);
|
|
82
|
+
// Ignore defs without operationId
|
|
83
|
+
for (let [p, v1] of Object.entries(api.paths)) {
|
|
84
|
+
for (let [m, v2] of Object.entries(v1)) {
|
|
85
|
+
if (!getOperationId(v2) ||
|
|
86
|
+
opts.filterDef?.({ pathname: p, method: m }) === false) {
|
|
87
|
+
delete api.paths[p][m];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const optsTypeGet = "{headers?: {[k: string]: string}, strategy?: 'network'}";
|
|
92
|
+
const optsType = "{headers?: {[k: string]: string}, clearCache?: boolean}";
|
|
93
|
+
let routes = await Promise.all(Object.keys(api.paths)
|
|
94
|
+
.sort()
|
|
95
|
+
.map(async (path) => {
|
|
96
|
+
let output = "";
|
|
97
|
+
if (api.paths[path].get) {
|
|
98
|
+
let responseType = await createResponseBodyType(api.paths[path], "get", opts.throwOnNotOkayRes);
|
|
99
|
+
let queryType = await createQueryType(api.paths[path].get.parameters);
|
|
100
|
+
const hasUrlParameters = (api.paths[path].get.parameters || []).some((x) => x.in === "path");
|
|
101
|
+
let getFcn = `${getOperationId(api.paths[path].get)}(
|
|
102
|
+
params${hasUrlParameters ? "" : "?"}: ${createParamType(path).type},
|
|
103
|
+
query?: ${queryType},
|
|
104
|
+
opts?: ${optsTypeGet}
|
|
105
|
+
) : Promise<${responseType}> {
|
|
106
|
+
let path = "${convertPath(path)}";
|
|
107
|
+
return this._get(path, params, query, opts);
|
|
108
|
+
};`;
|
|
109
|
+
output += getFcn;
|
|
110
|
+
}
|
|
111
|
+
if (api.paths[path].post) {
|
|
112
|
+
const method = "post";
|
|
113
|
+
let requestBodyType = await createRequestBodyType(api.paths[path], method);
|
|
114
|
+
const hasNoBody = requestBodyType.type === "undefined";
|
|
115
|
+
const requestBodyContentType = api.paths[path][method]?.requestBody
|
|
116
|
+
? getRequestBodyContentType(api.paths[path], method)
|
|
117
|
+
: undefined;
|
|
118
|
+
let responseType = await createResponseBodyType(api.paths[path], method, opts.throwOnNotOkayRes);
|
|
119
|
+
// https://stackoverflow.com/a/35799817
|
|
120
|
+
let fcn = `${getOperationId(api.paths[path][method])}(
|
|
121
|
+
params: ${createParamType(path).type},
|
|
122
|
+
data${hasNoBody ? "?" : ""}: ${requestBodyType.type},
|
|
123
|
+
query?: ${await createQueryType(api.paths[path][method].parameters)},
|
|
124
|
+
opts?: ${optsType}
|
|
125
|
+
) : Promise<${responseType}> {
|
|
126
|
+
let path = "${convertPath(path)}";
|
|
127
|
+
return this._${method}(path, params, data, query, opts, ${requestBodyContentType === undefined ||
|
|
128
|
+
requestBodyContentType === "multipart/form-data"
|
|
129
|
+
? "undefined"
|
|
130
|
+
: `"${requestBodyContentType}"`});
|
|
131
|
+
};`;
|
|
132
|
+
output += fcn;
|
|
133
|
+
}
|
|
134
|
+
if (api.paths[path].patch) {
|
|
135
|
+
const method = "patch";
|
|
136
|
+
let requestBodyType = await createRequestBodyType(api.paths[path], method);
|
|
137
|
+
const hasNoBody = requestBodyType.type === "undefined";
|
|
138
|
+
const requestBodyContentType = api.paths[path][method]?.requestBody
|
|
139
|
+
? getRequestBodyContentType(api.paths[path], method)
|
|
140
|
+
: undefined;
|
|
141
|
+
let responseType = await createResponseBodyType(api.paths[path], method, opts.throwOnNotOkayRes);
|
|
142
|
+
// https://stackoverflow.com/a/35799817
|
|
143
|
+
let fcn = `${getOperationId(api.paths[path][method])}(
|
|
144
|
+
params: ${createParamType(path).type},
|
|
145
|
+
data${hasNoBody ? "?" : ""}: ${requestBodyType.type},
|
|
146
|
+
query?: ${await createQueryType(api.paths[path][method].parameters)},
|
|
147
|
+
opts?: ${optsType}
|
|
148
|
+
) : Promise<${responseType}> {
|
|
149
|
+
let path = "${convertPath(path)}";
|
|
150
|
+
return this._${method}(path, params, data, query, opts, ${requestBodyContentType === undefined ||
|
|
151
|
+
requestBodyContentType === "multipart/form-data"
|
|
152
|
+
? "undefined"
|
|
153
|
+
: `"${requestBodyContentType}"`});
|
|
154
|
+
};`;
|
|
155
|
+
output += fcn;
|
|
156
|
+
}
|
|
157
|
+
if (api.paths[path].put) {
|
|
158
|
+
const method = "put";
|
|
159
|
+
let requestBodyType = await createRequestBodyType(api.paths[path], method);
|
|
160
|
+
const hasNoBody = requestBodyType.type === "undefined";
|
|
161
|
+
const requestBodyContentType = api.paths[path][method]?.requestBody
|
|
162
|
+
? getRequestBodyContentType(api.paths[path], method)
|
|
163
|
+
: undefined;
|
|
164
|
+
let responseType = await createResponseBodyType(api.paths[path], method, opts.throwOnNotOkayRes);
|
|
165
|
+
// https://stackoverflow.com/a/35799817
|
|
166
|
+
let fcn = `${getOperationId(api.paths[path][method])}(
|
|
167
|
+
params: ${createParamType(path).type},
|
|
168
|
+
data${hasNoBody ? "?" : ""}: ${requestBodyType.type},
|
|
169
|
+
query?: ${await createQueryType(api.paths[path][method].parameters)},
|
|
170
|
+
opts?: ${optsType}
|
|
171
|
+
) : Promise<${responseType}> {
|
|
172
|
+
let path = "${convertPath(path)}";
|
|
173
|
+
return this._${method}(path, params, data, query, opts, ${requestBodyContentType === undefined ||
|
|
174
|
+
requestBodyContentType === "multipart/form-data"
|
|
175
|
+
? "undefined"
|
|
176
|
+
: `"${requestBodyContentType}"`});
|
|
177
|
+
};`;
|
|
178
|
+
output += fcn;
|
|
179
|
+
}
|
|
180
|
+
if (api.paths[path].delete) {
|
|
181
|
+
let responseType = await createResponseBodyType(api.paths[path], "delete", opts.throwOnNotOkayRes);
|
|
182
|
+
const hasUrlParameters = (api.paths[path].delete.parameters || []).some((x) => x.in === "path");
|
|
183
|
+
let deleteFcn = `${getOperationId(api.paths[path].delete)}(
|
|
184
|
+
params${hasUrlParameters ? "" : "?"}: ${createParamType(path).type},
|
|
185
|
+
query?: ${await createQueryType(api.paths[path].delete.parameters)},
|
|
186
|
+
opts?: ${optsType}
|
|
187
|
+
) : Promise<${responseType}> {
|
|
188
|
+
let path = "${convertPath(path)}";
|
|
189
|
+
return this._delete(path, params, query, opts);
|
|
190
|
+
};`;
|
|
191
|
+
output += deleteFcn;
|
|
192
|
+
}
|
|
193
|
+
return output;
|
|
194
|
+
}));
|
|
195
|
+
const code = prettier.format(getSDKCode(routes.join(" "), opts.throwOnNotOkayRes, opts.cacheType), { parser: "typescript" });
|
|
196
|
+
const tsConfigJSON = {
|
|
197
|
+
compilerOptions: {
|
|
198
|
+
target: "ES2020",
|
|
199
|
+
module: opts.module,
|
|
200
|
+
moduleResolution: "node",
|
|
201
|
+
declaration: true,
|
|
202
|
+
outDir: "./dist",
|
|
203
|
+
forceConsistentCasingInFileNames: true,
|
|
204
|
+
},
|
|
205
|
+
include: ["index.ts"],
|
|
206
|
+
};
|
|
207
|
+
const typeScriptVersion = "4.5.5";
|
|
208
|
+
const packageJSON = {
|
|
209
|
+
name: "temp",
|
|
210
|
+
version: "1.0.0",
|
|
211
|
+
dependencies: {
|
|
212
|
+
"cross-fetch": "^3.1.5",
|
|
213
|
+
"node-cache": "^4.2.1",
|
|
214
|
+
qs: "^6.9.4",
|
|
215
|
+
"@types/qs": "^6.9.5",
|
|
216
|
+
},
|
|
217
|
+
devDependencies: {
|
|
218
|
+
"@types/node": "13.9.1",
|
|
219
|
+
typescript: typeScriptVersion,
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
const tmpDirPath = path.join(os.tmpdir(), `osg-${Date.now()}`);
|
|
223
|
+
fse.mkdirpSync(tmpDirPath);
|
|
224
|
+
fs.writeFileSync(path.join(tmpDirPath, "index.ts"), code);
|
|
225
|
+
fs.writeFileSync(path.join(tmpDirPath, "package.json"), JSON.stringify(packageJSON, null, 2));
|
|
226
|
+
fs.writeFileSync(path.join(tmpDirPath, "tsconfig.json"), JSON.stringify(tsConfigJSON, null, 2));
|
|
227
|
+
child_process.execSync("npm i", { cwd: tmpDirPath, stdio: "inherit" });
|
|
228
|
+
const buildOutputPath = path.join(tmpDirPath, "dist");
|
|
229
|
+
// const nccVersion = "0.33.1";
|
|
230
|
+
// child_process.execSync(
|
|
231
|
+
// `npx --yes -p @vercel/ncc@${nccVersion} ncc build ./index.ts -o dist`,
|
|
232
|
+
// { cwd: tmpDirPath, stdio: "inherit" }
|
|
233
|
+
// );
|
|
234
|
+
// TODO - remove if we get ncc build output to work with CRA build
|
|
235
|
+
child_process.execSync(`npx -p typescript@${typeScriptVersion} tsc`, {
|
|
236
|
+
cwd: tmpDirPath,
|
|
237
|
+
stdio: "inherit",
|
|
238
|
+
});
|
|
239
|
+
// Disable ESLint for the compiled file. Mainly for CRA.
|
|
240
|
+
prependFile.sync(path.join(buildOutputPath, "index.js"), `/* eslint-disable */\n\n`);
|
|
241
|
+
const outdir = path.resolve(opts.outdir);
|
|
242
|
+
if (!fs.existsSync(outdir)) {
|
|
243
|
+
fse.mkdirpSync(outdir);
|
|
244
|
+
}
|
|
245
|
+
const isGeneratePackageMode = opts.packageName != null;
|
|
246
|
+
const distOutputPath = isGeneratePackageMode
|
|
247
|
+
? path.join(outdir, "dist")
|
|
248
|
+
: outdir;
|
|
249
|
+
fse.copySync(buildOutputPath, distOutputPath);
|
|
250
|
+
fse.removeSync(tmpDirPath);
|
|
251
|
+
if (isGeneratePackageMode) {
|
|
252
|
+
const packageJSON = {
|
|
253
|
+
name: opts.packageName,
|
|
254
|
+
version: "1.0.0",
|
|
255
|
+
author: opts.packageAuthor ?? "",
|
|
256
|
+
license: "MIT",
|
|
257
|
+
description: opts.packageDescription ?? "",
|
|
258
|
+
main: "./dist/index.js",
|
|
259
|
+
types: "./dist/index.d.ts",
|
|
260
|
+
};
|
|
261
|
+
const gitignoreSource = `/node_modules
|
|
262
|
+
/.pnp
|
|
263
|
+
.pnp.js
|
|
264
|
+
|
|
265
|
+
npm-debug.log*
|
|
266
|
+
yarn-debug.log*
|
|
267
|
+
yarn-error.log*
|
|
268
|
+
|
|
269
|
+
.vscode
|
|
270
|
+
`;
|
|
271
|
+
fs.writeFileSync(path.join(outdir, "package.json"), JSON.stringify(packageJSON, null, 2));
|
|
272
|
+
fs.writeFileSync(path.join(outdir, ".gitignore"), gitignoreSource);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
exports.generate = generate;
|
|
276
|
+
function getSDKCode(methods, throwOnNotOkayRes, cacheType) {
|
|
277
|
+
const useMemoryCache = cacheType === "memory";
|
|
278
|
+
return `import "cross-fetch/polyfill";
|
|
279
|
+
import * as qs from "qs";
|
|
280
|
+
${useMemoryCache ? `const NodeCache = require("node-cache");` : ""}
|
|
281
|
+
|
|
282
|
+
type Headers = { [k: string]: string };
|
|
283
|
+
|
|
284
|
+
${throwOnNotOkayRes
|
|
285
|
+
? `class CustomError extends Error {
|
|
286
|
+
constructor(message) {
|
|
287
|
+
super(message);
|
|
288
|
+
this.name = this.constructor.name;
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
export class SDKResponseError extends CustomError {
|
|
293
|
+
error: any;
|
|
294
|
+
status: number;
|
|
295
|
+
|
|
296
|
+
constructor(error, status) {
|
|
297
|
+
super("Bad response");
|
|
298
|
+
this.error = error;
|
|
299
|
+
this.status = status;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
`
|
|
303
|
+
: ""}
|
|
304
|
+
|
|
305
|
+
type QueryStringOpts = Parameters<typeof qs.stringify>[1]
|
|
306
|
+
|
|
307
|
+
export class SDK {
|
|
308
|
+
_baseUrl?: string;
|
|
309
|
+
_headers?: Headers;
|
|
310
|
+
_queryStringOpts?: QueryStringOpts;
|
|
311
|
+
${useMemoryCache ? `_cache: InstanceType<typeof NodeCache>;` : ""}
|
|
312
|
+
|
|
313
|
+
constructor(opts: { baseUrl: string; headers?: Headers, queryStringOpts?: QueryStringOpts }) {
|
|
314
|
+
this.__setBaseUrl(opts.baseUrl);
|
|
315
|
+
this._queryStringOpts = opts.queryStringOpts;
|
|
316
|
+
if (opts?.headers) {
|
|
317
|
+
this._headers = opts.headers ?? {};
|
|
318
|
+
}
|
|
319
|
+
${useMemoryCache
|
|
320
|
+
? `this._cache = new NodeCache({
|
|
321
|
+
// 10 min
|
|
322
|
+
stdTTL: 600,
|
|
323
|
+
// 5 min
|
|
324
|
+
checkperiod: 300,
|
|
325
|
+
});`
|
|
326
|
+
: ""}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
__setBaseUrl(baseUrl: string) {
|
|
330
|
+
this._baseUrl = baseUrl;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
__setHeaders(fn: any) {
|
|
334
|
+
this._headers = fn(this._headers);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
__clearCache() {
|
|
338
|
+
${useMemoryCache ? `this._cache.flushAll();` : ""}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
${methods}
|
|
342
|
+
|
|
343
|
+
async _get(path, params, query, opts) {
|
|
344
|
+
const { hydratedPath } = findParamArguments(path, params);
|
|
345
|
+
const url = \`\${this._baseUrl}\${hydratedPath}\${generateQueryString(query, this._queryStringOpts)}\`;
|
|
346
|
+
const cacheKey = \`GET\${url}\`;
|
|
347
|
+
|
|
348
|
+
${useMemoryCache
|
|
349
|
+
? `if (opts?.strategy !== "network") {
|
|
350
|
+
const cached = this._cache.get(cacheKey);
|
|
351
|
+
if (cached !== undefined) {
|
|
352
|
+
return cached;
|
|
353
|
+
}
|
|
354
|
+
}`
|
|
355
|
+
: ""}
|
|
356
|
+
|
|
357
|
+
let headers = { "Content-Type": "application/json" };
|
|
358
|
+
if (this._headers) {
|
|
359
|
+
headers = { ...headers, ...this._headers };
|
|
360
|
+
}
|
|
361
|
+
if (opts?.headers) {
|
|
362
|
+
headers = { ...headers, ...opts.headers };
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
let res = await fetch(url, { headers });
|
|
366
|
+
|
|
367
|
+
const json = await res.json();
|
|
368
|
+
|
|
369
|
+
if (!res.ok) {
|
|
370
|
+
${throwOnNotOkayRes
|
|
371
|
+
? `throw new SDKResponseError(json, res.status);`
|
|
372
|
+
: `return { error: json, status: res.status };`}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const out = { data: json, status: res.status };
|
|
376
|
+
${useMemoryCache ? `this._cache.set(cacheKey, out);` : ""}
|
|
377
|
+
|
|
378
|
+
return out;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async _post(path, params, data, query, opts, requestBodyContentType) {
|
|
382
|
+
const { hydratedPath } = findParamArguments(path, params);
|
|
383
|
+
|
|
384
|
+
let headers = !requestBodyContentType ? {} : { "Content-Type": requestBodyContentType };
|
|
385
|
+
if (this._headers) {
|
|
386
|
+
headers = { ...headers, ...this._headers };
|
|
387
|
+
}
|
|
388
|
+
if (opts?.headers) {
|
|
389
|
+
headers = { ...headers, ...opts.headers };
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
let res = await fetch(
|
|
393
|
+
\`\${this._baseUrl}\${hydratedPath}\${generateQueryString(query, this._queryStringOpts)}\`,
|
|
394
|
+
{
|
|
395
|
+
method: "POST",
|
|
396
|
+
headers,
|
|
397
|
+
body: requestBodyContentType === "application/json" ? JSON.stringify(data) : data,
|
|
398
|
+
}
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
if (res.status === 204) {
|
|
402
|
+
${useMemoryCache
|
|
403
|
+
? `if (opts?.clearCache !== false) {
|
|
404
|
+
this._cache.flushAll();
|
|
405
|
+
}`
|
|
406
|
+
: ""}
|
|
407
|
+
return { data: undefined, status: res.status };
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const json = await res.json();
|
|
411
|
+
|
|
412
|
+
if (!res.ok) {
|
|
413
|
+
${throwOnNotOkayRes
|
|
414
|
+
? `throw new SDKResponseError(json, res.status);`
|
|
415
|
+
: `return { error: json, status: res.status };`}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
${useMemoryCache
|
|
419
|
+
? `if (opts?.clearCache !== false) {
|
|
420
|
+
this._cache.flushAll();
|
|
421
|
+
}`
|
|
422
|
+
: ""}
|
|
423
|
+
|
|
424
|
+
return { data: json, status: res.status };
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
async _patch(path, params, data, query, opts, requestBodyContentType) {
|
|
428
|
+
const { hydratedPath } = findParamArguments(path, params);
|
|
429
|
+
|
|
430
|
+
let headers = !requestBodyContentType ? {} : { "Content-Type": requestBodyContentType };
|
|
431
|
+
if (this._headers) {
|
|
432
|
+
headers = { ...headers, ...this._headers };
|
|
433
|
+
}
|
|
434
|
+
if (opts?.headers) {
|
|
435
|
+
headers = { ...headers, ...opts.headers };
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
let res = await fetch(
|
|
439
|
+
\`\${this._baseUrl}\${hydratedPath}\${generateQueryString(query, this._queryStringOpts)}\`,
|
|
440
|
+
{
|
|
441
|
+
method: "PATCH",
|
|
442
|
+
headers,
|
|
443
|
+
body: requestBodyContentType === "application/json" ? JSON.stringify(data) : data,
|
|
444
|
+
}
|
|
445
|
+
);
|
|
446
|
+
|
|
447
|
+
if (res.status === 204) {
|
|
448
|
+
${useMemoryCache
|
|
449
|
+
? `if (opts?.clearCache !== false) {
|
|
450
|
+
this._cache.flushAll();
|
|
451
|
+
}`
|
|
452
|
+
: ""}
|
|
453
|
+
return { data: undefined, status: res.status };
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const json = await res.json();
|
|
457
|
+
|
|
458
|
+
if (!res.ok) {
|
|
459
|
+
${throwOnNotOkayRes
|
|
460
|
+
? `throw new SDKResponseError(json, res.status);`
|
|
461
|
+
: `return { error: json, status: res.status };`}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
${useMemoryCache
|
|
465
|
+
? `if (opts?.clearCache !== false) {
|
|
466
|
+
this._cache.flushAll();
|
|
467
|
+
}`
|
|
468
|
+
: ""}
|
|
469
|
+
|
|
470
|
+
return { data: json, status: res.status };
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
async _put(path, params, data, query, opts, requestBodyContentType) {
|
|
474
|
+
const { hydratedPath } = findParamArguments(path, params);
|
|
475
|
+
|
|
476
|
+
let headers = !requestBodyContentType ? {} : { "Content-Type": requestBodyContentType };
|
|
477
|
+
if (this._headers) {
|
|
478
|
+
headers = { ...headers, ...this._headers };
|
|
479
|
+
}
|
|
480
|
+
if (opts?.headers) {
|
|
481
|
+
headers = { ...headers, ...opts.headers };
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
let res = await fetch(
|
|
485
|
+
\`\${this._baseUrl}\${hydratedPath}\${generateQueryString(query, this._queryStringOpts)}\`,
|
|
486
|
+
{
|
|
487
|
+
method: "PUT",
|
|
488
|
+
headers,
|
|
489
|
+
body: requestBodyContentType === "application/json" ? JSON.stringify(data) : data,
|
|
490
|
+
}
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
if (res.status === 204) {
|
|
494
|
+
${useMemoryCache
|
|
495
|
+
? `if (opts?.clearCache !== false) {
|
|
496
|
+
this._cache.flushAll();
|
|
497
|
+
}`
|
|
498
|
+
: ""}
|
|
499
|
+
return { data: undefined, status: res.status };
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const json = await res.json();
|
|
503
|
+
|
|
504
|
+
if (!res.ok) {
|
|
505
|
+
${throwOnNotOkayRes
|
|
506
|
+
? `throw new SDKResponseError(json, res.status);`
|
|
507
|
+
: `return { error: json, status: res.status };`}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
${useMemoryCache
|
|
511
|
+
? `if (opts?.clearCache !== false) {
|
|
512
|
+
this._cache.flushAll();
|
|
513
|
+
}`
|
|
514
|
+
: ""}
|
|
515
|
+
|
|
516
|
+
return { data: json, status: res.status };
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
async _delete(path, params, query, opts) {
|
|
520
|
+
const { hydratedPath } = findParamArguments(path, params);
|
|
521
|
+
|
|
522
|
+
let headers = { "Content-Type": "application/json" };
|
|
523
|
+
if (this._headers) {
|
|
524
|
+
headers = { ...headers, ...this._headers };
|
|
525
|
+
}
|
|
526
|
+
if (opts?.headers) {
|
|
527
|
+
headers = { ...headers, ...opts.headers };
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
let res = await fetch(
|
|
531
|
+
\`\${this._baseUrl}\${hydratedPath}\${generateQueryString(query, this._queryStringOpts)}\`,
|
|
532
|
+
{ method: "DELETE", headers }
|
|
533
|
+
);
|
|
534
|
+
|
|
535
|
+
if (res.status === 204) {
|
|
536
|
+
${useMemoryCache
|
|
537
|
+
? `if (opts?.clearCache !== false) {
|
|
538
|
+
this._cache.flushAll();
|
|
539
|
+
}`
|
|
540
|
+
: ""}
|
|
541
|
+
return { data: undefined, status: res.status };
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const json = await res.json();
|
|
545
|
+
|
|
546
|
+
if (!res.ok) {
|
|
547
|
+
${throwOnNotOkayRes
|
|
548
|
+
? `throw new SDKResponseError(json, res.status);`
|
|
549
|
+
: `return { error: json, status: res.status };`}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
${useMemoryCache
|
|
553
|
+
? `if (opts?.clearCache !== false) {
|
|
554
|
+
this._cache.flushAll();
|
|
555
|
+
}`
|
|
556
|
+
: ""}
|
|
557
|
+
|
|
558
|
+
return { data: json, status: res.status };
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function findParamArguments(path, obj) {
|
|
563
|
+
let parts = path.split(/\\//);
|
|
564
|
+
let params = parts.filter((p) => p.length > 0 && p[0] === ":");
|
|
565
|
+
let args = params.map((p) => p.substring(1, p.length));
|
|
566
|
+
let out = {};
|
|
567
|
+
let hydratedPath = path;
|
|
568
|
+
if (obj) {
|
|
569
|
+
args.forEach((e) => {
|
|
570
|
+
out[e] = obj[e];
|
|
571
|
+
hydratedPath = hydratedPath.replace(\`:\${e}\`, obj[e]);
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
return { args, out, hydratedPath };
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function generateQueryString(obj, _opts) {
|
|
579
|
+
let opts = { addQueryPrefix: true, encode: true };
|
|
580
|
+
if (_opts) {
|
|
581
|
+
opts = {...opts, ..._opts};
|
|
582
|
+
}
|
|
583
|
+
return qs.stringify(obj, opts);
|
|
584
|
+
}
|
|
585
|
+
`;
|
|
586
|
+
}
|
|
587
|
+
function getRequestBodyContentType(operationDef, method) {
|
|
588
|
+
const content = operationDef?.[method]?.requestBody?.content;
|
|
589
|
+
if (content == null) {
|
|
590
|
+
throw new Error("No content: " + getOperationId(operationDef));
|
|
591
|
+
}
|
|
592
|
+
const keys = Object.keys(content);
|
|
593
|
+
if (keys.length === 0) {
|
|
594
|
+
throw new Error("No content type: " + getOperationId(operationDef));
|
|
595
|
+
}
|
|
596
|
+
if (keys.length > 1) {
|
|
597
|
+
throw new Error("More than 1 content type. Not allowed: " + getOperationId(operationDef));
|
|
598
|
+
}
|
|
599
|
+
return keys[0];
|
|
600
|
+
}
|
|
601
|
+
async function createRequestBodyType(operationDef, method) {
|
|
602
|
+
let typeName = capitalize(getOperationId(operationDef[method])) + "Body";
|
|
603
|
+
if (operationDef?.[method]?.requestBody == null) {
|
|
604
|
+
return { type: "undefined", name: "" };
|
|
605
|
+
}
|
|
606
|
+
if (operationDef?.[method]?.requestBody?.content?.["application/json"] == null) {
|
|
607
|
+
return { type: "any", name: "" };
|
|
608
|
+
}
|
|
609
|
+
if (operationDef?.[method]?.requestBody?.content?.["application/json"]
|
|
610
|
+
?.schema == null ||
|
|
611
|
+
operationDef[method].requestBody.content["application/json"].schema
|
|
612
|
+
.length === 0) {
|
|
613
|
+
return { type: "object", name: "" };
|
|
614
|
+
}
|
|
615
|
+
const schema = toJsonSchema(operationDef[method].requestBody.content["application/json"].schema);
|
|
616
|
+
// Delete title, since if there's a title, json-schema-to-typescript
|
|
617
|
+
// generates references.
|
|
618
|
+
traverse(schema, {
|
|
619
|
+
cb: (schema) => {
|
|
620
|
+
if (schema.title) {
|
|
621
|
+
delete schema.title;
|
|
622
|
+
}
|
|
623
|
+
},
|
|
624
|
+
});
|
|
625
|
+
return {
|
|
626
|
+
type: (await json_schema_to_typescript_1.compile(schema, "IRootObject", {
|
|
627
|
+
bannerComment: "",
|
|
628
|
+
declareExternallyReferenced: false,
|
|
629
|
+
}))
|
|
630
|
+
.replace(/export interface IRootObject/g, "")
|
|
631
|
+
.replace(/export type IRootObject =/g, "")
|
|
632
|
+
.replace(/};/g, "}")
|
|
633
|
+
.replace(`/**\n * The request body is an empty object.\n */\n{}\n`, "object"),
|
|
634
|
+
name: typeName,
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
function createParamType(path) {
|
|
638
|
+
// TODO - use `parameters` instead
|
|
639
|
+
let params = findParamArguments(path);
|
|
640
|
+
if (params.args.length === 0) {
|
|
641
|
+
return { type: "null", name: "" };
|
|
642
|
+
}
|
|
643
|
+
return {
|
|
644
|
+
type: json2ts(JSON.stringify(params.out)).replace("interface IRootObject ", ""),
|
|
645
|
+
name: "",
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
async function createQueryType(parameters) {
|
|
649
|
+
if (parameters == null) {
|
|
650
|
+
return "object";
|
|
651
|
+
}
|
|
652
|
+
const queryParameters = parameters.filter((x) => x.in === "query");
|
|
653
|
+
if (queryParameters.length === 0) {
|
|
654
|
+
return "object";
|
|
655
|
+
}
|
|
656
|
+
const schema = queryParameters.reduce((acc, v) => {
|
|
657
|
+
acc.properties[v.name] = v.schema;
|
|
658
|
+
if (v.required) {
|
|
659
|
+
acc.required.push(v.name);
|
|
660
|
+
}
|
|
661
|
+
return acc;
|
|
662
|
+
}, { properties: {}, required: [] });
|
|
663
|
+
// Delete title, since if there's a title, json-schema-to-typescript
|
|
664
|
+
// generates references.
|
|
665
|
+
traverse(schema, {
|
|
666
|
+
cb: (schema) => {
|
|
667
|
+
if (schema.title) {
|
|
668
|
+
delete schema.title;
|
|
669
|
+
}
|
|
670
|
+
},
|
|
671
|
+
});
|
|
672
|
+
return (await json_schema_to_typescript_1.compile(schema, "IRootObject", {
|
|
673
|
+
bannerComment: "",
|
|
674
|
+
declareExternallyReferenced: false,
|
|
675
|
+
}))
|
|
676
|
+
.replace("export interface IRootObject ", "")
|
|
677
|
+
.replace(`/**\n * The request body is an empty object.\n */\n{}\n`, "object")
|
|
678
|
+
.replace("[k: string]: any;", "");
|
|
679
|
+
}
|
|
680
|
+
async function createResponseBodyType(operationDef, method, throwOnNotOkayRes) {
|
|
681
|
+
let responses = operationDef[method]?.responses ?? {};
|
|
682
|
+
let out = "";
|
|
683
|
+
for (let [status, response] of Object.entries(responses)) {
|
|
684
|
+
const _schema = response?.content?.["application/json"]?.schema;
|
|
685
|
+
const schema = _schema == null ? _schema : toJsonSchema(_schema, { strictMode: false });
|
|
686
|
+
// Delete title, since if there's a title, json-schema-to-typescript
|
|
687
|
+
// generates references.
|
|
688
|
+
if (schema != null) {
|
|
689
|
+
traverse(schema, {
|
|
690
|
+
cb: (schema) => {
|
|
691
|
+
if (schema.title) {
|
|
692
|
+
delete schema.title;
|
|
693
|
+
}
|
|
694
|
+
},
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
let type = schema == null
|
|
698
|
+
? schema
|
|
699
|
+
: (await json_schema_to_typescript_1.compile(schema, "IRootObject", {
|
|
700
|
+
bannerComment: "",
|
|
701
|
+
declareExternallyReferenced: false,
|
|
702
|
+
}))
|
|
703
|
+
.replace(/export interface IRootObject /g, "")
|
|
704
|
+
.replace("export type IRootObject =", "")
|
|
705
|
+
.trim();
|
|
706
|
+
if (type?.endsWith(";")) {
|
|
707
|
+
type = type.slice(0, -1);
|
|
708
|
+
}
|
|
709
|
+
const notOk = parseInt(status) >= 400;
|
|
710
|
+
if (throwOnNotOkayRes) {
|
|
711
|
+
if (notOk) {
|
|
712
|
+
continue;
|
|
713
|
+
}
|
|
714
|
+
else {
|
|
715
|
+
out += `| {data: ${type}, status: number}`;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
else {
|
|
719
|
+
if (notOk) {
|
|
720
|
+
out += `| {error: ${type}, status: number}`;
|
|
721
|
+
}
|
|
722
|
+
else {
|
|
723
|
+
out += `| {data: ${type}, error?: undefined, status: number}`;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
if (!throwOnNotOkayRes) {
|
|
728
|
+
// TODO?
|
|
729
|
+
// if (!Object.keys(responses).some(x => parseInt(x) >= 400)) {
|
|
730
|
+
out += "| {error: {[k: string]: any}, status: number}";
|
|
731
|
+
// }
|
|
732
|
+
}
|
|
733
|
+
return out;
|
|
734
|
+
}
|
|
735
|
+
function getOperationId(x) {
|
|
736
|
+
return x?.["x-technicity-sdk-name"] ?? x?.["operationId"];
|
|
737
|
+
}
|
|
738
|
+
function findParamArguments(path) {
|
|
739
|
+
let parts = path.split(/\//);
|
|
740
|
+
let params = parts
|
|
741
|
+
.map((p) => (p.startsWith("{") ? `:${p.slice(1, p.length - 1)}` : p))
|
|
742
|
+
.filter((p) => p.length > 0 && p[0] === ":");
|
|
743
|
+
let args = params.map((p) => p.substring(1, p.length));
|
|
744
|
+
let out = {};
|
|
745
|
+
let hydratedPath = path;
|
|
746
|
+
args.forEach((e) => {
|
|
747
|
+
out[e] = e;
|
|
748
|
+
hydratedPath = hydratedPath.replace(`:${e}`, e);
|
|
749
|
+
});
|
|
750
|
+
return { args, out, hydratedPath };
|
|
751
|
+
}
|
|
752
|
+
function convertPath(p) {
|
|
753
|
+
return p
|
|
754
|
+
.split(/\//)
|
|
755
|
+
.map((p) => (p.startsWith("{") ? `:${p.slice(1, p.length - 1)}` : p))
|
|
756
|
+
.join("/");
|
|
757
|
+
}
|
|
758
|
+
function capitalize(s) {
|
|
759
|
+
if (typeof s !== "string")
|
|
760
|
+
return "";
|
|
761
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
762
|
+
}
|
|
763
|
+
//# sourceMappingURL=generate.js.map
|