typed-openapi 1.5.0 → 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.
@@ -1,3 +1,7 @@
1
+ import {
2
+ prettify
3
+ } from "./chunk-KAEXXJ7X.js";
4
+
1
5
  // src/asserts.ts
2
6
  var isPrimitiveType = (type2) => primitiveTypeList.includes(type2);
3
7
  var primitiveTypeList = ["string", "number", "integer", "boolean", "null"];
@@ -278,6 +282,69 @@ import { capitalize as capitalize2, groupBy } from "pastable/server";
278
282
  import * as Codegen from "@sinclair/typebox-codegen";
279
283
  import { match } from "ts-pattern";
280
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
+ ];
281
348
  var allowedRuntimes = type("'none' | 'arktype' | 'io-ts' | 'typebox' | 'valibot' | 'yup' | 'zod'");
282
349
  var runtimeValidationGenerator = {
283
350
  arktype: Codegen.ModelToArkType.Generate,
@@ -310,10 +377,17 @@ var replacerByRuntime = {
310
377
  )
311
378
  };
312
379
  var generateFile = (options) => {
313
- 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
+ };
314
387
  const schemaList = generateSchemaList(ctx);
315
388
  const endpointSchemaList = options.schemasOnly ? "" : generateEndpointSchemaList(ctx);
316
- const apiClient = options.schemasOnly ? "" : generateApiClient(ctx);
389
+ const endpointByMethod = options.schemasOnly ? "" : generateEndpointByMethod(ctx);
390
+ const apiClient = options.schemasOnly || !ctx.includeClient ? "" : generateApiClient(ctx);
317
391
  const transform = ctx.runtime === "none" ? (file2) => file2 : (file2) => {
318
392
  const model = Codegen.TypeScriptToModel.Generate(file2);
319
393
  const transformer = runtimeValidationGenerator[ctx.runtime];
@@ -333,6 +407,7 @@ var generateFile = (options) => {
333
407
  };
334
408
  const file = `
335
409
  ${transform(schemaList + endpointSchemaList)}
410
+ ${endpointByMethod}
336
411
  ${apiClient}
337
412
  `;
338
413
  return file;
@@ -376,6 +451,20 @@ var responseHeadersObjectToString = (responseHeaders, ctx) => {
376
451
  }
377
452
  return str + "}";
378
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
+ };
379
468
  var generateEndpointSchemaList = (ctx) => {
380
469
  let file = `
381
470
  ${ctx.runtime === "none" ? "export namespace Endpoints {" : ""}
@@ -407,6 +496,7 @@ var generateEndpointSchemaList = (ctx) => {
407
496
  }
408
497
  return box;
409
498
  }).value : endpoint.response.value},
499
+ ${endpoint.responses ? `responses: ${generateResponsesObject(endpoint.responses, ctx)},` : ""}
410
500
  ${endpoint.responseHeaders ? `responseHeaders: ${responseHeadersObjectToString(endpoint.responseHeaders, ctx)},` : ""}
411
501
  }
412
502
  `;
@@ -427,7 +517,7 @@ var generateEndpointByMethod = (ctx) => {
427
517
  return `${method}: {
428
518
  ${list.map(
429
519
  (endpoint) => `"${endpoint.path}": ${ctx.runtime === "none" ? "Endpoints." : ""}${endpoint.meta.alias}`
430
- )}
520
+ ).join(",\n")}
431
521
  }`;
432
522
  }).join(",\n")}
433
523
  }
@@ -443,9 +533,11 @@ var generateEndpointByMethod = (ctx) => {
443
533
  return endpointByMethod + shorthands;
444
534
  };
445
535
  var generateApiClient = (ctx) => {
536
+ if (!ctx.includeClient) {
537
+ return "";
538
+ }
446
539
  const { endpointList } = ctx;
447
540
  const byMethods = groupBy(endpointList, "method");
448
- const endpointSchemaList = generateEndpointByMethod(ctx);
449
541
  const apiClientTypes = `
450
542
  // <ApiClientTypes>
451
543
  export type EndpointParameters = {
@@ -463,6 +555,7 @@ type RequestFormat = "json" | "form-data" | "form-url" | "binary" | "text";
463
555
  export type DefaultEndpoint = {
464
556
  parameters?: EndpointParameters | undefined;
465
557
  response: unknown;
558
+ responses?: Record<string, unknown>;
466
559
  responseHeaders?: Record<string, unknown>;
467
560
  };
468
561
 
@@ -478,11 +571,64 @@ export type Endpoint<TConfig extends DefaultEndpoint = DefaultEndpoint> = {
478
571
  areParametersRequired: boolean;
479
572
  };
480
573
  response: TConfig["response"];
574
+ responses?: TConfig["responses"];
481
575
  responseHeaders?: TConfig["responseHeaders"]
482
576
  };
483
577
 
484
578
  export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise<Response>;
485
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
+
486
632
  type RequiredKeys<T> = {
487
633
  [P in keyof T]-?: undefined extends T[P] ? never : P;
488
634
  }[keyof T];
@@ -492,9 +638,23 @@ type MaybeOptionalArg<T> = RequiredKeys<T> extends never ? [config?: T] : [confi
492
638
  // </ApiClientTypes>
493
639
  `;
494
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>
495
653
  // <ApiClient>
496
654
  export class ApiClient {
497
655
  baseUrl: string = "";
656
+ successStatusCodes = successStatusCodes;
657
+ errorStatusCodes = errorStatusCodes;
498
658
 
499
659
  constructor(public fetcher: Fetcher) {}
500
660
 
@@ -517,10 +677,44 @@ export class ApiClient {
517
677
  return endpointByMethod.length ? `// <ApiClient.${method}>
518
678
  ${method}<Path extends keyof ${capitalizedMethod}Endpoints, TEndpoint extends ${capitalizedMethod}Endpoints[Path]>(
519
679
  path: Path,
520
- ...params: MaybeOptionalArg<${match(ctx.runtime).with("zod", "yup", () => infer(`TEndpoint["parameters"]`)).with("arktype", "io-ts", "typebox", "valibot", () => infer(`TEndpoint`) + `["parameters"]`).otherwise(() => `TEndpoint["parameters"]`)}>
521
- ): Promise<${match(ctx.runtime).with("zod", "yup", () => infer(`TEndpoint["response"]`)).with("arktype", "io-ts", "typebox", "valibot", () => infer(`TEndpoint`) + `["response"]`).otherwise(() => `TEndpoint["response"]`)}> {
522
- return this.fetcher("${method}", this.baseUrl + path, params[0])
523
- .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"]>`)}
524
718
  }
525
719
  // </ApiClient.${method}>
526
720
  ` : "";
@@ -548,11 +742,8 @@ export class ApiClient {
548
742
  "valibot",
549
743
  () => inferByRuntime[ctx.runtime](`TEndpoint`) + `["parameters"]`
550
744
  ).otherwise(() => `TEndpoint extends { parameters: infer Params } ? Params : never`)}>)
551
- : Promise<Omit<Response, "json"> & {
552
- /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json) */
553
- json: () => Promise<TEndpoint extends { response: infer Res } ? Res : never>;
554
- }> {
555
- 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>>;
556
747
  }
557
748
  // </ApiClient.request>
558
749
  }
@@ -570,11 +761,26 @@ export function createApiClient(fetcher: Fetcher, baseUrl?: string) {
570
761
  api.get("/users").then((users) => console.log(users));
571
762
  api.post("/users", { body: { name: "John" } }).then((user) => console.log(user));
572
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
+ }
573
779
  */
574
780
 
575
- // </ApiClient
781
+ // </ApiClient>
576
782
  `;
577
- return endpointSchemaList + apiClientTypes + apiClient;
783
+ return apiClientTypes + apiClient;
578
784
  };
579
785
 
580
786
  // src/ref-resolver.ts
@@ -603,10 +809,90 @@ function topologicalSort(graph) {
603
809
  return sorted;
604
810
  }
605
811
 
812
+ // src/sanitize-name.ts
813
+ var reservedWords = /* @__PURE__ */ new Set([
814
+ // TS keywords and built-ins
815
+ "import",
816
+ "package",
817
+ "namespace",
818
+ "Record",
819
+ "Partial",
820
+ "Required",
821
+ "Readonly",
822
+ "Pick",
823
+ "Omit",
824
+ "String",
825
+ "Number",
826
+ "Boolean",
827
+ "Object",
828
+ "Array",
829
+ "Function",
830
+ "any",
831
+ "unknown",
832
+ "never",
833
+ "void",
834
+ "extends",
835
+ "super",
836
+ "class",
837
+ "interface",
838
+ "type",
839
+ "enum",
840
+ "const",
841
+ "let",
842
+ "var",
843
+ "if",
844
+ "else",
845
+ "for",
846
+ "while",
847
+ "do",
848
+ "switch",
849
+ "case",
850
+ "default",
851
+ "break",
852
+ "continue",
853
+ "return",
854
+ "try",
855
+ "catch",
856
+ "finally",
857
+ "throw",
858
+ "new",
859
+ "delete",
860
+ "in",
861
+ "instanceof",
862
+ "typeof",
863
+ "void",
864
+ "with",
865
+ "yield",
866
+ "await",
867
+ "static",
868
+ "public",
869
+ "private",
870
+ "protected",
871
+ "abstract",
872
+ "as",
873
+ "asserts",
874
+ "from",
875
+ "get",
876
+ "set",
877
+ "module",
878
+ "require",
879
+ "keyof",
880
+ "readonly",
881
+ "global",
882
+ "symbol",
883
+ "bigint"
884
+ ]);
885
+ function sanitizeName(name, type2) {
886
+ let n = name.replace(/[\W/]+/g, "_");
887
+ if (/^\d/.test(n)) n = "_" + n;
888
+ if (reservedWords.has(n)) n = (type2 === "schema" ? "Schema_" : "Endpoint_") + n;
889
+ return n;
890
+ }
891
+
606
892
  // src/ref-resolver.ts
607
893
  var autocorrectRef = (ref) => ref[1] === "/" ? ref : "#/" + ref.slice(1);
608
894
  var componentsWithSchemas = ["schemas", "responses", "parameters", "requestBodies", "headers"];
609
- var createRefResolver = (doc, factory2) => {
895
+ var createRefResolver = (doc, factory2, nameTransform) => {
610
896
  const nameByRef = /* @__PURE__ */ new Map();
611
897
  const refByName = /* @__PURE__ */ new Map();
612
898
  const byRef = /* @__PURE__ */ new Map();
@@ -619,7 +905,11 @@ var createRefResolver = (doc, factory2) => {
619
905
  const normalizedPath = path.replace("#/", "").replace("#", "").replaceAll("/", ".");
620
906
  const map = get(doc, normalizedPath) ?? {};
621
907
  const name = split[split.length - 1];
622
- const normalized = normalizeString(name);
908
+ let normalized = normalizeString(name);
909
+ if (nameTransform?.transformSchemaName) {
910
+ normalized = nameTransform.transformSchemaName(normalized);
911
+ }
912
+ normalized = sanitizeName(normalized, "schema");
623
913
  nameByRef.set(correctRef, normalized);
624
914
  refByName.set(normalized, correctRef);
625
915
  const infos = { ref: correctRef, name, normalized, kind: normalizedPath.split(".")[1] };
@@ -768,7 +1058,7 @@ var tsFactory = createFactory({
768
1058
  import { capitalize as capitalize3, pick } from "pastable/server";
769
1059
  import { match as match2, P } from "ts-pattern";
770
1060
  var factory = tsFactory;
771
- var mapOpenApiEndpoints = (doc) => {
1061
+ var mapOpenApiEndpoints = (doc, options) => {
772
1062
  const refs = createRefResolver(doc, factory);
773
1063
  const ctx = { refs, factory };
774
1064
  const endpointList = [];
@@ -776,6 +1066,10 @@ var mapOpenApiEndpoints = (doc) => {
776
1066
  const pathItem = pick(pathItemObj, ["get", "put", "post", "delete", "options", "head", "patch", "trace"]);
777
1067
  Object.entries(pathItem).forEach(([method, operation]) => {
778
1068
  if (operation.deprecated) return;
1069
+ let alias = getAlias({ path, method, operation });
1070
+ if (options?.nameTransform?.transformEndpointName) {
1071
+ alias = options.nameTransform.transformEndpointName({ alias, path, method, operation });
1072
+ }
779
1073
  const endpoint = {
780
1074
  operation,
781
1075
  method,
@@ -783,7 +1077,7 @@ var mapOpenApiEndpoints = (doc) => {
783
1077
  requestFormat: "json",
784
1078
  response: openApiSchemaToTs({ schema: {}, ctx }),
785
1079
  meta: {
786
- alias: getAlias({ path, method, operation }),
1080
+ alias,
787
1081
  areParametersRequired: false,
788
1082
  hasParameters: false
789
1083
  }
@@ -827,7 +1121,7 @@ var mapOpenApiEndpoints = (doc) => {
827
1121
  const matchingMediaType = Object.keys(content2).find(isAllowedParamMediaTypes);
828
1122
  if (matchingMediaType && content2[matchingMediaType]) {
829
1123
  params.body = openApiSchemaToTs({
830
- schema: content2[matchingMediaType]?.schema ?? {} ?? {},
1124
+ schema: content2[matchingMediaType]?.schema ?? {},
831
1125
  ctx
832
1126
  });
833
1127
  }
@@ -852,21 +1146,52 @@ var mapOpenApiEndpoints = (doc) => {
852
1146
  endpoint.parameters = Object.keys(params).length ? params : void 0;
853
1147
  }
854
1148
  let responseObject;
1149
+ const allResponses = {};
855
1150
  Object.entries(operation.responses ?? {}).map(([status, responseOrRef]) => {
856
1151
  const statusCode = Number(status);
857
- if (statusCode >= 200 && statusCode < 300) {
858
- 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;
859
1169
  }
860
1170
  });
861
1171
  if (!responseObject && operation.responses?.default) {
862
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;
863
1188
  }
864
1189
  const content = responseObject?.content;
865
1190
  if (content) {
866
1191
  const matchingMediaType = Object.keys(content).find(isResponseMediaType);
867
1192
  if (matchingMediaType && content[matchingMediaType]) {
868
1193
  endpoint.response = openApiSchemaToTs({
869
- schema: content[matchingMediaType]?.schema ?? {} ?? {},
1194
+ schema: content[matchingMediaType]?.schema ?? {},
870
1195
  ctx
871
1196
  });
872
1197
  }
@@ -895,25 +1220,10 @@ var allowedParamMediaTypes = [
895
1220
  ];
896
1221
  var isAllowedParamMediaTypes = (mediaType) => mediaType.includes("application/") && mediaType.includes("json") || allowedParamMediaTypes.includes(mediaType) || mediaType.includes("text/");
897
1222
  var isResponseMediaType = (mediaType) => mediaType === "application/json";
898
- var getAlias = ({ path, method, operation }) => (method + "_" + capitalize3(operation.operationId ?? pathToVariableName(path))).replace(/-/g, "__");
899
-
900
- // src/format.ts
901
- import prettier from "prettier";
902
- import parserTypescript from "prettier/parser-typescript";
903
- function maybePretty(input, options) {
904
- try {
905
- return prettier.format(input, {
906
- parser: "typescript",
907
- plugins: [parserTypescript],
908
- ...options
909
- });
910
- } catch (err) {
911
- console.warn("Failed to format code");
912
- console.warn(err);
913
- return input;
914
- }
915
- }
916
- var prettify = (str, options) => maybePretty(str, { printWidth: 120, trailingComma: "all", ...options });
1223
+ var getAlias = ({ path, method, operation }) => sanitizeName(
1224
+ (method + "_" + capitalize3(operation.operationId ?? pathToVariableName(path))).replace(/-/g, "__"),
1225
+ "endpoint"
1226
+ );
917
1227
 
918
1228
  // src/tanstack-query.generator.ts
919
1229
  import { capitalize as capitalize4 } from "pastable/server";
@@ -921,7 +1231,8 @@ var generateTanstackQueryFile = async (ctx) => {
921
1231
  const endpointMethods = new Set(ctx.endpointList.map((endpoint) => endpoint.method.toLowerCase()));
922
1232
  const file = `
923
1233
  import { queryOptions } from "@tanstack/react-query"
924
- 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}"
925
1236
 
926
1237
  type EndpointQueryKey<TOptions extends EndpointParameters> = [
927
1238
  TOptions & {
@@ -985,30 +1296,36 @@ var generateTanstackQueryFile = async (ctx) => {
985
1296
  path: Path,
986
1297
  ...params: MaybeOptionalArg<TEndpoint["parameters"]>
987
1298
  ) {
988
- const queryKey = createQueryKey(path, params[0]);
1299
+ const queryKey = createQueryKey(path as string, params[0]);
989
1300
  const query = {
990
1301
  /** type-only property if you need easy access to the endpoint params */
991
1302
  "~endpoint": {} as TEndpoint,
992
1303
  queryKey,
1304
+ queryFn: {} as "You need to pass .queryOptions to the useQuery hook",
993
1305
  queryOptions: queryOptions({
994
1306
  queryFn: async ({ queryKey, signal, }) => {
995
- const res = await this.client.${method}(path, {
996
- ...params,
997
- ...queryKey[0],
1307
+ const requestParams = {
1308
+ ...(params[0] || {}),
1309
+ ...(queryKey[0] || {}),
998
1310
  signal,
999
- });
1311
+ withResponse: false as const
1312
+ };
1313
+ const res = await this.client.${method}(path, requestParams);
1000
1314
  return res as TEndpoint["response"];
1001
1315
  },
1002
1316
  queryKey: queryKey
1003
1317
  }),
1318
+ mutationFn: {} as "You need to pass .mutationOptions to the useMutation hook",
1004
1319
  mutationOptions: {
1005
1320
  mutationKey: queryKey,
1006
1321
  mutationFn: async (localOptions: TEndpoint extends { parameters: infer Parameters} ? Parameters: never) => {
1007
- const res = await this.client.${method}(path, {
1008
- ...params,
1009
- ...queryKey[0],
1010
- ...localOptions,
1011
- });
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);
1012
1329
  return res as TEndpoint["response"];
1013
1330
  }
1014
1331
  }
@@ -1022,32 +1339,67 @@ var generateTanstackQueryFile = async (ctx) => {
1022
1339
 
1023
1340
  // <ApiClient.request>
1024
1341
  /**
1025
- * 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
1026
1344
  */
1027
1345
  mutation<
1028
1346
  TMethod extends keyof EndpointByMethod,
1029
1347
  TPath extends keyof EndpointByMethod[TMethod],
1030
1348
  TEndpoint extends EndpointByMethod[TMethod][TPath],
1031
- TSelection,
1032
- >(method: TMethod, path: TPath, selectFn?: (res: Omit<Response, "json"> & {
1033
- /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json) */
1034
- json: () => Promise<TEndpoint extends { response: infer Res } ? Res : never>;
1035
- }) => 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
+ }) {
1036
1366
  const mutationKey = [{ method, path }] as const;
1037
1367
  return {
1038
1368
  /** type-only property if you need easy access to the endpoint params */
1039
1369
  "~endpoint": {} as TEndpoint,
1040
1370
  mutationKey: mutationKey,
1371
+ mutationFn: {} as "You need to pass .mutationOptions to the useMutation hook",
1041
1372
  mutationOptions: {
1042
1373
  mutationKey: mutationKey,
1043
- mutationFn: async (params: TEndpoint extends { parameters: infer Parameters } ? Parameters : never) => {
1044
- const response = await this.client.request(method, path, params);
1045
- const res = selectFn ? selectFn(response) : response
1046
- return res as unknown extends TSelection ? typeof response : Awaited<TSelection>
1047
- },
1048
- },
1049
- };
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
+ }>,
1050
1401
  }
1402
+ }
1051
1403
  // </ApiClient.request>
1052
1404
  }
1053
1405
  `;
@@ -1059,11 +1411,12 @@ export {
1059
1411
  createFactory,
1060
1412
  createBoxFactory,
1061
1413
  openApiSchemaToTs,
1414
+ DEFAULT_SUCCESS_STATUS_CODES,
1415
+ DEFAULT_ERROR_STATUS_CODES,
1062
1416
  allowedRuntimes,
1063
1417
  generateFile,
1064
1418
  createRefResolver,
1065
1419
  tsFactory,
1066
1420
  mapOpenApiEndpoints,
1067
- prettify,
1068
1421
  generateTanstackQueryFile
1069
1422
  };