apigen-ts 0.1.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/_template.ts CHANGED
@@ -1,8 +1,11 @@
1
1
  // Note: Use uppercase for names in ApiClient to avoid conflict with the generated code
2
2
 
3
+ type Headers = Record<string, string>
4
+ export type ApigenHeaders = Headers | ((method: string, path: string) => Headers | Promise<Headers>)
5
+
3
6
  export interface ApigenConfig {
4
7
  baseUrl: string
5
- headers: Record<string, string>
8
+ headers: ApigenHeaders
6
9
  }
7
10
 
8
11
  export interface ApigenRequest extends Omit<RequestInit, "body"> {
@@ -40,7 +43,7 @@ export class ApiClient {
40
43
  }
41
44
  }
42
45
 
43
- async Fetch<T>(method: string, path: string, opts: ApigenRequest = {}): Promise<T> {
46
+ PrepareFetchUrl(path: string): URL {
44
47
  let base = this.Config.baseUrl
45
48
  if ("location" in globalThis && (base === "" || base.startsWith("/"))) {
46
49
  // make ts happy in pure nodejs environment, should never pass here
@@ -48,12 +51,22 @@ export class ApiClient {
48
51
  base = `${location.origin}${base.endsWith("/") ? base : `/${base}`}`
49
52
  }
50
53
 
51
- const url = new URL(path, base)
54
+ return new URL(path, base)
55
+ }
56
+
57
+ async Fetch<T>(method: string, path: string, opts: ApigenRequest = {}): Promise<T> {
58
+ const url = this.PrepareFetchUrl(path)
59
+
52
60
  for (const [k, v] of Object.entries(opts?.search ?? {})) {
53
61
  url.searchParams.append(k, Array.isArray(v) ? v.join(",") : (v as string))
54
62
  }
55
63
 
56
- const headers = new Headers({ ...this.Config.headers, ...opts.headers })
64
+ const configHeaders =
65
+ typeof this.Config.headers === "function"
66
+ ? await this.Config.headers(method, path)
67
+ : this.Config.headers
68
+
69
+ const headers = new Headers({ ...configHeaders, ...opts.headers })
57
70
  const ct = headers.get("content-type") ?? "application/json"
58
71
 
59
72
  let body: FormData | URLSearchParams | string | undefined = undefined
package/dist/cli.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var main$1 = require('./main-c2426ec2.cjs');
3
+ var main$1 = require('./main-oWgfChRG.cjs');
4
4
  require('fs/promises');
5
5
  require('path');
6
6
  require('url');
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as apigen, g as getCliConfig } from './main-bbe33f1b.js';
2
+ import { a as apigen, g as getCliConfig } from './main-BTcttgNf.js';
3
3
  import 'fs/promises';
4
4
  import 'path';
5
5
  import 'url';
package/dist/cli.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as apigen, g as getCliConfig } from './main-bbe33f1b.mjs';
1
+ import { a as apigen, g as getCliConfig } from './main-BTcttgNf.mjs';
2
2
  import 'fs/promises';
3
3
  import 'path';
4
4
  import 'url';
@@ -4,11 +4,14 @@ import { fileURLToPath } from 'url';
4
4
  import { cli } from 'cleye';
5
5
  import redocly from '@redocly/openapi-core';
6
6
  import { filterEmpty, filterNullable } from 'array-utils-ts';
7
- import { get, uniq, upperFirst, isArray, isObject, sortBy, lowerFirst, uniqBy } from 'lodash-es';
7
+ import { get, uniq, upperFirst, isArray, isBoolean, isObject, sortBy, 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
+ var name = "apigen-ts";
13
+ var version = "1.0.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: "apigen",
27
- version: "0.1.1",
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
- name = `$${name}`;
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
- s.type = "string";
187
- if (values.every((x) => typeof x === "number"))
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,44 +187,44 @@ const makeInlineEnum = (s) => {
199
187
  }
200
188
  if (s.type === "boolean") {
201
189
  const tokens = [];
202
- if (values.includes(true))
203
- tokens.push(f$2.createToken(ts.SyntaxKind.TrueKeyword));
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);
209
195
  return void 0;
210
196
  };
197
+ const makeObject = (ctx, s) => {
198
+ if (s.type !== "object") throw new Error(`makeObject: not an object ${JSON.stringify(s)}`);
199
+ if (s.additionalProperties && !isBoolean(s.additionalProperties)) {
200
+ return f$2.createTypeReferenceNode("Record", [
201
+ f$2.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
202
+ makeType(ctx, s.additionalProperties)
203
+ ]);
204
+ }
205
+ return f$2.createKeywordTypeNode(ts.SyntaxKind.ObjectKeyword);
206
+ };
211
207
  const makeType = (ctx, s) => {
212
208
  const mk = makeType.bind(null, ctx);
213
- if (s === void 0)
214
- return f$2.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
215
- if (s === null)
216
- return f$2.createLiteralTypeNode(f$2.createNull());
209
+ if (s === void 0) return f$2.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
210
+ if (s === null) return f$2.createLiteralTypeNode(f$2.createNull());
217
211
  if ("$ref" in s && s.$ref) {
218
212
  const parts = s.$ref.replace("#/", "").split("/");
219
213
  if (parts.length === 3 && parts[0] === "components" && parts[1] === "schemas") {
220
214
  return f$2.createTypeReferenceNode(normalizeIdentifier(parts[2], true));
221
215
  }
222
216
  const t = unref(ctx, s);
223
- if (!t)
224
- throw new Error(`makeTypeRef: ref not found ${JSON.stringify(s)}`);
217
+ if (!t) throw new Error(`makeTypeRef: ref not found ${JSON.stringify(s)}`);
225
218
  return makeType(ctx, t);
226
219
  }
227
- if ("oneOf" in s && s.oneOf)
228
- return f$2.createUnionTypeNode(s.oneOf.map(mk));
229
- if ("anyOf" in s && s.anyOf)
230
- return f$2.createUnionTypeNode(s.anyOf.map(mk));
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";
220
+ if ("oneOf" in s && s.oneOf) return f$2.createUnionTypeNode(s.oneOf.map(mk));
221
+ if ("anyOf" in s && s.anyOf) return f$2.createUnionTypeNode(s.anyOf.map(mk));
222
+ if ("allOf" in s && s.allOf) return f$2.createIntersectionTypeNode(s.allOf.map(mk));
223
+ if ("type" in s && s.type === "integer") s.type = "number";
235
224
  if ("enum" in s && s.enum && !Array.isArray(s.type)) {
236
225
  const isArray2 = s.type === "array";
237
226
  const t = makeInlineEnum(isArray2 ? { ...s, type: s.items?.type } : s);
238
- if (t)
239
- return isArray2 ? f$2.createArrayTypeNode(t) : t;
227
+ if (t) return isArray2 ? f$2.createArrayTypeNode(t) : t;
240
228
  }
241
229
  if ("properties" in s && s.properties) {
242
230
  return f$2.createTypeLiteralNode(
@@ -251,37 +239,26 @@ const makeType = (ctx, s) => {
251
239
  if (Array.isArray(s.type)) {
252
240
  const types = [];
253
241
  for (const type of s.type) {
254
- if (type === "null")
255
- types.push({ type: "null" });
256
- else
257
- types.push({ ...s, type });
242
+ if (type === "null") types.push({ type: "null" });
243
+ else types.push({ ...s, type });
258
244
  }
259
245
  return mk({ oneOf: types });
260
246
  }
261
247
  let t;
262
- if (s.type === "object")
263
- t = f$2.createKeywordTypeNode(ts.SyntaxKind.ObjectKeyword);
264
- else if (s.type === "boolean")
265
- t = f$2.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
266
- else if (s.type === "number")
267
- t = f$2.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
268
- else if (s.type === "string")
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 })));
248
+ if (s.type === "object") t = makeObject(ctx, s);
249
+ else if (s.type === "boolean") t = f$2.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
250
+ else if (s.type === "number") t = f$2.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
251
+ else if (s.type === "string") t = f$2.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
252
+ else if (s.type === "array") t = f$2.createArrayTypeNode(mk(s.items));
253
+ else if (s.type === "null") t = f$2.createLiteralTypeNode(f$2.createNull());
254
+ else if (isArray(s.type)) t = f$2.createUnionTypeNode(s.type.map((x) => mk({ type: x })));
276
255
  else {
277
256
  console.warn(`makeType: unknown type "${s.type}"`);
278
257
  return f$2.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
279
258
  }
280
259
  if (s.type === "string") {
281
- if (s.format === "binary")
282
- t = f$2.createTypeReferenceNode("File");
283
- if (s.format === "date-time" && ctx.parseDates)
284
- t = f$2.createTypeReferenceNode("Date");
260
+ if (s.format === "binary") t = f$2.createTypeReferenceNode("File");
261
+ if (s.format === "date-time" && ctx.parseDates) t = f$2.createTypeReferenceNode("Date");
285
262
  }
286
263
  return s.nullable ? f$2.createUnionTypeNode([t, f$2.createLiteralTypeNode(f$2.createNull())]) : t;
287
264
  }
@@ -333,15 +310,13 @@ const getOpName = (ctx, op) => {
333
310
  }
334
311
  fn = normalizeOpName(fn);
335
312
  let nsr = ns.split("").map((x) => `[${x.toUpperCase()}${x.toLowerCase()}]`).join("");
336
- if (nsr.endsWith("[Ss]"))
337
- nsr += "?";
313
+ if (nsr.endsWith("[Ss]")) nsr += "?";
338
314
  fn = fn.replace(new RegExp(`^${nsr}([Cc]ontroller|[Ss]ervice)?([A-Z].*)$`), "$2");
339
315
  fn = lowerFirst(fn);
340
316
  const proposal = [ns, fn];
341
317
  if (ctx.resolveName) {
342
318
  const res = ctx.resolveName(ctx, op, proposal);
343
- if (Array.isArray(res) && res.length === 2)
344
- return res;
319
+ if (Array.isArray(res) && res.length === 2) return res;
345
320
  if (res !== void 0) {
346
321
  console.warn(`${ctx.logTag} resolveName should return [ns, fn] or undefined (skipping)`);
347
322
  }
@@ -349,11 +324,9 @@ const getOpName = (ctx, op) => {
349
324
  return proposal;
350
325
  };
351
326
  const prepareUrl = (url, rename) => {
352
- for (const [k, v] of Object.entries(rename))
353
- url = url.replaceAll(`{${k}}`, "${" + v + "}");
327
+ for (const [k, v] of Object.entries(rename)) url = url.replaceAll(`{${k}}`, "${" + v + "}");
354
328
  const parts = url.split("${");
355
- if (parts.length === 1)
356
- return f$1.createStringLiteral(url);
329
+ if (parts.length === 1) return f$1.createStringLiteral(url);
357
330
  return f$1.createTemplateExpression(
358
331
  f$1.createTemplateHead(parts[0]),
359
332
  parts.slice(1).map((x, i) => {
@@ -373,13 +346,11 @@ const prepareOp = (ctx, cfg, opName) => {
373
346
  const repSchema = getRepSchema(ctx, cfg);
374
347
  const allParams = filterNullable(cfg.parameters.map((x) => unref(ctx, x)));
375
348
  const params = uniqBy(allParams.filter((x) => x.in === "path"), "name");
376
- if (reqSchema)
377
- params.push({ name: "body", schema: reqSchema[1] });
349
+ if (reqSchema) params.push({ name: "body", schema: reqSchema[1] });
378
350
  const search = allParams.filter((x) => x.in === "query");
379
351
  allParams.filter((x) => x.in === "header");
380
352
  for (const [name, v] of Object.entries({ search })) {
381
- if (!v.length)
382
- continue;
353
+ if (!v.length) continue;
383
354
  const properties = v.reduce((acc, x) => ({ ...acc, [x.name]: x.schema }), {});
384
355
  params.push({ name, schema: { type: "object", properties } });
385
356
  }
@@ -443,8 +414,7 @@ const prepareRoutes = async (ctx) => {
443
414
  const routes = {};
444
415
  for (const [path, pathConfig] of Object.entries(ctx.doc.paths ?? {})) {
445
416
  ctx.logTag = `${"[ALL]".toUpperCase().padEnd(6, " ")} ${path}`;
446
- if (!isObject(pathConfig))
447
- continue;
417
+ if (!isObject(pathConfig)) continue;
448
418
  if ("$ref" in pathConfig) {
449
419
  console.warn(`${ctx.logTag} $ref should be resolved before (skipping)`);
450
420
  continue;
@@ -452,14 +422,12 @@ const prepareRoutes = async (ctx) => {
452
422
  for (const method of HttpMethods) {
453
423
  ctx.logTag = `${method.toUpperCase().padEnd(6, " ")} ${path}`;
454
424
  const config = pathConfig[method];
455
- if (!config)
456
- continue;
425
+ if (!config) continue;
457
426
  if (pathConfig.parameters) {
458
427
  config.parameters = [...config.parameters ?? [], ...pathConfig.parameters];
459
428
  }
460
429
  const [ns, op] = getOpName(ctx, { ...config, method, path });
461
- if (!routes[ns])
462
- routes[ns] = [];
430
+ if (!routes[ns]) routes[ns] = [];
463
431
  const joined = [ns, op].join(".");
464
432
  if (ctx.usedNames.has(joined)) {
465
433
  continue;
@@ -499,8 +467,7 @@ const generateAst = async (ctx) => {
499
467
  return { modules, types };
500
468
  };
501
469
  const loadSchema = async (url, upgrade = true) => {
502
- if (url.startsWith("file://"))
503
- url = url.substring(7);
470
+ if (url.startsWith("file://")) url = url.substring(7);
504
471
  const { bundle } = await redocly.bundle({
505
472
  ref: url,
506
473
  config: await redocly.createConfig({}),
@@ -4,11 +4,14 @@ import { fileURLToPath } from 'url';
4
4
  import { cli } from 'cleye';
5
5
  import redocly from '@redocly/openapi-core';
6
6
  import { filterEmpty, filterNullable } from 'array-utils-ts';
7
- import { get, uniq, upperFirst, isArray, isObject, sortBy, lowerFirst, uniqBy } from 'lodash-es';
7
+ import { get, uniq, upperFirst, isArray, isBoolean, isObject, sortBy, 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
+ var name = "apigen-ts";
13
+ var version = "1.0.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: "apigen",
27
- version: "0.1.1",
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
- name = `$${name}`;
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
- s.type = "string";
187
- if (values.every((x) => typeof x === "number"))
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,44 +187,44 @@ const makeInlineEnum = (s) => {
199
187
  }
200
188
  if (s.type === "boolean") {
201
189
  const tokens = [];
202
- if (values.includes(true))
203
- tokens.push(f$2.createToken(ts.SyntaxKind.TrueKeyword));
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);
209
195
  return void 0;
210
196
  };
197
+ const makeObject = (ctx, s) => {
198
+ if (s.type !== "object") throw new Error(`makeObject: not an object ${JSON.stringify(s)}`);
199
+ if (s.additionalProperties && !isBoolean(s.additionalProperties)) {
200
+ return f$2.createTypeReferenceNode("Record", [
201
+ f$2.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
202
+ makeType(ctx, s.additionalProperties)
203
+ ]);
204
+ }
205
+ return f$2.createKeywordTypeNode(ts.SyntaxKind.ObjectKeyword);
206
+ };
211
207
  const makeType = (ctx, s) => {
212
208
  const mk = makeType.bind(null, ctx);
213
- if (s === void 0)
214
- return f$2.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
215
- if (s === null)
216
- return f$2.createLiteralTypeNode(f$2.createNull());
209
+ if (s === void 0) return f$2.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
210
+ if (s === null) return f$2.createLiteralTypeNode(f$2.createNull());
217
211
  if ("$ref" in s && s.$ref) {
218
212
  const parts = s.$ref.replace("#/", "").split("/");
219
213
  if (parts.length === 3 && parts[0] === "components" && parts[1] === "schemas") {
220
214
  return f$2.createTypeReferenceNode(normalizeIdentifier(parts[2], true));
221
215
  }
222
216
  const t = unref(ctx, s);
223
- if (!t)
224
- throw new Error(`makeTypeRef: ref not found ${JSON.stringify(s)}`);
217
+ if (!t) throw new Error(`makeTypeRef: ref not found ${JSON.stringify(s)}`);
225
218
  return makeType(ctx, t);
226
219
  }
227
- if ("oneOf" in s && s.oneOf)
228
- return f$2.createUnionTypeNode(s.oneOf.map(mk));
229
- if ("anyOf" in s && s.anyOf)
230
- return f$2.createUnionTypeNode(s.anyOf.map(mk));
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";
220
+ if ("oneOf" in s && s.oneOf) return f$2.createUnionTypeNode(s.oneOf.map(mk));
221
+ if ("anyOf" in s && s.anyOf) return f$2.createUnionTypeNode(s.anyOf.map(mk));
222
+ if ("allOf" in s && s.allOf) return f$2.createIntersectionTypeNode(s.allOf.map(mk));
223
+ if ("type" in s && s.type === "integer") s.type = "number";
235
224
  if ("enum" in s && s.enum && !Array.isArray(s.type)) {
236
225
  const isArray2 = s.type === "array";
237
226
  const t = makeInlineEnum(isArray2 ? { ...s, type: s.items?.type } : s);
238
- if (t)
239
- return isArray2 ? f$2.createArrayTypeNode(t) : t;
227
+ if (t) return isArray2 ? f$2.createArrayTypeNode(t) : t;
240
228
  }
241
229
  if ("properties" in s && s.properties) {
242
230
  return f$2.createTypeLiteralNode(
@@ -251,37 +239,26 @@ const makeType = (ctx, s) => {
251
239
  if (Array.isArray(s.type)) {
252
240
  const types = [];
253
241
  for (const type of s.type) {
254
- if (type === "null")
255
- types.push({ type: "null" });
256
- else
257
- types.push({ ...s, type });
242
+ if (type === "null") types.push({ type: "null" });
243
+ else types.push({ ...s, type });
258
244
  }
259
245
  return mk({ oneOf: types });
260
246
  }
261
247
  let t;
262
- if (s.type === "object")
263
- t = f$2.createKeywordTypeNode(ts.SyntaxKind.ObjectKeyword);
264
- else if (s.type === "boolean")
265
- t = f$2.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
266
- else if (s.type === "number")
267
- t = f$2.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
268
- else if (s.type === "string")
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 })));
248
+ if (s.type === "object") t = makeObject(ctx, s);
249
+ else if (s.type === "boolean") t = f$2.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
250
+ else if (s.type === "number") t = f$2.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
251
+ else if (s.type === "string") t = f$2.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
252
+ else if (s.type === "array") t = f$2.createArrayTypeNode(mk(s.items));
253
+ else if (s.type === "null") t = f$2.createLiteralTypeNode(f$2.createNull());
254
+ else if (isArray(s.type)) t = f$2.createUnionTypeNode(s.type.map((x) => mk({ type: x })));
276
255
  else {
277
256
  console.warn(`makeType: unknown type "${s.type}"`);
278
257
  return f$2.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
279
258
  }
280
259
  if (s.type === "string") {
281
- if (s.format === "binary")
282
- t = f$2.createTypeReferenceNode("File");
283
- if (s.format === "date-time" && ctx.parseDates)
284
- t = f$2.createTypeReferenceNode("Date");
260
+ if (s.format === "binary") t = f$2.createTypeReferenceNode("File");
261
+ if (s.format === "date-time" && ctx.parseDates) t = f$2.createTypeReferenceNode("Date");
285
262
  }
286
263
  return s.nullable ? f$2.createUnionTypeNode([t, f$2.createLiteralTypeNode(f$2.createNull())]) : t;
287
264
  }
@@ -333,15 +310,13 @@ const getOpName = (ctx, op) => {
333
310
  }
334
311
  fn = normalizeOpName(fn);
335
312
  let nsr = ns.split("").map((x) => `[${x.toUpperCase()}${x.toLowerCase()}]`).join("");
336
- if (nsr.endsWith("[Ss]"))
337
- nsr += "?";
313
+ if (nsr.endsWith("[Ss]")) nsr += "?";
338
314
  fn = fn.replace(new RegExp(`^${nsr}([Cc]ontroller|[Ss]ervice)?([A-Z].*)$`), "$2");
339
315
  fn = lowerFirst(fn);
340
316
  const proposal = [ns, fn];
341
317
  if (ctx.resolveName) {
342
318
  const res = ctx.resolveName(ctx, op, proposal);
343
- if (Array.isArray(res) && res.length === 2)
344
- return res;
319
+ if (Array.isArray(res) && res.length === 2) return res;
345
320
  if (res !== void 0) {
346
321
  console.warn(`${ctx.logTag} resolveName should return [ns, fn] or undefined (skipping)`);
347
322
  }
@@ -349,11 +324,9 @@ const getOpName = (ctx, op) => {
349
324
  return proposal;
350
325
  };
351
326
  const prepareUrl = (url, rename) => {
352
- for (const [k, v] of Object.entries(rename))
353
- url = url.replaceAll(`{${k}}`, "${" + v + "}");
327
+ for (const [k, v] of Object.entries(rename)) url = url.replaceAll(`{${k}}`, "${" + v + "}");
354
328
  const parts = url.split("${");
355
- if (parts.length === 1)
356
- return f$1.createStringLiteral(url);
329
+ if (parts.length === 1) return f$1.createStringLiteral(url);
357
330
  return f$1.createTemplateExpression(
358
331
  f$1.createTemplateHead(parts[0]),
359
332
  parts.slice(1).map((x, i) => {
@@ -373,13 +346,11 @@ const prepareOp = (ctx, cfg, opName) => {
373
346
  const repSchema = getRepSchema(ctx, cfg);
374
347
  const allParams = filterNullable(cfg.parameters.map((x) => unref(ctx, x)));
375
348
  const params = uniqBy(allParams.filter((x) => x.in === "path"), "name");
376
- if (reqSchema)
377
- params.push({ name: "body", schema: reqSchema[1] });
349
+ if (reqSchema) params.push({ name: "body", schema: reqSchema[1] });
378
350
  const search = allParams.filter((x) => x.in === "query");
379
351
  allParams.filter((x) => x.in === "header");
380
352
  for (const [name, v] of Object.entries({ search })) {
381
- if (!v.length)
382
- continue;
353
+ if (!v.length) continue;
383
354
  const properties = v.reduce((acc, x) => ({ ...acc, [x.name]: x.schema }), {});
384
355
  params.push({ name, schema: { type: "object", properties } });
385
356
  }
@@ -443,8 +414,7 @@ const prepareRoutes = async (ctx) => {
443
414
  const routes = {};
444
415
  for (const [path, pathConfig] of Object.entries(ctx.doc.paths ?? {})) {
445
416
  ctx.logTag = `${"[ALL]".toUpperCase().padEnd(6, " ")} ${path}`;
446
- if (!isObject(pathConfig))
447
- continue;
417
+ if (!isObject(pathConfig)) continue;
448
418
  if ("$ref" in pathConfig) {
449
419
  console.warn(`${ctx.logTag} $ref should be resolved before (skipping)`);
450
420
  continue;
@@ -452,14 +422,12 @@ const prepareRoutes = async (ctx) => {
452
422
  for (const method of HttpMethods) {
453
423
  ctx.logTag = `${method.toUpperCase().padEnd(6, " ")} ${path}`;
454
424
  const config = pathConfig[method];
455
- if (!config)
456
- continue;
425
+ if (!config) continue;
457
426
  if (pathConfig.parameters) {
458
427
  config.parameters = [...config.parameters ?? [], ...pathConfig.parameters];
459
428
  }
460
429
  const [ns, op] = getOpName(ctx, { ...config, method, path });
461
- if (!routes[ns])
462
- routes[ns] = [];
430
+ if (!routes[ns]) routes[ns] = [];
463
431
  const joined = [ns, op].join(".");
464
432
  if (ctx.usedNames.has(joined)) {
465
433
  continue;
@@ -499,8 +467,7 @@ const generateAst = async (ctx) => {
499
467
  return { modules, types };
500
468
  };
501
469
  const loadSchema = async (url, upgrade = true) => {
502
- if (url.startsWith("file://"))
503
- url = url.substring(7);
470
+ if (url.startsWith("file://")) url = url.substring(7);
504
471
  const { bundle } = await redocly.bundle({
505
472
  ref: url,
506
473
  config: await redocly.createConfig({}),
@@ -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 = "1.0.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: "apigen",
30
- version: "0.1.1",
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
- name = `$${name}`;
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
- s.type = "string";
190
- if (values.every((x) => typeof x === "number"))
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,44 +190,44 @@ const makeInlineEnum = (s) => {
202
190
  }
203
191
  if (s.type === "boolean") {
204
192
  const tokens = [];
205
- if (values.includes(true))
206
- tokens.push(f$2.createToken(ts.SyntaxKind.TrueKeyword));
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);
212
198
  return void 0;
213
199
  };
200
+ const makeObject = (ctx, s) => {
201
+ if (s.type !== "object") throw new Error(`makeObject: not an object ${JSON.stringify(s)}`);
202
+ if (s.additionalProperties && !lodashEs.isBoolean(s.additionalProperties)) {
203
+ return f$2.createTypeReferenceNode("Record", [
204
+ f$2.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
205
+ makeType(ctx, s.additionalProperties)
206
+ ]);
207
+ }
208
+ return f$2.createKeywordTypeNode(ts.SyntaxKind.ObjectKeyword);
209
+ };
214
210
  const makeType = (ctx, s) => {
215
211
  const mk = makeType.bind(null, ctx);
216
- if (s === void 0)
217
- return f$2.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
218
- if (s === null)
219
- return f$2.createLiteralTypeNode(f$2.createNull());
212
+ if (s === void 0) return f$2.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
213
+ if (s === null) return f$2.createLiteralTypeNode(f$2.createNull());
220
214
  if ("$ref" in s && s.$ref) {
221
215
  const parts = s.$ref.replace("#/", "").split("/");
222
216
  if (parts.length === 3 && parts[0] === "components" && parts[1] === "schemas") {
223
217
  return f$2.createTypeReferenceNode(normalizeIdentifier(parts[2], true));
224
218
  }
225
219
  const t = unref(ctx, s);
226
- if (!t)
227
- throw new Error(`makeTypeRef: ref not found ${JSON.stringify(s)}`);
220
+ if (!t) throw new Error(`makeTypeRef: ref not found ${JSON.stringify(s)}`);
228
221
  return makeType(ctx, t);
229
222
  }
230
- if ("oneOf" in s && s.oneOf)
231
- return f$2.createUnionTypeNode(s.oneOf.map(mk));
232
- if ("anyOf" in s && s.anyOf)
233
- return f$2.createUnionTypeNode(s.anyOf.map(mk));
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";
223
+ if ("oneOf" in s && s.oneOf) return f$2.createUnionTypeNode(s.oneOf.map(mk));
224
+ if ("anyOf" in s && s.anyOf) return f$2.createUnionTypeNode(s.anyOf.map(mk));
225
+ if ("allOf" in s && s.allOf) return f$2.createIntersectionTypeNode(s.allOf.map(mk));
226
+ if ("type" in s && s.type === "integer") s.type = "number";
238
227
  if ("enum" in s && s.enum && !Array.isArray(s.type)) {
239
228
  const isArray2 = s.type === "array";
240
229
  const t = makeInlineEnum(isArray2 ? { ...s, type: s.items?.type } : s);
241
- if (t)
242
- return isArray2 ? f$2.createArrayTypeNode(t) : t;
230
+ if (t) return isArray2 ? f$2.createArrayTypeNode(t) : t;
243
231
  }
244
232
  if ("properties" in s && s.properties) {
245
233
  return f$2.createTypeLiteralNode(
@@ -254,37 +242,26 @@ const makeType = (ctx, s) => {
254
242
  if (Array.isArray(s.type)) {
255
243
  const types = [];
256
244
  for (const type of s.type) {
257
- if (type === "null")
258
- types.push({ type: "null" });
259
- else
260
- types.push({ ...s, type });
245
+ if (type === "null") types.push({ type: "null" });
246
+ else types.push({ ...s, type });
261
247
  }
262
248
  return mk({ oneOf: types });
263
249
  }
264
250
  let t;
265
- if (s.type === "object")
266
- t = f$2.createKeywordTypeNode(ts.SyntaxKind.ObjectKeyword);
267
- else if (s.type === "boolean")
268
- t = f$2.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
269
- else if (s.type === "number")
270
- t = f$2.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
271
- else if (s.type === "string")
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 })));
251
+ if (s.type === "object") t = makeObject(ctx, s);
252
+ else if (s.type === "boolean") t = f$2.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
253
+ else if (s.type === "number") t = f$2.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
254
+ else if (s.type === "string") t = f$2.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
255
+ else if (s.type === "array") t = f$2.createArrayTypeNode(mk(s.items));
256
+ else if (s.type === "null") t = f$2.createLiteralTypeNode(f$2.createNull());
257
+ else if (lodashEs.isArray(s.type)) t = f$2.createUnionTypeNode(s.type.map((x) => mk({ type: x })));
279
258
  else {
280
259
  console.warn(`makeType: unknown type "${s.type}"`);
281
260
  return f$2.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
282
261
  }
283
262
  if (s.type === "string") {
284
- if (s.format === "binary")
285
- t = f$2.createTypeReferenceNode("File");
286
- if (s.format === "date-time" && ctx.parseDates)
287
- t = f$2.createTypeReferenceNode("Date");
263
+ if (s.format === "binary") t = f$2.createTypeReferenceNode("File");
264
+ if (s.format === "date-time" && ctx.parseDates) t = f$2.createTypeReferenceNode("Date");
288
265
  }
289
266
  return s.nullable ? f$2.createUnionTypeNode([t, f$2.createLiteralTypeNode(f$2.createNull())]) : t;
290
267
  }
@@ -336,15 +313,13 @@ const getOpName = (ctx, op) => {
336
313
  }
337
314
  fn = normalizeOpName(fn);
338
315
  let nsr = ns.split("").map((x) => `[${x.toUpperCase()}${x.toLowerCase()}]`).join("");
339
- if (nsr.endsWith("[Ss]"))
340
- nsr += "?";
316
+ if (nsr.endsWith("[Ss]")) nsr += "?";
341
317
  fn = fn.replace(new RegExp(`^${nsr}([Cc]ontroller|[Ss]ervice)?([A-Z].*)$`), "$2");
342
318
  fn = lodashEs.lowerFirst(fn);
343
319
  const proposal = [ns, fn];
344
320
  if (ctx.resolveName) {
345
321
  const res = ctx.resolveName(ctx, op, proposal);
346
- if (Array.isArray(res) && res.length === 2)
347
- return res;
322
+ if (Array.isArray(res) && res.length === 2) return res;
348
323
  if (res !== void 0) {
349
324
  console.warn(`${ctx.logTag} resolveName should return [ns, fn] or undefined (skipping)`);
350
325
  }
@@ -352,11 +327,9 @@ const getOpName = (ctx, op) => {
352
327
  return proposal;
353
328
  };
354
329
  const prepareUrl = (url, rename) => {
355
- for (const [k, v] of Object.entries(rename))
356
- url = url.replaceAll(`{${k}}`, "${" + v + "}");
330
+ for (const [k, v] of Object.entries(rename)) url = url.replaceAll(`{${k}}`, "${" + v + "}");
357
331
  const parts = url.split("${");
358
- if (parts.length === 1)
359
- return f$1.createStringLiteral(url);
332
+ if (parts.length === 1) return f$1.createStringLiteral(url);
360
333
  return f$1.createTemplateExpression(
361
334
  f$1.createTemplateHead(parts[0]),
362
335
  parts.slice(1).map((x, i) => {
@@ -376,13 +349,11 @@ const prepareOp = (ctx, cfg, opName) => {
376
349
  const repSchema = getRepSchema(ctx, cfg);
377
350
  const allParams = arrayUtilsTs.filterNullable(cfg.parameters.map((x) => unref(ctx, x)));
378
351
  const params = lodashEs.uniqBy(allParams.filter((x) => x.in === "path"), "name");
379
- if (reqSchema)
380
- params.push({ name: "body", schema: reqSchema[1] });
352
+ if (reqSchema) params.push({ name: "body", schema: reqSchema[1] });
381
353
  const search = allParams.filter((x) => x.in === "query");
382
354
  allParams.filter((x) => x.in === "header");
383
355
  for (const [name, v] of Object.entries({ search })) {
384
- if (!v.length)
385
- continue;
356
+ if (!v.length) continue;
386
357
  const properties = v.reduce((acc, x) => ({ ...acc, [x.name]: x.schema }), {});
387
358
  params.push({ name, schema: { type: "object", properties } });
388
359
  }
@@ -446,8 +417,7 @@ const prepareRoutes = async (ctx) => {
446
417
  const routes = {};
447
418
  for (const [path, pathConfig] of Object.entries(ctx.doc.paths ?? {})) {
448
419
  ctx.logTag = `${"[ALL]".toUpperCase().padEnd(6, " ")} ${path}`;
449
- if (!lodashEs.isObject(pathConfig))
450
- continue;
420
+ if (!lodashEs.isObject(pathConfig)) continue;
451
421
  if ("$ref" in pathConfig) {
452
422
  console.warn(`${ctx.logTag} $ref should be resolved before (skipping)`);
453
423
  continue;
@@ -455,14 +425,12 @@ const prepareRoutes = async (ctx) => {
455
425
  for (const method of HttpMethods) {
456
426
  ctx.logTag = `${method.toUpperCase().padEnd(6, " ")} ${path}`;
457
427
  const config = pathConfig[method];
458
- if (!config)
459
- continue;
428
+ if (!config) continue;
460
429
  if (pathConfig.parameters) {
461
430
  config.parameters = [...config.parameters ?? [], ...pathConfig.parameters];
462
431
  }
463
432
  const [ns, op] = getOpName(ctx, { ...config, method, path });
464
- if (!routes[ns])
465
- routes[ns] = [];
433
+ if (!routes[ns]) routes[ns] = [];
466
434
  const joined = [ns, op].join(".");
467
435
  if (ctx.usedNames.has(joined)) {
468
436
  continue;
@@ -502,8 +470,7 @@ const generateAst = async (ctx) => {
502
470
  return { modules, types };
503
471
  };
504
472
  const loadSchema = async (url, upgrade = true) => {
505
- if (url.startsWith("file://"))
506
- url = url.substring(7);
473
+ if (url.startsWith("file://")) url = url.substring(7);
507
474
  const { bundle } = await redocly.bundle({
508
475
  ref: url,
509
476
  config: await redocly.createConfig({}),
@@ -548,7 +515,7 @@ const apigen = async (config) => {
548
515
  const doc = await loadSchema(config.source);
549
516
  const ctx = initCtx({ ...config, doc });
550
517
  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-c2426ec2.cjs', document.baseURI).href)))), "_template.ts");
518
+ 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-oWgfChRG.cjs', document.baseURI).href)))), "_template.ts");
552
519
  const file = await fs.readFile(filepath, "utf-8");
553
520
  let code = [
554
521
  `// Auto-generated by https://github.com/vladkens/apigen-ts`,
package/dist/main.cjs CHANGED
@@ -3,7 +3,7 @@
3
3
  require('fs/promises');
4
4
  require('path');
5
5
  require('url');
6
- var main = require('./main-c2426ec2.cjs');
6
+ var main = require('./main-oWgfChRG.cjs');
7
7
  require('cleye');
8
8
  require('@redocly/openapi-core');
9
9
  require('array-utils-ts');
package/dist/main.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import 'fs/promises';
2
2
  import 'path';
3
3
  import 'url';
4
- export { a as apigen } from './main-bbe33f1b.js';
4
+ export { a as apigen } from './main-BTcttgNf.js';
5
5
  import 'cleye';
6
6
  import '@redocly/openapi-core';
7
7
  import 'array-utils-ts';
package/dist/main.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import 'fs/promises';
2
2
  import 'path';
3
3
  import 'url';
4
- export { a as apigen } from './main-bbe33f1b.mjs';
4
+ export { a as apigen } from './main-BTcttgNf.mjs';
5
5
  import 'cleye';
6
6
  import '@redocly/openapi-core';
7
7
  import 'array-utils-ts';
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "apigen-ts",
4
- "version": "0.1.2",
4
+ "version": "1.0.0",
5
5
  "license": "MIT",
6
- "author": "Vlad Pronsky <v.pronsky@gmail.com>",
6
+ "author": "vladkens <v.pronsky@gmail.com>",
7
7
  "repository": "vladkens/apigen-ts",
8
- "description": "OpenAPI TypeScript client generator",
8
+ "description": "Simple typed OpenAPI client generator",
9
9
  "keywords": [
10
10
  "openapi",
11
11
  "swagger",
@@ -22,24 +22,24 @@
22
22
  "ci": "tsc --noEmit && yarn test-cov && yarn build"
23
23
  },
24
24
  "dependencies": {
25
- "@redocly/openapi-core": "^1.6.0",
26
- "@types/lodash-es": "^4.17.12",
27
- "@types/swagger2openapi": "^7.0.4",
28
- "array-utils-ts": "^0.1.2",
29
- "cleye": "^1.3.2",
30
- "lodash-es": "^4.17.21",
31
- "swagger2openapi": "^7.0.8"
25
+ "@redocly/openapi-core": "1.25.5",
26
+ "@types/lodash-es": "4.17.12",
27
+ "@types/swagger2openapi": "7.0.4",
28
+ "array-utils-ts": "0.1.2",
29
+ "cleye": "1.3.2",
30
+ "lodash-es": "4.17.21",
31
+ "swagger2openapi": "7.0.8"
32
32
  },
33
33
  "devDependencies": {
34
- "@types/node": "^20.10.8",
35
- "c8": "^9.0.0",
36
- "fetch-mock": "^9.11.0",
37
- "pkgroll": "^2.0.1",
38
- "prettier": "^3.1.0",
39
- "prettier-plugin-organize-imports": "^3.2.4",
40
- "tsm": "^2.3.0",
41
- "typescript": "^5.3.2",
42
- "uvu": "^0.5.6"
34
+ "@types/node": "22.7.5",
35
+ "c8": "10.1.2",
36
+ "fetch-mock": "11.1.5",
37
+ "pkgroll": "2.5.0",
38
+ "prettier": "3.3.3",
39
+ "prettier-plugin-organize-imports": "4.1.0",
40
+ "tsm": "2.3.0",
41
+ "typescript": "5.6.3",
42
+ "uvu": "0.5.6"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "prettier": "^3.0.0",
package/readme.md CHANGED
@@ -12,7 +12,7 @@
12
12
 
13
13
  <div align="center">
14
14
  <img src="./logo.svg" alt="apigen-ts logo" height="80" />
15
- <div>TypeScript client generator from OpenAPI schema</div>
15
+ <div>Simple typed OpenAPI client generator</div>
16
16
  </div>
17
17
 
18
18
  ## Features
@@ -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:
@@ -147,6 +168,33 @@ await apigen({
147
168
 
148
169
  Then run with: `node apigen.mjs`
149
170
 
171
+ ## Usage with different backend frameworks
172
+
173
+ ### FastAPI
174
+
175
+ By default `apigen-ts` generates noisy method names when used with FastAPI. This can be fixed by [custom resolving](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#using-the-path-operation-function-name-as-the-operationid) for operations ids.
176
+
177
+ ```py
178
+ from fastapi import FastAPI
179
+ from fastapi.routing import APIRoute
180
+
181
+ app = FastAPI()
182
+
183
+ # add your routes here
184
+
185
+ def update_operation_ids(app: FastAPI) -> None:
186
+ for route in app.routes:
187
+ if isinstance(route, APIRoute):
188
+ ns = route.tags[0] if route.tags else "general"
189
+ route.operation_id = f"{ns}_{route.name}".lower()
190
+
191
+
192
+ # this function should be after all routes added
193
+ update_operation_ids(app)
194
+ ```
195
+
196
+ _Note: If you want FastAPI to be added as preset, open PR please._
197
+
150
198
  ## Compare
151
199
 
152
200
  - [openapi-typescript-codegen](https://github.com/ferdikoomen/openapi-typescript-codegen) ([npm](https://www.npmjs.com/package/openapi-typescript-codegen)): no single file mode [#1263](https://github.com/ferdikoomen/openapi-typescript-codegen/issues/1263#issuecomment-1502890838)