typed-openapi 1.3.2 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,7 +4,7 @@ import {
4
4
  generateTanstackQueryFile,
5
5
  mapOpenApiEndpoints,
6
6
  prettify
7
- } from "./chunk-YLPQQ24J.js";
7
+ } from "./chunk-FM2BOVDQ.js";
8
8
 
9
9
  // src/generate-client-files.ts
10
10
  import SwaggerParser from "@apidevtools/swagger-parser";
@@ -16,13 +16,18 @@ var now = /* @__PURE__ */ new Date();
16
16
  var optionsSchema = type({
17
17
  "output?": "string",
18
18
  runtime: allowedRuntimes,
19
- tanstack: "boolean | string"
19
+ tanstack: "boolean | string",
20
+ schemasOnly: "boolean"
20
21
  });
21
22
  async function generateClientFiles(input, options) {
22
23
  const openApiDoc = await SwaggerParser.bundle(input);
23
24
  const ctx = mapOpenApiEndpoints(openApiDoc);
24
25
  console.log(`Found ${ctx.endpointList.length} endpoints`);
25
- const content = await prettify(generateFile({ ...ctx, runtime: options.runtime }));
26
+ const content = await prettify(generateFile({
27
+ ...ctx,
28
+ runtime: options.runtime,
29
+ schemasOnly: options.schemasOnly
30
+ }));
26
31
  const outputPath = join(
27
32
  cwd,
28
33
  options.output ?? input + `.${options.runtime === "none" ? "client" : options.runtime}.ts`
@@ -77,7 +77,7 @@ var openApiSchemaToTs = ({ schema, meta: _inheritedMeta, ctx }) => {
77
77
  if (schema.enum) {
78
78
  if (schema.enum.length === 1) {
79
79
  const value = schema.enum[0];
80
- return t.literal(value === null ? "null" : `"${value}"`);
80
+ return t.literal(value === null ? "null" : value === true ? "true" : value === false ? "false" : `"${value}"`);
81
81
  }
82
82
  if (schemaType === "string") {
83
83
  return t.union(schema.enum.map((value) => t.literal(`"${value}"`)));
@@ -276,14 +276,20 @@ var methods = ["get", "put", "post", "delete", "options", "head", "patch", "trac
276
276
  var methodsRegex = new RegExp(`(?:${methods.join("|")})_`);
277
277
  var endpointExport = new RegExp(`export (?:type|const) (?:${methodsRegex.source})`);
278
278
  var replacerByRuntime = {
279
- yup: (line) => line.replace(/y\.InferType<\s*?typeof (.*?)\s*?>/g, "typeof $1").replace(new RegExp(`(${endpointExport.source})` + new RegExp(/([\s\S]*? )(y\.object)(\()/).source, "g"), "$1$2("),
280
- zod: (line) => line.replace(/z\.infer<\s*?typeof (.*?)\s*?>/g, "typeof $1").replace(new RegExp(`(${endpointExport.source})` + new RegExp(/([\s\S]*? )(z\.object)(\()/).source, "g"), "$1$2(")
279
+ yup: (line) => line.replace(/y\.InferType<\s*?typeof (.*?)\s*?>/g, "typeof $1").replace(
280
+ new RegExp(`(${endpointExport.source})` + new RegExp(/([\s\S]*? )(y\.object)(\()/).source, "g"),
281
+ "$1$2("
282
+ ),
283
+ zod: (line) => line.replace(/z\.infer<\s*?typeof (.*?)\s*?>/g, "typeof $1").replace(
284
+ new RegExp(`(${endpointExport.source})` + new RegExp(/([\s\S]*? )(z\.object)(\()/).source, "g"),
285
+ "$1$2("
286
+ )
281
287
  };
282
288
  var generateFile = (options) => {
283
289
  const ctx = { ...options, runtime: options.runtime ?? "none" };
284
290
  const schemaList = generateSchemaList(ctx);
285
- const endpointSchemaList = generateEndpointSchemaList(ctx);
286
- const apiClient = generateApiClient(ctx);
291
+ const endpointSchemaList = options.schemasOnly ? "" : generateEndpointSchemaList(ctx);
292
+ const apiClient = options.schemasOnly ? "" : generateApiClient(ctx);
287
293
  const transform = ctx.runtime === "none" ? (file2) => file2 : (file2) => {
288
294
  const model = Codegen.TypeScriptToModel.Generate(file2);
289
295
  const transformer = runtimeValidationGenerator[ctx.runtime];
@@ -327,7 +333,7 @@ var parameterObjectToString = (parameters) => {
327
333
  if (parameters instanceof Box) return parameters.value;
328
334
  let str = "{";
329
335
  for (const [key, box] of Object.entries(parameters)) {
330
- str += `${wrapWithQuotesIfNeeded(key)}: ${box.value},
336
+ str += `${wrapWithQuotesIfNeeded(key)}${box.type === "optional" ? "?" : ""}: ${box.value},
331
337
  `;
332
338
  }
333
339
  return str + "}";
@@ -490,7 +496,17 @@ export class ApiClient {
490
496
  >(
491
497
  method: TMethod,
492
498
  path: TPath,
493
- ...params: MaybeOptionalArg<${match(ctx.runtime).with("zod", "yup", () => inferByRuntime[ctx.runtime](`TEndpoint extends { parameters: infer Params } ? Params : never`)).with("arktype", "io-ts", "typebox", "valibot", () => inferByRuntime[ctx.runtime](`TEndpoint`) + `["parameters"]`).otherwise(() => `TEndpoint extends { parameters: infer Params } ? Params : never`)}>)
499
+ ...params: MaybeOptionalArg<${match(ctx.runtime).with(
500
+ "zod",
501
+ "yup",
502
+ () => inferByRuntime[ctx.runtime](`TEndpoint extends { parameters: infer Params } ? Params : never`)
503
+ ).with(
504
+ "arktype",
505
+ "io-ts",
506
+ "typebox",
507
+ "valibot",
508
+ () => inferByRuntime[ctx.runtime](`TEndpoint`) + `["parameters"]`
509
+ ).otherwise(() => `TEndpoint extends { parameters: infer Params } ? Params : never`)}>)
494
510
  : Promise<Omit<Response, "json"> & {
495
511
  /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json) */
496
512
  json: () => Promise<TEndpoint extends { response: infer Res } ? Res : never>;
@@ -754,12 +770,15 @@ var mapOpenApiEndpoints = (doc) => {
754
770
  },
755
771
  { query: {}, path: {}, header: {} }
756
772
  );
757
- const params = Object.entries(paramObjects).reduce((acc, [key, value]) => {
758
- if (Object.keys(value).length) {
759
- acc[key] = value;
760
- }
761
- return acc;
762
- }, {});
773
+ const params = Object.entries(paramObjects).reduce(
774
+ (acc, [key, value]) => {
775
+ if (Object.keys(value).length) {
776
+ acc[key] = value;
777
+ }
778
+ return acc;
779
+ },
780
+ {}
781
+ );
763
782
  if (operation.requestBody) {
764
783
  endpoint.meta.hasParameters = true;
765
784
  const requestBody = refs.unwrap(operation.requestBody ?? {});
@@ -907,7 +926,8 @@ var generateTanstackQueryFile = async (ctx) => {
907
926
  export class TanstackQueryApiClient {
908
927
  constructor(public client: ApiClient) { }
909
928
 
910
- ${Array.from(endpointMethods).map((method) => `
929
+ ${Array.from(endpointMethods).map(
930
+ (method) => `
911
931
  // <ApiClient.${method}>
912
932
  ${method}<Path extends keyof ${capitalize4(method)}Endpoints, TEndpoint extends ${capitalize4(method)}Endpoints[Path]>(
913
933
  path: Path,
@@ -945,7 +965,8 @@ var generateTanstackQueryFile = async (ctx) => {
945
965
  return query
946
966
  }
947
967
  // </ApiClient.${method}>
948
- `).join("\n")}
968
+ `
969
+ ).join("\n")}
949
970
 
950
971
  // <ApiClient.request>
951
972
  /**
package/dist/cli.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  generateClientFiles
3
- } from "./chunk-YRJ7W5RY.js";
3
+ } from "./chunk-7FXHZEBG.js";
4
4
  import {
5
5
  allowedRuntimes
6
- } from "./chunk-YLPQQ24J.js";
6
+ } from "./chunk-FM2BOVDQ.js";
7
7
 
8
8
  // src/cli.ts
9
9
  import { cac } from "cac";
@@ -14,7 +14,14 @@ cli.command("<input>", "Generate").option("-o, --output <path>", "Output path fo
14
14
  "-r, --runtime <name>",
15
15
  `Runtime to use for validation; defaults to \`none\`; available: ${allowedRuntimes.toString()}`,
16
16
  { default: "none" }
17
- ).option("--tanstack [name]", "Generate tanstack client, defaults to false, can optionally specify a name for the generated file").action(async (input, _options) => {
17
+ ).option(
18
+ "--schemas-only",
19
+ "Only generate schemas, skipping client generation (defaults to false)",
20
+ { default: false }
21
+ ).option(
22
+ "--tanstack [name]",
23
+ "Generate tanstack client, defaults to false, can optionally specify a name for the generated file"
24
+ ).action(async (input, _options) => {
18
25
  return generateClientFiles(input, _options);
19
26
  });
20
27
  cli.help();
package/dist/index.d.ts CHANGED
@@ -236,6 +236,7 @@ type Endpoint<TConfig extends DefaultEndpoint = DefaultEndpoint> = {
236
236
 
237
237
  type GeneratorOptions$1 = ReturnType<typeof mapOpenApiEndpoints> & {
238
238
  runtime?: "none" | keyof typeof runtimeValidationGenerator;
239
+ schemasOnly?: boolean;
239
240
  };
240
241
  declare const allowedRuntimes: arktype_internal_methods_string_ts.StringType<"none" | "arktype" | "io-ts" | "typebox" | "valibot" | "yup" | "zod", {}>;
241
242
  type OutputRuntime = typeof allowedRuntimes.infer;
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  openApiSchemaToTs,
9
9
  tsFactory,
10
10
  unwrap
11
- } from "./chunk-YLPQQ24J.js";
11
+ } from "./chunk-FM2BOVDQ.js";
12
12
  export {
13
13
  createBoxFactory,
14
14
  createFactory,
@@ -6,6 +6,7 @@ declare const prettify: (str: string, options?: Options | null) => string | Prom
6
6
  declare const optionsSchema: arktype_internal_methods_object_ts.ObjectType<{
7
7
  runtime: "none" | "arktype" | "io-ts" | "typebox" | "valibot" | "yup" | "zod";
8
8
  tanstack: string | boolean;
9
+ schemasOnly: boolean;
9
10
  output?: string;
10
11
  }, {}>;
11
12
  declare function generateClientFiles(input: string, options: typeof optionsSchema.infer): Promise<void>;
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  generateClientFiles
3
- } from "./chunk-YRJ7W5RY.js";
3
+ } from "./chunk-7FXHZEBG.js";
4
4
  import {
5
5
  prettify
6
- } from "./chunk-YLPQQ24J.js";
6
+ } from "./chunk-FM2BOVDQ.js";
7
7
  export {
8
8
  generateClientFiles,
9
9
  prettify
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "typed-openapi",
3
3
  "type": "module",
4
- "version": "1.3.2",
4
+ "version": "1.4.1",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
7
7
  "exports": {
@@ -63,6 +63,7 @@
63
63
  "dev": "tsup --watch",
64
64
  "build": "tsup",
65
65
  "test": "vitest",
66
+ "fmt": "prettier --write src",
66
67
  "typecheck": "tsc -b ./tsconfig.build.json"
67
68
  }
68
69
  }
package/src/cli.ts CHANGED
@@ -15,7 +15,15 @@ cli
15
15
  `Runtime to use for validation; defaults to \`none\`; available: ${allowedRuntimes.toString()}`,
16
16
  { default: "none" },
17
17
  )
18
- .option("--tanstack [name]", "Generate tanstack client, defaults to false, can optionally specify a name for the generated file")
18
+ .option(
19
+ "--schemas-only",
20
+ "Only generate schemas, skipping client generation (defaults to false)",
21
+ { default: false },
22
+ )
23
+ .option(
24
+ "--tanstack [name]",
25
+ "Generate tanstack client, defaults to false, can optionally specify a name for the generated file",
26
+ )
19
27
  .action(async (input, _options) => {
20
28
  return generateClientFiles(input, _options);
21
29
  });
@@ -12,35 +12,40 @@ const cwd = process.cwd();
12
12
  const now = new Date();
13
13
 
14
14
  export const optionsSchema = type({
15
- "output?": "string",
16
- runtime: allowedRuntimes,
17
- tanstack: "boolean | string"
15
+ "output?": "string",
16
+ runtime: allowedRuntimes,
17
+ tanstack: "boolean | string",
18
+ schemasOnly: "boolean",
18
19
  });
19
20
 
20
21
  export async function generateClientFiles(input: string, options: typeof optionsSchema.infer) {
21
- const openApiDoc = (await SwaggerParser.bundle(input)) as OpenAPIObject;
22
-
23
- const ctx = mapOpenApiEndpoints(openApiDoc);
24
- console.log(`Found ${ctx.endpointList.length} endpoints`);
25
-
26
- const content = await prettify(generateFile({ ...ctx, runtime: options.runtime }));
27
- const outputPath = join(
28
- cwd,
29
- options.output ?? input + `.${options.runtime === "none" ? "client" : options.runtime}.ts`,
30
- );
31
-
32
- console.log("Generating client...", outputPath);
33
- await writeFile(outputPath, content);
34
-
35
- if (options.tanstack) {
36
- const tanstackContent = await generateTanstackQueryFile({
37
- ...ctx,
38
- relativeApiClientPath: './' + basename(outputPath),
39
- });
40
- const tanstackOutputPath = join(dirname(outputPath), typeof options.tanstack === "string" ? options.tanstack : `tanstack.client.ts`);
41
- console.log("Generating tanstack client...", tanstackOutputPath);
42
- await writeFile(tanstackOutputPath, tanstackContent);
43
- }
44
-
45
- console.log(`Done in ${new Date().getTime() - now.getTime()}ms !`);
22
+ const openApiDoc = (await SwaggerParser.bundle(input)) as OpenAPIObject;
23
+
24
+ const ctx = mapOpenApiEndpoints(openApiDoc);
25
+ console.log(`Found ${ctx.endpointList.length} endpoints`);
26
+
27
+ const content = await prettify(generateFile({
28
+ ...ctx,
29
+ runtime: options.runtime,
30
+ schemasOnly: options.schemasOnly,
31
+ }));
32
+ const outputPath = join(
33
+ cwd,
34
+ options.output ?? input + `.${options.runtime === "none" ? "client" : options.runtime}.ts`,
35
+ );
36
+
37
+ console.log("Generating client...", outputPath);
38
+ await writeFile(outputPath, content);
39
+
40
+ if (options.tanstack) {
41
+ const tanstackContent = await generateTanstackQueryFile({
42
+ ...ctx,
43
+ relativeApiClientPath: './' + basename(outputPath),
44
+ });
45
+ const tanstackOutputPath = join(dirname(outputPath), typeof options.tanstack === "string" ? options.tanstack : `tanstack.client.ts`);
46
+ console.log("Generating tanstack client...", tanstackOutputPath);
47
+ await writeFile(tanstackOutputPath, tanstackContent);
48
+ }
49
+
50
+ console.log(`Done in ${new Date().getTime() - now.getTime()}ms !`);
46
51
  }
package/src/generator.ts CHANGED
@@ -9,6 +9,7 @@ import { wrapWithQuotesIfNeeded } from "./string-utils.ts";
9
9
 
10
10
  type GeneratorOptions = ReturnType<typeof mapOpenApiEndpoints> & {
11
11
  runtime?: "none" | keyof typeof runtimeValidationGenerator;
12
+ schemasOnly?: boolean;
12
13
  };
13
14
  type GeneratorContext = Required<GeneratorOptions>;
14
15
 
@@ -45,54 +46,60 @@ const replacerByRuntime = {
45
46
  yup: (line: string) =>
46
47
  line
47
48
  .replace(/y\.InferType<\s*?typeof (.*?)\s*?>/g, "typeof $1")
48
- .replace(new RegExp(`(${endpointExport.source})` + new RegExp(/([\s\S]*? )(y\.object)(\()/).source, "g"), "$1$2("),
49
+ .replace(
50
+ new RegExp(`(${endpointExport.source})` + new RegExp(/([\s\S]*? )(y\.object)(\()/).source, "g"),
51
+ "$1$2(",
52
+ ),
49
53
  zod: (line: string) =>
50
54
  line
51
55
  .replace(/z\.infer<\s*?typeof (.*?)\s*?>/g, "typeof $1")
52
- .replace(new RegExp(`(${endpointExport.source})` + new RegExp(/([\s\S]*? )(z\.object)(\()/).source, "g"), "$1$2("),
56
+ .replace(
57
+ new RegExp(`(${endpointExport.source})` + new RegExp(/([\s\S]*? )(z\.object)(\()/).source, "g"),
58
+ "$1$2(",
59
+ ),
53
60
  };
54
61
 
55
62
  export const generateFile = (options: GeneratorOptions) => {
56
63
  const ctx = { ...options, runtime: options.runtime ?? "none" } as GeneratorContext;
57
64
 
58
65
  const schemaList = generateSchemaList(ctx);
59
- const endpointSchemaList = generateEndpointSchemaList(ctx);
60
- const apiClient = generateApiClient(ctx);
66
+ const endpointSchemaList = options.schemasOnly ? "" : generateEndpointSchemaList(ctx);
67
+ const apiClient = options.schemasOnly ? "" : generateApiClient(ctx);
61
68
 
62
69
  const transform =
63
70
  ctx.runtime === "none"
64
71
  ? (file: string) => file
65
72
  : (file: string) => {
66
- const model = Codegen.TypeScriptToModel.Generate(file);
67
- const transformer = runtimeValidationGenerator[ctx.runtime as Exclude<typeof ctx.runtime, "none">];
68
- // tmp fix for typebox, there's currently a "// todo" only with Codegen.ModelToTypeBox.Generate
69
- // https://github.com/sinclairzx81/typebox-codegen/blob/44d44d55932371b69f349331b1c8a60f5d760d9e/src/model/model-to-typebox.ts#L31
70
- const generated = ctx.runtime === "typebox" ? Codegen.TypeScriptToTypeBox.Generate(file) : transformer(model);
71
-
72
- let converted = "";
73
- const match = generated.match(/(const __ENDPOINTS_START__ =)([\s\S]*?)(export type __ENDPOINTS_END__)/);
74
- const content = match?.[2];
75
-
76
- if (content && ctx.runtime in replacerByRuntime) {
77
- const before = generated.slice(0, generated.indexOf("export type __ENDPOINTS_START"));
78
- converted =
79
- before +
80
- replacerByRuntime[ctx.runtime as keyof typeof replacerByRuntime](
81
- content.slice(content.indexOf("export")),
82
- );
83
- } else {
84
- converted = generated;
85
- }
73
+ const model = Codegen.TypeScriptToModel.Generate(file);
74
+ const transformer = runtimeValidationGenerator[ctx.runtime as Exclude<typeof ctx.runtime, "none">];
75
+ // tmp fix for typebox, there's currently a "// todo" only with Codegen.ModelToTypeBox.Generate
76
+ // https://github.com/sinclairzx81/typebox-codegen/blob/44d44d55932371b69f349331b1c8a60f5d760d9e/src/model/model-to-typebox.ts#L31
77
+ const generated = ctx.runtime === "typebox" ? Codegen.TypeScriptToTypeBox.Generate(file) : transformer(model);
78
+
79
+ let converted = "";
80
+ const match = generated.match(/(const __ENDPOINTS_START__ =)([\s\S]*?)(export type __ENDPOINTS_END__)/);
81
+ const content = match?.[2];
82
+
83
+ if (content && ctx.runtime in replacerByRuntime) {
84
+ const before = generated.slice(0, generated.indexOf("export type __ENDPOINTS_START"));
85
+ converted =
86
+ before +
87
+ replacerByRuntime[ctx.runtime as keyof typeof replacerByRuntime](
88
+ content.slice(content.indexOf("export")),
89
+ );
90
+ } else {
91
+ converted = generated;
92
+ }
86
93
 
87
- return converted;
88
- };
94
+ return converted;
95
+ };
89
96
 
90
97
  const file = `
91
98
  ${transform(schemaList + endpointSchemaList)}
92
99
  ${apiClient}
93
100
  `;
94
101
 
95
- return (file);
102
+ return file;
96
103
  };
97
104
 
98
105
  const generateSchemaList = ({ refs, runtime }: GeneratorContext) => {
@@ -121,7 +128,7 @@ const parameterObjectToString = (parameters: Box<AnyBoxDef> | Record<string, Any
121
128
 
122
129
  let str = "{";
123
130
  for (const [key, box] of Object.entries(parameters)) {
124
- str += `${wrapWithQuotesIfNeeded(key)}: ${box.value},\n`;
131
+ str += `${wrapWithQuotesIfNeeded(key)}${box.type === "optional" ? "?" : ""}: ${box.value},\n`;
125
132
  }
126
133
  return str + "}";
127
134
  };
@@ -137,36 +144,39 @@ const generateEndpointSchemaList = (ctx: GeneratorContext) => {
137
144
  method: "${endpoint.method.toUpperCase()}",
138
145
  path: "${endpoint.path}",
139
146
  requestFormat: "${endpoint.requestFormat}",
140
- ${endpoint.meta.hasParameters
141
- ? `parameters: {
147
+ ${
148
+ endpoint.meta.hasParameters
149
+ ? `parameters: {
142
150
  ${parameters.query ? `query: ${parameterObjectToString(parameters.query)},` : ""}
143
151
  ${parameters.path ? `path: ${parameterObjectToString(parameters.path)},` : ""}
144
152
  ${parameters.header ? `header: ${parameterObjectToString(parameters.header)},` : ""}
145
- ${parameters.body
146
- ? `body: ${parameterObjectToString(
147
- ctx.runtime === "none"
148
- ? parameters.body.recompute((box) => {
149
- if (Box.isReference(box) && !box.params.generics) {
150
- box.value = `Schemas.${box.value}`;
151
- }
152
- return box;
153
- })
154
- : parameters.body,
155
- )},`
156
- : ""
153
+ ${
154
+ parameters.body
155
+ ? `body: ${parameterObjectToString(
156
+ ctx.runtime === "none"
157
+ ? parameters.body.recompute((box) => {
158
+ if (Box.isReference(box) && !box.params.generics) {
159
+ box.value = `Schemas.${box.value}`;
160
+ }
161
+ return box;
162
+ })
163
+ : parameters.body,
164
+ )},`
165
+ : ""
157
166
  }
158
167
  }`
159
- : "parameters: never,"
168
+ : "parameters: never,"
160
169
  }
161
- response: ${ctx.runtime === "none"
162
- ? endpoint.response.recompute((box) => {
163
- if (Box.isReference(box) && !box.params.generics && box.value !== "null") {
164
- box.value = `Schemas.${box.value}`;
165
- }
166
-
167
- return box;
168
- }).value
169
- : endpoint.response.value
170
+ response: ${
171
+ ctx.runtime === "none"
172
+ ? endpoint.response.recompute((box) => {
173
+ if (Box.isReference(box) && !box.params.generics && box.value !== "null") {
174
+ box.value = `Schemas.${box.value}`;
175
+ }
176
+
177
+ return box;
178
+ }).value
179
+ : endpoint.response.value
170
180
  },
171
181
  }\n`;
172
182
  });
@@ -189,14 +199,14 @@ const generateEndpointByMethod = (ctx: GeneratorContext) => {
189
199
  // <EndpointByMethod>
190
200
  export ${ctx.runtime === "none" ? "type" : "const"} EndpointByMethod = {
191
201
  ${Object.entries(byMethods)
192
- .map(([method, list]) => {
193
- return `${method}: {
202
+ .map(([method, list]) => {
203
+ return `${method}: {
194
204
  ${list.map(
195
- (endpoint) => `"${endpoint.path}": ${ctx.runtime === "none" ? "Endpoints." : ""}${endpoint.meta.alias}`,
196
- )}
205
+ (endpoint) => `"${endpoint.path}": ${ctx.runtime === "none" ? "Endpoints." : ""}${endpoint.meta.alias}`,
206
+ )}
197
207
  }`;
198
- })
199
- .join(",\n")}
208
+ })
209
+ .join(",\n")}
200
210
  }
201
211
  ${ctx.runtime === "none" ? "" : "export type EndpointByMethod = typeof EndpointByMethod;"}
202
212
  // </EndpointByMethod>
@@ -263,7 +273,6 @@ type MaybeOptionalArg<T> = RequiredKeys<T> extends never ? [config?: T] : [confi
263
273
  // </ApiClientTypes>
264
274
  `;
265
275
 
266
-
267
276
  const apiClient = `
268
277
  // <ApiClient>
269
278
  export class ApiClient {
@@ -285,22 +294,22 @@ export class ApiClient {
285
294
  }
286
295
 
287
296
  ${Object.entries(byMethods)
288
- .map(([method, endpointByMethod]) => {
289
- const capitalizedMethod = capitalize(method);
290
- const infer = inferByRuntime[ctx.runtime];
297
+ .map(([method, endpointByMethod]) => {
298
+ const capitalizedMethod = capitalize(method);
299
+ const infer = inferByRuntime[ctx.runtime];
291
300
 
292
- return endpointByMethod.length
293
- ? `// <ApiClient.${method}>
301
+ return endpointByMethod.length
302
+ ? `// <ApiClient.${method}>
294
303
  ${method}<Path extends keyof ${capitalizedMethod}Endpoints, TEndpoint extends ${capitalizedMethod}Endpoints[Path]>(
295
304
  path: Path,
296
305
  ...params: MaybeOptionalArg<${match(ctx.runtime)
297
- .with("zod", "yup", () => infer(`TEndpoint["parameters"]`))
298
- .with("arktype", "io-ts", "typebox", "valibot", () => infer(`TEndpoint`) + `["parameters"]`)
299
- .otherwise(() => `TEndpoint["parameters"]`)}>
306
+ .with("zod", "yup", () => infer(`TEndpoint["parameters"]`))
307
+ .with("arktype", "io-ts", "typebox", "valibot", () => infer(`TEndpoint`) + `["parameters"]`)
308
+ .otherwise(() => `TEndpoint["parameters"]`)}>
300
309
  ): Promise<${match(ctx.runtime)
301
- .with("zod", "yup", () => infer(`TEndpoint["response"]`))
302
- .with("arktype", "io-ts", "typebox", "valibot", () => infer(`TEndpoint`) + `["response"]`)
303
- .otherwise(() => `TEndpoint["response"]`)}> {
310
+ .with("zod", "yup", () => infer(`TEndpoint["response"]`))
311
+ .with("arktype", "io-ts", "typebox", "valibot", () => infer(`TEndpoint`) + `["response"]`)
312
+ .otherwise(() => `TEndpoint["response"]`)}> {
304
313
  return this.fetcher("${method}", this.baseUrl + path, params[0])
305
314
  .then(response => this.parseResponse(response))${match(ctx.runtime)
306
315
  .with("zod", "yup", () => `as Promise<${infer(`TEndpoint["response"]`)}>`)
@@ -309,9 +318,9 @@ export class ApiClient {
309
318
  }
310
319
  // </ApiClient.${method}>
311
320
  `
312
- : "";
313
- })
314
- .join("\n")}
321
+ : "";
322
+ })
323
+ .join("\n")}
315
324
 
316
325
  // <ApiClient.request>
317
326
  /**
@@ -325,9 +334,17 @@ export class ApiClient {
325
334
  method: TMethod,
326
335
  path: TPath,
327
336
  ...params: MaybeOptionalArg<${match(ctx.runtime)
328
- .with("zod", "yup", () => inferByRuntime[ctx.runtime](`TEndpoint extends { parameters: infer Params } ? Params : never`))
329
- .with("arktype", "io-ts", "typebox", "valibot", () => inferByRuntime[ctx.runtime](`TEndpoint`) + `["parameters"]`)
330
- .otherwise(() => `TEndpoint extends { parameters: infer Params } ? Params : never`)}>)
337
+ .with("zod", "yup", () =>
338
+ inferByRuntime[ctx.runtime](`TEndpoint extends { parameters: infer Params } ? Params : never`),
339
+ )
340
+ .with(
341
+ "arktype",
342
+ "io-ts",
343
+ "typebox",
344
+ "valibot",
345
+ () => inferByRuntime[ctx.runtime](`TEndpoint`) + `["parameters"]`,
346
+ )
347
+ .otherwise(() => `TEndpoint extends { parameters: infer Params } ? Params : never`)}>)
331
348
  : Promise<Omit<Response, "json"> & {
332
349
  /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json) */
333
350
  json: () => Promise<TEndpoint extends { response: infer Res } ? Res : never>;
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export * from "./box-factory.ts";
2
2
  export { generateFile, type OutputRuntime } from "./generator.ts";
3
- export * from "./tanstack-query.generator.ts"
3
+ export * from "./tanstack-query.generator.ts";
4
4
  export * from "./map-openapi-endpoints.ts";
5
5
  export * from "./openapi-schema-to-ts.ts";
6
6
  export * from "./ref-resolver.ts";
@@ -64,13 +64,16 @@ export const mapOpenApiEndpoints = (doc: OpenAPIObject) => {
64
64
  );
65
65
 
66
66
  // Filter out empty objects
67
- const params = Object.entries(paramObjects).reduce((acc, [key, value]) => {
68
- if (Object.keys(value).length) {
69
- // @ts-expect-error
70
- acc[key] = value;
71
- }
72
- return acc;
73
- }, {} as { query?: Record<string, Box>; path?: Record<string, Box>; header?: Record<string, Box>; body?: Box });
67
+ const params = Object.entries(paramObjects).reduce(
68
+ (acc, [key, value]) => {
69
+ if (Object.keys(value).length) {
70
+ // @ts-expect-error
71
+ acc[key] = value;
72
+ }
73
+ return acc;
74
+ },
75
+ {} as { query?: Record<string, Box>; path?: Record<string, Box>; header?: Record<string, Box>; body?: Box },
76
+ );
74
77
 
75
78
  // Body
76
79
  if (operation.requestBody) {
@@ -1,2 +1,2 @@
1
1
  export { prettify } from "./format.ts";
2
- export { generateClientFiles } from "./generate-client-files.ts"
2
+ export { generateClientFiles } from "./generate-client-files.ts";
@@ -65,7 +65,7 @@ export const openApiSchemaToTs = ({ schema, meta: _inheritedMeta, ctx }: Openapi
65
65
  if (schema.enum) {
66
66
  if (schema.enum.length === 1) {
67
67
  const value = schema.enum[0];
68
- return t.literal(value === null ? "null" : `"${value}"`);
68
+ return t.literal(value === null ? "null" : value === true ? "true" : value === false ? "false" : `"${value}"`);
69
69
  }
70
70
 
71
71
  if (schemaType === "string") {
@@ -100,7 +100,12 @@ export const openApiSchemaToTs = ({ schema, meta: _inheritedMeta, ctx }: Openapi
100
100
 
101
101
  if (schemaType === "object" || schema.properties || schema.additionalProperties) {
102
102
  if (!schema.properties) {
103
- if (schema.additionalProperties && !isReferenceObject(schema.additionalProperties) && typeof schema.additionalProperties !== "boolean" && schema.additionalProperties.type) {
103
+ if (
104
+ schema.additionalProperties &&
105
+ !isReferenceObject(schema.additionalProperties) &&
106
+ typeof schema.additionalProperties !== "boolean" &&
107
+ schema.additionalProperties.type
108
+ ) {
104
109
  const valueSchema = openApiSchemaToTs({ schema: schema.additionalProperties, ctx, meta });
105
110
  return t.literal(`Record<string, ${valueSchema.value}>`);
106
111
  }
@@ -119,7 +119,7 @@ export const createRefResolver = (doc: OpenAPIObject, factory: GenericFactory) =
119
119
  };
120
120
  };
121
121
 
122
- export interface RefResolver extends ReturnType<typeof createRefResolver> { }
122
+ export interface RefResolver extends ReturnType<typeof createRefResolver> {}
123
123
 
124
124
  const setSchemaDependencies = (schema: LibSchemaObject, deps: Set<string>) => {
125
125
  const visit = (schema: LibSchemaObject | ReferenceObject): void => {
@@ -35,9 +35,8 @@ const prefixStringStartingWithNumberIfNeeded = (str: string) => {
35
35
  const pathParamWithBracketsRegex = /({\w+})/g;
36
36
  const wordPrecededByNonWordCharacter = /[^\w\-]+/g;
37
37
 
38
-
39
38
  /** @example turns `/media-objects/{id}` into `MediaObjectsId` */
40
39
  export const pathToVariableName = (path: string) =>
41
- capitalize(kebabToCamel((path)).replaceAll("/", "_")) // /media-objects/{id} -> MediaObjects{id}
40
+ capitalize(kebabToCamel(path).replaceAll("/", "_")) // /media-objects/{id} -> MediaObjects{id}
42
41
  .replace(pathParamWithBracketsRegex, (group) => capitalize(group.slice(1, -1))) // {id} -> Id
43
42
  .replace(wordPrecededByNonWordCharacter, "_"); // "/robots.txt" -> "/robots_txt"
@@ -2,13 +2,13 @@ import { capitalize } from "pastable/server";
2
2
  import { prettify } from "./format.ts";
3
3
  import type { mapOpenApiEndpoints } from "./map-openapi-endpoints.ts";
4
4
 
5
- type GeneratorOptions = ReturnType<typeof mapOpenApiEndpoints>
5
+ type GeneratorOptions = ReturnType<typeof mapOpenApiEndpoints>;
6
6
  type GeneratorContext = Required<GeneratorOptions>;
7
7
 
8
8
  export const generateTanstackQueryFile = async (ctx: GeneratorContext & { relativeApiClientPath: string }) => {
9
- const endpointMethods = (new Set(ctx.endpointList.map((endpoint) => endpoint.method.toLowerCase())));
9
+ const endpointMethods = new Set(ctx.endpointList.map((endpoint) => endpoint.method.toLowerCase()));
10
10
 
11
- const file = `
11
+ const file = `
12
12
  import { queryOptions } from "@tanstack/react-query"
13
13
  import type { EndpointByMethod, ApiClient } from "${ctx.relativeApiClientPath}"
14
14
 
@@ -44,7 +44,9 @@ export const generateTanstackQueryFile = async (ctx: GeneratorContext & { relati
44
44
  };
45
45
 
46
46
  // <EndpointByMethod.Shorthands>
47
- ${Array.from(endpointMethods).map((method) => `export type ${capitalize(method)}Endpoints = EndpointByMethod["${method}"];`).join("\n")}
47
+ ${Array.from(endpointMethods)
48
+ .map((method) => `export type ${capitalize(method)}Endpoints = EndpointByMethod["${method}"];`)
49
+ .join("\n")}
48
50
  // </EndpointByMethod.Shorthands>
49
51
 
50
52
  // <ApiClientTypes>
@@ -67,7 +69,9 @@ export const generateTanstackQueryFile = async (ctx: GeneratorContext & { relati
67
69
  export class TanstackQueryApiClient {
68
70
  constructor(public client: ApiClient) { }
69
71
 
70
- ${Array.from(endpointMethods).map((method) => `
72
+ ${Array.from(endpointMethods)
73
+ .map(
74
+ (method) => `
71
75
  // <ApiClient.${method}>
72
76
  ${method}<Path extends keyof ${capitalize(method)}Endpoints, TEndpoint extends ${capitalize(method)}Endpoints[Path]>(
73
77
  path: Path,
@@ -105,7 +109,9 @@ export const generateTanstackQueryFile = async (ctx: GeneratorContext & { relati
105
109
  return query
106
110
  }
107
111
  // </ApiClient.${method}>
108
- `).join("\n")}
112
+ `,
113
+ )
114
+ .join("\n")}
109
115
 
110
116
  // <ApiClient.request>
111
117
  /**
@@ -139,5 +145,5 @@ export const generateTanstackQueryFile = async (ctx: GeneratorContext & { relati
139
145
  }
140
146
  `;
141
147
 
142
- return prettify(file);
148
+ return prettify(file);
143
149
  };
package/src/types.ts CHANGED
@@ -4,7 +4,7 @@ import type { SchemaObject as SchemaObject3 } from "openapi3-ts/oas30";
4
4
  import type { RefResolver } from "./ref-resolver.ts";
5
5
  import { Box } from "./box.ts";
6
6
 
7
- export type LibSchemaObject = SchemaObject & SchemaObject3
7
+ export type LibSchemaObject = SchemaObject & SchemaObject3;
8
8
 
9
9
  export type BoxDefinition = {
10
10
  type: string;