typed-openapi 1.5.1 → 2.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.
@@ -282,6 +282,69 @@ import { capitalize as capitalize2, groupBy } from "pastable/server";
282
282
  import * as Codegen from "@sinclair/typebox-codegen";
283
283
  import { match } from "ts-pattern";
284
284
  import { type } from "arktype";
285
+ var DEFAULT_SUCCESS_STATUS_CODES = [
286
+ 200,
287
+ 201,
288
+ 202,
289
+ 203,
290
+ 204,
291
+ 205,
292
+ 206,
293
+ 207,
294
+ 208,
295
+ 226,
296
+ 300,
297
+ 301,
298
+ 302,
299
+ 303,
300
+ 304,
301
+ 305,
302
+ 306,
303
+ 307,
304
+ 308
305
+ ];
306
+ var DEFAULT_ERROR_STATUS_CODES = [
307
+ 400,
308
+ 401,
309
+ 402,
310
+ 403,
311
+ 404,
312
+ 405,
313
+ 406,
314
+ 407,
315
+ 408,
316
+ 409,
317
+ 410,
318
+ 411,
319
+ 412,
320
+ 413,
321
+ 414,
322
+ 415,
323
+ 416,
324
+ 417,
325
+ 418,
326
+ 421,
327
+ 422,
328
+ 423,
329
+ 424,
330
+ 425,
331
+ 426,
332
+ 428,
333
+ 429,
334
+ 431,
335
+ 451,
336
+ 500,
337
+ 501,
338
+ 502,
339
+ 503,
340
+ 504,
341
+ 505,
342
+ 506,
343
+ 507,
344
+ 508,
345
+ 510,
346
+ 511
347
+ ];
285
348
  var allowedRuntimes = type("'none' | 'arktype' | 'io-ts' | 'typebox' | 'valibot' | 'yup' | 'zod'");
286
349
  var runtimeValidationGenerator = {
287
350
  arktype: Codegen.ModelToArkType.Generate,
@@ -314,10 +377,17 @@ var replacerByRuntime = {
314
377
  )
315
378
  };
316
379
  var generateFile = (options) => {
317
- const ctx = { ...options, runtime: options.runtime ?? "none" };
380
+ const ctx = {
381
+ ...options,
382
+ runtime: options.runtime ?? "none",
383
+ successStatusCodes: options.successStatusCodes ?? DEFAULT_SUCCESS_STATUS_CODES,
384
+ errorStatusCodes: options.errorStatusCodes ?? DEFAULT_ERROR_STATUS_CODES,
385
+ includeClient: options.includeClient ?? true
386
+ };
318
387
  const schemaList = generateSchemaList(ctx);
319
388
  const endpointSchemaList = options.schemasOnly ? "" : generateEndpointSchemaList(ctx);
320
- const apiClient = options.schemasOnly ? "" : generateApiClient(ctx);
389
+ const endpointByMethod = options.schemasOnly ? "" : generateEndpointByMethod(ctx);
390
+ const apiClient = options.schemasOnly || !ctx.includeClient ? "" : generateApiClient(ctx);
321
391
  const transform = ctx.runtime === "none" ? (file2) => file2 : (file2) => {
322
392
  const model = Codegen.TypeScriptToModel.Generate(file2);
323
393
  const transformer = runtimeValidationGenerator[ctx.runtime];
@@ -337,6 +407,7 @@ var generateFile = (options) => {
337
407
  };
338
408
  const file = `
339
409
  ${transform(schemaList + endpointSchemaList)}
410
+ ${endpointByMethod}
340
411
  ${apiClient}
341
412
  `;
342
413
  return file;
@@ -380,6 +451,20 @@ var responseHeadersObjectToString = (responseHeaders, ctx) => {
380
451
  }
381
452
  return str + "}";
382
453
  };
454
+ var generateResponsesObject = (responses, ctx) => {
455
+ let str = "{";
456
+ for (const [statusCode, responseType] of Object.entries(responses)) {
457
+ const value = ctx.runtime === "none" ? responseType.recompute((box) => {
458
+ if (Box.isReference(box) && !box.params.generics && box.value !== "null") {
459
+ box.value = `Schemas.${box.value}`;
460
+ }
461
+ return box;
462
+ }).value : responseType.value;
463
+ str += `${wrapWithQuotesIfNeeded(statusCode)}: ${value},
464
+ `;
465
+ }
466
+ return str + "}";
467
+ };
383
468
  var generateEndpointSchemaList = (ctx) => {
384
469
  let file = `
385
470
  ${ctx.runtime === "none" ? "export namespace Endpoints {" : ""}
@@ -411,6 +496,7 @@ var generateEndpointSchemaList = (ctx) => {
411
496
  }
412
497
  return box;
413
498
  }).value : endpoint.response.value},
499
+ ${endpoint.responses ? `responses: ${generateResponsesObject(endpoint.responses, ctx)},` : ""}
414
500
  ${endpoint.responseHeaders ? `responseHeaders: ${responseHeadersObjectToString(endpoint.responseHeaders, ctx)},` : ""}
415
501
  }
416
502
  `;
@@ -431,7 +517,7 @@ var generateEndpointByMethod = (ctx) => {
431
517
  return `${method}: {
432
518
  ${list.map(
433
519
  (endpoint) => `"${endpoint.path}": ${ctx.runtime === "none" ? "Endpoints." : ""}${endpoint.meta.alias}`
434
- )}
520
+ ).join(",\n")}
435
521
  }`;
436
522
  }).join(",\n")}
437
523
  }
@@ -447,9 +533,11 @@ var generateEndpointByMethod = (ctx) => {
447
533
  return endpointByMethod + shorthands;
448
534
  };
449
535
  var generateApiClient = (ctx) => {
536
+ if (!ctx.includeClient) {
537
+ return "";
538
+ }
450
539
  const { endpointList } = ctx;
451
540
  const byMethods = groupBy(endpointList, "method");
452
- const endpointSchemaList = generateEndpointByMethod(ctx);
453
541
  const apiClientTypes = `
454
542
  // <ApiClientTypes>
455
543
  export type EndpointParameters = {
@@ -467,6 +555,7 @@ type RequestFormat = "json" | "form-data" | "form-url" | "binary" | "text";
467
555
  export type DefaultEndpoint = {
468
556
  parameters?: EndpointParameters | undefined;
469
557
  response: unknown;
558
+ responses?: Record<string, unknown>;
470
559
  responseHeaders?: Record<string, unknown>;
471
560
  };
472
561
 
@@ -482,11 +571,64 @@ export type Endpoint<TConfig extends DefaultEndpoint = DefaultEndpoint> = {
482
571
  areParametersRequired: boolean;
483
572
  };
484
573
  response: TConfig["response"];
574
+ responses?: TConfig["responses"];
485
575
  responseHeaders?: TConfig["responseHeaders"]
486
576
  };
487
577
 
488
578
  export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise<Response>;
489
579
 
580
+ export const successStatusCodes = [${ctx.successStatusCodes.join(",")}] as const;
581
+ export type SuccessStatusCode = typeof successStatusCodes[number];
582
+
583
+ export const errorStatusCodes = [${ctx.errorStatusCodes.join(",")}] as const;
584
+ export type ErrorStatusCode = typeof errorStatusCodes[number];
585
+
586
+ // Error handling types
587
+ /** @see https://developer.mozilla.org/en-US/docs/Web/API/Response */
588
+ interface SuccessResponse<TSuccess, TStatusCode> extends Omit<Response, "ok" | "status" | "json"> {
589
+ ok: true;
590
+ status: TStatusCode;
591
+ data: TSuccess;
592
+ /** [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Response/json) */
593
+ json: () => Promise<TSuccess>;
594
+ }
595
+
596
+ /** @see https://developer.mozilla.org/en-US/docs/Web/API/Response */
597
+ interface ErrorResponse<TData, TStatusCode> extends Omit<Response, "ok" | "status" | "json"> {
598
+ ok: false;
599
+ status: TStatusCode;
600
+ data: TData;
601
+ /** [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Response/json) */
602
+ json: () => Promise<TData>;
603
+ }
604
+
605
+ export type TypedApiResponse<TSuccess, TAllResponses extends Record<string | number, unknown> = {}> =
606
+ (keyof TAllResponses extends never
607
+ ? SuccessResponse<TSuccess, number>
608
+ : {
609
+ [K in keyof TAllResponses]: K extends string
610
+ ? K extends \`\${infer TStatusCode extends number}\`
611
+ ? TStatusCode extends SuccessStatusCode
612
+ ? SuccessResponse<TSuccess, TStatusCode>
613
+ : ErrorResponse<TAllResponses[K], TStatusCode>
614
+ : never
615
+ : K extends number
616
+ ? K extends SuccessStatusCode
617
+ ? SuccessResponse<TSuccess, K>
618
+ : ErrorResponse<TAllResponses[K], K>
619
+ : never;
620
+ }[keyof TAllResponses]);
621
+
622
+ export type SafeApiResponse<TEndpoint> = TEndpoint extends { response: infer TSuccess; responses: infer TResponses }
623
+ ? TResponses extends Record<string, unknown>
624
+ ? TypedApiResponse<TSuccess, TResponses>
625
+ : SuccessResponse<TSuccess, number>
626
+ : TEndpoint extends { response: infer TSuccess }
627
+ ? SuccessResponse<TSuccess, number>
628
+ : never;
629
+
630
+ export type InferResponseByStatus<TEndpoint, TStatusCode> = Extract<SafeApiResponse<TEndpoint>, { status: TStatusCode }>
631
+
490
632
  type RequiredKeys<T> = {
491
633
  [P in keyof T]-?: undefined extends T[P] ? never : P;
492
634
  }[keyof T];
@@ -496,9 +638,23 @@ type MaybeOptionalArg<T> = RequiredKeys<T> extends never ? [config?: T] : [confi
496
638
  // </ApiClientTypes>
497
639
  `;
498
640
  const apiClient = `
641
+ // <TypedResponseError>
642
+ export class TypedResponseError extends Error {
643
+ response: ErrorResponse<unknown, ErrorStatusCode>;
644
+ status: number;
645
+ constructor(response: ErrorResponse<unknown, ErrorStatusCode>) {
646
+ super(\`HTTP \${response.status}: \${response.statusText}\`);
647
+ this.name = 'TypedResponseError';
648
+ this.response = response;
649
+ this.status = response.status;
650
+ }
651
+ }
652
+ // </TypedResponseError>
499
653
  // <ApiClient>
500
654
  export class ApiClient {
501
655
  baseUrl: string = "";
656
+ successStatusCodes = successStatusCodes;
657
+ errorStatusCodes = errorStatusCodes;
502
658
 
503
659
  constructor(public fetcher: Fetcher) {}
504
660
 
@@ -521,10 +677,44 @@ export class ApiClient {
521
677
  return endpointByMethod.length ? `// <ApiClient.${method}>
522
678
  ${method}<Path extends keyof ${capitalizedMethod}Endpoints, TEndpoint extends ${capitalizedMethod}Endpoints[Path]>(
523
679
  path: Path,
524
- ...params: MaybeOptionalArg<${match(ctx.runtime).with("zod", "yup", () => infer(`TEndpoint["parameters"]`)).with("arktype", "io-ts", "typebox", "valibot", () => infer(`TEndpoint`) + `["parameters"]`).otherwise(() => `TEndpoint["parameters"]`)}>
525
- ): Promise<${match(ctx.runtime).with("zod", "yup", () => infer(`TEndpoint["response"]`)).with("arktype", "io-ts", "typebox", "valibot", () => infer(`TEndpoint`) + `["response"]`).otherwise(() => `TEndpoint["response"]`)}> {
526
- return this.fetcher("${method}", this.baseUrl + path, params[0])
527
- .then(response => this.parseResponse(response))${match(ctx.runtime).with("zod", "yup", () => `as Promise<${infer(`TEndpoint["response"]`)}>`).with("arktype", "io-ts", "typebox", "valibot", () => `as Promise<${infer(`TEndpoint`) + `["response"]`}>`).otherwise(() => `as Promise<TEndpoint["response"]>`)};
680
+ ...params: MaybeOptionalArg<${match(ctx.runtime).with("zod", "yup", () => infer(`TEndpoint["parameters"]`)).with("arktype", "io-ts", "typebox", "valibot", () => infer(`TEndpoint`) + `["parameters"]`).otherwise(() => `TEndpoint["parameters"]`)} & { withResponse?: false; throwOnStatusError?: boolean }>
681
+ ): Promise<${match(ctx.runtime).with("zod", "yup", () => infer(`TEndpoint["response"]`)).with("arktype", "io-ts", "typebox", "valibot", () => infer(`TEndpoint`) + `["response"]`).otherwise(() => `TEndpoint["response"]`)}>;
682
+
683
+ ${method}<Path extends keyof ${capitalizedMethod}Endpoints, TEndpoint extends ${capitalizedMethod}Endpoints[Path]>(
684
+ path: Path,
685
+ ...params: MaybeOptionalArg<${match(ctx.runtime).with("zod", "yup", () => infer(`TEndpoint["parameters"]`)).with("arktype", "io-ts", "typebox", "valibot", () => infer(`TEndpoint`) + `["parameters"]`).otherwise(() => `TEndpoint["parameters"]`)} & { withResponse: true; throwOnStatusError?: boolean }>
686
+ ): Promise<SafeApiResponse<TEndpoint>>;
687
+
688
+ ${method}<Path extends keyof ${capitalizedMethod}Endpoints, TEndpoint extends ${capitalizedMethod}Endpoints[Path]>(
689
+ path: Path,
690
+ ...params: MaybeOptionalArg<any>
691
+ ): Promise<any> {
692
+ const requestParams = params[0];
693
+ const withResponse = requestParams?.withResponse;
694
+ const { withResponse: _, throwOnStatusError = withResponse ? false : true, ...fetchParams } = requestParams || {};
695
+
696
+ const promise = this.fetcher("${method}", this.baseUrl + path, Object.keys(fetchParams).length ? requestParams : undefined)
697
+ .then(async (response) => {
698
+ const data = await this.parseResponse(response);
699
+ const typedResponse = Object.assign(response, {
700
+ data: data,
701
+ json: () => Promise.resolve(data)
702
+ }) as SafeApiResponse<TEndpoint>;
703
+
704
+ if (throwOnStatusError && errorStatusCodes.includes(response.status as never)) {
705
+ throw new TypedResponseError(typedResponse as never);
706
+ }
707
+
708
+ return withResponse ? typedResponse : data;
709
+ });
710
+
711
+ return promise ${match(ctx.runtime).with("zod", "yup", () => `as Promise<${infer(`TEndpoint["response"]`)}>`).with(
712
+ "arktype",
713
+ "io-ts",
714
+ "typebox",
715
+ "valibot",
716
+ () => `as Promise<${infer(`TEndpoint`) + `["response"]`}>`
717
+ ).otherwise(() => `as Promise<TEndpoint["response"]>`)}
528
718
  }
529
719
  // </ApiClient.${method}>
530
720
  ` : "";
@@ -552,11 +742,8 @@ export class ApiClient {
552
742
  "valibot",
553
743
  () => inferByRuntime[ctx.runtime](`TEndpoint`) + `["parameters"]`
554
744
  ).otherwise(() => `TEndpoint extends { parameters: infer Params } ? Params : never`)}>)
555
- : Promise<Omit<Response, "json"> & {
556
- /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json) */
557
- json: () => Promise<TEndpoint extends { response: infer Res } ? Res : never>;
558
- }> {
559
- return this.fetcher(method, this.baseUrl + (path as string), params[0] as EndpointParameters);
745
+ : Promise<SafeApiResponse<TEndpoint>> {
746
+ return this.fetcher(method, this.baseUrl + (path as string), params[0] as EndpointParameters) as Promise<SafeApiResponse<TEndpoint>>;
560
747
  }
561
748
  // </ApiClient.request>
562
749
  }
@@ -574,11 +761,26 @@ export function createApiClient(fetcher: Fetcher, baseUrl?: string) {
574
761
  api.get("/users").then((users) => console.log(users));
575
762
  api.post("/users", { body: { name: "John" } }).then((user) => console.log(user));
576
763
  api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user));
764
+
765
+ // With error handling
766
+ const result = await api.get("/users/{id}", { path: { id: "123" }, withResponse: true });
767
+ if (result.ok) {
768
+ // Access data directly
769
+ const user = result.data;
770
+ console.log(user);
771
+
772
+ // Or use the json() method for compatibility
773
+ const userFromJson = await result.json();
774
+ console.log(userFromJson);
775
+ } else {
776
+ const error = result.data;
777
+ console.error(\`Error \${result.status}:\`, error);
778
+ }
577
779
  */
578
780
 
579
- // </ApiClient
781
+ // </ApiClient>
580
782
  `;
581
- return endpointSchemaList + apiClientTypes + apiClient;
783
+ return apiClientTypes + apiClient;
582
784
  };
583
785
 
584
786
  // src/ref-resolver.ts
@@ -944,14 +1146,45 @@ var mapOpenApiEndpoints = (doc, options) => {
944
1146
  endpoint.parameters = Object.keys(params).length ? params : void 0;
945
1147
  }
946
1148
  let responseObject;
1149
+ const allResponses = {};
947
1150
  Object.entries(operation.responses ?? {}).map(([status, responseOrRef]) => {
948
1151
  const statusCode = Number(status);
949
- if (statusCode >= 200 && statusCode < 300) {
950
- responseObject = refs.unwrap(responseOrRef);
1152
+ const responseObj = refs.unwrap(responseOrRef);
1153
+ const content2 = responseObj?.content;
1154
+ if (content2) {
1155
+ const matchingMediaType = Object.keys(content2).find(isResponseMediaType);
1156
+ if (matchingMediaType && content2[matchingMediaType]) {
1157
+ allResponses[status] = openApiSchemaToTs({
1158
+ schema: content2[matchingMediaType]?.schema ?? {},
1159
+ ctx
1160
+ });
1161
+ } else {
1162
+ allResponses[status] = openApiSchemaToTs({ schema: {}, ctx });
1163
+ }
1164
+ } else {
1165
+ allResponses[status] = openApiSchemaToTs({ schema: {}, ctx });
1166
+ }
1167
+ if (statusCode >= 200 && statusCode < 300 && !responseObject) {
1168
+ responseObject = responseObj;
951
1169
  }
952
1170
  });
953
1171
  if (!responseObject && operation.responses?.default) {
954
1172
  responseObject = refs.unwrap(operation.responses.default);
1173
+ if (!allResponses["default"]) {
1174
+ const content2 = responseObject?.content;
1175
+ if (content2) {
1176
+ const matchingMediaType = Object.keys(content2).find(isResponseMediaType);
1177
+ if (matchingMediaType && content2[matchingMediaType]) {
1178
+ allResponses["default"] = openApiSchemaToTs({
1179
+ schema: content2[matchingMediaType]?.schema ?? {},
1180
+ ctx
1181
+ });
1182
+ }
1183
+ }
1184
+ }
1185
+ }
1186
+ if (Object.keys(allResponses).length > 0) {
1187
+ endpoint.responses = allResponses;
955
1188
  }
956
1189
  const content = responseObject?.content;
957
1190
  if (content) {
@@ -998,7 +1231,8 @@ var generateTanstackQueryFile = async (ctx) => {
998
1231
  const endpointMethods = new Set(ctx.endpointList.map((endpoint) => endpoint.method.toLowerCase()));
999
1232
  const file = `
1000
1233
  import { queryOptions } from "@tanstack/react-query"
1001
- import type { EndpointByMethod, ApiClient } from "${ctx.relativeApiClientPath}"
1234
+ import type { EndpointByMethod, ApiClient, SuccessStatusCode, ErrorStatusCode, InferResponseByStatus } from "${ctx.relativeApiClientPath}"
1235
+ import { errorStatusCodes, TypedResponseError } from "${ctx.relativeApiClientPath}"
1002
1236
 
1003
1237
  type EndpointQueryKey<TOptions extends EndpointParameters> = [
1004
1238
  TOptions & {
@@ -1062,30 +1296,36 @@ var generateTanstackQueryFile = async (ctx) => {
1062
1296
  path: Path,
1063
1297
  ...params: MaybeOptionalArg<TEndpoint["parameters"]>
1064
1298
  ) {
1065
- const queryKey = createQueryKey(path, params[0]);
1299
+ const queryKey = createQueryKey(path as string, params[0]);
1066
1300
  const query = {
1067
1301
  /** type-only property if you need easy access to the endpoint params */
1068
1302
  "~endpoint": {} as TEndpoint,
1069
1303
  queryKey,
1304
+ queryFn: {} as "You need to pass .queryOptions to the useQuery hook",
1070
1305
  queryOptions: queryOptions({
1071
1306
  queryFn: async ({ queryKey, signal, }) => {
1072
- const res = await this.client.${method}(path, {
1073
- ...params,
1074
- ...queryKey[0],
1307
+ const requestParams = {
1308
+ ...(params[0] || {}),
1309
+ ...(queryKey[0] || {}),
1075
1310
  signal,
1076
- });
1311
+ withResponse: false as const
1312
+ };
1313
+ const res = await this.client.${method}(path, requestParams);
1077
1314
  return res as TEndpoint["response"];
1078
1315
  },
1079
1316
  queryKey: queryKey
1080
1317
  }),
1318
+ mutationFn: {} as "You need to pass .mutationOptions to the useMutation hook",
1081
1319
  mutationOptions: {
1082
1320
  mutationKey: queryKey,
1083
1321
  mutationFn: async (localOptions: TEndpoint extends { parameters: infer Parameters} ? Parameters: never) => {
1084
- const res = await this.client.${method}(path, {
1085
- ...params,
1086
- ...queryKey[0],
1087
- ...localOptions,
1088
- });
1322
+ const requestParams = {
1323
+ ...(params[0] || {}),
1324
+ ...(queryKey[0] || {}),
1325
+ ...(localOptions || {}),
1326
+ withResponse: false as const
1327
+ };
1328
+ const res = await this.client.${method}(path, requestParams);
1089
1329
  return res as TEndpoint["response"];
1090
1330
  }
1091
1331
  }
@@ -1099,32 +1339,67 @@ var generateTanstackQueryFile = async (ctx) => {
1099
1339
 
1100
1340
  // <ApiClient.request>
1101
1341
  /**
1102
- * Generic mutation method with full type-safety for any endpoint that doesnt require parameters to be passed initially
1342
+ * Generic mutation method with full type-safety for any endpoint; it doesnt require parameters to be passed initially
1343
+ * but instead will require them to be passed when calling the mutation.mutate() method
1103
1344
  */
1104
1345
  mutation<
1105
1346
  TMethod extends keyof EndpointByMethod,
1106
1347
  TPath extends keyof EndpointByMethod[TMethod],
1107
1348
  TEndpoint extends EndpointByMethod[TMethod][TPath],
1108
- TSelection,
1109
- >(method: TMethod, path: TPath, selectFn?: (res: Omit<Response, "json"> & {
1110
- /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json) */
1111
- json: () => Promise<TEndpoint extends { response: infer Res } ? Res : never>;
1112
- }) => TSelection) {
1349
+ TWithResponse extends boolean = false,
1350
+ TSelection = TWithResponse extends true
1351
+ ? InferResponseByStatus<TEndpoint, SuccessStatusCode>
1352
+ : TEndpoint extends { response: infer Res } ? Res : never,
1353
+ TError = TEndpoint extends { responses: infer TResponses }
1354
+ ? TResponses extends Record<string | number, unknown>
1355
+ ? InferResponseByStatus<TEndpoint, ErrorStatusCode>
1356
+ : Error
1357
+ : Error
1358
+ >(method: TMethod, path: TPath, options?: {
1359
+ withResponse?: TWithResponse;
1360
+ selectFn?: (res: TWithResponse extends true
1361
+ ? InferResponseByStatus<TEndpoint, SuccessStatusCode>
1362
+ : TEndpoint extends { response: infer Res } ? Res : never
1363
+ ) => TSelection;
1364
+ throwOnStatusError?: boolean
1365
+ }) {
1113
1366
  const mutationKey = [{ method, path }] as const;
1114
1367
  return {
1115
1368
  /** type-only property if you need easy access to the endpoint params */
1116
1369
  "~endpoint": {} as TEndpoint,
1117
1370
  mutationKey: mutationKey,
1371
+ mutationFn: {} as "You need to pass .mutationOptions to the useMutation hook",
1118
1372
  mutationOptions: {
1119
1373
  mutationKey: mutationKey,
1120
- mutationFn: async (params: TEndpoint extends { parameters: infer Parameters } ? Parameters : never) => {
1121
- const response = await this.client.request(method, path, params);
1122
- const res = selectFn ? selectFn(response) : response
1123
- return res as unknown extends TSelection ? typeof response : Awaited<TSelection>
1124
- },
1125
- },
1126
- };
1374
+ mutationFn: async <TLocalWithResponse extends boolean = TWithResponse, TLocalSelection = TLocalWithResponse extends true
1375
+ ? InferResponseByStatus<TEndpoint, SuccessStatusCode>
1376
+ : TEndpoint extends { response: infer Res }
1377
+ ? Res
1378
+ : never>
1379
+ (params: (TEndpoint extends { parameters: infer Parameters } ? Parameters : {}) & {
1380
+ withResponse?: TLocalWithResponse;
1381
+ throwOnStatusError?: boolean;
1382
+ }): Promise<TLocalSelection> => {
1383
+ const withResponse = params.withResponse ??options?.withResponse ?? false;
1384
+ const throwOnStatusError = params.throwOnStatusError ?? options?.throwOnStatusError ?? (withResponse ? false : true);
1385
+ const selectFn = options?.selectFn;
1386
+ const response = await (this.client as any)[method](path, { ...params as any, withResponse: true, throwOnStatusError: false });
1387
+
1388
+ if (throwOnStatusError && errorStatusCodes.includes(response.status as never)) {
1389
+ throw new TypedResponseError(response as never);
1390
+ }
1391
+
1392
+ // Return just the data if withResponse is false, otherwise return the full response
1393
+ const finalResponse = withResponse ? response : response.data;
1394
+ const res = selectFn ? selectFn(finalResponse as any) : finalResponse;
1395
+ return res as never;
1396
+ }
1397
+ } satisfies import("@tanstack/react-query").UseMutationOptions<TSelection, TError, (TEndpoint extends { parameters: infer Parameters } ? Parameters : {}) & {
1398
+ withResponse?: boolean;
1399
+ throwOnStatusError?: boolean;
1400
+ }>,
1127
1401
  }
1402
+ }
1128
1403
  // </ApiClient.request>
1129
1404
  }
1130
1405
  `;
@@ -1136,6 +1411,8 @@ export {
1136
1411
  createFactory,
1137
1412
  createBoxFactory,
1138
1413
  openApiSchemaToTs,
1414
+ DEFAULT_SUCCESS_STATUS_CODES,
1415
+ DEFAULT_ERROR_STATUS_CODES,
1139
1416
  allowedRuntimes,
1140
1417
  generateFile,
1141
1418
  createRefResolver,
@@ -1,9 +1,11 @@
1
1
  import {
2
+ DEFAULT_ERROR_STATUS_CODES,
3
+ DEFAULT_SUCCESS_STATUS_CODES,
2
4
  allowedRuntimes,
3
5
  generateFile,
4
6
  generateTanstackQueryFile,
5
7
  mapOpenApiEndpoints
6
- } from "./chunk-OVT6OLBK.js";
8
+ } from "./chunk-E6A7N4ND.js";
7
9
  import {
8
10
  prettify
9
11
  } from "./chunk-KAEXXJ7X.js";
@@ -26,20 +28,28 @@ var optionsSchema = type({
26
28
  "output?": "string",
27
29
  runtime: allowedRuntimes,
28
30
  tanstack: "boolean | string",
29
- schemasOnly: "boolean"
31
+ schemasOnly: "boolean",
32
+ "includeClient?": "boolean | 'true' | 'false'",
33
+ "successStatusCodes?": "string",
34
+ "errorStatusCodes?": "string"
30
35
  });
31
36
  async function generateClientFiles(input, options) {
32
37
  const openApiDoc = await SwaggerParser.bundle(input);
33
38
  const ctx = mapOpenApiEndpoints(openApiDoc, options);
34
39
  console.log(`Found ${ctx.endpointList.length} endpoints`);
35
- const content = await prettify(
36
- generateFile({
37
- ...ctx,
38
- runtime: options.runtime,
39
- schemasOnly: options.schemasOnly,
40
- nameTransform: options.nameTransform
41
- })
42
- );
40
+ const successStatusCodes = options.successStatusCodes ? options.successStatusCodes.split(",").map((code) => parseInt(code.trim(), 10)) : void 0;
41
+ const errorStatusCodes = options.errorStatusCodes ? options.errorStatusCodes.split(",").map((code) => parseInt(code.trim(), 10)) : void 0;
42
+ const includeClient = options.includeClient === "false" ? false : options.includeClient === "true" ? true : options.includeClient;
43
+ const generatorOptions = {
44
+ ...ctx,
45
+ runtime: options.runtime,
46
+ schemasOnly: options.schemasOnly,
47
+ nameTransform: options.nameTransform,
48
+ includeClient: includeClient ?? true,
49
+ successStatusCodes: successStatusCodes ?? DEFAULT_SUCCESS_STATUS_CODES,
50
+ errorStatusCodes: errorStatusCodes ?? DEFAULT_ERROR_STATUS_CODES
51
+ };
52
+ const content = await prettify(generateFile(generatorOptions));
43
53
  const outputPath = join(
44
54
  cwd,
45
55
  options.output ?? input + `.${options.runtime === "none" ? "client" : options.runtime}.ts`
@@ -49,7 +59,7 @@ async function generateClientFiles(input, options) {
49
59
  await writeFile(outputPath, content);
50
60
  if (options.tanstack) {
51
61
  const tanstackContent = await generateTanstackQueryFile({
52
- ...ctx,
62
+ ...generatorOptions,
53
63
  relativeApiClientPath: "./" + basename(outputPath)
54
64
  });
55
65
  const tanstackOutputPath = join(
package/dist/cli.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  generateClientFiles
3
- } from "./chunk-O7DZWQK4.js";
3
+ } from "./chunk-MCVYB63W.js";
4
4
  import {
5
5
  allowedRuntimes
6
- } from "./chunk-OVT6OLBK.js";
6
+ } from "./chunk-E6A7N4ND.js";
7
7
  import "./chunk-KAEXXJ7X.js";
8
8
 
9
9
  // src/cli.ts
@@ -12,10 +12,13 @@ import { readFileSync } from "fs";
12
12
  var { name, version } = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
13
13
  var cli = cac(name);
14
14
  cli.command("<input>", "Generate").option("-o, --output <path>", "Output path for the api client ts file (defaults to `<input>.<runtime>.ts`)").option(
15
- "-r, --runtime <name>",
15
+ "-r, --runtime <n>",
16
16
  `Runtime to use for validation; defaults to \`none\`; available: ${allowedRuntimes.toString()}`,
17
17
  { default: "none" }
18
- ).option("--schemas-only", "Only generate schemas, skipping client generation (defaults to false)", { default: false }).option(
18
+ ).option("--schemas-only", "Only generate schemas, skipping client generation (defaults to false)", { default: false }).option("--include-client", "Include API client types and implementation (defaults to true)", { default: true }).option(
19
+ "--success-status-codes <codes>",
20
+ "Comma-separated list of success status codes (defaults to 2xx and 3xx ranges)"
21
+ ).option("--error-status-codes <codes>", "Comma-separated list of error status codes (defaults to 4xx and 5xx ranges)").option(
19
22
  "--tanstack [name]",
20
23
  "Generate tanstack client, defaults to false, can optionally specify a name for the generated file"
21
24
  ).action(async (input, _options) => {
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ReferenceObject } from 'openapi3-ts/oas31';
2
- import { S as StringOrBox, O as OpenapiSchemaConvertContext, L as LibSchemaObject, B as BoxFactory, m as mapOpenApiEndpoints, N as NameTransformOptions, a as OpenapiSchemaConvertArgs, b as Box, A as AnyBoxDef } from './types-DLE5RaXi.js';
3
- export { q as AnyBox, j as BoxArray, f as BoxDefinition, i as BoxIntersection, o as BoxKeyword, n as BoxLiteral, p as BoxObject, k as BoxOptional, g as BoxParams, l as BoxRef, h as BoxUnion, c as Endpoint, E as EndpointParameters, F as FactoryCreator, G as GenericFactory, M as Method, R as RefInfo, e as RefResolver, W as WithSchema, d as createRefResolver } from './types-DLE5RaXi.js';
2
+ import { S as StringOrBox, O as OpenapiSchemaConvertContext, L as LibSchemaObject, B as BoxFactory, m as mapOpenApiEndpoints, N as NameTransformOptions, a as OpenapiSchemaConvertArgs, b as Box, A as AnyBoxDef } from './types-DsI2d-HE.js';
3
+ export { q as AnyBox, j as BoxArray, f as BoxDefinition, i as BoxIntersection, o as BoxKeyword, n as BoxLiteral, p as BoxObject, k as BoxOptional, g as BoxParams, l as BoxRef, h as BoxUnion, c as Endpoint, E as EndpointParameters, F as FactoryCreator, G as GenericFactory, M as Method, R as RefInfo, e as RefResolver, W as WithSchema, d as createRefResolver } from './types-DsI2d-HE.js';
4
4
  import * as arktype_internal_methods_string_ts from 'arktype/internal/methods/string.ts';
5
5
  import * as Codegen from '@sinclair/typebox-codegen';
6
6
  import 'openapi3-ts/oas30';
@@ -16,6 +16,9 @@ type GeneratorOptions$1 = ReturnType<typeof mapOpenApiEndpoints> & {
16
16
  runtime?: "none" | keyof typeof runtimeValidationGenerator;
17
17
  schemasOnly?: boolean;
18
18
  nameTransform?: NameTransformOptions | undefined;
19
+ successStatusCodes?: readonly number[];
20
+ errorStatusCodes?: readonly number[];
21
+ includeClient?: boolean;
19
22
  };
20
23
  declare const allowedRuntimes: arktype_internal_methods_string_ts.StringType<"none" | "arktype" | "io-ts" | "typebox" | "valibot" | "yup" | "zod", {}>;
21
24
  type OutputRuntime = typeof allowedRuntimes.infer;
@@ -30,7 +33,9 @@ declare const runtimeValidationGenerator: {
30
33
  declare const generateFile: (options: GeneratorOptions$1) => string;
31
34
 
32
35
  type GeneratorOptions = ReturnType<typeof mapOpenApiEndpoints>;
33
- type GeneratorContext = Required<GeneratorOptions>;
36
+ type GeneratorContext = Required<GeneratorOptions> & {
37
+ errorStatusCodes?: readonly number[];
38
+ };
34
39
  declare const generateTanstackQueryFile: (ctx: GeneratorContext & {
35
40
  relativeApiClientPath: string;
36
41
  }) => Promise<string>;
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  openApiSchemaToTs,
9
9
  tsFactory,
10
10
  unwrap
11
- } from "./chunk-OVT6OLBK.js";
11
+ } from "./chunk-E6A7N4ND.js";
12
12
  import "./chunk-KAEXXJ7X.js";
13
13
  export {
14
14
  createBoxFactory,