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.
@@ -3,14 +3,17 @@ import { prettify } from "./format.ts";
3
3
  import type { mapOpenApiEndpoints } from "./map-openapi-endpoints.ts";
4
4
 
5
5
  type GeneratorOptions = ReturnType<typeof mapOpenApiEndpoints>;
6
- type GeneratorContext = Required<GeneratorOptions>;
6
+ type GeneratorContext = Required<GeneratorOptions> & {
7
+ errorStatusCodes?: readonly number[];
8
+ };
7
9
 
8
10
  export const generateTanstackQueryFile = async (ctx: GeneratorContext & { relativeApiClientPath: string }) => {
9
11
  const endpointMethods = new Set(ctx.endpointList.map((endpoint) => endpoint.method.toLowerCase()));
10
12
 
11
13
  const file = `
12
14
  import { queryOptions } from "@tanstack/react-query"
13
- import type { EndpointByMethod, ApiClient } from "${ctx.relativeApiClientPath}"
15
+ import type { EndpointByMethod, ApiClient, SuccessStatusCode, ErrorStatusCode, InferResponseByStatus } from "${ctx.relativeApiClientPath}"
16
+ import { errorStatusCodes, TypedResponseError } from "${ctx.relativeApiClientPath}"
14
17
 
15
18
  type EndpointQueryKey<TOptions extends EndpointParameters> = [
16
19
  TOptions & {
@@ -77,30 +80,36 @@ export const generateTanstackQueryFile = async (ctx: GeneratorContext & { relati
77
80
  path: Path,
78
81
  ...params: MaybeOptionalArg<TEndpoint["parameters"]>
79
82
  ) {
80
- const queryKey = createQueryKey(path, params[0]);
83
+ const queryKey = createQueryKey(path as string, params[0]);
81
84
  const query = {
82
85
  /** type-only property if you need easy access to the endpoint params */
83
86
  "~endpoint": {} as TEndpoint,
84
87
  queryKey,
88
+ queryFn: {} as "You need to pass .queryOptions to the useQuery hook",
85
89
  queryOptions: queryOptions({
86
90
  queryFn: async ({ queryKey, signal, }) => {
87
- const res = await this.client.${method}(path, {
88
- ...params,
89
- ...queryKey[0],
91
+ const requestParams = {
92
+ ...(params[0] || {}),
93
+ ...(queryKey[0] || {}),
90
94
  signal,
91
- });
95
+ withResponse: false as const
96
+ };
97
+ const res = await this.client.${method}(path, requestParams);
92
98
  return res as TEndpoint["response"];
93
99
  },
94
100
  queryKey: queryKey
95
101
  }),
102
+ mutationFn: {} as "You need to pass .mutationOptions to the useMutation hook",
96
103
  mutationOptions: {
97
104
  mutationKey: queryKey,
98
105
  mutationFn: async (localOptions: TEndpoint extends { parameters: infer Parameters} ? Parameters: never) => {
99
- const res = await this.client.${method}(path, {
100
- ...params,
101
- ...queryKey[0],
102
- ...localOptions,
103
- });
106
+ const requestParams = {
107
+ ...(params[0] || {}),
108
+ ...(queryKey[0] || {}),
109
+ ...(localOptions || {}),
110
+ withResponse: false as const
111
+ };
112
+ const res = await this.client.${method}(path, requestParams);
104
113
  return res as TEndpoint["response"];
105
114
  }
106
115
  }
@@ -115,32 +124,67 @@ export const generateTanstackQueryFile = async (ctx: GeneratorContext & { relati
115
124
 
116
125
  // <ApiClient.request>
117
126
  /**
118
- * Generic mutation method with full type-safety for any endpoint that doesnt require parameters to be passed initially
127
+ * Generic mutation method with full type-safety for any endpoint; it doesnt require parameters to be passed initially
128
+ * but instead will require them to be passed when calling the mutation.mutate() method
119
129
  */
120
130
  mutation<
121
131
  TMethod extends keyof EndpointByMethod,
122
132
  TPath extends keyof EndpointByMethod[TMethod],
123
133
  TEndpoint extends EndpointByMethod[TMethod][TPath],
124
- TSelection,
125
- >(method: TMethod, path: TPath, selectFn?: (res: Omit<Response, "json"> & {
126
- /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json) */
127
- json: () => Promise<TEndpoint extends { response: infer Res } ? Res : never>;
128
- }) => TSelection) {
134
+ TWithResponse extends boolean = false,
135
+ TSelection = TWithResponse extends true
136
+ ? InferResponseByStatus<TEndpoint, SuccessStatusCode>
137
+ : TEndpoint extends { response: infer Res } ? Res : never,
138
+ TError = TEndpoint extends { responses: infer TResponses }
139
+ ? TResponses extends Record<string | number, unknown>
140
+ ? InferResponseByStatus<TEndpoint, ErrorStatusCode>
141
+ : Error
142
+ : Error
143
+ >(method: TMethod, path: TPath, options?: {
144
+ withResponse?: TWithResponse;
145
+ selectFn?: (res: TWithResponse extends true
146
+ ? InferResponseByStatus<TEndpoint, SuccessStatusCode>
147
+ : TEndpoint extends { response: infer Res } ? Res : never
148
+ ) => TSelection;
149
+ throwOnStatusError?: boolean
150
+ }) {
129
151
  const mutationKey = [{ method, path }] as const;
130
152
  return {
131
153
  /** type-only property if you need easy access to the endpoint params */
132
154
  "~endpoint": {} as TEndpoint,
133
155
  mutationKey: mutationKey,
156
+ mutationFn: {} as "You need to pass .mutationOptions to the useMutation hook",
134
157
  mutationOptions: {
135
158
  mutationKey: mutationKey,
136
- mutationFn: async (params: TEndpoint extends { parameters: infer Parameters } ? Parameters : never) => {
137
- const response = await this.client.request(method, path, params);
138
- const res = selectFn ? selectFn(response) : response
139
- return res as unknown extends TSelection ? typeof response : Awaited<TSelection>
140
- },
141
- },
142
- };
159
+ mutationFn: async <TLocalWithResponse extends boolean = TWithResponse, TLocalSelection = TLocalWithResponse extends true
160
+ ? InferResponseByStatus<TEndpoint, SuccessStatusCode>
161
+ : TEndpoint extends { response: infer Res }
162
+ ? Res
163
+ : never>
164
+ (params: (TEndpoint extends { parameters: infer Parameters } ? Parameters : {}) & {
165
+ withResponse?: TLocalWithResponse;
166
+ throwOnStatusError?: boolean;
167
+ }): Promise<TLocalSelection> => {
168
+ const withResponse = params.withResponse ??options?.withResponse ?? false;
169
+ const throwOnStatusError = params.throwOnStatusError ?? options?.throwOnStatusError ?? (withResponse ? false : true);
170
+ const selectFn = options?.selectFn;
171
+ const response = await (this.client as any)[method](path, { ...params as any, withResponse: true, throwOnStatusError: false });
172
+
173
+ if (throwOnStatusError && errorStatusCodes.includes(response.status as never)) {
174
+ throw new TypedResponseError(response as never);
175
+ }
176
+
177
+ // Return just the data if withResponse is false, otherwise return the full response
178
+ const finalResponse = withResponse ? response : response.data;
179
+ const res = selectFn ? selectFn(finalResponse as any) : finalResponse;
180
+ return res as never;
181
+ }
182
+ } satisfies import("@tanstack/react-query").UseMutationOptions<TSelection, TError, (TEndpoint extends { parameters: infer Parameters } ? Parameters : {}) & {
183
+ withResponse?: boolean;
184
+ throwOnStatusError?: boolean;
185
+ }>,
143
186
  }
187
+ }
144
188
  // </ApiClient.request>
145
189
  }
146
190
  `;