typed-openapi 1.5.1 → 2.0.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.
@@ -150,7 +150,9 @@ var openApiSchemaToTs = ({ schema, meta: _inheritedMeta, ctx }) => {
150
150
  meta
151
151
  });
152
152
  }
153
- additionalProperties = t.object({ [t.string().value]: additionalPropertiesType });
153
+ additionalProperties = t.literal(
154
+ `Record<string, ${additionalPropertiesType ? additionalPropertiesType.value : t.any().value}>`
155
+ );
154
156
  }
155
157
  const hasRequiredArray = schema.required && schema.required.length > 0;
156
158
  const isPartial = !schema.required?.length;
@@ -282,6 +284,69 @@ import { capitalize as capitalize2, groupBy } from "pastable/server";
282
284
  import * as Codegen from "@sinclair/typebox-codegen";
283
285
  import { match } from "ts-pattern";
284
286
  import { type } from "arktype";
287
+ var DEFAULT_SUCCESS_STATUS_CODES = [
288
+ 200,
289
+ 201,
290
+ 202,
291
+ 203,
292
+ 204,
293
+ 205,
294
+ 206,
295
+ 207,
296
+ 208,
297
+ 226,
298
+ 300,
299
+ 301,
300
+ 302,
301
+ 303,
302
+ 304,
303
+ 305,
304
+ 306,
305
+ 307,
306
+ 308
307
+ ];
308
+ var DEFAULT_ERROR_STATUS_CODES = [
309
+ 400,
310
+ 401,
311
+ 402,
312
+ 403,
313
+ 404,
314
+ 405,
315
+ 406,
316
+ 407,
317
+ 408,
318
+ 409,
319
+ 410,
320
+ 411,
321
+ 412,
322
+ 413,
323
+ 414,
324
+ 415,
325
+ 416,
326
+ 417,
327
+ 418,
328
+ 421,
329
+ 422,
330
+ 423,
331
+ 424,
332
+ 425,
333
+ 426,
334
+ 428,
335
+ 429,
336
+ 431,
337
+ 451,
338
+ 500,
339
+ 501,
340
+ 502,
341
+ 503,
342
+ 504,
343
+ 505,
344
+ 506,
345
+ 507,
346
+ 508,
347
+ 510,
348
+ 511
349
+ ];
285
350
  var allowedRuntimes = type("'none' | 'arktype' | 'io-ts' | 'typebox' | 'valibot' | 'yup' | 'zod'");
286
351
  var runtimeValidationGenerator = {
287
352
  arktype: Codegen.ModelToArkType.Generate,
@@ -314,10 +379,17 @@ var replacerByRuntime = {
314
379
  )
315
380
  };
316
381
  var generateFile = (options) => {
317
- const ctx = { ...options, runtime: options.runtime ?? "none" };
382
+ const ctx = {
383
+ ...options,
384
+ runtime: options.runtime ?? "none",
385
+ successStatusCodes: options.successStatusCodes ?? DEFAULT_SUCCESS_STATUS_CODES,
386
+ errorStatusCodes: options.errorStatusCodes ?? DEFAULT_ERROR_STATUS_CODES,
387
+ includeClient: options.includeClient ?? true
388
+ };
318
389
  const schemaList = generateSchemaList(ctx);
319
390
  const endpointSchemaList = options.schemasOnly ? "" : generateEndpointSchemaList(ctx);
320
- const apiClient = options.schemasOnly ? "" : generateApiClient(ctx);
391
+ const endpointByMethod = options.schemasOnly ? "" : generateEndpointByMethod(ctx);
392
+ const apiClient = options.schemasOnly || !ctx.includeClient ? "" : generateApiClient(ctx);
321
393
  const transform = ctx.runtime === "none" ? (file2) => file2 : (file2) => {
322
394
  const model = Codegen.TypeScriptToModel.Generate(file2);
323
395
  const transformer = runtimeValidationGenerator[ctx.runtime];
@@ -337,6 +409,7 @@ var generateFile = (options) => {
337
409
  };
338
410
  const file = `
339
411
  ${transform(schemaList + endpointSchemaList)}
412
+ ${endpointByMethod}
340
413
  ${apiClient}
341
414
  `;
342
415
  return file;
@@ -357,8 +430,18 @@ var generateSchemaList = ({ refs, runtime }) => {
357
430
  ${runtime === "none" ? "}" : ""}
358
431
  `;
359
432
  };
360
- var parameterObjectToString = (parameters) => {
361
- if (parameters instanceof Box) return parameters.value;
433
+ var parameterObjectToString = (parameters, ctx) => {
434
+ if (parameters instanceof Box) {
435
+ if (ctx.runtime === "none") {
436
+ return parameters.recompute((box) => {
437
+ if (Box.isReference(box) && !box.params.generics && box.value !== "null") {
438
+ box.value = `Schemas.${box.value}`;
439
+ }
440
+ return box;
441
+ }).value;
442
+ }
443
+ return parameters.value;
444
+ }
362
445
  let str = "{";
363
446
  for (const [key, box] of Object.entries(parameters)) {
364
447
  str += `${wrapWithQuotesIfNeeded(key)}${box.type === "optional" ? "?" : ""}: ${box.value},
@@ -380,6 +463,20 @@ var responseHeadersObjectToString = (responseHeaders, ctx) => {
380
463
  }
381
464
  return str + "}";
382
465
  };
466
+ var generateResponsesObject = (responses, ctx) => {
467
+ let str = "{";
468
+ for (const [statusCode, responseType] of Object.entries(responses)) {
469
+ const value = ctx.runtime === "none" ? responseType.recompute((box) => {
470
+ if (Box.isReference(box) && !box.params.generics && box.value !== "null") {
471
+ box.value = `Schemas.${box.value}`;
472
+ }
473
+ return box;
474
+ }).value : responseType.value;
475
+ str += `${wrapWithQuotesIfNeeded(statusCode)}: ${value},
476
+ `;
477
+ }
478
+ return str + "}";
479
+ };
383
480
  var generateEndpointSchemaList = (ctx) => {
384
481
  let file = `
385
482
  ${ctx.runtime === "none" ? "export namespace Endpoints {" : ""}
@@ -393,16 +490,17 @@ var generateEndpointSchemaList = (ctx) => {
393
490
  path: "${endpoint.path}",
394
491
  requestFormat: "${endpoint.requestFormat}",
395
492
  ${endpoint.meta.hasParameters ? `parameters: {
396
- ${parameters.query ? `query: ${parameterObjectToString(parameters.query)},` : ""}
397
- ${parameters.path ? `path: ${parameterObjectToString(parameters.path)},` : ""}
398
- ${parameters.header ? `header: ${parameterObjectToString(parameters.header)},` : ""}
493
+ ${parameters.query ? `query: ${parameterObjectToString(parameters.query, ctx)},` : ""}
494
+ ${parameters.path ? `path: ${parameterObjectToString(parameters.path, ctx)},` : ""}
495
+ ${parameters.header ? `header: ${parameterObjectToString(parameters.header, ctx)},` : ""}
399
496
  ${parameters.body ? `body: ${parameterObjectToString(
400
497
  ctx.runtime === "none" ? parameters.body.recompute((box) => {
401
498
  if (Box.isReference(box) && !box.params.generics) {
402
499
  box.value = `Schemas.${box.value}`;
403
500
  }
404
501
  return box;
405
- }) : parameters.body
502
+ }) : parameters.body,
503
+ ctx
406
504
  )},` : ""}
407
505
  }` : "parameters: never,"}
408
506
  response: ${ctx.runtime === "none" ? endpoint.response.recompute((box) => {
@@ -411,6 +509,7 @@ var generateEndpointSchemaList = (ctx) => {
411
509
  }
412
510
  return box;
413
511
  }).value : endpoint.response.value},
512
+ ${endpoint.responses ? `responses: ${generateResponsesObject(endpoint.responses, ctx)},` : ""}
414
513
  ${endpoint.responseHeaders ? `responseHeaders: ${responseHeadersObjectToString(endpoint.responseHeaders, ctx)},` : ""}
415
514
  }
416
515
  `;
@@ -431,7 +530,7 @@ var generateEndpointByMethod = (ctx) => {
431
530
  return `${method}: {
432
531
  ${list.map(
433
532
  (endpoint) => `"${endpoint.path}": ${ctx.runtime === "none" ? "Endpoints." : ""}${endpoint.meta.alias}`
434
- )}
533
+ ).join(",\n")}
435
534
  }`;
436
535
  }).join(",\n")}
437
536
  }
@@ -447,9 +546,11 @@ var generateEndpointByMethod = (ctx) => {
447
546
  return endpointByMethod + shorthands;
448
547
  };
449
548
  var generateApiClient = (ctx) => {
549
+ if (!ctx.includeClient) {
550
+ return "";
551
+ }
450
552
  const { endpointList } = ctx;
451
553
  const byMethods = groupBy(endpointList, "method");
452
- const endpointSchemaList = generateEndpointByMethod(ctx);
453
554
  const apiClientTypes = `
454
555
  // <ApiClientTypes>
455
556
  export type EndpointParameters = {
@@ -467,6 +568,7 @@ type RequestFormat = "json" | "form-data" | "form-url" | "binary" | "text";
467
568
  export type DefaultEndpoint = {
468
569
  parameters?: EndpointParameters | undefined;
469
570
  response: unknown;
571
+ responses?: Record<string, unknown>;
470
572
  responseHeaders?: Record<string, unknown>;
471
573
  };
472
574
 
@@ -482,11 +584,64 @@ export type Endpoint<TConfig extends DefaultEndpoint = DefaultEndpoint> = {
482
584
  areParametersRequired: boolean;
483
585
  };
484
586
  response: TConfig["response"];
587
+ responses?: TConfig["responses"];
485
588
  responseHeaders?: TConfig["responseHeaders"]
486
589
  };
487
590
 
488
591
  export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise<Response>;
489
592
 
593
+ export const successStatusCodes = [${ctx.successStatusCodes.join(",")}] as const;
594
+ export type SuccessStatusCode = typeof successStatusCodes[number];
595
+
596
+ export const errorStatusCodes = [${ctx.errorStatusCodes.join(",")}] as const;
597
+ export type ErrorStatusCode = typeof errorStatusCodes[number];
598
+
599
+ // Error handling types
600
+ /** @see https://developer.mozilla.org/en-US/docs/Web/API/Response */
601
+ interface SuccessResponse<TSuccess, TStatusCode> extends Omit<Response, "ok" | "status" | "json"> {
602
+ ok: true;
603
+ status: TStatusCode;
604
+ data: TSuccess;
605
+ /** [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Response/json) */
606
+ json: () => Promise<TSuccess>;
607
+ }
608
+
609
+ /** @see https://developer.mozilla.org/en-US/docs/Web/API/Response */
610
+ interface ErrorResponse<TData, TStatusCode> extends Omit<Response, "ok" | "status" | "json"> {
611
+ ok: false;
612
+ status: TStatusCode;
613
+ data: TData;
614
+ /** [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Response/json) */
615
+ json: () => Promise<TData>;
616
+ }
617
+
618
+ export type TypedApiResponse<TSuccess, TAllResponses extends Record<string | number, unknown> = {}> =
619
+ (keyof TAllResponses extends never
620
+ ? SuccessResponse<TSuccess, number>
621
+ : {
622
+ [K in keyof TAllResponses]: K extends string
623
+ ? K extends \`\${infer TStatusCode extends number}\`
624
+ ? TStatusCode extends SuccessStatusCode
625
+ ? SuccessResponse<TSuccess, TStatusCode>
626
+ : ErrorResponse<TAllResponses[K], TStatusCode>
627
+ : never
628
+ : K extends number
629
+ ? K extends SuccessStatusCode
630
+ ? SuccessResponse<TSuccess, K>
631
+ : ErrorResponse<TAllResponses[K], K>
632
+ : never;
633
+ }[keyof TAllResponses]);
634
+
635
+ export type SafeApiResponse<TEndpoint> = TEndpoint extends { response: infer TSuccess; responses: infer TResponses }
636
+ ? TResponses extends Record<string, unknown>
637
+ ? TypedApiResponse<TSuccess, TResponses>
638
+ : SuccessResponse<TSuccess, number>
639
+ : TEndpoint extends { response: infer TSuccess }
640
+ ? SuccessResponse<TSuccess, number>
641
+ : never;
642
+
643
+ export type InferResponseByStatus<TEndpoint, TStatusCode> = Extract<SafeApiResponse<TEndpoint>, { status: TStatusCode }>
644
+
490
645
  type RequiredKeys<T> = {
491
646
  [P in keyof T]-?: undefined extends T[P] ? never : P;
492
647
  }[keyof T];
@@ -496,9 +651,23 @@ type MaybeOptionalArg<T> = RequiredKeys<T> extends never ? [config?: T] : [confi
496
651
  // </ApiClientTypes>
497
652
  `;
498
653
  const apiClient = `
654
+ // <TypedResponseError>
655
+ export class TypedResponseError extends Error {
656
+ response: ErrorResponse<unknown, ErrorStatusCode>;
657
+ status: number;
658
+ constructor(response: ErrorResponse<unknown, ErrorStatusCode>) {
659
+ super(\`HTTP \${response.status}: \${response.statusText}\`);
660
+ this.name = 'TypedResponseError';
661
+ this.response = response;
662
+ this.status = response.status;
663
+ }
664
+ }
665
+ // </TypedResponseError>
499
666
  // <ApiClient>
500
667
  export class ApiClient {
501
668
  baseUrl: string = "";
669
+ successStatusCodes = successStatusCodes;
670
+ errorStatusCodes = errorStatusCodes;
502
671
 
503
672
  constructor(public fetcher: Fetcher) {}
504
673
 
@@ -521,10 +690,38 @@ export class ApiClient {
521
690
  return endpointByMethod.length ? `// <ApiClient.${method}>
522
691
  ${method}<Path extends keyof ${capitalizedMethod}Endpoints, TEndpoint extends ${capitalizedMethod}Endpoints[Path]>(
523
692
  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"]>`)};
693
+ ...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 }>
694
+ ): Promise<${match(ctx.runtime).with("zod", "yup", () => infer(`TEndpoint["response"]`)).with("arktype", "io-ts", "typebox", "valibot", () => infer(`TEndpoint`) + `["response"]`).otherwise(() => `TEndpoint["response"]`)}>;
695
+
696
+ ${method}<Path extends keyof ${capitalizedMethod}Endpoints, TEndpoint extends ${capitalizedMethod}Endpoints[Path]>(
697
+ path: Path,
698
+ ...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 }>
699
+ ): Promise<SafeApiResponse<TEndpoint>>;
700
+
701
+ ${method}<Path extends keyof ${capitalizedMethod}Endpoints, TEndpoint extends ${capitalizedMethod}Endpoints[Path]>(
702
+ path: Path,
703
+ ...params: MaybeOptionalArg<any>
704
+ ): Promise<any> {
705
+ const requestParams = params[0];
706
+ const withResponse = requestParams?.withResponse;
707
+ const { withResponse: _, throwOnStatusError = withResponse ? false : true, ...fetchParams } = requestParams || {};
708
+
709
+ const promise = this.fetcher("${method}", this.baseUrl + path, Object.keys(fetchParams).length ? requestParams : undefined)
710
+ .then(async (response) => {
711
+ const data = await this.parseResponse(response);
712
+ const typedResponse = Object.assign(response, {
713
+ data: data,
714
+ json: () => Promise.resolve(data)
715
+ }) as SafeApiResponse<TEndpoint>;
716
+
717
+ if (throwOnStatusError && errorStatusCodes.includes(response.status as never)) {
718
+ throw new TypedResponseError(typedResponse as never);
719
+ }
720
+
721
+ return withResponse ? typedResponse : data;
722
+ });
723
+
724
+ return promise ${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"]>`)}
528
725
  }
529
726
  // </ApiClient.${method}>
530
727
  ` : "";
@@ -552,11 +749,8 @@ export class ApiClient {
552
749
  "valibot",
553
750
  () => inferByRuntime[ctx.runtime](`TEndpoint`) + `["parameters"]`
554
751
  ).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);
752
+ : Promise<SafeApiResponse<TEndpoint>> {
753
+ return this.fetcher(method, this.baseUrl + (path as string), params[0] as EndpointParameters) as Promise<SafeApiResponse<TEndpoint>>;
560
754
  }
561
755
  // </ApiClient.request>
562
756
  }
@@ -574,11 +768,26 @@ export function createApiClient(fetcher: Fetcher, baseUrl?: string) {
574
768
  api.get("/users").then((users) => console.log(users));
575
769
  api.post("/users", { body: { name: "John" } }).then((user) => console.log(user));
576
770
  api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user));
771
+
772
+ // With error handling
773
+ const result = await api.get("/users/{id}", { path: { id: "123" }, withResponse: true });
774
+ if (result.ok) {
775
+ // Access data directly
776
+ const user = result.data;
777
+ console.log(user);
778
+
779
+ // Or use the json() method for compatibility
780
+ const userFromJson = await result.json();
781
+ console.log(userFromJson);
782
+ } else {
783
+ const error = result.data;
784
+ console.error(\`Error \${result.status}:\`, error);
785
+ }
577
786
  */
578
787
 
579
- // </ApiClient
788
+ // </ApiClient>
580
789
  `;
581
- return endpointSchemaList + apiClientTypes + apiClient;
790
+ return apiClientTypes + apiClient;
582
791
  };
583
792
 
584
793
  // src/ref-resolver.ts
@@ -926,10 +1135,17 @@ var mapOpenApiEndpoints = (doc, options) => {
926
1135
  endpoint.requestFormat = match2(matchingMediaType).with("application/octet-stream", () => "binary").with("multipart/form-data", () => "form-data").with("application/x-www-form-urlencoded", () => "form-url").with(P.string.includes("json"), () => "json").otherwise(() => "text");
927
1136
  }
928
1137
  if (params) {
929
- const t = createBoxFactory({}, ctx);
930
1138
  const filtered_params = ["query", "path", "header"];
931
1139
  for (const k of filtered_params) {
932
1140
  if (params[k] && lists[k].length) {
1141
+ const properties = Object.entries(params[k]).reduce(
1142
+ (acc, [key, value]) => {
1143
+ if (value.schema) acc[key] = value.schema;
1144
+ return acc;
1145
+ },
1146
+ {}
1147
+ );
1148
+ const t = createBoxFactory({ type: "object", properties }, ctx);
933
1149
  if (lists[k].every((param) => !param.required)) {
934
1150
  params[k] = t.reference("Partial", [t.object(params[k])]);
935
1151
  } else {
@@ -944,14 +1160,45 @@ var mapOpenApiEndpoints = (doc, options) => {
944
1160
  endpoint.parameters = Object.keys(params).length ? params : void 0;
945
1161
  }
946
1162
  let responseObject;
1163
+ const allResponses = {};
947
1164
  Object.entries(operation.responses ?? {}).map(([status, responseOrRef]) => {
948
1165
  const statusCode = Number(status);
949
- if (statusCode >= 200 && statusCode < 300) {
950
- responseObject = refs.unwrap(responseOrRef);
1166
+ const responseObj = refs.unwrap(responseOrRef);
1167
+ const content2 = responseObj?.content;
1168
+ if (content2) {
1169
+ const matchingMediaType = Object.keys(content2).find(isResponseMediaType);
1170
+ if (matchingMediaType && content2[matchingMediaType]) {
1171
+ allResponses[status] = openApiSchemaToTs({
1172
+ schema: content2[matchingMediaType]?.schema ?? {},
1173
+ ctx
1174
+ });
1175
+ } else {
1176
+ allResponses[status] = openApiSchemaToTs({ schema: {}, ctx });
1177
+ }
1178
+ } else {
1179
+ allResponses[status] = openApiSchemaToTs({ schema: {}, ctx });
1180
+ }
1181
+ if (statusCode >= 200 && statusCode < 300 && !responseObject) {
1182
+ responseObject = responseObj;
951
1183
  }
952
1184
  });
953
1185
  if (!responseObject && operation.responses?.default) {
954
1186
  responseObject = refs.unwrap(operation.responses.default);
1187
+ if (!allResponses["default"]) {
1188
+ const content2 = responseObject?.content;
1189
+ if (content2) {
1190
+ const matchingMediaType = Object.keys(content2).find(isResponseMediaType);
1191
+ if (matchingMediaType && content2[matchingMediaType]) {
1192
+ allResponses["default"] = openApiSchemaToTs({
1193
+ schema: content2[matchingMediaType]?.schema ?? {},
1194
+ ctx
1195
+ });
1196
+ }
1197
+ }
1198
+ }
1199
+ }
1200
+ if (Object.keys(allResponses).length > 0) {
1201
+ endpoint.responses = allResponses;
955
1202
  }
956
1203
  const content = responseObject?.content;
957
1204
  if (content) {
@@ -986,7 +1233,7 @@ var allowedParamMediaTypes = [
986
1233
  "*/*"
987
1234
  ];
988
1235
  var isAllowedParamMediaTypes = (mediaType) => mediaType.includes("application/") && mediaType.includes("json") || allowedParamMediaTypes.includes(mediaType) || mediaType.includes("text/");
989
- var isResponseMediaType = (mediaType) => mediaType === "application/json";
1236
+ var isResponseMediaType = (mediaType) => mediaType === "application/json" || mediaType === "*/*";
990
1237
  var getAlias = ({ path, method, operation }) => sanitizeName(
991
1238
  (method + "_" + capitalize3(operation.operationId ?? pathToVariableName(path))).replace(/-/g, "__"),
992
1239
  "endpoint"
@@ -998,7 +1245,8 @@ var generateTanstackQueryFile = async (ctx) => {
998
1245
  const endpointMethods = new Set(ctx.endpointList.map((endpoint) => endpoint.method.toLowerCase()));
999
1246
  const file = `
1000
1247
  import { queryOptions } from "@tanstack/react-query"
1001
- import type { EndpointByMethod, ApiClient } from "${ctx.relativeApiClientPath}"
1248
+ import type { EndpointByMethod, ApiClient, SuccessStatusCode, ErrorStatusCode, InferResponseByStatus } from "${ctx.relativeApiClientPath}"
1249
+ import { errorStatusCodes, TypedResponseError } from "${ctx.relativeApiClientPath}"
1002
1250
 
1003
1251
  type EndpointQueryKey<TOptions extends EndpointParameters> = [
1004
1252
  TOptions & {
@@ -1062,30 +1310,36 @@ var generateTanstackQueryFile = async (ctx) => {
1062
1310
  path: Path,
1063
1311
  ...params: MaybeOptionalArg<TEndpoint["parameters"]>
1064
1312
  ) {
1065
- const queryKey = createQueryKey(path, params[0]);
1313
+ const queryKey = createQueryKey(path as string, params[0]);
1066
1314
  const query = {
1067
1315
  /** type-only property if you need easy access to the endpoint params */
1068
1316
  "~endpoint": {} as TEndpoint,
1069
1317
  queryKey,
1318
+ queryFn: {} as "You need to pass .queryOptions to the useQuery hook",
1070
1319
  queryOptions: queryOptions({
1071
1320
  queryFn: async ({ queryKey, signal, }) => {
1072
- const res = await this.client.${method}(path, {
1073
- ...params,
1074
- ...queryKey[0],
1321
+ const requestParams = {
1322
+ ...(params[0] || {}),
1323
+ ...(queryKey[0] || {}),
1075
1324
  signal,
1076
- });
1325
+ withResponse: false as const
1326
+ };
1327
+ const res = await this.client.${method}(path, requestParams);
1077
1328
  return res as TEndpoint["response"];
1078
1329
  },
1079
1330
  queryKey: queryKey
1080
1331
  }),
1332
+ mutationFn: {} as "You need to pass .mutationOptions to the useMutation hook",
1081
1333
  mutationOptions: {
1082
1334
  mutationKey: queryKey,
1083
1335
  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
- });
1336
+ const requestParams = {
1337
+ ...(params[0] || {}),
1338
+ ...(queryKey[0] || {}),
1339
+ ...(localOptions || {}),
1340
+ withResponse: false as const
1341
+ };
1342
+ const res = await this.client.${method}(path, requestParams);
1089
1343
  return res as TEndpoint["response"];
1090
1344
  }
1091
1345
  }
@@ -1099,32 +1353,67 @@ var generateTanstackQueryFile = async (ctx) => {
1099
1353
 
1100
1354
  // <ApiClient.request>
1101
1355
  /**
1102
- * Generic mutation method with full type-safety for any endpoint that doesnt require parameters to be passed initially
1356
+ * Generic mutation method with full type-safety for any endpoint; it doesnt require parameters to be passed initially
1357
+ * but instead will require them to be passed when calling the mutation.mutate() method
1103
1358
  */
1104
1359
  mutation<
1105
1360
  TMethod extends keyof EndpointByMethod,
1106
1361
  TPath extends keyof EndpointByMethod[TMethod],
1107
1362
  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) {
1363
+ TWithResponse extends boolean = false,
1364
+ TSelection = TWithResponse extends true
1365
+ ? InferResponseByStatus<TEndpoint, SuccessStatusCode>
1366
+ : TEndpoint extends { response: infer Res } ? Res : never,
1367
+ TError = TEndpoint extends { responses: infer TResponses }
1368
+ ? TResponses extends Record<string | number, unknown>
1369
+ ? InferResponseByStatus<TEndpoint, ErrorStatusCode>
1370
+ : Error
1371
+ : Error
1372
+ >(method: TMethod, path: TPath, options?: {
1373
+ withResponse?: TWithResponse;
1374
+ selectFn?: (res: TWithResponse extends true
1375
+ ? InferResponseByStatus<TEndpoint, SuccessStatusCode>
1376
+ : TEndpoint extends { response: infer Res } ? Res : never
1377
+ ) => TSelection;
1378
+ throwOnStatusError?: boolean
1379
+ }) {
1113
1380
  const mutationKey = [{ method, path }] as const;
1114
1381
  return {
1115
1382
  /** type-only property if you need easy access to the endpoint params */
1116
1383
  "~endpoint": {} as TEndpoint,
1117
1384
  mutationKey: mutationKey,
1385
+ mutationFn: {} as "You need to pass .mutationOptions to the useMutation hook",
1118
1386
  mutationOptions: {
1119
1387
  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
- };
1388
+ mutationFn: async <TLocalWithResponse extends boolean = TWithResponse, TLocalSelection = TLocalWithResponse extends true
1389
+ ? InferResponseByStatus<TEndpoint, SuccessStatusCode>
1390
+ : TEndpoint extends { response: infer Res }
1391
+ ? Res
1392
+ : never>
1393
+ (params: (TEndpoint extends { parameters: infer Parameters } ? Parameters : {}) & {
1394
+ withResponse?: TLocalWithResponse;
1395
+ throwOnStatusError?: boolean;
1396
+ }): Promise<TLocalSelection> => {
1397
+ const withResponse = params.withResponse ??options?.withResponse ?? false;
1398
+ const throwOnStatusError = params.throwOnStatusError ?? options?.throwOnStatusError ?? (withResponse ? false : true);
1399
+ const selectFn = options?.selectFn;
1400
+ const response = await (this.client as any)[method](path, { ...params as any, withResponse: true, throwOnStatusError: false });
1401
+
1402
+ if (throwOnStatusError && errorStatusCodes.includes(response.status as never)) {
1403
+ throw new TypedResponseError(response as never);
1404
+ }
1405
+
1406
+ // Return just the data if withResponse is false, otherwise return the full response
1407
+ const finalResponse = withResponse ? response : response.data;
1408
+ const res = selectFn ? selectFn(finalResponse as any) : finalResponse;
1409
+ return res as never;
1410
+ }
1411
+ } satisfies import("@tanstack/react-query").UseMutationOptions<TSelection, TError, (TEndpoint extends { parameters: infer Parameters } ? Parameters : {}) & {
1412
+ withResponse?: boolean;
1413
+ throwOnStatusError?: boolean;
1414
+ }>,
1127
1415
  }
1416
+ }
1128
1417
  // </ApiClient.request>
1129
1418
  }
1130
1419
  `;
@@ -1136,6 +1425,8 @@ export {
1136
1425
  createFactory,
1137
1426
  createBoxFactory,
1138
1427
  openApiSchemaToTs,
1428
+ DEFAULT_SUCCESS_STATUS_CODES,
1429
+ DEFAULT_ERROR_STATUS_CODES,
1139
1430
  allowedRuntimes,
1140
1431
  generateFile,
1141
1432
  createRefResolver,