apigen-ts 1.3.1 → 1.4.0

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