apigen-ts 1.3.1 → 1.4.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/cli.js +1 -1
- package/dist/{main-BiX2OIVV.js → main-BkWtFDlL.js} +100 -50
- package/dist/main.cjs +98 -48
- package/dist/main.js +1 -1
- package/dist/main.mjs +99 -49
- package/package.json +13 -7
- package/readme.md +13 -13
package/dist/cli.js
CHANGED
|
@@ -4,13 +4,13 @@ import { fileURLToPath } from 'url';
|
|
|
4
4
|
import { cli } from 'cleye';
|
|
5
5
|
import { bundle, BaseResolver, createConfig } from '@redocly/openapi-core';
|
|
6
6
|
import { filterEmpty, filterNullable } from 'array-utils-ts';
|
|
7
|
-
import { get, uniq, upperFirst,
|
|
7
|
+
import { get, uniq, upperFirst, isBoolean, isArray, 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.
|
|
13
|
+
var version = "1.4.0";
|
|
14
14
|
|
|
15
15
|
const initCtx = (config) => {
|
|
16
16
|
return {
|
|
@@ -106,7 +106,8 @@ const getCliConfig = () => {
|
|
|
106
106
|
};
|
|
107
107
|
|
|
108
108
|
const unref = (ctx, s) => {
|
|
109
|
-
if (
|
|
109
|
+
if (s === void 0) return void 0;
|
|
110
|
+
if (typeof s !== "object" || s === null) return s;
|
|
110
111
|
if ("$ref" in s && s.$ref) {
|
|
111
112
|
const parts = s.$ref.replace("#/", "").split("/");
|
|
112
113
|
const obj = parts.reduce(
|
|
@@ -114,16 +115,24 @@ const unref = (ctx, s) => {
|
|
|
114
115
|
(acc, x) => get(acc, x, get(acc, decodeURIComponent(x).replaceAll("~1", "/"))),
|
|
115
116
|
ctx.doc
|
|
116
117
|
);
|
|
117
|
-
if (obj) return obj;
|
|
118
|
+
if (obj !== void 0) return obj;
|
|
118
119
|
console.warn(`${ctx.logTag} ref ${s.$ref} not found`);
|
|
119
120
|
return void 0;
|
|
120
121
|
}
|
|
121
122
|
return s;
|
|
122
123
|
};
|
|
124
|
+
const getContent = (ctx, content) => {
|
|
125
|
+
const result = [];
|
|
126
|
+
for (const [cType, value] of Object.entries(content ?? {})) {
|
|
127
|
+
const mType = unref(ctx, value);
|
|
128
|
+
if (mType) result.push([cType.split(";")[0], mType]);
|
|
129
|
+
}
|
|
130
|
+
return result;
|
|
131
|
+
};
|
|
123
132
|
const getReqSchema = (ctx, config) => {
|
|
124
133
|
const req = unref(ctx, config.requestBody);
|
|
125
134
|
if (!req) return void 0;
|
|
126
|
-
const cts =
|
|
135
|
+
const cts = getContent(ctx, req.content).map((x) => [x[0], x[1].schema]).filter((x) => x[1] !== void 0);
|
|
127
136
|
if (cts.length === 0) return void 0;
|
|
128
137
|
const pretenders = [
|
|
129
138
|
"application/json",
|
|
@@ -138,12 +147,15 @@ const getReqSchema = (ctx, config) => {
|
|
|
138
147
|
cts.map((x) => x[0]);
|
|
139
148
|
return void 0;
|
|
140
149
|
};
|
|
141
|
-
const
|
|
150
|
+
const getRepContent = (ctx, config) => {
|
|
142
151
|
const successCodes = Object.keys(config.responses ?? {}).filter((x) => x.startsWith("2")).filter((x) => get(config, ["responses", x]));
|
|
143
152
|
const firstSuccess = unref(ctx, get(config, ["responses", successCodes[0]], {}));
|
|
144
|
-
if (!firstSuccess || !("content" in firstSuccess)) return
|
|
145
|
-
|
|
146
|
-
|
|
153
|
+
if (!firstSuccess || !("content" in firstSuccess)) return [];
|
|
154
|
+
return getContent(ctx, firstSuccess.content);
|
|
155
|
+
};
|
|
156
|
+
const getRepSchema = (ctx, config) => {
|
|
157
|
+
const cts = getRepContent(ctx, config).filter((x) => x[1].schema !== void 0);
|
|
158
|
+
if (!cts.length) return void 0;
|
|
147
159
|
const ctJson = cts.find((x) => x[0].startsWith("application/json"));
|
|
148
160
|
if (ctJson) return ctJson[1].schema;
|
|
149
161
|
const ctText = cts.find((x) => x[0].startsWith("text/"));
|
|
@@ -213,10 +225,22 @@ const Keywords = /* @__PURE__ */ new Set([
|
|
|
213
225
|
"Exclude",
|
|
214
226
|
"Extract",
|
|
215
227
|
// ts keywords
|
|
228
|
+
"Array",
|
|
216
229
|
"Date",
|
|
217
|
-
"
|
|
218
|
-
"
|
|
219
|
-
|
|
230
|
+
"File",
|
|
231
|
+
"FormData",
|
|
232
|
+
"Headers",
|
|
233
|
+
"JSON",
|
|
234
|
+
"Object",
|
|
235
|
+
"Promise",
|
|
236
|
+
"RequestInit",
|
|
237
|
+
"Response",
|
|
238
|
+
"URL",
|
|
239
|
+
"URLSearchParams",
|
|
240
|
+
"fetch",
|
|
241
|
+
"globalThis",
|
|
242
|
+
"object"
|
|
243
|
+
// runtime names
|
|
220
244
|
]);
|
|
221
245
|
const normalizeIdentifier = (val, asVar = false) => {
|
|
222
246
|
let name = val.replace("#/components/schemas/", "").replaceAll("'", "").replace(/[^a-zA-Z0-9]/g, "_");
|
|
@@ -225,7 +249,7 @@ const normalizeIdentifier = (val, asVar = false) => {
|
|
|
225
249
|
return name;
|
|
226
250
|
};
|
|
227
251
|
const makeInlineEnum = (s) => {
|
|
228
|
-
if (!s.enum) return void 0;
|
|
252
|
+
if (isBoolean(s) || !s.enum) return void 0;
|
|
229
253
|
const values = filterEmpty(s.enum);
|
|
230
254
|
if (!values.length) return void 0;
|
|
231
255
|
if (!s.type) {
|
|
@@ -258,8 +282,16 @@ const makeInlineEnum = (s) => {
|
|
|
258
282
|
console.warn(`enum with unknown type "${s.type}" in`, s);
|
|
259
283
|
return void 0;
|
|
260
284
|
};
|
|
285
|
+
const makeConst = (s) => {
|
|
286
|
+
if (isBoolean(s) || !("const" in s)) return void 0;
|
|
287
|
+
const value = s.const;
|
|
288
|
+
if (!["string", "number", "boolean"].includes(typeof value)) return void 0;
|
|
289
|
+
const t = makeInlineEnum({ ...s, enum: [value] });
|
|
290
|
+
return t && ts.isUnionTypeNode(t) ? t.types[0] : t;
|
|
291
|
+
};
|
|
261
292
|
const makeObject = (ctx, s) => {
|
|
262
|
-
if (s.type !== "object")
|
|
293
|
+
if (isBoolean(s) || s.type !== "object")
|
|
294
|
+
throw new Error(`makeObject: not an object ${JSON.stringify(s)}`);
|
|
263
295
|
if (s.additionalProperties && !isBoolean(s.additionalProperties)) {
|
|
264
296
|
return f$2.createTypeReferenceNode("Record", [
|
|
265
297
|
f$2.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
|
@@ -278,19 +310,24 @@ const makeType = (ctx, s) => {
|
|
|
278
310
|
const mk = makeType.bind(null, ctx);
|
|
279
311
|
if (s === void 0) return f$2.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
|
|
280
312
|
if (s === null) return f$2.createLiteralTypeNode(f$2.createNull());
|
|
313
|
+
if (isBoolean(s)) {
|
|
314
|
+
return f$2.createKeywordTypeNode(s ? ts.SyntaxKind.UnknownKeyword : ts.SyntaxKind.NeverKeyword);
|
|
315
|
+
}
|
|
281
316
|
if ("$ref" in s && s.$ref) {
|
|
282
317
|
const parts = s.$ref.replace("#/", "").split("/");
|
|
283
318
|
if (parts.length === 3 && parts[0] === "components" && parts[1] === "schemas") {
|
|
284
319
|
return f$2.createTypeReferenceNode(normalizeIdentifier(parts[2], true));
|
|
285
320
|
}
|
|
286
321
|
const t = unref(ctx, s);
|
|
287
|
-
if (
|
|
322
|
+
if (t === void 0) throw new Error(`makeTypeRef: ref not found ${JSON.stringify(s)}`);
|
|
288
323
|
return makeType(ctx, t);
|
|
289
324
|
}
|
|
290
325
|
if ("oneOf" in s && s.oneOf) return f$2.createUnionTypeNode(s.oneOf.map(mk));
|
|
291
326
|
if ("anyOf" in s && s.anyOf) return f$2.createUnionTypeNode(s.anyOf.map(mk));
|
|
292
327
|
if ("allOf" in s && s.allOf) return f$2.createIntersectionTypeNode(s.allOf.map(mk));
|
|
293
328
|
if ("type" in s && s.type === "integer") s.type = "number";
|
|
329
|
+
const constant = makeConst(s);
|
|
330
|
+
if (constant) return constant;
|
|
294
331
|
if ("enum" in s && s.enum && !Array.isArray(s.type)) {
|
|
295
332
|
const isArray2 = s.type === "array";
|
|
296
333
|
const t = makeInlineEnum(isArray2 ? { ...s, type: s.items?.type } : s);
|
|
@@ -314,7 +351,6 @@ const makeType = (ctx, s) => {
|
|
|
314
351
|
if (s.type === "object") t = makeObject(ctx, s);
|
|
315
352
|
else if (s.type === "boolean") t = f$2.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
|
|
316
353
|
else if (s.type === "number") t = f$2.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
|
|
317
|
-
else if (isConstantString(s)) t = f$2.createLiteralTypeNode(f$2.createStringLiteral(s.const));
|
|
318
354
|
else if (s.type === "string") t = f$2.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
|
|
319
355
|
else if (s.type === "null") t = f$2.createLiteralTypeNode(f$2.createNull());
|
|
320
356
|
else if (isArray(s.type)) t = makeLiteralUnion(ctx, s.type);
|
|
@@ -337,16 +373,14 @@ const makeType = (ctx, s) => {
|
|
|
337
373
|
return f$2.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
338
374
|
};
|
|
339
375
|
const isStringEnum = (s) => {
|
|
376
|
+
if (isBoolean(s)) return false;
|
|
340
377
|
if ("enum" in s && s.enum) {
|
|
341
378
|
return s.enum.every((x) => typeof x === "string");
|
|
342
379
|
}
|
|
343
380
|
return false;
|
|
344
381
|
};
|
|
345
382
|
const isPrefixItems = (s) => {
|
|
346
|
-
return s.prefixItems !== void 0;
|
|
347
|
-
};
|
|
348
|
-
const isConstantString = (s) => {
|
|
349
|
-
return s.type === "string" && s.const !== void 0;
|
|
383
|
+
return !isBoolean(s) && s.prefixItems !== void 0;
|
|
350
384
|
};
|
|
351
385
|
const makeTypeAlias = (ctx, name, s) => {
|
|
352
386
|
if (isStringEnum(s) && !ctx.inlineEnums) {
|
|
@@ -372,7 +406,17 @@ const makeTypeAlias = (ctx, name, s) => {
|
|
|
372
406
|
};
|
|
373
407
|
|
|
374
408
|
const f$1 = ts.factory;
|
|
375
|
-
const HttpMethods = [
|
|
409
|
+
const HttpMethods = [
|
|
410
|
+
"get",
|
|
411
|
+
"post",
|
|
412
|
+
"put",
|
|
413
|
+
"patch",
|
|
414
|
+
"delete",
|
|
415
|
+
"head",
|
|
416
|
+
"options",
|
|
417
|
+
"trace",
|
|
418
|
+
"query"
|
|
419
|
+
];
|
|
376
420
|
const normalizeOpName = (val) => {
|
|
377
421
|
const articles = /* @__PURE__ */ new Set(["a", "an", "the"]);
|
|
378
422
|
const tmp = val.replace(/'/, "").replace(/[^a-zA-Z0-9]/g, "_").split("_").filter((x) => x !== "" && !articles.has(x)).map((x) => upperFirst(x));
|
|
@@ -418,7 +462,7 @@ const prepareUrl = (url, rename) => {
|
|
|
418
462
|
})
|
|
419
463
|
);
|
|
420
464
|
};
|
|
421
|
-
const prepareOp = (ctx, cfg, opName) => {
|
|
465
|
+
const prepareOp = (ctx, cfg, opName, httpMethod) => {
|
|
422
466
|
cfg.parameters = cfg.parameters ?? [];
|
|
423
467
|
const reqSchema = getReqSchema(ctx, cfg);
|
|
424
468
|
const repSchema = getRepSchema(ctx, cfg);
|
|
@@ -478,7 +522,7 @@ const prepareOp = (ctx, cfg, opName) => {
|
|
|
478
522
|
f$1.createIdentifier("this.Fetch"),
|
|
479
523
|
[makeType(ctx, repSchema)],
|
|
480
524
|
[
|
|
481
|
-
f$1.createStringLiteral(
|
|
525
|
+
f$1.createStringLiteral(httpMethod),
|
|
482
526
|
// method
|
|
483
527
|
prepareUrl(cfg.path, urlReplacements),
|
|
484
528
|
// path
|
|
@@ -500,43 +544,49 @@ const prepareNs = (ctx, name, handlers) => {
|
|
|
500
544
|
f$1.createObjectLiteralExpression(handlers)
|
|
501
545
|
);
|
|
502
546
|
};
|
|
547
|
+
const addOp = (ctx, routes, pathConfig, cfg, httpMethod) => {
|
|
548
|
+
ctx.logTag = `${httpMethod.padEnd(6, " ")} ${cfg.path}`;
|
|
549
|
+
if (pathConfig.parameters) {
|
|
550
|
+
cfg.parameters = [...cfg.parameters ?? [], ...pathConfig.parameters];
|
|
551
|
+
}
|
|
552
|
+
const opTags = cfg.tags ?? [];
|
|
553
|
+
if (ctx.includeTags?.length && !opTags.some((t) => ctx.includeTags.includes(t))) return;
|
|
554
|
+
if (ctx.excludeTags?.length && opTags.some((t) => ctx.excludeTags.includes(t))) return;
|
|
555
|
+
const [ns, op] = getOpName(ctx, cfg);
|
|
556
|
+
if (!routes[ns]) routes[ns] = [];
|
|
557
|
+
const joined = [ns, op].join(".");
|
|
558
|
+
if (ctx.usedNames.has(joined)) {
|
|
559
|
+
return;
|
|
560
|
+
} else {
|
|
561
|
+
ctx.usedNames.add(joined);
|
|
562
|
+
}
|
|
563
|
+
try {
|
|
564
|
+
routes[ns].push(prepareOp(ctx, cfg, op, httpMethod));
|
|
565
|
+
} catch (e) {
|
|
566
|
+
console.error(`${ctx.logTag} - ${e}`, cfg);
|
|
567
|
+
throw e;
|
|
568
|
+
}
|
|
569
|
+
};
|
|
503
570
|
const prepareRoutes = async (ctx) => {
|
|
504
571
|
const routes = {};
|
|
505
|
-
for (const [path,
|
|
572
|
+
for (const [path, value] of Object.entries(ctx.doc.paths ?? {})) {
|
|
506
573
|
ctx.logTag = `${"[ALL]".toUpperCase().padEnd(6, " ")} ${path}`;
|
|
507
|
-
if (!isObject(
|
|
574
|
+
if (!isObject(value)) continue;
|
|
508
575
|
if (ctx.filterPaths) {
|
|
509
576
|
const match = ctx.filterPaths instanceof RegExp ? ctx.filterPaths.test(path) : ctx.filterPaths(path);
|
|
510
577
|
if (!match) continue;
|
|
511
578
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
continue;
|
|
515
|
-
}
|
|
579
|
+
const pathConfig = unref(ctx, value);
|
|
580
|
+
if (!pathConfig) continue;
|
|
516
581
|
for (const method of HttpMethods) {
|
|
517
|
-
ctx.logTag = `${method.toUpperCase().padEnd(6, " ")} ${path}`;
|
|
518
582
|
const config = pathConfig[method];
|
|
519
583
|
if (!config) continue;
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
const
|
|
524
|
-
if (
|
|
525
|
-
|
|
526
|
-
const [ns, op] = getOpName(ctx, { ...config, method, path });
|
|
527
|
-
if (!routes[ns]) routes[ns] = [];
|
|
528
|
-
const joined = [ns, op].join(".");
|
|
529
|
-
if (ctx.usedNames.has(joined)) {
|
|
530
|
-
continue;
|
|
531
|
-
} else {
|
|
532
|
-
ctx.usedNames.add(joined);
|
|
533
|
-
}
|
|
534
|
-
try {
|
|
535
|
-
routes[ns].push(prepareOp(ctx, { ...config, method, path }, op));
|
|
536
|
-
} catch (e) {
|
|
537
|
-
console.error(`${ctx.logTag} - ${e}`, config);
|
|
538
|
-
throw e;
|
|
539
|
-
}
|
|
584
|
+
addOp(ctx, routes, pathConfig, { ...config, method, path }, method.toUpperCase());
|
|
585
|
+
}
|
|
586
|
+
for (const [method, value2] of Object.entries(pathConfig.additionalOperations ?? {})) {
|
|
587
|
+
const config = unref(ctx, value2);
|
|
588
|
+
if (!config) continue;
|
|
589
|
+
addOp(ctx, routes, pathConfig, { ...config, method, path }, method);
|
|
540
590
|
}
|
|
541
591
|
}
|
|
542
592
|
return routes;
|
package/dist/main.cjs
CHANGED
|
@@ -29,7 +29,8 @@ const initCtx = (config) => {
|
|
|
29
29
|
};
|
|
30
30
|
|
|
31
31
|
const unref = (ctx, s) => {
|
|
32
|
-
if (
|
|
32
|
+
if (s === void 0) return void 0;
|
|
33
|
+
if (typeof s !== "object" || s === null) return s;
|
|
33
34
|
if ("$ref" in s && s.$ref) {
|
|
34
35
|
const parts = s.$ref.replace("#/", "").split("/");
|
|
35
36
|
const obj = parts.reduce(
|
|
@@ -37,16 +38,24 @@ const unref = (ctx, s) => {
|
|
|
37
38
|
(acc, x) => lodashEs.get(acc, x, lodashEs.get(acc, decodeURIComponent(x).replaceAll("~1", "/"))),
|
|
38
39
|
ctx.doc
|
|
39
40
|
);
|
|
40
|
-
if (obj) return obj;
|
|
41
|
+
if (obj !== void 0) return obj;
|
|
41
42
|
console.warn(`${ctx.logTag} ref ${s.$ref} not found`);
|
|
42
43
|
return void 0;
|
|
43
44
|
}
|
|
44
45
|
return s;
|
|
45
46
|
};
|
|
47
|
+
const getContent = (ctx, content) => {
|
|
48
|
+
const result = [];
|
|
49
|
+
for (const [cType, value] of Object.entries(content ?? {})) {
|
|
50
|
+
const mType = unref(ctx, value);
|
|
51
|
+
if (mType) result.push([cType.split(";")[0], mType]);
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
};
|
|
46
55
|
const getReqSchema = (ctx, config) => {
|
|
47
56
|
const req = unref(ctx, config.requestBody);
|
|
48
57
|
if (!req) return void 0;
|
|
49
|
-
const cts =
|
|
58
|
+
const cts = getContent(ctx, req.content).map((x) => [x[0], x[1].schema]).filter((x) => x[1] !== void 0);
|
|
50
59
|
if (cts.length === 0) return void 0;
|
|
51
60
|
const pretenders = [
|
|
52
61
|
"application/json",
|
|
@@ -61,12 +70,15 @@ const getReqSchema = (ctx, config) => {
|
|
|
61
70
|
cts.map((x) => x[0]);
|
|
62
71
|
return void 0;
|
|
63
72
|
};
|
|
64
|
-
const
|
|
73
|
+
const getRepContent = (ctx, config) => {
|
|
65
74
|
const successCodes = Object.keys(config.responses ?? {}).filter((x) => x.startsWith("2")).filter((x) => lodashEs.get(config, ["responses", x]));
|
|
66
75
|
const firstSuccess = unref(ctx, lodashEs.get(config, ["responses", successCodes[0]], {}));
|
|
67
|
-
if (!firstSuccess || !("content" in firstSuccess)) return
|
|
68
|
-
|
|
69
|
-
|
|
76
|
+
if (!firstSuccess || !("content" in firstSuccess)) return [];
|
|
77
|
+
return getContent(ctx, firstSuccess.content);
|
|
78
|
+
};
|
|
79
|
+
const getRepSchema = (ctx, config) => {
|
|
80
|
+
const cts = getRepContent(ctx, config).filter((x) => x[1].schema !== void 0);
|
|
81
|
+
if (!cts.length) return void 0;
|
|
70
82
|
const ctJson = cts.find((x) => x[0].startsWith("application/json"));
|
|
71
83
|
if (ctJson) return ctJson[1].schema;
|
|
72
84
|
const ctText = cts.find((x) => x[0].startsWith("text/"));
|
|
@@ -136,10 +148,22 @@ const Keywords = /* @__PURE__ */ new Set([
|
|
|
136
148
|
"Exclude",
|
|
137
149
|
"Extract",
|
|
138
150
|
// ts keywords
|
|
151
|
+
"Array",
|
|
139
152
|
"Date",
|
|
140
|
-
"
|
|
141
|
-
"
|
|
142
|
-
|
|
153
|
+
"File",
|
|
154
|
+
"FormData",
|
|
155
|
+
"Headers",
|
|
156
|
+
"JSON",
|
|
157
|
+
"Object",
|
|
158
|
+
"Promise",
|
|
159
|
+
"RequestInit",
|
|
160
|
+
"Response",
|
|
161
|
+
"URL",
|
|
162
|
+
"URLSearchParams",
|
|
163
|
+
"fetch",
|
|
164
|
+
"globalThis",
|
|
165
|
+
"object"
|
|
166
|
+
// runtime names
|
|
143
167
|
]);
|
|
144
168
|
const normalizeIdentifier = (val, asVar = false) => {
|
|
145
169
|
let name = val.replace("#/components/schemas/", "").replaceAll("'", "").replace(/[^a-zA-Z0-9]/g, "_");
|
|
@@ -148,7 +172,7 @@ const normalizeIdentifier = (val, asVar = false) => {
|
|
|
148
172
|
return name;
|
|
149
173
|
};
|
|
150
174
|
const makeInlineEnum = (s) => {
|
|
151
|
-
if (!s.enum) return void 0;
|
|
175
|
+
if (lodashEs.isBoolean(s) || !s.enum) return void 0;
|
|
152
176
|
const values = arrayUtilsTs.filterEmpty(s.enum);
|
|
153
177
|
if (!values.length) return void 0;
|
|
154
178
|
if (!s.type) {
|
|
@@ -181,8 +205,16 @@ const makeInlineEnum = (s) => {
|
|
|
181
205
|
console.warn(`enum with unknown type "${s.type}" in`, s);
|
|
182
206
|
return void 0;
|
|
183
207
|
};
|
|
208
|
+
const makeConst = (s) => {
|
|
209
|
+
if (lodashEs.isBoolean(s) || !("const" in s)) return void 0;
|
|
210
|
+
const value = s.const;
|
|
211
|
+
if (!["string", "number", "boolean"].includes(typeof value)) return void 0;
|
|
212
|
+
const t = makeInlineEnum({ ...s, enum: [value] });
|
|
213
|
+
return t && ts.isUnionTypeNode(t) ? t.types[0] : t;
|
|
214
|
+
};
|
|
184
215
|
const makeObject = (ctx, s) => {
|
|
185
|
-
if (s.type !== "object")
|
|
216
|
+
if (lodashEs.isBoolean(s) || s.type !== "object")
|
|
217
|
+
throw new Error(`makeObject: not an object ${JSON.stringify(s)}`);
|
|
186
218
|
if (s.additionalProperties && !lodashEs.isBoolean(s.additionalProperties)) {
|
|
187
219
|
return f$2.createTypeReferenceNode("Record", [
|
|
188
220
|
f$2.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
|
@@ -201,19 +233,24 @@ const makeType = (ctx, s) => {
|
|
|
201
233
|
const mk = makeType.bind(null, ctx);
|
|
202
234
|
if (s === void 0) return f$2.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
|
|
203
235
|
if (s === null) return f$2.createLiteralTypeNode(f$2.createNull());
|
|
236
|
+
if (lodashEs.isBoolean(s)) {
|
|
237
|
+
return f$2.createKeywordTypeNode(s ? ts.SyntaxKind.UnknownKeyword : ts.SyntaxKind.NeverKeyword);
|
|
238
|
+
}
|
|
204
239
|
if ("$ref" in s && s.$ref) {
|
|
205
240
|
const parts = s.$ref.replace("#/", "").split("/");
|
|
206
241
|
if (parts.length === 3 && parts[0] === "components" && parts[1] === "schemas") {
|
|
207
242
|
return f$2.createTypeReferenceNode(normalizeIdentifier(parts[2], true));
|
|
208
243
|
}
|
|
209
244
|
const t = unref(ctx, s);
|
|
210
|
-
if (
|
|
245
|
+
if (t === void 0) throw new Error(`makeTypeRef: ref not found ${JSON.stringify(s)}`);
|
|
211
246
|
return makeType(ctx, t);
|
|
212
247
|
}
|
|
213
248
|
if ("oneOf" in s && s.oneOf) return f$2.createUnionTypeNode(s.oneOf.map(mk));
|
|
214
249
|
if ("anyOf" in s && s.anyOf) return f$2.createUnionTypeNode(s.anyOf.map(mk));
|
|
215
250
|
if ("allOf" in s && s.allOf) return f$2.createIntersectionTypeNode(s.allOf.map(mk));
|
|
216
251
|
if ("type" in s && s.type === "integer") s.type = "number";
|
|
252
|
+
const constant = makeConst(s);
|
|
253
|
+
if (constant) return constant;
|
|
217
254
|
if ("enum" in s && s.enum && !Array.isArray(s.type)) {
|
|
218
255
|
const isArray2 = s.type === "array";
|
|
219
256
|
const t = makeInlineEnum(isArray2 ? { ...s, type: s.items?.type } : s);
|
|
@@ -237,7 +274,6 @@ const makeType = (ctx, s) => {
|
|
|
237
274
|
if (s.type === "object") t = makeObject(ctx, s);
|
|
238
275
|
else if (s.type === "boolean") t = f$2.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
|
|
239
276
|
else if (s.type === "number") t = f$2.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
|
|
240
|
-
else if (isConstantString(s)) t = f$2.createLiteralTypeNode(f$2.createStringLiteral(s.const));
|
|
241
277
|
else if (s.type === "string") t = f$2.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
|
|
242
278
|
else if (s.type === "null") t = f$2.createLiteralTypeNode(f$2.createNull());
|
|
243
279
|
else if (lodashEs.isArray(s.type)) t = makeLiteralUnion(ctx, s.type);
|
|
@@ -260,16 +296,14 @@ const makeType = (ctx, s) => {
|
|
|
260
296
|
return f$2.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
261
297
|
};
|
|
262
298
|
const isStringEnum = (s) => {
|
|
299
|
+
if (lodashEs.isBoolean(s)) return false;
|
|
263
300
|
if ("enum" in s && s.enum) {
|
|
264
301
|
return s.enum.every((x) => typeof x === "string");
|
|
265
302
|
}
|
|
266
303
|
return false;
|
|
267
304
|
};
|
|
268
305
|
const isPrefixItems = (s) => {
|
|
269
|
-
return s.prefixItems !== void 0;
|
|
270
|
-
};
|
|
271
|
-
const isConstantString = (s) => {
|
|
272
|
-
return s.type === "string" && s.const !== void 0;
|
|
306
|
+
return !lodashEs.isBoolean(s) && s.prefixItems !== void 0;
|
|
273
307
|
};
|
|
274
308
|
const makeTypeAlias = (ctx, name, s) => {
|
|
275
309
|
if (isStringEnum(s) && !ctx.inlineEnums) {
|
|
@@ -295,7 +329,17 @@ const makeTypeAlias = (ctx, name, s) => {
|
|
|
295
329
|
};
|
|
296
330
|
|
|
297
331
|
const f$1 = ts.factory;
|
|
298
|
-
const HttpMethods = [
|
|
332
|
+
const HttpMethods = [
|
|
333
|
+
"get",
|
|
334
|
+
"post",
|
|
335
|
+
"put",
|
|
336
|
+
"patch",
|
|
337
|
+
"delete",
|
|
338
|
+
"head",
|
|
339
|
+
"options",
|
|
340
|
+
"trace",
|
|
341
|
+
"query"
|
|
342
|
+
];
|
|
299
343
|
const normalizeOpName = (val) => {
|
|
300
344
|
const articles = /* @__PURE__ */ new Set(["a", "an", "the"]);
|
|
301
345
|
const tmp = val.replace(/'/, "").replace(/[^a-zA-Z0-9]/g, "_").split("_").filter((x) => x !== "" && !articles.has(x)).map((x) => lodashEs.upperFirst(x));
|
|
@@ -341,7 +385,7 @@ const prepareUrl = (url, rename) => {
|
|
|
341
385
|
})
|
|
342
386
|
);
|
|
343
387
|
};
|
|
344
|
-
const prepareOp = (ctx, cfg, opName) => {
|
|
388
|
+
const prepareOp = (ctx, cfg, opName, httpMethod) => {
|
|
345
389
|
cfg.parameters = cfg.parameters ?? [];
|
|
346
390
|
const reqSchema = getReqSchema(ctx, cfg);
|
|
347
391
|
const repSchema = getRepSchema(ctx, cfg);
|
|
@@ -401,7 +445,7 @@ const prepareOp = (ctx, cfg, opName) => {
|
|
|
401
445
|
f$1.createIdentifier("this.Fetch"),
|
|
402
446
|
[makeType(ctx, repSchema)],
|
|
403
447
|
[
|
|
404
|
-
f$1.createStringLiteral(
|
|
448
|
+
f$1.createStringLiteral(httpMethod),
|
|
405
449
|
// method
|
|
406
450
|
prepareUrl(cfg.path, urlReplacements),
|
|
407
451
|
// path
|
|
@@ -423,43 +467,49 @@ const prepareNs = (ctx, name, handlers) => {
|
|
|
423
467
|
f$1.createObjectLiteralExpression(handlers)
|
|
424
468
|
);
|
|
425
469
|
};
|
|
470
|
+
const addOp = (ctx, routes, pathConfig, cfg, httpMethod) => {
|
|
471
|
+
ctx.logTag = `${httpMethod.padEnd(6, " ")} ${cfg.path}`;
|
|
472
|
+
if (pathConfig.parameters) {
|
|
473
|
+
cfg.parameters = [...cfg.parameters ?? [], ...pathConfig.parameters];
|
|
474
|
+
}
|
|
475
|
+
const opTags = cfg.tags ?? [];
|
|
476
|
+
if (ctx.includeTags?.length && !opTags.some((t) => ctx.includeTags.includes(t))) return;
|
|
477
|
+
if (ctx.excludeTags?.length && opTags.some((t) => ctx.excludeTags.includes(t))) return;
|
|
478
|
+
const [ns, op] = getOpName(ctx, cfg);
|
|
479
|
+
if (!routes[ns]) routes[ns] = [];
|
|
480
|
+
const joined = [ns, op].join(".");
|
|
481
|
+
if (ctx.usedNames.has(joined)) {
|
|
482
|
+
return;
|
|
483
|
+
} else {
|
|
484
|
+
ctx.usedNames.add(joined);
|
|
485
|
+
}
|
|
486
|
+
try {
|
|
487
|
+
routes[ns].push(prepareOp(ctx, cfg, op, httpMethod));
|
|
488
|
+
} catch (e) {
|
|
489
|
+
console.error(`${ctx.logTag} - ${e}`, cfg);
|
|
490
|
+
throw e;
|
|
491
|
+
}
|
|
492
|
+
};
|
|
426
493
|
const prepareRoutes = async (ctx) => {
|
|
427
494
|
const routes = {};
|
|
428
|
-
for (const [path,
|
|
495
|
+
for (const [path, value] of Object.entries(ctx.doc.paths ?? {})) {
|
|
429
496
|
ctx.logTag = `${"[ALL]".toUpperCase().padEnd(6, " ")} ${path}`;
|
|
430
|
-
if (!lodashEs.isObject(
|
|
497
|
+
if (!lodashEs.isObject(value)) continue;
|
|
431
498
|
if (ctx.filterPaths) {
|
|
432
499
|
const match = ctx.filterPaths instanceof RegExp ? ctx.filterPaths.test(path) : ctx.filterPaths(path);
|
|
433
500
|
if (!match) continue;
|
|
434
501
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
continue;
|
|
438
|
-
}
|
|
502
|
+
const pathConfig = unref(ctx, value);
|
|
503
|
+
if (!pathConfig) continue;
|
|
439
504
|
for (const method of HttpMethods) {
|
|
440
|
-
ctx.logTag = `${method.toUpperCase().padEnd(6, " ")} ${path}`;
|
|
441
505
|
const config = pathConfig[method];
|
|
442
506
|
if (!config) continue;
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
const
|
|
447
|
-
if (
|
|
448
|
-
|
|
449
|
-
const [ns, op] = getOpName(ctx, { ...config, method, path });
|
|
450
|
-
if (!routes[ns]) routes[ns] = [];
|
|
451
|
-
const joined = [ns, op].join(".");
|
|
452
|
-
if (ctx.usedNames.has(joined)) {
|
|
453
|
-
continue;
|
|
454
|
-
} else {
|
|
455
|
-
ctx.usedNames.add(joined);
|
|
456
|
-
}
|
|
457
|
-
try {
|
|
458
|
-
routes[ns].push(prepareOp(ctx, { ...config, method, path }, op));
|
|
459
|
-
} catch (e) {
|
|
460
|
-
console.error(`${ctx.logTag} - ${e}`, config);
|
|
461
|
-
throw e;
|
|
462
|
-
}
|
|
507
|
+
addOp(ctx, routes, pathConfig, { ...config, method, path }, method.toUpperCase());
|
|
508
|
+
}
|
|
509
|
+
for (const [method, value2] of Object.entries(pathConfig.additionalOperations ?? {})) {
|
|
510
|
+
const config = unref(ctx, value2);
|
|
511
|
+
if (!config) continue;
|
|
512
|
+
addOp(ctx, routes, pathConfig, { ...config, method, path }, method);
|
|
463
513
|
}
|
|
464
514
|
}
|
|
465
515
|
return routes;
|
package/dist/main.js
CHANGED
package/dist/main.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { fileURLToPath } from 'url';
|
|
|
4
4
|
import 'cleye';
|
|
5
5
|
import { bundle, BaseResolver, createConfig } from '@redocly/openapi-core';
|
|
6
6
|
import { filterEmpty, filterNullable } from 'array-utils-ts';
|
|
7
|
-
import { get, uniq, upperFirst,
|
|
7
|
+
import { get, uniq, upperFirst, isBoolean, isArray, 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';
|
|
@@ -26,7 +26,8 @@ const initCtx = (config) => {
|
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
const unref = (ctx, s) => {
|
|
29
|
-
if (
|
|
29
|
+
if (s === void 0) return void 0;
|
|
30
|
+
if (typeof s !== "object" || s === null) return s;
|
|
30
31
|
if ("$ref" in s && s.$ref) {
|
|
31
32
|
const parts = s.$ref.replace("#/", "").split("/");
|
|
32
33
|
const obj = parts.reduce(
|
|
@@ -34,16 +35,24 @@ const unref = (ctx, s) => {
|
|
|
34
35
|
(acc, x) => get(acc, x, get(acc, decodeURIComponent(x).replaceAll("~1", "/"))),
|
|
35
36
|
ctx.doc
|
|
36
37
|
);
|
|
37
|
-
if (obj) return obj;
|
|
38
|
+
if (obj !== void 0) return obj;
|
|
38
39
|
console.warn(`${ctx.logTag} ref ${s.$ref} not found`);
|
|
39
40
|
return void 0;
|
|
40
41
|
}
|
|
41
42
|
return s;
|
|
42
43
|
};
|
|
44
|
+
const getContent = (ctx, content) => {
|
|
45
|
+
const result = [];
|
|
46
|
+
for (const [cType, value] of Object.entries(content ?? {})) {
|
|
47
|
+
const mType = unref(ctx, value);
|
|
48
|
+
if (mType) result.push([cType.split(";")[0], mType]);
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
};
|
|
43
52
|
const getReqSchema = (ctx, config) => {
|
|
44
53
|
const req = unref(ctx, config.requestBody);
|
|
45
54
|
if (!req) return void 0;
|
|
46
|
-
const cts =
|
|
55
|
+
const cts = getContent(ctx, req.content).map((x) => [x[0], x[1].schema]).filter((x) => x[1] !== void 0);
|
|
47
56
|
if (cts.length === 0) return void 0;
|
|
48
57
|
const pretenders = [
|
|
49
58
|
"application/json",
|
|
@@ -58,12 +67,15 @@ const getReqSchema = (ctx, config) => {
|
|
|
58
67
|
cts.map((x) => x[0]);
|
|
59
68
|
return void 0;
|
|
60
69
|
};
|
|
61
|
-
const
|
|
70
|
+
const getRepContent = (ctx, config) => {
|
|
62
71
|
const successCodes = Object.keys(config.responses ?? {}).filter((x) => x.startsWith("2")).filter((x) => get(config, ["responses", x]));
|
|
63
72
|
const firstSuccess = unref(ctx, get(config, ["responses", successCodes[0]], {}));
|
|
64
|
-
if (!firstSuccess || !("content" in firstSuccess)) return
|
|
65
|
-
|
|
66
|
-
|
|
73
|
+
if (!firstSuccess || !("content" in firstSuccess)) return [];
|
|
74
|
+
return getContent(ctx, firstSuccess.content);
|
|
75
|
+
};
|
|
76
|
+
const getRepSchema = (ctx, config) => {
|
|
77
|
+
const cts = getRepContent(ctx, config).filter((x) => x[1].schema !== void 0);
|
|
78
|
+
if (!cts.length) return void 0;
|
|
67
79
|
const ctJson = cts.find((x) => x[0].startsWith("application/json"));
|
|
68
80
|
if (ctJson) return ctJson[1].schema;
|
|
69
81
|
const ctText = cts.find((x) => x[0].startsWith("text/"));
|
|
@@ -133,10 +145,22 @@ const Keywords = /* @__PURE__ */ new Set([
|
|
|
133
145
|
"Exclude",
|
|
134
146
|
"Extract",
|
|
135
147
|
// ts keywords
|
|
148
|
+
"Array",
|
|
136
149
|
"Date",
|
|
137
|
-
"
|
|
138
|
-
"
|
|
139
|
-
|
|
150
|
+
"File",
|
|
151
|
+
"FormData",
|
|
152
|
+
"Headers",
|
|
153
|
+
"JSON",
|
|
154
|
+
"Object",
|
|
155
|
+
"Promise",
|
|
156
|
+
"RequestInit",
|
|
157
|
+
"Response",
|
|
158
|
+
"URL",
|
|
159
|
+
"URLSearchParams",
|
|
160
|
+
"fetch",
|
|
161
|
+
"globalThis",
|
|
162
|
+
"object"
|
|
163
|
+
// runtime names
|
|
140
164
|
]);
|
|
141
165
|
const normalizeIdentifier = (val, asVar = false) => {
|
|
142
166
|
let name = val.replace("#/components/schemas/", "").replaceAll("'", "").replace(/[^a-zA-Z0-9]/g, "_");
|
|
@@ -145,7 +169,7 @@ const normalizeIdentifier = (val, asVar = false) => {
|
|
|
145
169
|
return name;
|
|
146
170
|
};
|
|
147
171
|
const makeInlineEnum = (s) => {
|
|
148
|
-
if (!s.enum) return void 0;
|
|
172
|
+
if (isBoolean(s) || !s.enum) return void 0;
|
|
149
173
|
const values = filterEmpty(s.enum);
|
|
150
174
|
if (!values.length) return void 0;
|
|
151
175
|
if (!s.type) {
|
|
@@ -178,8 +202,16 @@ const makeInlineEnum = (s) => {
|
|
|
178
202
|
console.warn(`enum with unknown type "${s.type}" in`, s);
|
|
179
203
|
return void 0;
|
|
180
204
|
};
|
|
205
|
+
const makeConst = (s) => {
|
|
206
|
+
if (isBoolean(s) || !("const" in s)) return void 0;
|
|
207
|
+
const value = s.const;
|
|
208
|
+
if (!["string", "number", "boolean"].includes(typeof value)) return void 0;
|
|
209
|
+
const t = makeInlineEnum({ ...s, enum: [value] });
|
|
210
|
+
return t && ts.isUnionTypeNode(t) ? t.types[0] : t;
|
|
211
|
+
};
|
|
181
212
|
const makeObject = (ctx, s) => {
|
|
182
|
-
if (s.type !== "object")
|
|
213
|
+
if (isBoolean(s) || s.type !== "object")
|
|
214
|
+
throw new Error(`makeObject: not an object ${JSON.stringify(s)}`);
|
|
183
215
|
if (s.additionalProperties && !isBoolean(s.additionalProperties)) {
|
|
184
216
|
return f$2.createTypeReferenceNode("Record", [
|
|
185
217
|
f$2.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
|
@@ -198,19 +230,24 @@ const makeType = (ctx, s) => {
|
|
|
198
230
|
const mk = makeType.bind(null, ctx);
|
|
199
231
|
if (s === void 0) return f$2.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
|
|
200
232
|
if (s === null) return f$2.createLiteralTypeNode(f$2.createNull());
|
|
233
|
+
if (isBoolean(s)) {
|
|
234
|
+
return f$2.createKeywordTypeNode(s ? ts.SyntaxKind.UnknownKeyword : ts.SyntaxKind.NeverKeyword);
|
|
235
|
+
}
|
|
201
236
|
if ("$ref" in s && s.$ref) {
|
|
202
237
|
const parts = s.$ref.replace("#/", "").split("/");
|
|
203
238
|
if (parts.length === 3 && parts[0] === "components" && parts[1] === "schemas") {
|
|
204
239
|
return f$2.createTypeReferenceNode(normalizeIdentifier(parts[2], true));
|
|
205
240
|
}
|
|
206
241
|
const t = unref(ctx, s);
|
|
207
|
-
if (
|
|
242
|
+
if (t === void 0) throw new Error(`makeTypeRef: ref not found ${JSON.stringify(s)}`);
|
|
208
243
|
return makeType(ctx, t);
|
|
209
244
|
}
|
|
210
245
|
if ("oneOf" in s && s.oneOf) return f$2.createUnionTypeNode(s.oneOf.map(mk));
|
|
211
246
|
if ("anyOf" in s && s.anyOf) return f$2.createUnionTypeNode(s.anyOf.map(mk));
|
|
212
247
|
if ("allOf" in s && s.allOf) return f$2.createIntersectionTypeNode(s.allOf.map(mk));
|
|
213
248
|
if ("type" in s && s.type === "integer") s.type = "number";
|
|
249
|
+
const constant = makeConst(s);
|
|
250
|
+
if (constant) return constant;
|
|
214
251
|
if ("enum" in s && s.enum && !Array.isArray(s.type)) {
|
|
215
252
|
const isArray2 = s.type === "array";
|
|
216
253
|
const t = makeInlineEnum(isArray2 ? { ...s, type: s.items?.type } : s);
|
|
@@ -234,7 +271,6 @@ const makeType = (ctx, s) => {
|
|
|
234
271
|
if (s.type === "object") t = makeObject(ctx, s);
|
|
235
272
|
else if (s.type === "boolean") t = f$2.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
|
|
236
273
|
else if (s.type === "number") t = f$2.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
|
|
237
|
-
else if (isConstantString(s)) t = f$2.createLiteralTypeNode(f$2.createStringLiteral(s.const));
|
|
238
274
|
else if (s.type === "string") t = f$2.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
|
|
239
275
|
else if (s.type === "null") t = f$2.createLiteralTypeNode(f$2.createNull());
|
|
240
276
|
else if (isArray(s.type)) t = makeLiteralUnion(ctx, s.type);
|
|
@@ -257,16 +293,14 @@ const makeType = (ctx, s) => {
|
|
|
257
293
|
return f$2.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
258
294
|
};
|
|
259
295
|
const isStringEnum = (s) => {
|
|
296
|
+
if (isBoolean(s)) return false;
|
|
260
297
|
if ("enum" in s && s.enum) {
|
|
261
298
|
return s.enum.every((x) => typeof x === "string");
|
|
262
299
|
}
|
|
263
300
|
return false;
|
|
264
301
|
};
|
|
265
302
|
const isPrefixItems = (s) => {
|
|
266
|
-
return s.prefixItems !== void 0;
|
|
267
|
-
};
|
|
268
|
-
const isConstantString = (s) => {
|
|
269
|
-
return s.type === "string" && s.const !== void 0;
|
|
303
|
+
return !isBoolean(s) && s.prefixItems !== void 0;
|
|
270
304
|
};
|
|
271
305
|
const makeTypeAlias = (ctx, name, s) => {
|
|
272
306
|
if (isStringEnum(s) && !ctx.inlineEnums) {
|
|
@@ -292,7 +326,17 @@ const makeTypeAlias = (ctx, name, s) => {
|
|
|
292
326
|
};
|
|
293
327
|
|
|
294
328
|
const f$1 = ts.factory;
|
|
295
|
-
const HttpMethods = [
|
|
329
|
+
const HttpMethods = [
|
|
330
|
+
"get",
|
|
331
|
+
"post",
|
|
332
|
+
"put",
|
|
333
|
+
"patch",
|
|
334
|
+
"delete",
|
|
335
|
+
"head",
|
|
336
|
+
"options",
|
|
337
|
+
"trace",
|
|
338
|
+
"query"
|
|
339
|
+
];
|
|
296
340
|
const normalizeOpName = (val) => {
|
|
297
341
|
const articles = /* @__PURE__ */ new Set(["a", "an", "the"]);
|
|
298
342
|
const tmp = val.replace(/'/, "").replace(/[^a-zA-Z0-9]/g, "_").split("_").filter((x) => x !== "" && !articles.has(x)).map((x) => upperFirst(x));
|
|
@@ -338,7 +382,7 @@ const prepareUrl = (url, rename) => {
|
|
|
338
382
|
})
|
|
339
383
|
);
|
|
340
384
|
};
|
|
341
|
-
const prepareOp = (ctx, cfg, opName) => {
|
|
385
|
+
const prepareOp = (ctx, cfg, opName, httpMethod) => {
|
|
342
386
|
cfg.parameters = cfg.parameters ?? [];
|
|
343
387
|
const reqSchema = getReqSchema(ctx, cfg);
|
|
344
388
|
const repSchema = getRepSchema(ctx, cfg);
|
|
@@ -398,7 +442,7 @@ const prepareOp = (ctx, cfg, opName) => {
|
|
|
398
442
|
f$1.createIdentifier("this.Fetch"),
|
|
399
443
|
[makeType(ctx, repSchema)],
|
|
400
444
|
[
|
|
401
|
-
f$1.createStringLiteral(
|
|
445
|
+
f$1.createStringLiteral(httpMethod),
|
|
402
446
|
// method
|
|
403
447
|
prepareUrl(cfg.path, urlReplacements),
|
|
404
448
|
// path
|
|
@@ -420,43 +464,49 @@ const prepareNs = (ctx, name, handlers) => {
|
|
|
420
464
|
f$1.createObjectLiteralExpression(handlers)
|
|
421
465
|
);
|
|
422
466
|
};
|
|
467
|
+
const addOp = (ctx, routes, pathConfig, cfg, httpMethod) => {
|
|
468
|
+
ctx.logTag = `${httpMethod.padEnd(6, " ")} ${cfg.path}`;
|
|
469
|
+
if (pathConfig.parameters) {
|
|
470
|
+
cfg.parameters = [...cfg.parameters ?? [], ...pathConfig.parameters];
|
|
471
|
+
}
|
|
472
|
+
const opTags = cfg.tags ?? [];
|
|
473
|
+
if (ctx.includeTags?.length && !opTags.some((t) => ctx.includeTags.includes(t))) return;
|
|
474
|
+
if (ctx.excludeTags?.length && opTags.some((t) => ctx.excludeTags.includes(t))) return;
|
|
475
|
+
const [ns, op] = getOpName(ctx, cfg);
|
|
476
|
+
if (!routes[ns]) routes[ns] = [];
|
|
477
|
+
const joined = [ns, op].join(".");
|
|
478
|
+
if (ctx.usedNames.has(joined)) {
|
|
479
|
+
return;
|
|
480
|
+
} else {
|
|
481
|
+
ctx.usedNames.add(joined);
|
|
482
|
+
}
|
|
483
|
+
try {
|
|
484
|
+
routes[ns].push(prepareOp(ctx, cfg, op, httpMethod));
|
|
485
|
+
} catch (e) {
|
|
486
|
+
console.error(`${ctx.logTag} - ${e}`, cfg);
|
|
487
|
+
throw e;
|
|
488
|
+
}
|
|
489
|
+
};
|
|
423
490
|
const prepareRoutes = async (ctx) => {
|
|
424
491
|
const routes = {};
|
|
425
|
-
for (const [path,
|
|
492
|
+
for (const [path, value] of Object.entries(ctx.doc.paths ?? {})) {
|
|
426
493
|
ctx.logTag = `${"[ALL]".toUpperCase().padEnd(6, " ")} ${path}`;
|
|
427
|
-
if (!isObject(
|
|
494
|
+
if (!isObject(value)) continue;
|
|
428
495
|
if (ctx.filterPaths) {
|
|
429
496
|
const match = ctx.filterPaths instanceof RegExp ? ctx.filterPaths.test(path) : ctx.filterPaths(path);
|
|
430
497
|
if (!match) continue;
|
|
431
498
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
continue;
|
|
435
|
-
}
|
|
499
|
+
const pathConfig = unref(ctx, value);
|
|
500
|
+
if (!pathConfig) continue;
|
|
436
501
|
for (const method of HttpMethods) {
|
|
437
|
-
ctx.logTag = `${method.toUpperCase().padEnd(6, " ")} ${path}`;
|
|
438
502
|
const config = pathConfig[method];
|
|
439
503
|
if (!config) continue;
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
const
|
|
444
|
-
if (
|
|
445
|
-
|
|
446
|
-
const [ns, op] = getOpName(ctx, { ...config, method, path });
|
|
447
|
-
if (!routes[ns]) routes[ns] = [];
|
|
448
|
-
const joined = [ns, op].join(".");
|
|
449
|
-
if (ctx.usedNames.has(joined)) {
|
|
450
|
-
continue;
|
|
451
|
-
} else {
|
|
452
|
-
ctx.usedNames.add(joined);
|
|
453
|
-
}
|
|
454
|
-
try {
|
|
455
|
-
routes[ns].push(prepareOp(ctx, { ...config, method, path }, op));
|
|
456
|
-
} catch (e) {
|
|
457
|
-
console.error(`${ctx.logTag} - ${e}`, config);
|
|
458
|
-
throw e;
|
|
459
|
-
}
|
|
504
|
+
addOp(ctx, routes, pathConfig, { ...config, method, path }, method.toUpperCase());
|
|
505
|
+
}
|
|
506
|
+
for (const [method, value2] of Object.entries(pathConfig.additionalOperations ?? {})) {
|
|
507
|
+
const config = unref(ctx, value2);
|
|
508
|
+
if (!config) continue;
|
|
509
|
+
addOp(ctx, routes, pathConfig, { ...config, method, path }, method);
|
|
460
510
|
}
|
|
461
511
|
}
|
|
462
512
|
return routes;
|
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "apigen-ts",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.4.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "vladkens <v.pronsky@gmail.com>",
|
|
7
|
-
"repository":
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "vladkens/apigen-ts"
|
|
10
|
+
},
|
|
8
11
|
"description": "Simple typed OpenAPI client generator",
|
|
9
12
|
"keywords": [
|
|
10
13
|
"openapi",
|
|
@@ -14,8 +17,11 @@
|
|
|
14
17
|
"generator",
|
|
15
18
|
"codegen"
|
|
16
19
|
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=22"
|
|
22
|
+
},
|
|
17
23
|
"dependencies": {
|
|
18
|
-
"@redocly/openapi-core": "2.
|
|
24
|
+
"@redocly/openapi-core": "2.47.0",
|
|
19
25
|
"@types/lodash-es": "4.17.12",
|
|
20
26
|
"@types/swagger2openapi": "7.0.4",
|
|
21
27
|
"array-utils-ts": "1.1.0",
|
|
@@ -24,13 +30,13 @@
|
|
|
24
30
|
"swagger2openapi": "7.0.8"
|
|
25
31
|
},
|
|
26
32
|
"devDependencies": {
|
|
27
|
-
"@types/node": "
|
|
33
|
+
"@types/node": "26.2.0",
|
|
28
34
|
"fetch-mock": "12.6.0",
|
|
29
|
-
"npm-check-updates": "
|
|
35
|
+
"npm-check-updates": "23.0.2",
|
|
30
36
|
"pkgroll": "2.27.1",
|
|
31
|
-
"prettier": "3.
|
|
37
|
+
"prettier": "3.9.6",
|
|
32
38
|
"prettier-plugin-organize-imports": "4.3.0",
|
|
33
|
-
"tsx": "4.
|
|
39
|
+
"tsx": "4.23.12",
|
|
34
40
|
"typescript": "6.0.3"
|
|
35
41
|
},
|
|
36
42
|
"peerDependencies": {
|
package/readme.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<div align="center">
|
|
4
4
|
|
|
5
5
|
[<img src="https://badges.ws/npm/v/apigen-ts" alt="version" />](https://npmjs.org/package/apigen-ts)
|
|
6
|
-
[<img src="https://
|
|
6
|
+
[<img src="https://badges.ws/packagephobia/publish/apigen-ts" alt="size" />](https://packagephobia.com/result?p=apigen-ts)
|
|
7
7
|
[<img src="https://badges.ws/npm/dm/apigen-ts" alt="downloads" />](https://npmjs.org/package/apigen-ts)
|
|
8
8
|
[<img src="https://badges.ws/github/license/vladkens/apigen-ts" alt="license" />](https://github.com/vladkens/apigen-ts/blob/main/LICENSE)
|
|
9
9
|
[<img src="https://badges.ws/badge/-/buy%20me%20a%20coffee/ff813f?icon=buymeacoffee&label" alt="donate" />](https://buymeacoffee.com/vladkens)
|
|
@@ -18,7 +18,7 @@ Turn your OpenAPI spec into a typed TypeScript client with one command.
|
|
|
18
18
|
|
|
19
19
|
- **One file.** Outputs a single `api-client.ts` — no scattered modules, no runtime deps in generated code.
|
|
20
20
|
- **Fully typed.** Every method returns the exact response type from your schema. No casting, no `any`.
|
|
21
|
-
- **Pure Node.js.** No Java, no Docker. Works with `
|
|
21
|
+
- **Pure Node.js.** No Java, no Docker. Works with `pnpm` in any project.
|
|
22
22
|
- **Fetch-based.** Uses native `fetch`. Override it with your own function for auth, retries, or logging.
|
|
23
23
|
- **All OpenAPI versions.** Supports v2 (Swagger), v3, and v3.1 — auto-upgrades v2 on the fly.
|
|
24
24
|
- **Extras built in.** Automatic date parsing, string literal unions instead of enums, Prettier formatting.
|
|
@@ -29,7 +29,7 @@ Unlike `openapi-typescript`, it generates a ready-to-call client — not just ty
|
|
|
29
29
|
## Install
|
|
30
30
|
|
|
31
31
|
```sh
|
|
32
|
-
|
|
32
|
+
pnpm add -D apigen-ts
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
## Usage
|
|
@@ -38,16 +38,16 @@ npm i -D apigen-ts
|
|
|
38
38
|
|
|
39
39
|
```sh
|
|
40
40
|
# From a local file
|
|
41
|
-
|
|
41
|
+
pnpm apigen-ts ./openapi.json ./api-client.ts
|
|
42
42
|
|
|
43
43
|
# From a URL
|
|
44
|
-
|
|
44
|
+
pnpm apigen-ts https://petstore3.swagger.io/api/v3/openapi.json ./api-client.ts
|
|
45
45
|
|
|
46
46
|
# From a protected URL
|
|
47
|
-
|
|
47
|
+
pnpm apigen-ts https://secret-api.example.com ./api-client.ts -H "x-api-key: secret-key"
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
Run `
|
|
50
|
+
Run `pnpm apigen-ts --help` for all options. See [generated examples](./examples/).
|
|
51
51
|
|
|
52
52
|
### 2. Import
|
|
53
53
|
|
|
@@ -87,7 +87,7 @@ await api.protectedRoute.get() // authenticated
|
|
|
87
87
|
### Automatic date parsing
|
|
88
88
|
|
|
89
89
|
```sh
|
|
90
|
-
|
|
90
|
+
pnpm apigen-ts ./openapi.json ./api-client.ts --parse-dates
|
|
91
91
|
```
|
|
92
92
|
|
|
93
93
|
```ts
|
|
@@ -100,7 +100,7 @@ const createdAt: Date = pet.createdAt // parsed from format=date-time string
|
|
|
100
100
|
Pass `--inline-enums` to generate string literal unions — useful for Node.js [type stripping](https://nodejs.org/api/typescript.html#type-stripping):
|
|
101
101
|
|
|
102
102
|
```sh
|
|
103
|
-
|
|
103
|
+
pnpm apigen-ts ./openapi.json ./api-client.ts --inline-enums
|
|
104
104
|
```
|
|
105
105
|
|
|
106
106
|
```ts
|
|
@@ -119,17 +119,17 @@ enum MyEnum {
|
|
|
119
119
|
Include only the endpoints you need — useful with large schemas (e.g. Cloudflare's 8 MB monolith):
|
|
120
120
|
|
|
121
121
|
```sh
|
|
122
|
-
|
|
122
|
+
pnpm apigen-ts ./openapi.json ./api-client.ts --filter-paths '^/accounts'
|
|
123
123
|
```
|
|
124
124
|
|
|
125
125
|
### Filter by tag
|
|
126
126
|
|
|
127
127
|
```sh
|
|
128
128
|
# include only endpoints tagged "pets" or "store"
|
|
129
|
-
|
|
129
|
+
pnpm apigen-ts ./openapi.json ./api-client.ts --include-tags pets,store
|
|
130
130
|
|
|
131
131
|
# exclude endpoints tagged "internal"
|
|
132
|
-
|
|
132
|
+
pnpm apigen-ts ./openapi.json ./api-client.ts --exclude-tags internal
|
|
133
133
|
```
|
|
134
134
|
|
|
135
135
|
When both flags are set, `--exclude-tags` wins.
|
|
@@ -139,7 +139,7 @@ When both flags are set, `--exclude-tags` wins.
|
|
|
139
139
|
Pass `--fetch-options` to add an optional last argument to every generated method, accepting any [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) field:
|
|
140
140
|
|
|
141
141
|
```sh
|
|
142
|
-
|
|
142
|
+
pnpm apigen-ts ./openapi.json ./api-client.ts --fetch-options
|
|
143
143
|
```
|
|
144
144
|
|
|
145
145
|
```ts
|