apigen-ts 0.1.1 → 0.2.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/_template.ts +9 -4
- package/dist/cli.cjs +1 -1
- package/dist/cli.js +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/{main-c2426ec2.cjs → main-Dkcn3PTN.cjs} +52 -95
- package/dist/{main-bbe33f1b.js → main-byN_-fkF.js} +51 -94
- package/dist/{main-bbe33f1b.mjs → main-byN_-fkF.mjs} +51 -94
- package/dist/main.cjs +1 -1
- package/dist/main.js +1 -1
- package/dist/main.mjs +1 -1
- package/package.json +10 -10
- package/readme.md +22 -1
package/dist/_template.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// Note: Use uppercase for names in ApiClient to avoid conflict with the generated code
|
|
2
2
|
|
|
3
|
-
interface ApigenConfig {
|
|
3
|
+
export interface ApigenConfig {
|
|
4
4
|
baseUrl: string
|
|
5
5
|
headers: Record<string, string>
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
interface ApigenRequest extends Omit<RequestInit, "body"> {
|
|
8
|
+
export interface ApigenRequest extends Omit<RequestInit, "body"> {
|
|
9
9
|
search?: Record<string, unknown>
|
|
10
10
|
body?: unknown
|
|
11
11
|
}
|
|
@@ -40,7 +40,7 @@ export class ApiClient {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
PrepareFetchUrl(path: string): URL {
|
|
44
44
|
let base = this.Config.baseUrl
|
|
45
45
|
if ("location" in globalThis && (base === "" || base.startsWith("/"))) {
|
|
46
46
|
// make ts happy in pure nodejs environment, should never pass here
|
|
@@ -48,7 +48,12 @@ export class ApiClient {
|
|
|
48
48
|
base = `${location.origin}${base.endsWith("/") ? base : `/${base}`}`
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
return new URL(path, base)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async Fetch<T>(method: string, path: string, opts: ApigenRequest = {}): Promise<T> {
|
|
55
|
+
const url = this.PrepareFetchUrl(path)
|
|
56
|
+
|
|
52
57
|
for (const [k, v] of Object.entries(opts?.search ?? {})) {
|
|
53
58
|
url.searchParams.append(k, Array.isArray(v) ? v.join(",") : (v as string))
|
|
54
59
|
}
|
package/dist/cli.cjs
CHANGED
package/dist/cli.js
CHANGED
package/dist/cli.mjs
CHANGED
|
@@ -12,6 +12,9 @@ var ts = require('typescript');
|
|
|
12
12
|
var path = require('node:path');
|
|
13
13
|
|
|
14
14
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
15
|
+
var name = "apigen-ts";
|
|
16
|
+
var version = "0.2.0";
|
|
17
|
+
|
|
15
18
|
const initCtx = (config) => {
|
|
16
19
|
return {
|
|
17
20
|
source: "",
|
|
@@ -26,8 +29,8 @@ const initCtx = (config) => {
|
|
|
26
29
|
};
|
|
27
30
|
const getCliConfig = () => {
|
|
28
31
|
const argv = cleye.cli({
|
|
29
|
-
name
|
|
30
|
-
version
|
|
32
|
+
name,
|
|
33
|
+
version,
|
|
31
34
|
parameters: ["<source>", "[output]"],
|
|
32
35
|
flags: {
|
|
33
36
|
name: {
|
|
@@ -52,8 +55,7 @@ const getCliConfig = () => {
|
|
|
52
55
|
};
|
|
53
56
|
|
|
54
57
|
const unref = (ctx, s) => {
|
|
55
|
-
if (!s)
|
|
56
|
-
return void 0;
|
|
58
|
+
if (!s) return void 0;
|
|
57
59
|
if ("$ref" in s && s.$ref) {
|
|
58
60
|
const parts = s.$ref.replace("#/", "").split("/");
|
|
59
61
|
const obj = parts.reduce(
|
|
@@ -61,8 +63,7 @@ const unref = (ctx, s) => {
|
|
|
61
63
|
(acc, x) => lodashEs.get(acc, x, lodashEs.get(acc, decodeURIComponent(x).replaceAll("~1", "/"))),
|
|
62
64
|
ctx.doc
|
|
63
65
|
);
|
|
64
|
-
if (obj)
|
|
65
|
-
return obj;
|
|
66
|
+
if (obj) return obj;
|
|
66
67
|
console.warn(`${ctx.logTag} ref ${s.$ref} not found`);
|
|
67
68
|
return void 0;
|
|
68
69
|
}
|
|
@@ -70,11 +71,9 @@ const unref = (ctx, s) => {
|
|
|
70
71
|
};
|
|
71
72
|
const getReqSchema = (ctx, config) => {
|
|
72
73
|
const req = unref(ctx, config.requestBody);
|
|
73
|
-
if (!req)
|
|
74
|
-
return void 0;
|
|
74
|
+
if (!req) return void 0;
|
|
75
75
|
const cts = Object.entries(req.content ?? {}).map((x) => [x[0].split(";")[0], x[1].schema]).filter((x) => x[1]);
|
|
76
|
-
if (cts.length === 0)
|
|
77
|
-
return void 0;
|
|
76
|
+
if (cts.length === 0) return void 0;
|
|
78
77
|
const pretenders = [
|
|
79
78
|
"application/json",
|
|
80
79
|
"text/",
|
|
@@ -83,8 +82,7 @@ const getReqSchema = (ctx, config) => {
|
|
|
83
82
|
];
|
|
84
83
|
for (const p of pretenders) {
|
|
85
84
|
const ct = cts.find((x) => x[0].startsWith(p));
|
|
86
|
-
if (ct)
|
|
87
|
-
return ct;
|
|
85
|
+
if (ct) return ct;
|
|
88
86
|
}
|
|
89
87
|
cts.map((x) => x[0]);
|
|
90
88
|
return void 0;
|
|
@@ -92,14 +90,11 @@ const getReqSchema = (ctx, config) => {
|
|
|
92
90
|
const getRepSchema = (ctx, config) => {
|
|
93
91
|
const successCodes = Object.keys(config.responses ?? {}).filter((x) => x.startsWith("2")).filter((x) => lodashEs.get(config, ["responses", x, "content"]));
|
|
94
92
|
const cts = Object.entries(lodashEs.get(config, ["responses", successCodes[0], "content"], {})).filter((x) => x[1].schema);
|
|
95
|
-
if (cts.length === 0)
|
|
96
|
-
return void 0;
|
|
93
|
+
if (cts.length === 0) return void 0;
|
|
97
94
|
const ctJson = cts.find((x) => x[0].startsWith("application/json"));
|
|
98
|
-
if (ctJson)
|
|
99
|
-
return ctJson[1].schema;
|
|
95
|
+
if (ctJson) return ctJson[1].schema;
|
|
100
96
|
const ctText = cts.find((x) => x[0].startsWith("text/"));
|
|
101
|
-
if (ctText)
|
|
102
|
-
return { type: "string" };
|
|
97
|
+
if (ctText) return { type: "string" };
|
|
103
98
|
cts.map((x) => x[0]).join(", ");
|
|
104
99
|
return void 0;
|
|
105
100
|
};
|
|
@@ -172,25 +167,18 @@ const Keywords = /* @__PURE__ */ new Set([
|
|
|
172
167
|
]);
|
|
173
168
|
const normalizeIdentifier = (val, asVar = false) => {
|
|
174
169
|
let name = val.replace("#/components/schemas/", "").replaceAll("'", "").replace(/[^a-zA-Z0-9]/g, "_");
|
|
175
|
-
if (name.match(/^\d/))
|
|
176
|
-
|
|
177
|
-
if (asVar && Keywords.has(name))
|
|
178
|
-
name = `$${name}`;
|
|
170
|
+
if (name.match(/^\d/)) name = `$${name}`;
|
|
171
|
+
if (asVar && Keywords.has(name)) name = `$${name}`;
|
|
179
172
|
return name;
|
|
180
173
|
};
|
|
181
174
|
const makeInlineEnum = (s) => {
|
|
182
|
-
if (!s.enum)
|
|
183
|
-
return void 0;
|
|
175
|
+
if (!s.enum) return void 0;
|
|
184
176
|
const values = arrayUtilsTs.filterEmpty(s.enum);
|
|
185
|
-
if (!values.length)
|
|
186
|
-
return void 0;
|
|
177
|
+
if (!values.length) return void 0;
|
|
187
178
|
if (!s.type) {
|
|
188
|
-
if (values.every((x) => typeof x === "string"))
|
|
189
|
-
|
|
190
|
-
if (values.every((x) => typeof x === "
|
|
191
|
-
s.type = "number";
|
|
192
|
-
if (values.every((x) => typeof x === "boolean"))
|
|
193
|
-
s.type = "boolean";
|
|
179
|
+
if (values.every((x) => typeof x === "string")) s.type = "string";
|
|
180
|
+
if (values.every((x) => typeof x === "number")) s.type = "number";
|
|
181
|
+
if (values.every((x) => typeof x === "boolean")) s.type = "boolean";
|
|
194
182
|
}
|
|
195
183
|
if (s.type === "string") {
|
|
196
184
|
const tokens = lodashEs.uniq(values).map((x) => f$2.createStringLiteral(x.toString()));
|
|
@@ -202,10 +190,8 @@ const makeInlineEnum = (s) => {
|
|
|
202
190
|
}
|
|
203
191
|
if (s.type === "boolean") {
|
|
204
192
|
const tokens = [];
|
|
205
|
-
if (values.includes(true))
|
|
206
|
-
|
|
207
|
-
if (values.includes(false))
|
|
208
|
-
tokens.push(f$2.createToken(ts.SyntaxKind.FalseKeyword));
|
|
193
|
+
if (values.includes(true)) tokens.push(f$2.createToken(ts.SyntaxKind.TrueKeyword));
|
|
194
|
+
if (values.includes(false)) tokens.push(f$2.createToken(ts.SyntaxKind.FalseKeyword));
|
|
209
195
|
return f$2.createUnionTypeNode(tokens.map((x) => f$2.createLiteralTypeNode(x)));
|
|
210
196
|
}
|
|
211
197
|
console.warn(`enum with unknown type "${s.type}" in`, s);
|
|
@@ -213,33 +199,25 @@ const makeInlineEnum = (s) => {
|
|
|
213
199
|
};
|
|
214
200
|
const makeType = (ctx, s) => {
|
|
215
201
|
const mk = makeType.bind(null, ctx);
|
|
216
|
-
if (s === void 0)
|
|
217
|
-
|
|
218
|
-
if (s === null)
|
|
219
|
-
return f$2.createLiteralTypeNode(f$2.createNull());
|
|
202
|
+
if (s === void 0) return f$2.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
|
|
203
|
+
if (s === null) return f$2.createLiteralTypeNode(f$2.createNull());
|
|
220
204
|
if ("$ref" in s && s.$ref) {
|
|
221
205
|
const parts = s.$ref.replace("#/", "").split("/");
|
|
222
206
|
if (parts.length === 3 && parts[0] === "components" && parts[1] === "schemas") {
|
|
223
207
|
return f$2.createTypeReferenceNode(normalizeIdentifier(parts[2], true));
|
|
224
208
|
}
|
|
225
209
|
const t = unref(ctx, s);
|
|
226
|
-
if (!t)
|
|
227
|
-
throw new Error(`makeTypeRef: ref not found ${JSON.stringify(s)}`);
|
|
210
|
+
if (!t) throw new Error(`makeTypeRef: ref not found ${JSON.stringify(s)}`);
|
|
228
211
|
return makeType(ctx, t);
|
|
229
212
|
}
|
|
230
|
-
if ("oneOf" in s && s.oneOf)
|
|
231
|
-
|
|
232
|
-
if ("
|
|
233
|
-
|
|
234
|
-
if ("allOf" in s && s.allOf)
|
|
235
|
-
return f$2.createIntersectionTypeNode(s.allOf.map(mk));
|
|
236
|
-
if ("type" in s && s.type === "integer")
|
|
237
|
-
s.type = "number";
|
|
213
|
+
if ("oneOf" in s && s.oneOf) return f$2.createUnionTypeNode(s.oneOf.map(mk));
|
|
214
|
+
if ("anyOf" in s && s.anyOf) return f$2.createUnionTypeNode(s.anyOf.map(mk));
|
|
215
|
+
if ("allOf" in s && s.allOf) return f$2.createIntersectionTypeNode(s.allOf.map(mk));
|
|
216
|
+
if ("type" in s && s.type === "integer") s.type = "number";
|
|
238
217
|
if ("enum" in s && s.enum && !Array.isArray(s.type)) {
|
|
239
218
|
const isArray2 = s.type === "array";
|
|
240
219
|
const t = makeInlineEnum(isArray2 ? { ...s, type: s.items?.type } : s);
|
|
241
|
-
if (t)
|
|
242
|
-
return isArray2 ? f$2.createArrayTypeNode(t) : t;
|
|
220
|
+
if (t) return isArray2 ? f$2.createArrayTypeNode(t) : t;
|
|
243
221
|
}
|
|
244
222
|
if ("properties" in s && s.properties) {
|
|
245
223
|
return f$2.createTypeLiteralNode(
|
|
@@ -254,37 +232,26 @@ const makeType = (ctx, s) => {
|
|
|
254
232
|
if (Array.isArray(s.type)) {
|
|
255
233
|
const types = [];
|
|
256
234
|
for (const type of s.type) {
|
|
257
|
-
if (type === "null")
|
|
258
|
-
|
|
259
|
-
else
|
|
260
|
-
types.push({ ...s, type });
|
|
235
|
+
if (type === "null") types.push({ type: "null" });
|
|
236
|
+
else types.push({ ...s, type });
|
|
261
237
|
}
|
|
262
238
|
return mk({ oneOf: types });
|
|
263
239
|
}
|
|
264
240
|
let t;
|
|
265
|
-
if (s.type === "object")
|
|
266
|
-
|
|
267
|
-
else if (s.type === "
|
|
268
|
-
|
|
269
|
-
else if (s.type === "
|
|
270
|
-
|
|
271
|
-
else if (s.type
|
|
272
|
-
t = f$2.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
|
|
273
|
-
else if (s.type === "array")
|
|
274
|
-
t = f$2.createArrayTypeNode(mk(s.items));
|
|
275
|
-
else if (s.type === "null")
|
|
276
|
-
t = f$2.createLiteralTypeNode(f$2.createNull());
|
|
277
|
-
else if (lodashEs.isArray(s.type))
|
|
278
|
-
t = f$2.createUnionTypeNode(s.type.map((x) => mk({ type: x })));
|
|
241
|
+
if (s.type === "object") t = f$2.createKeywordTypeNode(ts.SyntaxKind.ObjectKeyword);
|
|
242
|
+
else if (s.type === "boolean") t = f$2.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
|
|
243
|
+
else if (s.type === "number") t = f$2.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
|
|
244
|
+
else if (s.type === "string") t = f$2.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
|
|
245
|
+
else if (s.type === "array") t = f$2.createArrayTypeNode(mk(s.items));
|
|
246
|
+
else if (s.type === "null") t = f$2.createLiteralTypeNode(f$2.createNull());
|
|
247
|
+
else if (lodashEs.isArray(s.type)) t = f$2.createUnionTypeNode(s.type.map((x) => mk({ type: x })));
|
|
279
248
|
else {
|
|
280
249
|
console.warn(`makeType: unknown type "${s.type}"`);
|
|
281
250
|
return f$2.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
282
251
|
}
|
|
283
252
|
if (s.type === "string") {
|
|
284
|
-
if (s.format === "binary")
|
|
285
|
-
|
|
286
|
-
if (s.format === "date-time" && ctx.parseDates)
|
|
287
|
-
t = f$2.createTypeReferenceNode("Date");
|
|
253
|
+
if (s.format === "binary") t = f$2.createTypeReferenceNode("File");
|
|
254
|
+
if (s.format === "date-time" && ctx.parseDates) t = f$2.createTypeReferenceNode("Date");
|
|
288
255
|
}
|
|
289
256
|
return s.nullable ? f$2.createUnionTypeNode([t, f$2.createLiteralTypeNode(f$2.createNull())]) : t;
|
|
290
257
|
}
|
|
@@ -336,15 +303,13 @@ const getOpName = (ctx, op) => {
|
|
|
336
303
|
}
|
|
337
304
|
fn = normalizeOpName(fn);
|
|
338
305
|
let nsr = ns.split("").map((x) => `[${x.toUpperCase()}${x.toLowerCase()}]`).join("");
|
|
339
|
-
if (nsr.endsWith("[Ss]"))
|
|
340
|
-
nsr += "?";
|
|
306
|
+
if (nsr.endsWith("[Ss]")) nsr += "?";
|
|
341
307
|
fn = fn.replace(new RegExp(`^${nsr}([Cc]ontroller|[Ss]ervice)?([A-Z].*)$`), "$2");
|
|
342
308
|
fn = lodashEs.lowerFirst(fn);
|
|
343
309
|
const proposal = [ns, fn];
|
|
344
310
|
if (ctx.resolveName) {
|
|
345
311
|
const res = ctx.resolveName(ctx, op, proposal);
|
|
346
|
-
if (Array.isArray(res) && res.length === 2)
|
|
347
|
-
return res;
|
|
312
|
+
if (Array.isArray(res) && res.length === 2) return res;
|
|
348
313
|
if (res !== void 0) {
|
|
349
314
|
console.warn(`${ctx.logTag} resolveName should return [ns, fn] or undefined (skipping)`);
|
|
350
315
|
}
|
|
@@ -352,11 +317,9 @@ const getOpName = (ctx, op) => {
|
|
|
352
317
|
return proposal;
|
|
353
318
|
};
|
|
354
319
|
const prepareUrl = (url, rename) => {
|
|
355
|
-
for (const [k, v] of Object.entries(rename))
|
|
356
|
-
url = url.replaceAll(`{${k}}`, "${" + v + "}");
|
|
320
|
+
for (const [k, v] of Object.entries(rename)) url = url.replaceAll(`{${k}}`, "${" + v + "}");
|
|
357
321
|
const parts = url.split("${");
|
|
358
|
-
if (parts.length === 1)
|
|
359
|
-
return f$1.createStringLiteral(url);
|
|
322
|
+
if (parts.length === 1) return f$1.createStringLiteral(url);
|
|
360
323
|
return f$1.createTemplateExpression(
|
|
361
324
|
f$1.createTemplateHead(parts[0]),
|
|
362
325
|
parts.slice(1).map((x, i) => {
|
|
@@ -376,13 +339,11 @@ const prepareOp = (ctx, cfg, opName) => {
|
|
|
376
339
|
const repSchema = getRepSchema(ctx, cfg);
|
|
377
340
|
const allParams = arrayUtilsTs.filterNullable(cfg.parameters.map((x) => unref(ctx, x)));
|
|
378
341
|
const params = lodashEs.uniqBy(allParams.filter((x) => x.in === "path"), "name");
|
|
379
|
-
if (reqSchema)
|
|
380
|
-
params.push({ name: "body", schema: reqSchema[1] });
|
|
342
|
+
if (reqSchema) params.push({ name: "body", schema: reqSchema[1] });
|
|
381
343
|
const search = allParams.filter((x) => x.in === "query");
|
|
382
344
|
allParams.filter((x) => x.in === "header");
|
|
383
345
|
for (const [name, v] of Object.entries({ search })) {
|
|
384
|
-
if (!v.length)
|
|
385
|
-
continue;
|
|
346
|
+
if (!v.length) continue;
|
|
386
347
|
const properties = v.reduce((acc, x) => ({ ...acc, [x.name]: x.schema }), {});
|
|
387
348
|
params.push({ name, schema: { type: "object", properties } });
|
|
388
349
|
}
|
|
@@ -446,8 +407,7 @@ const prepareRoutes = async (ctx) => {
|
|
|
446
407
|
const routes = {};
|
|
447
408
|
for (const [path, pathConfig] of Object.entries(ctx.doc.paths ?? {})) {
|
|
448
409
|
ctx.logTag = `${"[ALL]".toUpperCase().padEnd(6, " ")} ${path}`;
|
|
449
|
-
if (!lodashEs.isObject(pathConfig))
|
|
450
|
-
continue;
|
|
410
|
+
if (!lodashEs.isObject(pathConfig)) continue;
|
|
451
411
|
if ("$ref" in pathConfig) {
|
|
452
412
|
console.warn(`${ctx.logTag} $ref should be resolved before (skipping)`);
|
|
453
413
|
continue;
|
|
@@ -455,14 +415,12 @@ const prepareRoutes = async (ctx) => {
|
|
|
455
415
|
for (const method of HttpMethods) {
|
|
456
416
|
ctx.logTag = `${method.toUpperCase().padEnd(6, " ")} ${path}`;
|
|
457
417
|
const config = pathConfig[method];
|
|
458
|
-
if (!config)
|
|
459
|
-
continue;
|
|
418
|
+
if (!config) continue;
|
|
460
419
|
if (pathConfig.parameters) {
|
|
461
420
|
config.parameters = [...config.parameters ?? [], ...pathConfig.parameters];
|
|
462
421
|
}
|
|
463
422
|
const [ns, op] = getOpName(ctx, { ...config, method, path });
|
|
464
|
-
if (!routes[ns])
|
|
465
|
-
routes[ns] = [];
|
|
423
|
+
if (!routes[ns]) routes[ns] = [];
|
|
466
424
|
const joined = [ns, op].join(".");
|
|
467
425
|
if (ctx.usedNames.has(joined)) {
|
|
468
426
|
continue;
|
|
@@ -502,8 +460,7 @@ const generateAst = async (ctx) => {
|
|
|
502
460
|
return { modules, types };
|
|
503
461
|
};
|
|
504
462
|
const loadSchema = async (url, upgrade = true) => {
|
|
505
|
-
if (url.startsWith("file://"))
|
|
506
|
-
url = url.substring(7);
|
|
463
|
+
if (url.startsWith("file://")) url = url.substring(7);
|
|
507
464
|
const { bundle } = await redocly.bundle({
|
|
508
465
|
ref: url,
|
|
509
466
|
config: await redocly.createConfig({}),
|
|
@@ -548,7 +505,7 @@ const apigen = async (config) => {
|
|
|
548
505
|
const doc = await loadSchema(config.source);
|
|
549
506
|
const ctx = initCtx({ ...config, doc });
|
|
550
507
|
const { modules, types } = await generateAst(ctx);
|
|
551
|
-
const filepath = path$1.join(path$1.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.src || new URL('main-
|
|
508
|
+
const filepath = path$1.join(path$1.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.src || new URL('main-Dkcn3PTN.cjs', document.baseURI).href)))), "_template.ts");
|
|
552
509
|
const file = await fs.readFile(filepath, "utf-8");
|
|
553
510
|
let code = [
|
|
554
511
|
`// Auto-generated by https://github.com/vladkens/apigen-ts`,
|
|
@@ -9,6 +9,9 @@ import { convertObj } from 'swagger2openapi';
|
|
|
9
9
|
import ts from 'typescript';
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
|
|
12
|
+
var name = "apigen-ts";
|
|
13
|
+
var version = "0.2.0";
|
|
14
|
+
|
|
12
15
|
const initCtx = (config) => {
|
|
13
16
|
return {
|
|
14
17
|
source: "",
|
|
@@ -23,8 +26,8 @@ const initCtx = (config) => {
|
|
|
23
26
|
};
|
|
24
27
|
const getCliConfig = () => {
|
|
25
28
|
const argv = cli({
|
|
26
|
-
name
|
|
27
|
-
version
|
|
29
|
+
name,
|
|
30
|
+
version,
|
|
28
31
|
parameters: ["<source>", "[output]"],
|
|
29
32
|
flags: {
|
|
30
33
|
name: {
|
|
@@ -49,8 +52,7 @@ const getCliConfig = () => {
|
|
|
49
52
|
};
|
|
50
53
|
|
|
51
54
|
const unref = (ctx, s) => {
|
|
52
|
-
if (!s)
|
|
53
|
-
return void 0;
|
|
55
|
+
if (!s) return void 0;
|
|
54
56
|
if ("$ref" in s && s.$ref) {
|
|
55
57
|
const parts = s.$ref.replace("#/", "").split("/");
|
|
56
58
|
const obj = parts.reduce(
|
|
@@ -58,8 +60,7 @@ const unref = (ctx, s) => {
|
|
|
58
60
|
(acc, x) => get(acc, x, get(acc, decodeURIComponent(x).replaceAll("~1", "/"))),
|
|
59
61
|
ctx.doc
|
|
60
62
|
);
|
|
61
|
-
if (obj)
|
|
62
|
-
return obj;
|
|
63
|
+
if (obj) return obj;
|
|
63
64
|
console.warn(`${ctx.logTag} ref ${s.$ref} not found`);
|
|
64
65
|
return void 0;
|
|
65
66
|
}
|
|
@@ -67,11 +68,9 @@ const unref = (ctx, s) => {
|
|
|
67
68
|
};
|
|
68
69
|
const getReqSchema = (ctx, config) => {
|
|
69
70
|
const req = unref(ctx, config.requestBody);
|
|
70
|
-
if (!req)
|
|
71
|
-
return void 0;
|
|
71
|
+
if (!req) return void 0;
|
|
72
72
|
const cts = Object.entries(req.content ?? {}).map((x) => [x[0].split(";")[0], x[1].schema]).filter((x) => x[1]);
|
|
73
|
-
if (cts.length === 0)
|
|
74
|
-
return void 0;
|
|
73
|
+
if (cts.length === 0) return void 0;
|
|
75
74
|
const pretenders = [
|
|
76
75
|
"application/json",
|
|
77
76
|
"text/",
|
|
@@ -80,8 +79,7 @@ const getReqSchema = (ctx, config) => {
|
|
|
80
79
|
];
|
|
81
80
|
for (const p of pretenders) {
|
|
82
81
|
const ct = cts.find((x) => x[0].startsWith(p));
|
|
83
|
-
if (ct)
|
|
84
|
-
return ct;
|
|
82
|
+
if (ct) return ct;
|
|
85
83
|
}
|
|
86
84
|
cts.map((x) => x[0]);
|
|
87
85
|
return void 0;
|
|
@@ -89,14 +87,11 @@ const getReqSchema = (ctx, config) => {
|
|
|
89
87
|
const getRepSchema = (ctx, config) => {
|
|
90
88
|
const successCodes = Object.keys(config.responses ?? {}).filter((x) => x.startsWith("2")).filter((x) => get(config, ["responses", x, "content"]));
|
|
91
89
|
const cts = Object.entries(get(config, ["responses", successCodes[0], "content"], {})).filter((x) => x[1].schema);
|
|
92
|
-
if (cts.length === 0)
|
|
93
|
-
return void 0;
|
|
90
|
+
if (cts.length === 0) return void 0;
|
|
94
91
|
const ctJson = cts.find((x) => x[0].startsWith("application/json"));
|
|
95
|
-
if (ctJson)
|
|
96
|
-
return ctJson[1].schema;
|
|
92
|
+
if (ctJson) return ctJson[1].schema;
|
|
97
93
|
const ctText = cts.find((x) => x[0].startsWith("text/"));
|
|
98
|
-
if (ctText)
|
|
99
|
-
return { type: "string" };
|
|
94
|
+
if (ctText) return { type: "string" };
|
|
100
95
|
cts.map((x) => x[0]).join(", ");
|
|
101
96
|
return void 0;
|
|
102
97
|
};
|
|
@@ -169,25 +164,18 @@ const Keywords = /* @__PURE__ */ new Set([
|
|
|
169
164
|
]);
|
|
170
165
|
const normalizeIdentifier = (val, asVar = false) => {
|
|
171
166
|
let name = val.replace("#/components/schemas/", "").replaceAll("'", "").replace(/[^a-zA-Z0-9]/g, "_");
|
|
172
|
-
if (name.match(/^\d/))
|
|
173
|
-
|
|
174
|
-
if (asVar && Keywords.has(name))
|
|
175
|
-
name = `$${name}`;
|
|
167
|
+
if (name.match(/^\d/)) name = `$${name}`;
|
|
168
|
+
if (asVar && Keywords.has(name)) name = `$${name}`;
|
|
176
169
|
return name;
|
|
177
170
|
};
|
|
178
171
|
const makeInlineEnum = (s) => {
|
|
179
|
-
if (!s.enum)
|
|
180
|
-
return void 0;
|
|
172
|
+
if (!s.enum) return void 0;
|
|
181
173
|
const values = filterEmpty(s.enum);
|
|
182
|
-
if (!values.length)
|
|
183
|
-
return void 0;
|
|
174
|
+
if (!values.length) return void 0;
|
|
184
175
|
if (!s.type) {
|
|
185
|
-
if (values.every((x) => typeof x === "string"))
|
|
186
|
-
|
|
187
|
-
if (values.every((x) => typeof x === "
|
|
188
|
-
s.type = "number";
|
|
189
|
-
if (values.every((x) => typeof x === "boolean"))
|
|
190
|
-
s.type = "boolean";
|
|
176
|
+
if (values.every((x) => typeof x === "string")) s.type = "string";
|
|
177
|
+
if (values.every((x) => typeof x === "number")) s.type = "number";
|
|
178
|
+
if (values.every((x) => typeof x === "boolean")) s.type = "boolean";
|
|
191
179
|
}
|
|
192
180
|
if (s.type === "string") {
|
|
193
181
|
const tokens = uniq(values).map((x) => f$2.createStringLiteral(x.toString()));
|
|
@@ -199,10 +187,8 @@ const makeInlineEnum = (s) => {
|
|
|
199
187
|
}
|
|
200
188
|
if (s.type === "boolean") {
|
|
201
189
|
const tokens = [];
|
|
202
|
-
if (values.includes(true))
|
|
203
|
-
|
|
204
|
-
if (values.includes(false))
|
|
205
|
-
tokens.push(f$2.createToken(ts.SyntaxKind.FalseKeyword));
|
|
190
|
+
if (values.includes(true)) tokens.push(f$2.createToken(ts.SyntaxKind.TrueKeyword));
|
|
191
|
+
if (values.includes(false)) tokens.push(f$2.createToken(ts.SyntaxKind.FalseKeyword));
|
|
206
192
|
return f$2.createUnionTypeNode(tokens.map((x) => f$2.createLiteralTypeNode(x)));
|
|
207
193
|
}
|
|
208
194
|
console.warn(`enum with unknown type "${s.type}" in`, s);
|
|
@@ -210,33 +196,25 @@ const makeInlineEnum = (s) => {
|
|
|
210
196
|
};
|
|
211
197
|
const makeType = (ctx, s) => {
|
|
212
198
|
const mk = makeType.bind(null, ctx);
|
|
213
|
-
if (s === void 0)
|
|
214
|
-
|
|
215
|
-
if (s === null)
|
|
216
|
-
return f$2.createLiteralTypeNode(f$2.createNull());
|
|
199
|
+
if (s === void 0) return f$2.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
|
|
200
|
+
if (s === null) return f$2.createLiteralTypeNode(f$2.createNull());
|
|
217
201
|
if ("$ref" in s && s.$ref) {
|
|
218
202
|
const parts = s.$ref.replace("#/", "").split("/");
|
|
219
203
|
if (parts.length === 3 && parts[0] === "components" && parts[1] === "schemas") {
|
|
220
204
|
return f$2.createTypeReferenceNode(normalizeIdentifier(parts[2], true));
|
|
221
205
|
}
|
|
222
206
|
const t = unref(ctx, s);
|
|
223
|
-
if (!t)
|
|
224
|
-
throw new Error(`makeTypeRef: ref not found ${JSON.stringify(s)}`);
|
|
207
|
+
if (!t) throw new Error(`makeTypeRef: ref not found ${JSON.stringify(s)}`);
|
|
225
208
|
return makeType(ctx, t);
|
|
226
209
|
}
|
|
227
|
-
if ("oneOf" in s && s.oneOf)
|
|
228
|
-
|
|
229
|
-
if ("
|
|
230
|
-
|
|
231
|
-
if ("allOf" in s && s.allOf)
|
|
232
|
-
return f$2.createIntersectionTypeNode(s.allOf.map(mk));
|
|
233
|
-
if ("type" in s && s.type === "integer")
|
|
234
|
-
s.type = "number";
|
|
210
|
+
if ("oneOf" in s && s.oneOf) return f$2.createUnionTypeNode(s.oneOf.map(mk));
|
|
211
|
+
if ("anyOf" in s && s.anyOf) return f$2.createUnionTypeNode(s.anyOf.map(mk));
|
|
212
|
+
if ("allOf" in s && s.allOf) return f$2.createIntersectionTypeNode(s.allOf.map(mk));
|
|
213
|
+
if ("type" in s && s.type === "integer") s.type = "number";
|
|
235
214
|
if ("enum" in s && s.enum && !Array.isArray(s.type)) {
|
|
236
215
|
const isArray2 = s.type === "array";
|
|
237
216
|
const t = makeInlineEnum(isArray2 ? { ...s, type: s.items?.type } : s);
|
|
238
|
-
if (t)
|
|
239
|
-
return isArray2 ? f$2.createArrayTypeNode(t) : t;
|
|
217
|
+
if (t) return isArray2 ? f$2.createArrayTypeNode(t) : t;
|
|
240
218
|
}
|
|
241
219
|
if ("properties" in s && s.properties) {
|
|
242
220
|
return f$2.createTypeLiteralNode(
|
|
@@ -251,37 +229,26 @@ const makeType = (ctx, s) => {
|
|
|
251
229
|
if (Array.isArray(s.type)) {
|
|
252
230
|
const types = [];
|
|
253
231
|
for (const type of s.type) {
|
|
254
|
-
if (type === "null")
|
|
255
|
-
|
|
256
|
-
else
|
|
257
|
-
types.push({ ...s, type });
|
|
232
|
+
if (type === "null") types.push({ type: "null" });
|
|
233
|
+
else types.push({ ...s, type });
|
|
258
234
|
}
|
|
259
235
|
return mk({ oneOf: types });
|
|
260
236
|
}
|
|
261
237
|
let t;
|
|
262
|
-
if (s.type === "object")
|
|
263
|
-
|
|
264
|
-
else if (s.type === "
|
|
265
|
-
|
|
266
|
-
else if (s.type === "
|
|
267
|
-
|
|
268
|
-
else if (s.type
|
|
269
|
-
t = f$2.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
|
|
270
|
-
else if (s.type === "array")
|
|
271
|
-
t = f$2.createArrayTypeNode(mk(s.items));
|
|
272
|
-
else if (s.type === "null")
|
|
273
|
-
t = f$2.createLiteralTypeNode(f$2.createNull());
|
|
274
|
-
else if (isArray(s.type))
|
|
275
|
-
t = f$2.createUnionTypeNode(s.type.map((x) => mk({ type: x })));
|
|
238
|
+
if (s.type === "object") t = f$2.createKeywordTypeNode(ts.SyntaxKind.ObjectKeyword);
|
|
239
|
+
else if (s.type === "boolean") t = f$2.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
|
|
240
|
+
else if (s.type === "number") t = f$2.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
|
|
241
|
+
else if (s.type === "string") t = f$2.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
|
|
242
|
+
else if (s.type === "array") t = f$2.createArrayTypeNode(mk(s.items));
|
|
243
|
+
else if (s.type === "null") t = f$2.createLiteralTypeNode(f$2.createNull());
|
|
244
|
+
else if (isArray(s.type)) t = f$2.createUnionTypeNode(s.type.map((x) => mk({ type: x })));
|
|
276
245
|
else {
|
|
277
246
|
console.warn(`makeType: unknown type "${s.type}"`);
|
|
278
247
|
return f$2.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
279
248
|
}
|
|
280
249
|
if (s.type === "string") {
|
|
281
|
-
if (s.format === "binary")
|
|
282
|
-
|
|
283
|
-
if (s.format === "date-time" && ctx.parseDates)
|
|
284
|
-
t = f$2.createTypeReferenceNode("Date");
|
|
250
|
+
if (s.format === "binary") t = f$2.createTypeReferenceNode("File");
|
|
251
|
+
if (s.format === "date-time" && ctx.parseDates) t = f$2.createTypeReferenceNode("Date");
|
|
285
252
|
}
|
|
286
253
|
return s.nullable ? f$2.createUnionTypeNode([t, f$2.createLiteralTypeNode(f$2.createNull())]) : t;
|
|
287
254
|
}
|
|
@@ -333,15 +300,13 @@ const getOpName = (ctx, op) => {
|
|
|
333
300
|
}
|
|
334
301
|
fn = normalizeOpName(fn);
|
|
335
302
|
let nsr = ns.split("").map((x) => `[${x.toUpperCase()}${x.toLowerCase()}]`).join("");
|
|
336
|
-
if (nsr.endsWith("[Ss]"))
|
|
337
|
-
nsr += "?";
|
|
303
|
+
if (nsr.endsWith("[Ss]")) nsr += "?";
|
|
338
304
|
fn = fn.replace(new RegExp(`^${nsr}([Cc]ontroller|[Ss]ervice)?([A-Z].*)$`), "$2");
|
|
339
305
|
fn = lowerFirst(fn);
|
|
340
306
|
const proposal = [ns, fn];
|
|
341
307
|
if (ctx.resolveName) {
|
|
342
308
|
const res = ctx.resolveName(ctx, op, proposal);
|
|
343
|
-
if (Array.isArray(res) && res.length === 2)
|
|
344
|
-
return res;
|
|
309
|
+
if (Array.isArray(res) && res.length === 2) return res;
|
|
345
310
|
if (res !== void 0) {
|
|
346
311
|
console.warn(`${ctx.logTag} resolveName should return [ns, fn] or undefined (skipping)`);
|
|
347
312
|
}
|
|
@@ -349,11 +314,9 @@ const getOpName = (ctx, op) => {
|
|
|
349
314
|
return proposal;
|
|
350
315
|
};
|
|
351
316
|
const prepareUrl = (url, rename) => {
|
|
352
|
-
for (const [k, v] of Object.entries(rename))
|
|
353
|
-
url = url.replaceAll(`{${k}}`, "${" + v + "}");
|
|
317
|
+
for (const [k, v] of Object.entries(rename)) url = url.replaceAll(`{${k}}`, "${" + v + "}");
|
|
354
318
|
const parts = url.split("${");
|
|
355
|
-
if (parts.length === 1)
|
|
356
|
-
return f$1.createStringLiteral(url);
|
|
319
|
+
if (parts.length === 1) return f$1.createStringLiteral(url);
|
|
357
320
|
return f$1.createTemplateExpression(
|
|
358
321
|
f$1.createTemplateHead(parts[0]),
|
|
359
322
|
parts.slice(1).map((x, i) => {
|
|
@@ -373,13 +336,11 @@ const prepareOp = (ctx, cfg, opName) => {
|
|
|
373
336
|
const repSchema = getRepSchema(ctx, cfg);
|
|
374
337
|
const allParams = filterNullable(cfg.parameters.map((x) => unref(ctx, x)));
|
|
375
338
|
const params = uniqBy(allParams.filter((x) => x.in === "path"), "name");
|
|
376
|
-
if (reqSchema)
|
|
377
|
-
params.push({ name: "body", schema: reqSchema[1] });
|
|
339
|
+
if (reqSchema) params.push({ name: "body", schema: reqSchema[1] });
|
|
378
340
|
const search = allParams.filter((x) => x.in === "query");
|
|
379
341
|
allParams.filter((x) => x.in === "header");
|
|
380
342
|
for (const [name, v] of Object.entries({ search })) {
|
|
381
|
-
if (!v.length)
|
|
382
|
-
continue;
|
|
343
|
+
if (!v.length) continue;
|
|
383
344
|
const properties = v.reduce((acc, x) => ({ ...acc, [x.name]: x.schema }), {});
|
|
384
345
|
params.push({ name, schema: { type: "object", properties } });
|
|
385
346
|
}
|
|
@@ -443,8 +404,7 @@ const prepareRoutes = async (ctx) => {
|
|
|
443
404
|
const routes = {};
|
|
444
405
|
for (const [path, pathConfig] of Object.entries(ctx.doc.paths ?? {})) {
|
|
445
406
|
ctx.logTag = `${"[ALL]".toUpperCase().padEnd(6, " ")} ${path}`;
|
|
446
|
-
if (!isObject(pathConfig))
|
|
447
|
-
continue;
|
|
407
|
+
if (!isObject(pathConfig)) continue;
|
|
448
408
|
if ("$ref" in pathConfig) {
|
|
449
409
|
console.warn(`${ctx.logTag} $ref should be resolved before (skipping)`);
|
|
450
410
|
continue;
|
|
@@ -452,14 +412,12 @@ const prepareRoutes = async (ctx) => {
|
|
|
452
412
|
for (const method of HttpMethods) {
|
|
453
413
|
ctx.logTag = `${method.toUpperCase().padEnd(6, " ")} ${path}`;
|
|
454
414
|
const config = pathConfig[method];
|
|
455
|
-
if (!config)
|
|
456
|
-
continue;
|
|
415
|
+
if (!config) continue;
|
|
457
416
|
if (pathConfig.parameters) {
|
|
458
417
|
config.parameters = [...config.parameters ?? [], ...pathConfig.parameters];
|
|
459
418
|
}
|
|
460
419
|
const [ns, op] = getOpName(ctx, { ...config, method, path });
|
|
461
|
-
if (!routes[ns])
|
|
462
|
-
routes[ns] = [];
|
|
420
|
+
if (!routes[ns]) routes[ns] = [];
|
|
463
421
|
const joined = [ns, op].join(".");
|
|
464
422
|
if (ctx.usedNames.has(joined)) {
|
|
465
423
|
continue;
|
|
@@ -499,8 +457,7 @@ const generateAst = async (ctx) => {
|
|
|
499
457
|
return { modules, types };
|
|
500
458
|
};
|
|
501
459
|
const loadSchema = async (url, upgrade = true) => {
|
|
502
|
-
if (url.startsWith("file://"))
|
|
503
|
-
url = url.substring(7);
|
|
460
|
+
if (url.startsWith("file://")) url = url.substring(7);
|
|
504
461
|
const { bundle } = await redocly.bundle({
|
|
505
462
|
ref: url,
|
|
506
463
|
config: await redocly.createConfig({}),
|
|
@@ -9,6 +9,9 @@ import { convertObj } from 'swagger2openapi';
|
|
|
9
9
|
import ts from 'typescript';
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
|
|
12
|
+
var name = "apigen-ts";
|
|
13
|
+
var version = "0.2.0";
|
|
14
|
+
|
|
12
15
|
const initCtx = (config) => {
|
|
13
16
|
return {
|
|
14
17
|
source: "",
|
|
@@ -23,8 +26,8 @@ const initCtx = (config) => {
|
|
|
23
26
|
};
|
|
24
27
|
const getCliConfig = () => {
|
|
25
28
|
const argv = cli({
|
|
26
|
-
name
|
|
27
|
-
version
|
|
29
|
+
name,
|
|
30
|
+
version,
|
|
28
31
|
parameters: ["<source>", "[output]"],
|
|
29
32
|
flags: {
|
|
30
33
|
name: {
|
|
@@ -49,8 +52,7 @@ const getCliConfig = () => {
|
|
|
49
52
|
};
|
|
50
53
|
|
|
51
54
|
const unref = (ctx, s) => {
|
|
52
|
-
if (!s)
|
|
53
|
-
return void 0;
|
|
55
|
+
if (!s) return void 0;
|
|
54
56
|
if ("$ref" in s && s.$ref) {
|
|
55
57
|
const parts = s.$ref.replace("#/", "").split("/");
|
|
56
58
|
const obj = parts.reduce(
|
|
@@ -58,8 +60,7 @@ const unref = (ctx, s) => {
|
|
|
58
60
|
(acc, x) => get(acc, x, get(acc, decodeURIComponent(x).replaceAll("~1", "/"))),
|
|
59
61
|
ctx.doc
|
|
60
62
|
);
|
|
61
|
-
if (obj)
|
|
62
|
-
return obj;
|
|
63
|
+
if (obj) return obj;
|
|
63
64
|
console.warn(`${ctx.logTag} ref ${s.$ref} not found`);
|
|
64
65
|
return void 0;
|
|
65
66
|
}
|
|
@@ -67,11 +68,9 @@ const unref = (ctx, s) => {
|
|
|
67
68
|
};
|
|
68
69
|
const getReqSchema = (ctx, config) => {
|
|
69
70
|
const req = unref(ctx, config.requestBody);
|
|
70
|
-
if (!req)
|
|
71
|
-
return void 0;
|
|
71
|
+
if (!req) return void 0;
|
|
72
72
|
const cts = Object.entries(req.content ?? {}).map((x) => [x[0].split(";")[0], x[1].schema]).filter((x) => x[1]);
|
|
73
|
-
if (cts.length === 0)
|
|
74
|
-
return void 0;
|
|
73
|
+
if (cts.length === 0) return void 0;
|
|
75
74
|
const pretenders = [
|
|
76
75
|
"application/json",
|
|
77
76
|
"text/",
|
|
@@ -80,8 +79,7 @@ const getReqSchema = (ctx, config) => {
|
|
|
80
79
|
];
|
|
81
80
|
for (const p of pretenders) {
|
|
82
81
|
const ct = cts.find((x) => x[0].startsWith(p));
|
|
83
|
-
if (ct)
|
|
84
|
-
return ct;
|
|
82
|
+
if (ct) return ct;
|
|
85
83
|
}
|
|
86
84
|
cts.map((x) => x[0]);
|
|
87
85
|
return void 0;
|
|
@@ -89,14 +87,11 @@ const getReqSchema = (ctx, config) => {
|
|
|
89
87
|
const getRepSchema = (ctx, config) => {
|
|
90
88
|
const successCodes = Object.keys(config.responses ?? {}).filter((x) => x.startsWith("2")).filter((x) => get(config, ["responses", x, "content"]));
|
|
91
89
|
const cts = Object.entries(get(config, ["responses", successCodes[0], "content"], {})).filter((x) => x[1].schema);
|
|
92
|
-
if (cts.length === 0)
|
|
93
|
-
return void 0;
|
|
90
|
+
if (cts.length === 0) return void 0;
|
|
94
91
|
const ctJson = cts.find((x) => x[0].startsWith("application/json"));
|
|
95
|
-
if (ctJson)
|
|
96
|
-
return ctJson[1].schema;
|
|
92
|
+
if (ctJson) return ctJson[1].schema;
|
|
97
93
|
const ctText = cts.find((x) => x[0].startsWith("text/"));
|
|
98
|
-
if (ctText)
|
|
99
|
-
return { type: "string" };
|
|
94
|
+
if (ctText) return { type: "string" };
|
|
100
95
|
cts.map((x) => x[0]).join(", ");
|
|
101
96
|
return void 0;
|
|
102
97
|
};
|
|
@@ -169,25 +164,18 @@ const Keywords = /* @__PURE__ */ new Set([
|
|
|
169
164
|
]);
|
|
170
165
|
const normalizeIdentifier = (val, asVar = false) => {
|
|
171
166
|
let name = val.replace("#/components/schemas/", "").replaceAll("'", "").replace(/[^a-zA-Z0-9]/g, "_");
|
|
172
|
-
if (name.match(/^\d/))
|
|
173
|
-
|
|
174
|
-
if (asVar && Keywords.has(name))
|
|
175
|
-
name = `$${name}`;
|
|
167
|
+
if (name.match(/^\d/)) name = `$${name}`;
|
|
168
|
+
if (asVar && Keywords.has(name)) name = `$${name}`;
|
|
176
169
|
return name;
|
|
177
170
|
};
|
|
178
171
|
const makeInlineEnum = (s) => {
|
|
179
|
-
if (!s.enum)
|
|
180
|
-
return void 0;
|
|
172
|
+
if (!s.enum) return void 0;
|
|
181
173
|
const values = filterEmpty(s.enum);
|
|
182
|
-
if (!values.length)
|
|
183
|
-
return void 0;
|
|
174
|
+
if (!values.length) return void 0;
|
|
184
175
|
if (!s.type) {
|
|
185
|
-
if (values.every((x) => typeof x === "string"))
|
|
186
|
-
|
|
187
|
-
if (values.every((x) => typeof x === "
|
|
188
|
-
s.type = "number";
|
|
189
|
-
if (values.every((x) => typeof x === "boolean"))
|
|
190
|
-
s.type = "boolean";
|
|
176
|
+
if (values.every((x) => typeof x === "string")) s.type = "string";
|
|
177
|
+
if (values.every((x) => typeof x === "number")) s.type = "number";
|
|
178
|
+
if (values.every((x) => typeof x === "boolean")) s.type = "boolean";
|
|
191
179
|
}
|
|
192
180
|
if (s.type === "string") {
|
|
193
181
|
const tokens = uniq(values).map((x) => f$2.createStringLiteral(x.toString()));
|
|
@@ -199,10 +187,8 @@ const makeInlineEnum = (s) => {
|
|
|
199
187
|
}
|
|
200
188
|
if (s.type === "boolean") {
|
|
201
189
|
const tokens = [];
|
|
202
|
-
if (values.includes(true))
|
|
203
|
-
|
|
204
|
-
if (values.includes(false))
|
|
205
|
-
tokens.push(f$2.createToken(ts.SyntaxKind.FalseKeyword));
|
|
190
|
+
if (values.includes(true)) tokens.push(f$2.createToken(ts.SyntaxKind.TrueKeyword));
|
|
191
|
+
if (values.includes(false)) tokens.push(f$2.createToken(ts.SyntaxKind.FalseKeyword));
|
|
206
192
|
return f$2.createUnionTypeNode(tokens.map((x) => f$2.createLiteralTypeNode(x)));
|
|
207
193
|
}
|
|
208
194
|
console.warn(`enum with unknown type "${s.type}" in`, s);
|
|
@@ -210,33 +196,25 @@ const makeInlineEnum = (s) => {
|
|
|
210
196
|
};
|
|
211
197
|
const makeType = (ctx, s) => {
|
|
212
198
|
const mk = makeType.bind(null, ctx);
|
|
213
|
-
if (s === void 0)
|
|
214
|
-
|
|
215
|
-
if (s === null)
|
|
216
|
-
return f$2.createLiteralTypeNode(f$2.createNull());
|
|
199
|
+
if (s === void 0) return f$2.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
|
|
200
|
+
if (s === null) return f$2.createLiteralTypeNode(f$2.createNull());
|
|
217
201
|
if ("$ref" in s && s.$ref) {
|
|
218
202
|
const parts = s.$ref.replace("#/", "").split("/");
|
|
219
203
|
if (parts.length === 3 && parts[0] === "components" && parts[1] === "schemas") {
|
|
220
204
|
return f$2.createTypeReferenceNode(normalizeIdentifier(parts[2], true));
|
|
221
205
|
}
|
|
222
206
|
const t = unref(ctx, s);
|
|
223
|
-
if (!t)
|
|
224
|
-
throw new Error(`makeTypeRef: ref not found ${JSON.stringify(s)}`);
|
|
207
|
+
if (!t) throw new Error(`makeTypeRef: ref not found ${JSON.stringify(s)}`);
|
|
225
208
|
return makeType(ctx, t);
|
|
226
209
|
}
|
|
227
|
-
if ("oneOf" in s && s.oneOf)
|
|
228
|
-
|
|
229
|
-
if ("
|
|
230
|
-
|
|
231
|
-
if ("allOf" in s && s.allOf)
|
|
232
|
-
return f$2.createIntersectionTypeNode(s.allOf.map(mk));
|
|
233
|
-
if ("type" in s && s.type === "integer")
|
|
234
|
-
s.type = "number";
|
|
210
|
+
if ("oneOf" in s && s.oneOf) return f$2.createUnionTypeNode(s.oneOf.map(mk));
|
|
211
|
+
if ("anyOf" in s && s.anyOf) return f$2.createUnionTypeNode(s.anyOf.map(mk));
|
|
212
|
+
if ("allOf" in s && s.allOf) return f$2.createIntersectionTypeNode(s.allOf.map(mk));
|
|
213
|
+
if ("type" in s && s.type === "integer") s.type = "number";
|
|
235
214
|
if ("enum" in s && s.enum && !Array.isArray(s.type)) {
|
|
236
215
|
const isArray2 = s.type === "array";
|
|
237
216
|
const t = makeInlineEnum(isArray2 ? { ...s, type: s.items?.type } : s);
|
|
238
|
-
if (t)
|
|
239
|
-
return isArray2 ? f$2.createArrayTypeNode(t) : t;
|
|
217
|
+
if (t) return isArray2 ? f$2.createArrayTypeNode(t) : t;
|
|
240
218
|
}
|
|
241
219
|
if ("properties" in s && s.properties) {
|
|
242
220
|
return f$2.createTypeLiteralNode(
|
|
@@ -251,37 +229,26 @@ const makeType = (ctx, s) => {
|
|
|
251
229
|
if (Array.isArray(s.type)) {
|
|
252
230
|
const types = [];
|
|
253
231
|
for (const type of s.type) {
|
|
254
|
-
if (type === "null")
|
|
255
|
-
|
|
256
|
-
else
|
|
257
|
-
types.push({ ...s, type });
|
|
232
|
+
if (type === "null") types.push({ type: "null" });
|
|
233
|
+
else types.push({ ...s, type });
|
|
258
234
|
}
|
|
259
235
|
return mk({ oneOf: types });
|
|
260
236
|
}
|
|
261
237
|
let t;
|
|
262
|
-
if (s.type === "object")
|
|
263
|
-
|
|
264
|
-
else if (s.type === "
|
|
265
|
-
|
|
266
|
-
else if (s.type === "
|
|
267
|
-
|
|
268
|
-
else if (s.type
|
|
269
|
-
t = f$2.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
|
|
270
|
-
else if (s.type === "array")
|
|
271
|
-
t = f$2.createArrayTypeNode(mk(s.items));
|
|
272
|
-
else if (s.type === "null")
|
|
273
|
-
t = f$2.createLiteralTypeNode(f$2.createNull());
|
|
274
|
-
else if (isArray(s.type))
|
|
275
|
-
t = f$2.createUnionTypeNode(s.type.map((x) => mk({ type: x })));
|
|
238
|
+
if (s.type === "object") t = f$2.createKeywordTypeNode(ts.SyntaxKind.ObjectKeyword);
|
|
239
|
+
else if (s.type === "boolean") t = f$2.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
|
|
240
|
+
else if (s.type === "number") t = f$2.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
|
|
241
|
+
else if (s.type === "string") t = f$2.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
|
|
242
|
+
else if (s.type === "array") t = f$2.createArrayTypeNode(mk(s.items));
|
|
243
|
+
else if (s.type === "null") t = f$2.createLiteralTypeNode(f$2.createNull());
|
|
244
|
+
else if (isArray(s.type)) t = f$2.createUnionTypeNode(s.type.map((x) => mk({ type: x })));
|
|
276
245
|
else {
|
|
277
246
|
console.warn(`makeType: unknown type "${s.type}"`);
|
|
278
247
|
return f$2.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
279
248
|
}
|
|
280
249
|
if (s.type === "string") {
|
|
281
|
-
if (s.format === "binary")
|
|
282
|
-
|
|
283
|
-
if (s.format === "date-time" && ctx.parseDates)
|
|
284
|
-
t = f$2.createTypeReferenceNode("Date");
|
|
250
|
+
if (s.format === "binary") t = f$2.createTypeReferenceNode("File");
|
|
251
|
+
if (s.format === "date-time" && ctx.parseDates) t = f$2.createTypeReferenceNode("Date");
|
|
285
252
|
}
|
|
286
253
|
return s.nullable ? f$2.createUnionTypeNode([t, f$2.createLiteralTypeNode(f$2.createNull())]) : t;
|
|
287
254
|
}
|
|
@@ -333,15 +300,13 @@ const getOpName = (ctx, op) => {
|
|
|
333
300
|
}
|
|
334
301
|
fn = normalizeOpName(fn);
|
|
335
302
|
let nsr = ns.split("").map((x) => `[${x.toUpperCase()}${x.toLowerCase()}]`).join("");
|
|
336
|
-
if (nsr.endsWith("[Ss]"))
|
|
337
|
-
nsr += "?";
|
|
303
|
+
if (nsr.endsWith("[Ss]")) nsr += "?";
|
|
338
304
|
fn = fn.replace(new RegExp(`^${nsr}([Cc]ontroller|[Ss]ervice)?([A-Z].*)$`), "$2");
|
|
339
305
|
fn = lowerFirst(fn);
|
|
340
306
|
const proposal = [ns, fn];
|
|
341
307
|
if (ctx.resolveName) {
|
|
342
308
|
const res = ctx.resolveName(ctx, op, proposal);
|
|
343
|
-
if (Array.isArray(res) && res.length === 2)
|
|
344
|
-
return res;
|
|
309
|
+
if (Array.isArray(res) && res.length === 2) return res;
|
|
345
310
|
if (res !== void 0) {
|
|
346
311
|
console.warn(`${ctx.logTag} resolveName should return [ns, fn] or undefined (skipping)`);
|
|
347
312
|
}
|
|
@@ -349,11 +314,9 @@ const getOpName = (ctx, op) => {
|
|
|
349
314
|
return proposal;
|
|
350
315
|
};
|
|
351
316
|
const prepareUrl = (url, rename) => {
|
|
352
|
-
for (const [k, v] of Object.entries(rename))
|
|
353
|
-
url = url.replaceAll(`{${k}}`, "${" + v + "}");
|
|
317
|
+
for (const [k, v] of Object.entries(rename)) url = url.replaceAll(`{${k}}`, "${" + v + "}");
|
|
354
318
|
const parts = url.split("${");
|
|
355
|
-
if (parts.length === 1)
|
|
356
|
-
return f$1.createStringLiteral(url);
|
|
319
|
+
if (parts.length === 1) return f$1.createStringLiteral(url);
|
|
357
320
|
return f$1.createTemplateExpression(
|
|
358
321
|
f$1.createTemplateHead(parts[0]),
|
|
359
322
|
parts.slice(1).map((x, i) => {
|
|
@@ -373,13 +336,11 @@ const prepareOp = (ctx, cfg, opName) => {
|
|
|
373
336
|
const repSchema = getRepSchema(ctx, cfg);
|
|
374
337
|
const allParams = filterNullable(cfg.parameters.map((x) => unref(ctx, x)));
|
|
375
338
|
const params = uniqBy(allParams.filter((x) => x.in === "path"), "name");
|
|
376
|
-
if (reqSchema)
|
|
377
|
-
params.push({ name: "body", schema: reqSchema[1] });
|
|
339
|
+
if (reqSchema) params.push({ name: "body", schema: reqSchema[1] });
|
|
378
340
|
const search = allParams.filter((x) => x.in === "query");
|
|
379
341
|
allParams.filter((x) => x.in === "header");
|
|
380
342
|
for (const [name, v] of Object.entries({ search })) {
|
|
381
|
-
if (!v.length)
|
|
382
|
-
continue;
|
|
343
|
+
if (!v.length) continue;
|
|
383
344
|
const properties = v.reduce((acc, x) => ({ ...acc, [x.name]: x.schema }), {});
|
|
384
345
|
params.push({ name, schema: { type: "object", properties } });
|
|
385
346
|
}
|
|
@@ -443,8 +404,7 @@ const prepareRoutes = async (ctx) => {
|
|
|
443
404
|
const routes = {};
|
|
444
405
|
for (const [path, pathConfig] of Object.entries(ctx.doc.paths ?? {})) {
|
|
445
406
|
ctx.logTag = `${"[ALL]".toUpperCase().padEnd(6, " ")} ${path}`;
|
|
446
|
-
if (!isObject(pathConfig))
|
|
447
|
-
continue;
|
|
407
|
+
if (!isObject(pathConfig)) continue;
|
|
448
408
|
if ("$ref" in pathConfig) {
|
|
449
409
|
console.warn(`${ctx.logTag} $ref should be resolved before (skipping)`);
|
|
450
410
|
continue;
|
|
@@ -452,14 +412,12 @@ const prepareRoutes = async (ctx) => {
|
|
|
452
412
|
for (const method of HttpMethods) {
|
|
453
413
|
ctx.logTag = `${method.toUpperCase().padEnd(6, " ")} ${path}`;
|
|
454
414
|
const config = pathConfig[method];
|
|
455
|
-
if (!config)
|
|
456
|
-
continue;
|
|
415
|
+
if (!config) continue;
|
|
457
416
|
if (pathConfig.parameters) {
|
|
458
417
|
config.parameters = [...config.parameters ?? [], ...pathConfig.parameters];
|
|
459
418
|
}
|
|
460
419
|
const [ns, op] = getOpName(ctx, { ...config, method, path });
|
|
461
|
-
if (!routes[ns])
|
|
462
|
-
routes[ns] = [];
|
|
420
|
+
if (!routes[ns]) routes[ns] = [];
|
|
463
421
|
const joined = [ns, op].join(".");
|
|
464
422
|
if (ctx.usedNames.has(joined)) {
|
|
465
423
|
continue;
|
|
@@ -499,8 +457,7 @@ const generateAst = async (ctx) => {
|
|
|
499
457
|
return { modules, types };
|
|
500
458
|
};
|
|
501
459
|
const loadSchema = async (url, upgrade = true) => {
|
|
502
|
-
if (url.startsWith("file://"))
|
|
503
|
-
url = url.substring(7);
|
|
460
|
+
if (url.startsWith("file://")) url = url.substring(7);
|
|
504
461
|
const { bundle } = await redocly.bundle({
|
|
505
462
|
ref: url,
|
|
506
463
|
config: await redocly.createConfig({}),
|
package/dist/main.cjs
CHANGED
package/dist/main.js
CHANGED
package/dist/main.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "apigen-ts",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"author": "
|
|
6
|
+
"author": "vladkens <v.pronsky@gmail.com>",
|
|
7
7
|
"repository": "vladkens/apigen-ts",
|
|
8
8
|
"description": "OpenAPI TypeScript client generator",
|
|
9
9
|
"keywords": [
|
|
@@ -22,7 +22,7 @@
|
|
|
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.22.1",
|
|
26
26
|
"@types/lodash-es": "^4.17.12",
|
|
27
27
|
"@types/swagger2openapi": "^7.0.4",
|
|
28
28
|
"array-utils-ts": "^0.1.2",
|
|
@@ -31,14 +31,14 @@
|
|
|
31
31
|
"swagger2openapi": "^7.0.8"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@types/node": "^
|
|
35
|
-
"c8": "^
|
|
36
|
-
"fetch-mock": "^
|
|
37
|
-
"pkgroll": "^2.
|
|
38
|
-
"prettier": "^3.
|
|
39
|
-
"prettier-plugin-organize-imports": "^
|
|
34
|
+
"@types/node": "^22.5.2",
|
|
35
|
+
"c8": "^10.1.2",
|
|
36
|
+
"fetch-mock": "^11.1.3",
|
|
37
|
+
"pkgroll": "^2.4.2",
|
|
38
|
+
"prettier": "^3.3.3",
|
|
39
|
+
"prettier-plugin-organize-imports": "^4.0.0",
|
|
40
40
|
"tsm": "^2.3.0",
|
|
41
|
-
"typescript": "^5.
|
|
41
|
+
"typescript": "^5.5.4",
|
|
42
42
|
"uvu": "^0.5.6"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
package/readme.md
CHANGED
|
@@ -115,13 +115,34 @@ class MyClient extends ApiClient {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
try {
|
|
118
|
-
const api = MyClient()
|
|
118
|
+
const api = new MyClient()
|
|
119
119
|
const pet = await api.pet.getPetById(404)
|
|
120
120
|
} catch (e) {
|
|
121
121
|
console.log(e) // e is { code: "API_ERROR" }
|
|
122
122
|
}
|
|
123
123
|
```
|
|
124
124
|
|
|
125
|
+
### Base url resolving
|
|
126
|
+
|
|
127
|
+
You can modify how the endpoint url is created. By default [URL constructor](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) used to resolve endpoint url like: `new URL(path, baseUrl)` which has specific resolving [rules](https://developer.mozilla.org/en-US/docs/Web/API/URL_API/Resolving_relative_references). E.g.:
|
|
128
|
+
|
|
129
|
+
- `new URL("/v2/cats", "https://example.com/v1/") // -> https://example.com/v2/cats`
|
|
130
|
+
- `new URL("v2/cats", "https://example.com/v1/") // -> https://example.com/v1/v2/cats`
|
|
131
|
+
|
|
132
|
+
If you want to have custom endpoint url resolving rules, you can override `PrepareFetchUrl` method. For more details see [issue](https://github.com/vladkens/apigen-ts/issues/2).
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
class MyClient extends ApiClient {
|
|
136
|
+
PrepareFetchUrl(path: string) {
|
|
137
|
+
return new URL(`${this.Config.baseUrl}/${path}`.replace(/\/{2,}/g, "/"))
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const api = new MyClient({ baseUrl: "https://example.com/v1" })
|
|
142
|
+
// will call: https://example.com/v1/pet/ instead of https://example.com/pet/
|
|
143
|
+
const pet = await api.pet.getPetById(404)
|
|
144
|
+
```
|
|
145
|
+
|
|
125
146
|
### Node.js API
|
|
126
147
|
|
|
127
148
|
Create file like `apigen.mjs` with content:
|