apigen-ts 1.1.0 → 1.2.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/dist/cli.cjs +1 -1
- package/dist/cli.js +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/{main-B2qgp9dv.cjs → main-BLaUzCCM.cjs} +78 -45
- package/dist/{main-BvB08bJo.js → main-BZrWxHPM.js} +79 -46
- package/dist/{main-BvB08bJo.mjs → main-BZrWxHPM.mjs} +79 -46
- package/dist/main.cjs +1 -1
- package/dist/main.d.cts +1 -0
- package/dist/main.d.mts +1 -0
- package/dist/main.js +1 -1
- package/dist/main.mjs +1 -1
- package/package.json +8 -8
- package/readme.md +20 -13
package/dist/cli.cjs
CHANGED
package/dist/cli.js
CHANGED
package/dist/cli.mjs
CHANGED
|
@@ -13,7 +13,7 @@ var path = require('node:path');
|
|
|
13
13
|
|
|
14
14
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
15
15
|
var name = "apigen-ts";
|
|
16
|
-
var version = "1.1
|
|
16
|
+
var version = "1.2.1";
|
|
17
17
|
|
|
18
18
|
const initCtx = (config) => {
|
|
19
19
|
return {
|
|
@@ -23,11 +23,20 @@ const initCtx = (config) => {
|
|
|
23
23
|
doc: { openapi: "3.1.0" },
|
|
24
24
|
parseDates: false,
|
|
25
25
|
inlineEnums: false,
|
|
26
|
+
headers: {},
|
|
26
27
|
...config,
|
|
27
28
|
logTag: "",
|
|
28
29
|
usedNames: /* @__PURE__ */ new Set()
|
|
29
30
|
};
|
|
30
31
|
};
|
|
32
|
+
const parseHeaders = (items) => {
|
|
33
|
+
const headers = {};
|
|
34
|
+
for (const item of items) {
|
|
35
|
+
const [key, val] = item.split(":");
|
|
36
|
+
if (key && val) headers[key.trim()] = val.trim();
|
|
37
|
+
}
|
|
38
|
+
return headers;
|
|
39
|
+
};
|
|
31
40
|
const getCliConfig = () => {
|
|
32
41
|
const argv = cleye.cli({
|
|
33
42
|
name,
|
|
@@ -36,18 +45,24 @@ const getCliConfig = () => {
|
|
|
36
45
|
flags: {
|
|
37
46
|
name: {
|
|
38
47
|
type: String,
|
|
39
|
-
description: "
|
|
48
|
+
description: "API class name to export",
|
|
40
49
|
default: "ApiClient"
|
|
41
50
|
},
|
|
42
51
|
parseDates: {
|
|
43
52
|
type: Boolean,
|
|
44
|
-
description: "
|
|
53
|
+
description: "Parse dates as Date objects",
|
|
45
54
|
default: false
|
|
46
55
|
},
|
|
47
56
|
inlineEnums: {
|
|
48
57
|
type: Boolean,
|
|
49
|
-
description: "
|
|
58
|
+
description: "Use inline enums instead of enum types",
|
|
50
59
|
default: false
|
|
60
|
+
},
|
|
61
|
+
header: {
|
|
62
|
+
type: [String],
|
|
63
|
+
alias: "H",
|
|
64
|
+
description: 'HTTP header as key=value (e.g., -H "x-api-key: your-key"). Used only when generating code.',
|
|
65
|
+
default: []
|
|
51
66
|
}
|
|
52
67
|
}
|
|
53
68
|
});
|
|
@@ -56,13 +71,14 @@ const getCliConfig = () => {
|
|
|
56
71
|
output: argv._.output ?? null,
|
|
57
72
|
name: argv.flags.name,
|
|
58
73
|
parseDates: argv.flags.parseDates,
|
|
59
|
-
inlineEnums: argv.flags.inlineEnums
|
|
74
|
+
inlineEnums: argv.flags.inlineEnums,
|
|
75
|
+
headers: parseHeaders(argv.flags.header)
|
|
60
76
|
};
|
|
61
77
|
return config;
|
|
62
78
|
};
|
|
63
79
|
|
|
64
80
|
const unref = (ctx, s) => {
|
|
65
|
-
if (!s) return
|
|
81
|
+
if (!s) return void 0;
|
|
66
82
|
if ("$ref" in s && s.$ref) {
|
|
67
83
|
const parts = s.$ref.replace("#/", "").split("/");
|
|
68
84
|
const obj = parts.reduce(
|
|
@@ -72,15 +88,15 @@ const unref = (ctx, s) => {
|
|
|
72
88
|
);
|
|
73
89
|
if (obj) return obj;
|
|
74
90
|
console.warn(`${ctx.logTag} ref ${s.$ref} not found`);
|
|
75
|
-
return
|
|
91
|
+
return void 0;
|
|
76
92
|
}
|
|
77
93
|
return s;
|
|
78
94
|
};
|
|
79
95
|
const getReqSchema = (ctx, config) => {
|
|
80
96
|
const req = unref(ctx, config.requestBody);
|
|
81
|
-
if (!req) return
|
|
97
|
+
if (!req) return void 0;
|
|
82
98
|
const cts = Object.entries(req.content ?? {}).map((x) => [x[0].split(";")[0], x[1].schema]).filter((x) => x[1]);
|
|
83
|
-
if (cts.length === 0) return
|
|
99
|
+
if (cts.length === 0) return void 0;
|
|
84
100
|
const pretenders = [
|
|
85
101
|
"application/json",
|
|
86
102
|
"text/",
|
|
@@ -92,18 +108,18 @@ const getReqSchema = (ctx, config) => {
|
|
|
92
108
|
if (ct) return ct;
|
|
93
109
|
}
|
|
94
110
|
cts.map((x) => x[0]);
|
|
95
|
-
return
|
|
111
|
+
return void 0;
|
|
96
112
|
};
|
|
97
113
|
const getRepSchema = (ctx, config) => {
|
|
98
114
|
const successCodes = Object.keys(config.responses ?? {}).filter((x) => x.startsWith("2")).filter((x) => lodashEs.get(config, ["responses", x, "content"]));
|
|
99
115
|
const cts = Object.entries(lodashEs.get(config, ["responses", successCodes[0], "content"], {})).filter((x) => x[1].schema);
|
|
100
|
-
if (cts.length === 0) return
|
|
116
|
+
if (cts.length === 0) return void 0;
|
|
101
117
|
const ctJson = cts.find((x) => x[0].startsWith("application/json"));
|
|
102
118
|
if (ctJson) return ctJson[1].schema;
|
|
103
119
|
const ctText = cts.find((x) => x[0].startsWith("text/"));
|
|
104
120
|
if (ctText) return { type: "string" };
|
|
105
121
|
cts.map((x) => x[0]).join(", ");
|
|
106
|
-
return
|
|
122
|
+
return void 0;
|
|
107
123
|
};
|
|
108
124
|
|
|
109
125
|
const f$2 = ts.factory;
|
|
@@ -179,9 +195,9 @@ const normalizeIdentifier = (val, asVar = false) => {
|
|
|
179
195
|
return name;
|
|
180
196
|
};
|
|
181
197
|
const makeInlineEnum = (s) => {
|
|
182
|
-
if (!s.enum) return
|
|
198
|
+
if (!s.enum) return void 0;
|
|
183
199
|
const values = arrayUtilsTs.filterEmpty(s.enum);
|
|
184
|
-
if (!values.length) return
|
|
200
|
+
if (!values.length) return void 0;
|
|
185
201
|
if (!s.type) {
|
|
186
202
|
if (values.every((x) => typeof x === "string")) s.type = "string";
|
|
187
203
|
if (values.every((x) => typeof x === "number")) s.type = "number";
|
|
@@ -202,7 +218,7 @@ const makeInlineEnum = (s) => {
|
|
|
202
218
|
return f$2.createUnionTypeNode(tokens.map((x) => f$2.createLiteralTypeNode(x)));
|
|
203
219
|
}
|
|
204
220
|
console.warn(`enum with unknown type "${s.type}" in`, s);
|
|
205
|
-
return
|
|
221
|
+
return void 0;
|
|
206
222
|
};
|
|
207
223
|
const makeObject = (ctx, s) => {
|
|
208
224
|
if (s.type !== "object") throw new Error(`makeObject: not an object ${JSON.stringify(s)}`);
|
|
@@ -214,9 +230,15 @@ const makeObject = (ctx, s) => {
|
|
|
214
230
|
}
|
|
215
231
|
return f$2.createKeywordTypeNode(ts.SyntaxKind.ObjectKeyword);
|
|
216
232
|
};
|
|
233
|
+
const makeLiteralUnion = (ctx, types) => {
|
|
234
|
+
const tokens = types.map((x) => makeType(ctx, { type: x }));
|
|
235
|
+
const hasUnknown = tokens.some((x) => x.kind === ts.SyntaxKind.UnknownKeyword);
|
|
236
|
+
if (hasUnknown) return f$2.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
237
|
+
return f$2.createUnionTypeNode(tokens);
|
|
238
|
+
};
|
|
217
239
|
const makeType = (ctx, s) => {
|
|
218
240
|
const mk = makeType.bind(null, ctx);
|
|
219
|
-
if (s ===
|
|
241
|
+
if (s === void 0) return f$2.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
|
|
220
242
|
if (s === null) return f$2.createLiteralTypeNode(f$2.createNull());
|
|
221
243
|
if ("$ref" in s && s.$ref) {
|
|
222
244
|
const parts = s.$ref.replace("#/", "").split("/");
|
|
@@ -240,28 +262,22 @@ const makeType = (ctx, s) => {
|
|
|
240
262
|
return f$2.createTypeLiteralNode(
|
|
241
263
|
Object.entries(s.properties).map(([k, v]) => {
|
|
242
264
|
const r = s.required ?? [];
|
|
243
|
-
const q = r.includes(k) ?
|
|
244
|
-
return f$2.createPropertySignature(
|
|
265
|
+
const q = r.includes(k) ? void 0 : f$2.createToken(ts.SyntaxKind.QuestionToken);
|
|
266
|
+
return f$2.createPropertySignature(void 0, f$2.createStringLiteral(k), q, mk(v));
|
|
245
267
|
})
|
|
246
268
|
);
|
|
247
269
|
}
|
|
248
270
|
if ("type" in s) {
|
|
249
|
-
if (Array.isArray(s.type)) {
|
|
250
|
-
const types = [];
|
|
251
|
-
for (const type of s.type) {
|
|
252
|
-
if (type === "null") types.push({ type: "null" });
|
|
253
|
-
else types.push({ ...s, type });
|
|
254
|
-
}
|
|
255
|
-
return mk({ oneOf: types });
|
|
256
|
-
}
|
|
257
271
|
let t;
|
|
258
272
|
if (s.type === "object") t = makeObject(ctx, s);
|
|
259
273
|
else if (s.type === "boolean") t = f$2.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
|
|
260
274
|
else if (s.type === "number") t = f$2.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
|
|
261
275
|
else if (s.type === "string") t = f$2.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
|
|
262
|
-
else if (s.type === "array") t = f$2.createArrayTypeNode(mk(s.items));
|
|
263
276
|
else if (s.type === "null") t = f$2.createLiteralTypeNode(f$2.createNull());
|
|
264
|
-
else if (lodashEs.isArray(s.type)) t =
|
|
277
|
+
else if (lodashEs.isArray(s.type)) t = makeLiteralUnion(ctx, s.type);
|
|
278
|
+
else if (s.type === "array" && isPrefixItems(s) && s.prefixItems && !s.items)
|
|
279
|
+
t = f$2.createTupleTypeNode(s.prefixItems.map(mk));
|
|
280
|
+
else if (s.type === "array" && !lodashEs.isBoolean(s.items)) t = f$2.createArrayTypeNode(mk(s.items));
|
|
265
281
|
else {
|
|
266
282
|
console.warn(`makeType: unknown type "${s.type}"`);
|
|
267
283
|
return f$2.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
@@ -270,7 +286,10 @@ const makeType = (ctx, s) => {
|
|
|
270
286
|
if (s.format === "binary") t = f$2.createTypeReferenceNode("File");
|
|
271
287
|
if (s.format === "date-time" && ctx.parseDates) t = f$2.createTypeReferenceNode("Date");
|
|
272
288
|
}
|
|
273
|
-
|
|
289
|
+
if ("nullable" in s && s.nullable) {
|
|
290
|
+
return f$2.createUnionTypeNode([t, f$2.createLiteralTypeNode(f$2.createNull())]);
|
|
291
|
+
}
|
|
292
|
+
return t;
|
|
274
293
|
}
|
|
275
294
|
return f$2.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
276
295
|
};
|
|
@@ -280,6 +299,9 @@ const isStringEnum = (s) => {
|
|
|
280
299
|
}
|
|
281
300
|
return false;
|
|
282
301
|
};
|
|
302
|
+
const isPrefixItems = (s) => {
|
|
303
|
+
return s.prefixItems !== void 0;
|
|
304
|
+
};
|
|
283
305
|
const makeTypeAlias = (ctx, name, s) => {
|
|
284
306
|
if (isStringEnum(s) && !ctx.inlineEnums) {
|
|
285
307
|
const tokens1 = lodashEs.uniq(s.enum);
|
|
@@ -298,7 +320,7 @@ const makeTypeAlias = (ctx, name, s) => {
|
|
|
298
320
|
return f$2.createTypeAliasDeclaration(
|
|
299
321
|
[f$2.createToken(ts.SyntaxKind.ExportKeyword)],
|
|
300
322
|
f$2.createIdentifier(normalizeIdentifier(name, true)),
|
|
301
|
-
|
|
323
|
+
void 0,
|
|
302
324
|
makeType(ctx, s)
|
|
303
325
|
);
|
|
304
326
|
};
|
|
@@ -327,7 +349,7 @@ const getOpName = (ctx, op) => {
|
|
|
327
349
|
if (ctx.resolveName) {
|
|
328
350
|
const res = ctx.resolveName(ctx, op, proposal);
|
|
329
351
|
if (Array.isArray(res) && res.length === 2) return res;
|
|
330
|
-
if (res !==
|
|
352
|
+
if (res !== void 0) {
|
|
331
353
|
console.warn(`${ctx.logTag} resolveName should return [ns, fn] or undefined (skipping)`);
|
|
332
354
|
}
|
|
333
355
|
}
|
|
@@ -369,10 +391,10 @@ const prepareOp = (ctx, cfg, opName) => {
|
|
|
369
391
|
const name = normalizeIdentifier(x.name, true);
|
|
370
392
|
const type = makeType(ctx, x.schema);
|
|
371
393
|
urlReplacements[x.name] = name;
|
|
372
|
-
return f$1.createParameterDeclaration(
|
|
394
|
+
return f$1.createParameterDeclaration(void 0, void 0, name, void 0, type);
|
|
373
395
|
});
|
|
374
396
|
const cbArgs = arrayUtilsTs.filterNullable([
|
|
375
|
-
search.length ? f$1.createShorthandPropertyAssignment("search") :
|
|
397
|
+
search.length ? f$1.createShorthandPropertyAssignment("search") : void 0,
|
|
376
398
|
reqSchema && f$1.createShorthandPropertyAssignment("body"),
|
|
377
399
|
reqSchema && reqSchema[0] !== "application/json" ? f$1.createPropertyAssignment(
|
|
378
400
|
"headers",
|
|
@@ -382,16 +404,16 @@ const prepareOp = (ctx, cfg, opName) => {
|
|
|
382
404
|
f$1.createStringLiteral(reqSchema[0])
|
|
383
405
|
)
|
|
384
406
|
])
|
|
385
|
-
) :
|
|
407
|
+
) : void 0
|
|
386
408
|
]);
|
|
387
409
|
return f$1.createPropertyAssignment(
|
|
388
410
|
f$1.createIdentifier(normalizeIdentifier(opName)),
|
|
389
411
|
f$1.createArrowFunction(
|
|
390
|
-
|
|
391
|
-
|
|
412
|
+
void 0,
|
|
413
|
+
void 0,
|
|
392
414
|
fnArgs,
|
|
393
|
-
|
|
394
|
-
|
|
415
|
+
void 0,
|
|
416
|
+
void 0,
|
|
395
417
|
f$1.createBlock([
|
|
396
418
|
f$1.createReturnStatement(
|
|
397
419
|
f$1.createCallExpression(
|
|
@@ -413,10 +435,10 @@ const prepareOp = (ctx, cfg, opName) => {
|
|
|
413
435
|
};
|
|
414
436
|
const prepareNs = (ctx, name, handlers) => {
|
|
415
437
|
return f$1.createPropertyDeclaration(
|
|
416
|
-
|
|
438
|
+
void 0,
|
|
417
439
|
normalizeIdentifier(name),
|
|
418
|
-
|
|
419
|
-
|
|
440
|
+
void 0,
|
|
441
|
+
void 0,
|
|
420
442
|
f$1.createObjectLiteralExpression(handlers)
|
|
421
443
|
);
|
|
422
444
|
};
|
|
@@ -476,12 +498,23 @@ const generateAst = async (ctx) => {
|
|
|
476
498
|
}
|
|
477
499
|
return { modules, types };
|
|
478
500
|
};
|
|
479
|
-
const loadSchema = async (
|
|
501
|
+
const loadSchema = async ({
|
|
502
|
+
url,
|
|
503
|
+
upgrade = true,
|
|
504
|
+
headers = {}
|
|
505
|
+
}) => {
|
|
480
506
|
if (url.startsWith("file://")) url = url.substring(7);
|
|
481
507
|
const { bundle } = await redocly.bundle({
|
|
482
508
|
ref: url,
|
|
483
509
|
config: await redocly.createConfig({}),
|
|
484
|
-
removeUnusedComponents: false
|
|
510
|
+
removeUnusedComponents: false,
|
|
511
|
+
externalRefResolver: new redocly.BaseResolver({
|
|
512
|
+
http: {
|
|
513
|
+
headers: Object.entries(headers).map(([name, value]) => {
|
|
514
|
+
return { name, value, matches: "**" };
|
|
515
|
+
})
|
|
516
|
+
}
|
|
517
|
+
})
|
|
485
518
|
});
|
|
486
519
|
if (bundle.parsed.swagger && upgrade) {
|
|
487
520
|
const { openapi } = await swagger2openapi.convertObj(bundle.parsed, { patch: true });
|
|
@@ -519,10 +552,10 @@ const formatCode = async (code) => {
|
|
|
519
552
|
};
|
|
520
553
|
|
|
521
554
|
const apigen = async (config) => {
|
|
522
|
-
const doc = await loadSchema(config.source);
|
|
555
|
+
const doc = await loadSchema({ url: config.source, headers: config.headers });
|
|
523
556
|
const ctx = initCtx({ ...config, doc });
|
|
524
557
|
const { modules, types } = await generateAst(ctx);
|
|
525
|
-
const filepath = path$1.join(path$1.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('main-
|
|
558
|
+
const filepath = path$1.join(path$1.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('main-BLaUzCCM.cjs', document.baseURI).href)))), "_template.ts");
|
|
526
559
|
const file = await fs.readFile(filepath, "utf-8");
|
|
527
560
|
let code = [
|
|
528
561
|
`// Auto-generated by https://github.com/vladkens/apigen-ts`,
|
|
@@ -2,15 +2,15 @@ import fs from 'fs/promises';
|
|
|
2
2
|
import { join, dirname } from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { cli } from 'cleye';
|
|
5
|
-
import redocly from '@redocly/openapi-core';
|
|
5
|
+
import redocly, { BaseResolver } from '@redocly/openapi-core';
|
|
6
6
|
import { filterEmpty, filterNullable } from 'array-utils-ts';
|
|
7
|
-
import { get, uniq, upperFirst, isArray, isBoolean,
|
|
7
|
+
import { get, uniq, upperFirst, isArray, isBoolean, sortBy, isObject, lowerFirst, uniqBy } from 'lodash-es';
|
|
8
8
|
import { convertObj } from 'swagger2openapi';
|
|
9
9
|
import ts from 'typescript';
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
|
|
12
12
|
var name = "apigen-ts";
|
|
13
|
-
var version = "1.1
|
|
13
|
+
var version = "1.2.1";
|
|
14
14
|
|
|
15
15
|
const initCtx = (config) => {
|
|
16
16
|
return {
|
|
@@ -20,11 +20,20 @@ const initCtx = (config) => {
|
|
|
20
20
|
doc: { openapi: "3.1.0" },
|
|
21
21
|
parseDates: false,
|
|
22
22
|
inlineEnums: false,
|
|
23
|
+
headers: {},
|
|
23
24
|
...config,
|
|
24
25
|
logTag: "",
|
|
25
26
|
usedNames: /* @__PURE__ */ new Set()
|
|
26
27
|
};
|
|
27
28
|
};
|
|
29
|
+
const parseHeaders = (items) => {
|
|
30
|
+
const headers = {};
|
|
31
|
+
for (const item of items) {
|
|
32
|
+
const [key, val] = item.split(":");
|
|
33
|
+
if (key && val) headers[key.trim()] = val.trim();
|
|
34
|
+
}
|
|
35
|
+
return headers;
|
|
36
|
+
};
|
|
28
37
|
const getCliConfig = () => {
|
|
29
38
|
const argv = cli({
|
|
30
39
|
name,
|
|
@@ -33,18 +42,24 @@ const getCliConfig = () => {
|
|
|
33
42
|
flags: {
|
|
34
43
|
name: {
|
|
35
44
|
type: String,
|
|
36
|
-
description: "
|
|
45
|
+
description: "API class name to export",
|
|
37
46
|
default: "ApiClient"
|
|
38
47
|
},
|
|
39
48
|
parseDates: {
|
|
40
49
|
type: Boolean,
|
|
41
|
-
description: "
|
|
50
|
+
description: "Parse dates as Date objects",
|
|
42
51
|
default: false
|
|
43
52
|
},
|
|
44
53
|
inlineEnums: {
|
|
45
54
|
type: Boolean,
|
|
46
|
-
description: "
|
|
55
|
+
description: "Use inline enums instead of enum types",
|
|
47
56
|
default: false
|
|
57
|
+
},
|
|
58
|
+
header: {
|
|
59
|
+
type: [String],
|
|
60
|
+
alias: "H",
|
|
61
|
+
description: 'HTTP header as key=value (e.g., -H "x-api-key: your-key"). Used only when generating code.',
|
|
62
|
+
default: []
|
|
48
63
|
}
|
|
49
64
|
}
|
|
50
65
|
});
|
|
@@ -53,13 +68,14 @@ const getCliConfig = () => {
|
|
|
53
68
|
output: argv._.output ?? null,
|
|
54
69
|
name: argv.flags.name,
|
|
55
70
|
parseDates: argv.flags.parseDates,
|
|
56
|
-
inlineEnums: argv.flags.inlineEnums
|
|
71
|
+
inlineEnums: argv.flags.inlineEnums,
|
|
72
|
+
headers: parseHeaders(argv.flags.header)
|
|
57
73
|
};
|
|
58
74
|
return config;
|
|
59
75
|
};
|
|
60
76
|
|
|
61
77
|
const unref = (ctx, s) => {
|
|
62
|
-
if (!s) return
|
|
78
|
+
if (!s) return void 0;
|
|
63
79
|
if ("$ref" in s && s.$ref) {
|
|
64
80
|
const parts = s.$ref.replace("#/", "").split("/");
|
|
65
81
|
const obj = parts.reduce(
|
|
@@ -69,15 +85,15 @@ const unref = (ctx, s) => {
|
|
|
69
85
|
);
|
|
70
86
|
if (obj) return obj;
|
|
71
87
|
console.warn(`${ctx.logTag} ref ${s.$ref} not found`);
|
|
72
|
-
return
|
|
88
|
+
return void 0;
|
|
73
89
|
}
|
|
74
90
|
return s;
|
|
75
91
|
};
|
|
76
92
|
const getReqSchema = (ctx, config) => {
|
|
77
93
|
const req = unref(ctx, config.requestBody);
|
|
78
|
-
if (!req) return
|
|
94
|
+
if (!req) return void 0;
|
|
79
95
|
const cts = Object.entries(req.content ?? {}).map((x) => [x[0].split(";")[0], x[1].schema]).filter((x) => x[1]);
|
|
80
|
-
if (cts.length === 0) return
|
|
96
|
+
if (cts.length === 0) return void 0;
|
|
81
97
|
const pretenders = [
|
|
82
98
|
"application/json",
|
|
83
99
|
"text/",
|
|
@@ -89,18 +105,18 @@ const getReqSchema = (ctx, config) => {
|
|
|
89
105
|
if (ct) return ct;
|
|
90
106
|
}
|
|
91
107
|
cts.map((x) => x[0]);
|
|
92
|
-
return
|
|
108
|
+
return void 0;
|
|
93
109
|
};
|
|
94
110
|
const getRepSchema = (ctx, config) => {
|
|
95
111
|
const successCodes = Object.keys(config.responses ?? {}).filter((x) => x.startsWith("2")).filter((x) => get(config, ["responses", x, "content"]));
|
|
96
112
|
const cts = Object.entries(get(config, ["responses", successCodes[0], "content"], {})).filter((x) => x[1].schema);
|
|
97
|
-
if (cts.length === 0) return
|
|
113
|
+
if (cts.length === 0) return void 0;
|
|
98
114
|
const ctJson = cts.find((x) => x[0].startsWith("application/json"));
|
|
99
115
|
if (ctJson) return ctJson[1].schema;
|
|
100
116
|
const ctText = cts.find((x) => x[0].startsWith("text/"));
|
|
101
117
|
if (ctText) return { type: "string" };
|
|
102
118
|
cts.map((x) => x[0]).join(", ");
|
|
103
|
-
return
|
|
119
|
+
return void 0;
|
|
104
120
|
};
|
|
105
121
|
|
|
106
122
|
const f$2 = ts.factory;
|
|
@@ -176,9 +192,9 @@ const normalizeIdentifier = (val, asVar = false) => {
|
|
|
176
192
|
return name;
|
|
177
193
|
};
|
|
178
194
|
const makeInlineEnum = (s) => {
|
|
179
|
-
if (!s.enum) return
|
|
195
|
+
if (!s.enum) return void 0;
|
|
180
196
|
const values = filterEmpty(s.enum);
|
|
181
|
-
if (!values.length) return
|
|
197
|
+
if (!values.length) return void 0;
|
|
182
198
|
if (!s.type) {
|
|
183
199
|
if (values.every((x) => typeof x === "string")) s.type = "string";
|
|
184
200
|
if (values.every((x) => typeof x === "number")) s.type = "number";
|
|
@@ -199,7 +215,7 @@ const makeInlineEnum = (s) => {
|
|
|
199
215
|
return f$2.createUnionTypeNode(tokens.map((x) => f$2.createLiteralTypeNode(x)));
|
|
200
216
|
}
|
|
201
217
|
console.warn(`enum with unknown type "${s.type}" in`, s);
|
|
202
|
-
return
|
|
218
|
+
return void 0;
|
|
203
219
|
};
|
|
204
220
|
const makeObject = (ctx, s) => {
|
|
205
221
|
if (s.type !== "object") throw new Error(`makeObject: not an object ${JSON.stringify(s)}`);
|
|
@@ -211,9 +227,15 @@ const makeObject = (ctx, s) => {
|
|
|
211
227
|
}
|
|
212
228
|
return f$2.createKeywordTypeNode(ts.SyntaxKind.ObjectKeyword);
|
|
213
229
|
};
|
|
230
|
+
const makeLiteralUnion = (ctx, types) => {
|
|
231
|
+
const tokens = types.map((x) => makeType(ctx, { type: x }));
|
|
232
|
+
const hasUnknown = tokens.some((x) => x.kind === ts.SyntaxKind.UnknownKeyword);
|
|
233
|
+
if (hasUnknown) return f$2.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
234
|
+
return f$2.createUnionTypeNode(tokens);
|
|
235
|
+
};
|
|
214
236
|
const makeType = (ctx, s) => {
|
|
215
237
|
const mk = makeType.bind(null, ctx);
|
|
216
|
-
if (s ===
|
|
238
|
+
if (s === void 0) return f$2.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
|
|
217
239
|
if (s === null) return f$2.createLiteralTypeNode(f$2.createNull());
|
|
218
240
|
if ("$ref" in s && s.$ref) {
|
|
219
241
|
const parts = s.$ref.replace("#/", "").split("/");
|
|
@@ -237,28 +259,22 @@ const makeType = (ctx, s) => {
|
|
|
237
259
|
return f$2.createTypeLiteralNode(
|
|
238
260
|
Object.entries(s.properties).map(([k, v]) => {
|
|
239
261
|
const r = s.required ?? [];
|
|
240
|
-
const q = r.includes(k) ?
|
|
241
|
-
return f$2.createPropertySignature(
|
|
262
|
+
const q = r.includes(k) ? void 0 : f$2.createToken(ts.SyntaxKind.QuestionToken);
|
|
263
|
+
return f$2.createPropertySignature(void 0, f$2.createStringLiteral(k), q, mk(v));
|
|
242
264
|
})
|
|
243
265
|
);
|
|
244
266
|
}
|
|
245
267
|
if ("type" in s) {
|
|
246
|
-
if (Array.isArray(s.type)) {
|
|
247
|
-
const types = [];
|
|
248
|
-
for (const type of s.type) {
|
|
249
|
-
if (type === "null") types.push({ type: "null" });
|
|
250
|
-
else types.push({ ...s, type });
|
|
251
|
-
}
|
|
252
|
-
return mk({ oneOf: types });
|
|
253
|
-
}
|
|
254
268
|
let t;
|
|
255
269
|
if (s.type === "object") t = makeObject(ctx, s);
|
|
256
270
|
else if (s.type === "boolean") t = f$2.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
|
|
257
271
|
else if (s.type === "number") t = f$2.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
|
|
258
272
|
else if (s.type === "string") t = f$2.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
|
|
259
|
-
else if (s.type === "array") t = f$2.createArrayTypeNode(mk(s.items));
|
|
260
273
|
else if (s.type === "null") t = f$2.createLiteralTypeNode(f$2.createNull());
|
|
261
|
-
else if (isArray(s.type)) t =
|
|
274
|
+
else if (isArray(s.type)) t = makeLiteralUnion(ctx, s.type);
|
|
275
|
+
else if (s.type === "array" && isPrefixItems(s) && s.prefixItems && !s.items)
|
|
276
|
+
t = f$2.createTupleTypeNode(s.prefixItems.map(mk));
|
|
277
|
+
else if (s.type === "array" && !isBoolean(s.items)) t = f$2.createArrayTypeNode(mk(s.items));
|
|
262
278
|
else {
|
|
263
279
|
console.warn(`makeType: unknown type "${s.type}"`);
|
|
264
280
|
return f$2.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
@@ -267,7 +283,10 @@ const makeType = (ctx, s) => {
|
|
|
267
283
|
if (s.format === "binary") t = f$2.createTypeReferenceNode("File");
|
|
268
284
|
if (s.format === "date-time" && ctx.parseDates) t = f$2.createTypeReferenceNode("Date");
|
|
269
285
|
}
|
|
270
|
-
|
|
286
|
+
if ("nullable" in s && s.nullable) {
|
|
287
|
+
return f$2.createUnionTypeNode([t, f$2.createLiteralTypeNode(f$2.createNull())]);
|
|
288
|
+
}
|
|
289
|
+
return t;
|
|
271
290
|
}
|
|
272
291
|
return f$2.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
273
292
|
};
|
|
@@ -277,6 +296,9 @@ const isStringEnum = (s) => {
|
|
|
277
296
|
}
|
|
278
297
|
return false;
|
|
279
298
|
};
|
|
299
|
+
const isPrefixItems = (s) => {
|
|
300
|
+
return s.prefixItems !== void 0;
|
|
301
|
+
};
|
|
280
302
|
const makeTypeAlias = (ctx, name, s) => {
|
|
281
303
|
if (isStringEnum(s) && !ctx.inlineEnums) {
|
|
282
304
|
const tokens1 = uniq(s.enum);
|
|
@@ -295,7 +317,7 @@ const makeTypeAlias = (ctx, name, s) => {
|
|
|
295
317
|
return f$2.createTypeAliasDeclaration(
|
|
296
318
|
[f$2.createToken(ts.SyntaxKind.ExportKeyword)],
|
|
297
319
|
f$2.createIdentifier(normalizeIdentifier(name, true)),
|
|
298
|
-
|
|
320
|
+
void 0,
|
|
299
321
|
makeType(ctx, s)
|
|
300
322
|
);
|
|
301
323
|
};
|
|
@@ -324,7 +346,7 @@ const getOpName = (ctx, op) => {
|
|
|
324
346
|
if (ctx.resolveName) {
|
|
325
347
|
const res = ctx.resolveName(ctx, op, proposal);
|
|
326
348
|
if (Array.isArray(res) && res.length === 2) return res;
|
|
327
|
-
if (res !==
|
|
349
|
+
if (res !== void 0) {
|
|
328
350
|
console.warn(`${ctx.logTag} resolveName should return [ns, fn] or undefined (skipping)`);
|
|
329
351
|
}
|
|
330
352
|
}
|
|
@@ -366,10 +388,10 @@ const prepareOp = (ctx, cfg, opName) => {
|
|
|
366
388
|
const name = normalizeIdentifier(x.name, true);
|
|
367
389
|
const type = makeType(ctx, x.schema);
|
|
368
390
|
urlReplacements[x.name] = name;
|
|
369
|
-
return f$1.createParameterDeclaration(
|
|
391
|
+
return f$1.createParameterDeclaration(void 0, void 0, name, void 0, type);
|
|
370
392
|
});
|
|
371
393
|
const cbArgs = filterNullable([
|
|
372
|
-
search.length ? f$1.createShorthandPropertyAssignment("search") :
|
|
394
|
+
search.length ? f$1.createShorthandPropertyAssignment("search") : void 0,
|
|
373
395
|
reqSchema && f$1.createShorthandPropertyAssignment("body"),
|
|
374
396
|
reqSchema && reqSchema[0] !== "application/json" ? f$1.createPropertyAssignment(
|
|
375
397
|
"headers",
|
|
@@ -379,16 +401,16 @@ const prepareOp = (ctx, cfg, opName) => {
|
|
|
379
401
|
f$1.createStringLiteral(reqSchema[0])
|
|
380
402
|
)
|
|
381
403
|
])
|
|
382
|
-
) :
|
|
404
|
+
) : void 0
|
|
383
405
|
]);
|
|
384
406
|
return f$1.createPropertyAssignment(
|
|
385
407
|
f$1.createIdentifier(normalizeIdentifier(opName)),
|
|
386
408
|
f$1.createArrowFunction(
|
|
387
|
-
|
|
388
|
-
|
|
409
|
+
void 0,
|
|
410
|
+
void 0,
|
|
389
411
|
fnArgs,
|
|
390
|
-
|
|
391
|
-
|
|
412
|
+
void 0,
|
|
413
|
+
void 0,
|
|
392
414
|
f$1.createBlock([
|
|
393
415
|
f$1.createReturnStatement(
|
|
394
416
|
f$1.createCallExpression(
|
|
@@ -410,10 +432,10 @@ const prepareOp = (ctx, cfg, opName) => {
|
|
|
410
432
|
};
|
|
411
433
|
const prepareNs = (ctx, name, handlers) => {
|
|
412
434
|
return f$1.createPropertyDeclaration(
|
|
413
|
-
|
|
435
|
+
void 0,
|
|
414
436
|
normalizeIdentifier(name),
|
|
415
|
-
|
|
416
|
-
|
|
437
|
+
void 0,
|
|
438
|
+
void 0,
|
|
417
439
|
f$1.createObjectLiteralExpression(handlers)
|
|
418
440
|
);
|
|
419
441
|
};
|
|
@@ -473,12 +495,23 @@ const generateAst = async (ctx) => {
|
|
|
473
495
|
}
|
|
474
496
|
return { modules, types };
|
|
475
497
|
};
|
|
476
|
-
const loadSchema = async (
|
|
498
|
+
const loadSchema = async ({
|
|
499
|
+
url,
|
|
500
|
+
upgrade = true,
|
|
501
|
+
headers = {}
|
|
502
|
+
}) => {
|
|
477
503
|
if (url.startsWith("file://")) url = url.substring(7);
|
|
478
504
|
const { bundle } = await redocly.bundle({
|
|
479
505
|
ref: url,
|
|
480
506
|
config: await redocly.createConfig({}),
|
|
481
|
-
removeUnusedComponents: false
|
|
507
|
+
removeUnusedComponents: false,
|
|
508
|
+
externalRefResolver: new BaseResolver({
|
|
509
|
+
http: {
|
|
510
|
+
headers: Object.entries(headers).map(([name, value]) => {
|
|
511
|
+
return { name, value, matches: "**" };
|
|
512
|
+
})
|
|
513
|
+
}
|
|
514
|
+
})
|
|
482
515
|
});
|
|
483
516
|
if (bundle.parsed.swagger && upgrade) {
|
|
484
517
|
const { openapi } = await convertObj(bundle.parsed, { patch: true });
|
|
@@ -516,7 +549,7 @@ const formatCode = async (code) => {
|
|
|
516
549
|
};
|
|
517
550
|
|
|
518
551
|
const apigen = async (config) => {
|
|
519
|
-
const doc = await loadSchema(config.source);
|
|
552
|
+
const doc = await loadSchema({ url: config.source, headers: config.headers });
|
|
520
553
|
const ctx = initCtx({ ...config, doc });
|
|
521
554
|
const { modules, types } = await generateAst(ctx);
|
|
522
555
|
const filepath = join(dirname(fileURLToPath(import.meta.url)), "_template.ts");
|
|
@@ -2,15 +2,15 @@ import fs from 'fs/promises';
|
|
|
2
2
|
import { join, dirname } from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { cli } from 'cleye';
|
|
5
|
-
import redocly from '@redocly/openapi-core';
|
|
5
|
+
import redocly, { BaseResolver } from '@redocly/openapi-core';
|
|
6
6
|
import { filterEmpty, filterNullable } from 'array-utils-ts';
|
|
7
|
-
import { get, uniq, upperFirst, isArray, isBoolean,
|
|
7
|
+
import { get, uniq, upperFirst, isArray, isBoolean, sortBy, isObject, lowerFirst, uniqBy } from 'lodash-es';
|
|
8
8
|
import { convertObj } from 'swagger2openapi';
|
|
9
9
|
import ts from 'typescript';
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
|
|
12
12
|
var name = "apigen-ts";
|
|
13
|
-
var version = "1.1
|
|
13
|
+
var version = "1.2.1";
|
|
14
14
|
|
|
15
15
|
const initCtx = (config) => {
|
|
16
16
|
return {
|
|
@@ -20,11 +20,20 @@ const initCtx = (config) => {
|
|
|
20
20
|
doc: { openapi: "3.1.0" },
|
|
21
21
|
parseDates: false,
|
|
22
22
|
inlineEnums: false,
|
|
23
|
+
headers: {},
|
|
23
24
|
...config,
|
|
24
25
|
logTag: "",
|
|
25
26
|
usedNames: /* @__PURE__ */ new Set()
|
|
26
27
|
};
|
|
27
28
|
};
|
|
29
|
+
const parseHeaders = (items) => {
|
|
30
|
+
const headers = {};
|
|
31
|
+
for (const item of items) {
|
|
32
|
+
const [key, val] = item.split(":");
|
|
33
|
+
if (key && val) headers[key.trim()] = val.trim();
|
|
34
|
+
}
|
|
35
|
+
return headers;
|
|
36
|
+
};
|
|
28
37
|
const getCliConfig = () => {
|
|
29
38
|
const argv = cli({
|
|
30
39
|
name,
|
|
@@ -33,18 +42,24 @@ const getCliConfig = () => {
|
|
|
33
42
|
flags: {
|
|
34
43
|
name: {
|
|
35
44
|
type: String,
|
|
36
|
-
description: "
|
|
45
|
+
description: "API class name to export",
|
|
37
46
|
default: "ApiClient"
|
|
38
47
|
},
|
|
39
48
|
parseDates: {
|
|
40
49
|
type: Boolean,
|
|
41
|
-
description: "
|
|
50
|
+
description: "Parse dates as Date objects",
|
|
42
51
|
default: false
|
|
43
52
|
},
|
|
44
53
|
inlineEnums: {
|
|
45
54
|
type: Boolean,
|
|
46
|
-
description: "
|
|
55
|
+
description: "Use inline enums instead of enum types",
|
|
47
56
|
default: false
|
|
57
|
+
},
|
|
58
|
+
header: {
|
|
59
|
+
type: [String],
|
|
60
|
+
alias: "H",
|
|
61
|
+
description: 'HTTP header as key=value (e.g., -H "x-api-key: your-key"). Used only when generating code.',
|
|
62
|
+
default: []
|
|
48
63
|
}
|
|
49
64
|
}
|
|
50
65
|
});
|
|
@@ -53,13 +68,14 @@ const getCliConfig = () => {
|
|
|
53
68
|
output: argv._.output ?? null,
|
|
54
69
|
name: argv.flags.name,
|
|
55
70
|
parseDates: argv.flags.parseDates,
|
|
56
|
-
inlineEnums: argv.flags.inlineEnums
|
|
71
|
+
inlineEnums: argv.flags.inlineEnums,
|
|
72
|
+
headers: parseHeaders(argv.flags.header)
|
|
57
73
|
};
|
|
58
74
|
return config;
|
|
59
75
|
};
|
|
60
76
|
|
|
61
77
|
const unref = (ctx, s) => {
|
|
62
|
-
if (!s) return
|
|
78
|
+
if (!s) return void 0;
|
|
63
79
|
if ("$ref" in s && s.$ref) {
|
|
64
80
|
const parts = s.$ref.replace("#/", "").split("/");
|
|
65
81
|
const obj = parts.reduce(
|
|
@@ -69,15 +85,15 @@ const unref = (ctx, s) => {
|
|
|
69
85
|
);
|
|
70
86
|
if (obj) return obj;
|
|
71
87
|
console.warn(`${ctx.logTag} ref ${s.$ref} not found`);
|
|
72
|
-
return
|
|
88
|
+
return void 0;
|
|
73
89
|
}
|
|
74
90
|
return s;
|
|
75
91
|
};
|
|
76
92
|
const getReqSchema = (ctx, config) => {
|
|
77
93
|
const req = unref(ctx, config.requestBody);
|
|
78
|
-
if (!req) return
|
|
94
|
+
if (!req) return void 0;
|
|
79
95
|
const cts = Object.entries(req.content ?? {}).map((x) => [x[0].split(";")[0], x[1].schema]).filter((x) => x[1]);
|
|
80
|
-
if (cts.length === 0) return
|
|
96
|
+
if (cts.length === 0) return void 0;
|
|
81
97
|
const pretenders = [
|
|
82
98
|
"application/json",
|
|
83
99
|
"text/",
|
|
@@ -89,18 +105,18 @@ const getReqSchema = (ctx, config) => {
|
|
|
89
105
|
if (ct) return ct;
|
|
90
106
|
}
|
|
91
107
|
cts.map((x) => x[0]);
|
|
92
|
-
return
|
|
108
|
+
return void 0;
|
|
93
109
|
};
|
|
94
110
|
const getRepSchema = (ctx, config) => {
|
|
95
111
|
const successCodes = Object.keys(config.responses ?? {}).filter((x) => x.startsWith("2")).filter((x) => get(config, ["responses", x, "content"]));
|
|
96
112
|
const cts = Object.entries(get(config, ["responses", successCodes[0], "content"], {})).filter((x) => x[1].schema);
|
|
97
|
-
if (cts.length === 0) return
|
|
113
|
+
if (cts.length === 0) return void 0;
|
|
98
114
|
const ctJson = cts.find((x) => x[0].startsWith("application/json"));
|
|
99
115
|
if (ctJson) return ctJson[1].schema;
|
|
100
116
|
const ctText = cts.find((x) => x[0].startsWith("text/"));
|
|
101
117
|
if (ctText) return { type: "string" };
|
|
102
118
|
cts.map((x) => x[0]).join(", ");
|
|
103
|
-
return
|
|
119
|
+
return void 0;
|
|
104
120
|
};
|
|
105
121
|
|
|
106
122
|
const f$2 = ts.factory;
|
|
@@ -176,9 +192,9 @@ const normalizeIdentifier = (val, asVar = false) => {
|
|
|
176
192
|
return name;
|
|
177
193
|
};
|
|
178
194
|
const makeInlineEnum = (s) => {
|
|
179
|
-
if (!s.enum) return
|
|
195
|
+
if (!s.enum) return void 0;
|
|
180
196
|
const values = filterEmpty(s.enum);
|
|
181
|
-
if (!values.length) return
|
|
197
|
+
if (!values.length) return void 0;
|
|
182
198
|
if (!s.type) {
|
|
183
199
|
if (values.every((x) => typeof x === "string")) s.type = "string";
|
|
184
200
|
if (values.every((x) => typeof x === "number")) s.type = "number";
|
|
@@ -199,7 +215,7 @@ const makeInlineEnum = (s) => {
|
|
|
199
215
|
return f$2.createUnionTypeNode(tokens.map((x) => f$2.createLiteralTypeNode(x)));
|
|
200
216
|
}
|
|
201
217
|
console.warn(`enum with unknown type "${s.type}" in`, s);
|
|
202
|
-
return
|
|
218
|
+
return void 0;
|
|
203
219
|
};
|
|
204
220
|
const makeObject = (ctx, s) => {
|
|
205
221
|
if (s.type !== "object") throw new Error(`makeObject: not an object ${JSON.stringify(s)}`);
|
|
@@ -211,9 +227,15 @@ const makeObject = (ctx, s) => {
|
|
|
211
227
|
}
|
|
212
228
|
return f$2.createKeywordTypeNode(ts.SyntaxKind.ObjectKeyword);
|
|
213
229
|
};
|
|
230
|
+
const makeLiteralUnion = (ctx, types) => {
|
|
231
|
+
const tokens = types.map((x) => makeType(ctx, { type: x }));
|
|
232
|
+
const hasUnknown = tokens.some((x) => x.kind === ts.SyntaxKind.UnknownKeyword);
|
|
233
|
+
if (hasUnknown) return f$2.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
234
|
+
return f$2.createUnionTypeNode(tokens);
|
|
235
|
+
};
|
|
214
236
|
const makeType = (ctx, s) => {
|
|
215
237
|
const mk = makeType.bind(null, ctx);
|
|
216
|
-
if (s ===
|
|
238
|
+
if (s === void 0) return f$2.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
|
|
217
239
|
if (s === null) return f$2.createLiteralTypeNode(f$2.createNull());
|
|
218
240
|
if ("$ref" in s && s.$ref) {
|
|
219
241
|
const parts = s.$ref.replace("#/", "").split("/");
|
|
@@ -237,28 +259,22 @@ const makeType = (ctx, s) => {
|
|
|
237
259
|
return f$2.createTypeLiteralNode(
|
|
238
260
|
Object.entries(s.properties).map(([k, v]) => {
|
|
239
261
|
const r = s.required ?? [];
|
|
240
|
-
const q = r.includes(k) ?
|
|
241
|
-
return f$2.createPropertySignature(
|
|
262
|
+
const q = r.includes(k) ? void 0 : f$2.createToken(ts.SyntaxKind.QuestionToken);
|
|
263
|
+
return f$2.createPropertySignature(void 0, f$2.createStringLiteral(k), q, mk(v));
|
|
242
264
|
})
|
|
243
265
|
);
|
|
244
266
|
}
|
|
245
267
|
if ("type" in s) {
|
|
246
|
-
if (Array.isArray(s.type)) {
|
|
247
|
-
const types = [];
|
|
248
|
-
for (const type of s.type) {
|
|
249
|
-
if (type === "null") types.push({ type: "null" });
|
|
250
|
-
else types.push({ ...s, type });
|
|
251
|
-
}
|
|
252
|
-
return mk({ oneOf: types });
|
|
253
|
-
}
|
|
254
268
|
let t;
|
|
255
269
|
if (s.type === "object") t = makeObject(ctx, s);
|
|
256
270
|
else if (s.type === "boolean") t = f$2.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
|
|
257
271
|
else if (s.type === "number") t = f$2.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
|
|
258
272
|
else if (s.type === "string") t = f$2.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
|
|
259
|
-
else if (s.type === "array") t = f$2.createArrayTypeNode(mk(s.items));
|
|
260
273
|
else if (s.type === "null") t = f$2.createLiteralTypeNode(f$2.createNull());
|
|
261
|
-
else if (isArray(s.type)) t =
|
|
274
|
+
else if (isArray(s.type)) t = makeLiteralUnion(ctx, s.type);
|
|
275
|
+
else if (s.type === "array" && isPrefixItems(s) && s.prefixItems && !s.items)
|
|
276
|
+
t = f$2.createTupleTypeNode(s.prefixItems.map(mk));
|
|
277
|
+
else if (s.type === "array" && !isBoolean(s.items)) t = f$2.createArrayTypeNode(mk(s.items));
|
|
262
278
|
else {
|
|
263
279
|
console.warn(`makeType: unknown type "${s.type}"`);
|
|
264
280
|
return f$2.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
@@ -267,7 +283,10 @@ const makeType = (ctx, s) => {
|
|
|
267
283
|
if (s.format === "binary") t = f$2.createTypeReferenceNode("File");
|
|
268
284
|
if (s.format === "date-time" && ctx.parseDates) t = f$2.createTypeReferenceNode("Date");
|
|
269
285
|
}
|
|
270
|
-
|
|
286
|
+
if ("nullable" in s && s.nullable) {
|
|
287
|
+
return f$2.createUnionTypeNode([t, f$2.createLiteralTypeNode(f$2.createNull())]);
|
|
288
|
+
}
|
|
289
|
+
return t;
|
|
271
290
|
}
|
|
272
291
|
return f$2.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
273
292
|
};
|
|
@@ -277,6 +296,9 @@ const isStringEnum = (s) => {
|
|
|
277
296
|
}
|
|
278
297
|
return false;
|
|
279
298
|
};
|
|
299
|
+
const isPrefixItems = (s) => {
|
|
300
|
+
return s.prefixItems !== void 0;
|
|
301
|
+
};
|
|
280
302
|
const makeTypeAlias = (ctx, name, s) => {
|
|
281
303
|
if (isStringEnum(s) && !ctx.inlineEnums) {
|
|
282
304
|
const tokens1 = uniq(s.enum);
|
|
@@ -295,7 +317,7 @@ const makeTypeAlias = (ctx, name, s) => {
|
|
|
295
317
|
return f$2.createTypeAliasDeclaration(
|
|
296
318
|
[f$2.createToken(ts.SyntaxKind.ExportKeyword)],
|
|
297
319
|
f$2.createIdentifier(normalizeIdentifier(name, true)),
|
|
298
|
-
|
|
320
|
+
void 0,
|
|
299
321
|
makeType(ctx, s)
|
|
300
322
|
);
|
|
301
323
|
};
|
|
@@ -324,7 +346,7 @@ const getOpName = (ctx, op) => {
|
|
|
324
346
|
if (ctx.resolveName) {
|
|
325
347
|
const res = ctx.resolveName(ctx, op, proposal);
|
|
326
348
|
if (Array.isArray(res) && res.length === 2) return res;
|
|
327
|
-
if (res !==
|
|
349
|
+
if (res !== void 0) {
|
|
328
350
|
console.warn(`${ctx.logTag} resolveName should return [ns, fn] or undefined (skipping)`);
|
|
329
351
|
}
|
|
330
352
|
}
|
|
@@ -366,10 +388,10 @@ const prepareOp = (ctx, cfg, opName) => {
|
|
|
366
388
|
const name = normalizeIdentifier(x.name, true);
|
|
367
389
|
const type = makeType(ctx, x.schema);
|
|
368
390
|
urlReplacements[x.name] = name;
|
|
369
|
-
return f$1.createParameterDeclaration(
|
|
391
|
+
return f$1.createParameterDeclaration(void 0, void 0, name, void 0, type);
|
|
370
392
|
});
|
|
371
393
|
const cbArgs = filterNullable([
|
|
372
|
-
search.length ? f$1.createShorthandPropertyAssignment("search") :
|
|
394
|
+
search.length ? f$1.createShorthandPropertyAssignment("search") : void 0,
|
|
373
395
|
reqSchema && f$1.createShorthandPropertyAssignment("body"),
|
|
374
396
|
reqSchema && reqSchema[0] !== "application/json" ? f$1.createPropertyAssignment(
|
|
375
397
|
"headers",
|
|
@@ -379,16 +401,16 @@ const prepareOp = (ctx, cfg, opName) => {
|
|
|
379
401
|
f$1.createStringLiteral(reqSchema[0])
|
|
380
402
|
)
|
|
381
403
|
])
|
|
382
|
-
) :
|
|
404
|
+
) : void 0
|
|
383
405
|
]);
|
|
384
406
|
return f$1.createPropertyAssignment(
|
|
385
407
|
f$1.createIdentifier(normalizeIdentifier(opName)),
|
|
386
408
|
f$1.createArrowFunction(
|
|
387
|
-
|
|
388
|
-
|
|
409
|
+
void 0,
|
|
410
|
+
void 0,
|
|
389
411
|
fnArgs,
|
|
390
|
-
|
|
391
|
-
|
|
412
|
+
void 0,
|
|
413
|
+
void 0,
|
|
392
414
|
f$1.createBlock([
|
|
393
415
|
f$1.createReturnStatement(
|
|
394
416
|
f$1.createCallExpression(
|
|
@@ -410,10 +432,10 @@ const prepareOp = (ctx, cfg, opName) => {
|
|
|
410
432
|
};
|
|
411
433
|
const prepareNs = (ctx, name, handlers) => {
|
|
412
434
|
return f$1.createPropertyDeclaration(
|
|
413
|
-
|
|
435
|
+
void 0,
|
|
414
436
|
normalizeIdentifier(name),
|
|
415
|
-
|
|
416
|
-
|
|
437
|
+
void 0,
|
|
438
|
+
void 0,
|
|
417
439
|
f$1.createObjectLiteralExpression(handlers)
|
|
418
440
|
);
|
|
419
441
|
};
|
|
@@ -473,12 +495,23 @@ const generateAst = async (ctx) => {
|
|
|
473
495
|
}
|
|
474
496
|
return { modules, types };
|
|
475
497
|
};
|
|
476
|
-
const loadSchema = async (
|
|
498
|
+
const loadSchema = async ({
|
|
499
|
+
url,
|
|
500
|
+
upgrade = true,
|
|
501
|
+
headers = {}
|
|
502
|
+
}) => {
|
|
477
503
|
if (url.startsWith("file://")) url = url.substring(7);
|
|
478
504
|
const { bundle } = await redocly.bundle({
|
|
479
505
|
ref: url,
|
|
480
506
|
config: await redocly.createConfig({}),
|
|
481
|
-
removeUnusedComponents: false
|
|
507
|
+
removeUnusedComponents: false,
|
|
508
|
+
externalRefResolver: new BaseResolver({
|
|
509
|
+
http: {
|
|
510
|
+
headers: Object.entries(headers).map(([name, value]) => {
|
|
511
|
+
return { name, value, matches: "**" };
|
|
512
|
+
})
|
|
513
|
+
}
|
|
514
|
+
})
|
|
482
515
|
});
|
|
483
516
|
if (bundle.parsed.swagger && upgrade) {
|
|
484
517
|
const { openapi } = await convertObj(bundle.parsed, { patch: true });
|
|
@@ -516,7 +549,7 @@ const formatCode = async (code) => {
|
|
|
516
549
|
};
|
|
517
550
|
|
|
518
551
|
const apigen = async (config) => {
|
|
519
|
-
const doc = await loadSchema(config.source);
|
|
552
|
+
const doc = await loadSchema({ url: config.source, headers: config.headers });
|
|
520
553
|
const ctx = initCtx({ ...config, doc });
|
|
521
554
|
const { modules, types } = await generateAst(ctx);
|
|
522
555
|
const filepath = join(dirname(fileURLToPath(import.meta.url)), "_template.ts");
|
package/dist/main.cjs
CHANGED
package/dist/main.d.cts
CHANGED
package/dist/main.d.mts
CHANGED
package/dist/main.js
CHANGED
package/dist/main.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "apigen-ts",
|
|
4
|
-
"version": "1.1
|
|
4
|
+
"version": "1.2.1",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "vladkens <v.pronsky@gmail.com>",
|
|
7
7
|
"repository": "vladkens/apigen-ts",
|
|
@@ -22,23 +22,23 @@
|
|
|
22
22
|
"ci": "tsc --noEmit && yarn test-cov && yarn build"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@redocly/openapi-core": "1.
|
|
25
|
+
"@redocly/openapi-core": "1.34.3",
|
|
26
26
|
"@types/lodash-es": "4.17.12",
|
|
27
27
|
"@types/swagger2openapi": "7.0.4",
|
|
28
28
|
"array-utils-ts": "1.0.2",
|
|
29
|
-
"cleye": "1.3.
|
|
29
|
+
"cleye": "1.3.4",
|
|
30
30
|
"lodash-es": "4.17.21",
|
|
31
31
|
"swagger2openapi": "7.0.8"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@types/node": "22.
|
|
34
|
+
"@types/node": "22.15.18",
|
|
35
35
|
"c8": "10.1.3",
|
|
36
|
-
"fetch-mock": "12.2
|
|
37
|
-
"pkgroll": "2.
|
|
38
|
-
"prettier": "3.
|
|
36
|
+
"fetch-mock": "12.5.2",
|
|
37
|
+
"pkgroll": "2.12.2",
|
|
38
|
+
"prettier": "3.5.3",
|
|
39
39
|
"prettier-plugin-organize-imports": "4.1.0",
|
|
40
40
|
"tsm": "2.3.0",
|
|
41
|
-
"typescript": "5.
|
|
41
|
+
"typescript": "5.8.3",
|
|
42
42
|
"uvu": "0.5.6"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
package/readme.md
CHANGED
|
@@ -2,17 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
4
|
|
|
5
|
-
[<img src="https://
|
|
6
|
-
[<img src="https://
|
|
7
|
-
[<img src="https://
|
|
8
|
-
[<img src="https://
|
|
9
|
-
[<img src="https://
|
|
5
|
+
[<img src="https://badges.ws/npm/v/apigen-ts" alt="version" />](https://npmjs.org/package/apigen-ts)
|
|
6
|
+
[<img src="https://badges.ws/packagephobia/publish/apigen-ts" alt="size" />](https://packagephobia.now.sh/result?p=apigen-ts)
|
|
7
|
+
[<img src="https://badges.ws/npm/dm/apigen-ts" alt="downloads" />](https://npmjs.org/package/apigen-ts)
|
|
8
|
+
[<img src="https://badges.ws/github/license/vladkens/apigen-ts" alt="license" />](https://github.com/vladkens/apigen-ts/blob/main/LICENSE)
|
|
9
|
+
[<img src="https://badges.ws/badge/-/buy%20me%20a%20coffee/ff813f?icon=buymeacoffee&label" alt="donate" />](https://buymeacoffee.com/vladkens)
|
|
10
10
|
|
|
11
11
|
</div>
|
|
12
12
|
|
|
13
13
|
<div align="center">
|
|
14
14
|
<img src="./logo.svg" alt="apigen-ts logo" height="80" />
|
|
15
|
-
<div>Simple typed OpenAPI client generator</div>
|
|
16
15
|
</div>
|
|
17
16
|
|
|
18
17
|
## Features
|
|
@@ -29,7 +28,11 @@
|
|
|
29
28
|
## Install
|
|
30
29
|
|
|
31
30
|
```sh
|
|
32
|
-
|
|
31
|
+
npm install apigen-ts --save-dev
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
yarn add -D apigen-ts
|
|
33
36
|
```
|
|
34
37
|
|
|
35
38
|
## Usage
|
|
@@ -37,14 +40,17 @@ yarn install -D apigen-ts
|
|
|
37
40
|
### 1. Generate
|
|
38
41
|
|
|
39
42
|
```sh
|
|
43
|
+
# From file
|
|
44
|
+
npx apigen-ts ./openapi.json ./api-client.ts
|
|
45
|
+
|
|
40
46
|
# From url
|
|
41
|
-
|
|
47
|
+
npx apigen-ts https://petstore3.swagger.io/api/v3/openapi.json ./api-client.ts
|
|
42
48
|
|
|
43
|
-
# From
|
|
44
|
-
|
|
49
|
+
# From protected url
|
|
50
|
+
npx apigen-ts https://secret-api.example.com ./api-client.ts -H "x-api-key: secret-key"
|
|
45
51
|
```
|
|
46
52
|
|
|
47
|
-
Run `
|
|
53
|
+
Run `npx apigen-ts --help` for more options. Examples of generated clients [here](./examples/).
|
|
48
54
|
|
|
49
55
|
### 2. Import
|
|
50
56
|
|
|
@@ -84,7 +90,7 @@ await api.protectedRoute.get() // here authenticated
|
|
|
84
90
|
### Automatic date parsing
|
|
85
91
|
|
|
86
92
|
```sh
|
|
87
|
-
|
|
93
|
+
npx apigen-ts ./openapi.json ./api-client.ts --parse-dates
|
|
88
94
|
```
|
|
89
95
|
|
|
90
96
|
```ts
|
|
@@ -97,7 +103,7 @@ const createdAt: Date = pet.createdAt // date parsed from string with format=dat
|
|
|
97
103
|
You can generate string literal union instead of native enums in case you want to run in Node.js environment with [type-stripping](https://nodejs.org/api/typescript.html#type-stripping). To achive this pass `--inline-enums` command line argument or use `inlineEnums: true` in Node.js API.
|
|
98
104
|
|
|
99
105
|
```sh
|
|
100
|
-
|
|
106
|
+
npx apigen-ts ./openapi.json ./api-client.ts --inline-enums
|
|
101
107
|
```
|
|
102
108
|
|
|
103
109
|
This will generate:
|
|
@@ -177,6 +183,7 @@ await apigen({
|
|
|
177
183
|
name: "MyApiClient", // default "ApiClient"
|
|
178
184
|
parseDates: true, // default false
|
|
179
185
|
inlineEnums: false, // default false, use string literal union instead of enum
|
|
186
|
+
headers: { "x-api-key": "secret-key" }, // Custom HTTP headers to use when fetching schema
|
|
180
187
|
resolveName(ctx, op, proposal) {
|
|
181
188
|
// proposal is [string, string] which represents module.funcName
|
|
182
189
|
if (proposal[0] === "users") return // will use default proposal
|