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.
- package/dist/{chunk-OVT6OLBK.js → chunk-E6A7N4ND.js} +318 -41
- package/dist/{chunk-O7DZWQK4.js → chunk-MCVYB63W.js} +21 -11
- package/dist/cli.js +7 -4
- package/dist/index.d.ts +8 -3
- package/dist/index.js +1 -1
- package/dist/node.export.d.ts +4 -1
- package/dist/node.export.js +2 -2
- package/dist/{types-DLE5RaXi.d.ts → types-DsI2d-HE.d.ts} +2 -0
- package/package.json +6 -1
- package/src/cli.ts +8 -3
- package/src/generate-client-files.ts +36 -10
- package/src/generator.ts +185 -20
- package/src/map-openapi-endpoints.ts +46 -2
- package/src/tanstack-query.generator.ts +69 -25
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
`;
|