Skip to content

Commit fd82fe8

Browse files
committed
fix: add overrides?: RequestInit; on mutationFn
test: abort signal example
1 parent 1d71435 commit fd82fe8

File tree

4 files changed

+56
-3
lines changed

4 files changed

+56
-3
lines changed

.changeset/slow-pianos-obey.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
---
2-
"typed-openapi": patch
2+
"typed-openapi": minor
33
---
44

5-
feat: allow specifying headers with HeadersInit on any request
5+
feat: allow specifying overrides on any request
66
fix: infer/narrow response with multiple json media types
7+
fix: properly handle mutation errors while retaining genericity on output based on mutationFn withResponse: true/false
8+
feat: decodePathParams/encodeSearchParams/parseResponseData
9+
feat: allow passing overrides/withResponse even if there's no endpoint parameters

packages/typed-openapi/src/tanstack-query.generator.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,11 +156,16 @@ export const generateTanstackQueryFile = async (ctx: GeneratorContext & { relati
156156
(params: (TEndpoint extends { parameters: infer Parameters } ? Parameters : {}) & {
157157
withResponse?: TLocalWithResponse;
158158
throwOnStatusError?: boolean;
159+
overrides?: RequestInit;
159160
}): Promise<TLocalSelection> => {
160161
const withResponse = params.withResponse ??options?.withResponse ?? false;
161162
const throwOnStatusError = params.throwOnStatusError ?? options?.throwOnStatusError ?? (withResponse ? false : true);
162163
const selectFn = options?.selectFn;
163-
const response = await (this.client as any)[method](path, { ...params as any, withResponse: true, throwOnStatusError: false });
164+
const response = await (this.client as any)[method](path, {
165+
...params as any,
166+
withResponse: true,
167+
throwOnStatusError: false,
168+
});
164169
165170
if (throwOnStatusError && errorStatusCodes.includes(response.status as never)) {
166171
throw new TypedResponseError(response as never);

packages/typed-openapi/tests/integration-runtime-msw.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ const server = setupServer(
4646
status: "sold",
4747
});
4848
}),
49+
// Slow endpoint to allow testing AbortController cancellation
50+
http.get("http://localhost/pet/111", async () => {
51+
// artificial delay so we have time to abort
52+
await new Promise((resolve) => setTimeout(resolve, 200));
53+
return HttpResponse.json({ id: 500, name: "Slow", photoUrls: [], status: "pending" }, { status: 200 });
54+
}),
4955
// GET with path (404 error)
5056
http.get("http://localhost/pet/9999", () => {
5157
return HttpResponse.json({ code: 404, message: "Pet not found" }, { status: 404 });
@@ -209,6 +215,29 @@ describe("Example API Client", () => {
209215
]);
210216
});
211217

218+
it("should allow aborting requests via AbortController signal", async () => {
219+
// custom client that forwards the provided signal to fetch
220+
const mini = createApiClient(
221+
{
222+
fetch(input) {
223+
return fetch(input.url.toString(), {
224+
method: input.method,
225+
signal: input.overrides?.signal ?? null,
226+
});
227+
},
228+
},
229+
"http://localhost",
230+
);
231+
232+
const controller = new AbortController();
233+
const promise = mini.get("/pet/{petId}", { path: { petId: 111 }, overrides: { signal: controller.signal } });
234+
235+
// abort immediately
236+
controller.abort();
237+
238+
await expect(promise).rejects.toHaveProperty("name", "AbortError");
239+
});
240+
212241
it("should fetch /pet/findByStatus and receive mocked pets", async () => {
213242
const result = await api.get("/pet/findByStatus", { query: {} });
214243
expect(result).toEqual(mockPets);
@@ -455,6 +484,21 @@ describe("Example API Client", () => {
455484
expect(result.data).toEqual({ code: 404, message: expect.any(String) });
456485
});
457486

487+
it("should allow aborting requests via AbortController signal (tanstack)", async () => {
488+
const mutation = tanstack.mutation("get", "/pet/{petId}");
489+
const controller = new AbortController();
490+
491+
const promise = mutation.mutationOptions.mutationFn!({
492+
path: { petId: 111 },
493+
overrides: { signal: controller.signal },
494+
});
495+
496+
// abort immediately
497+
controller.abort();
498+
499+
await expect(promise).rejects.toHaveProperty("name", "AbortError");
500+
});
501+
458502
it("should throw when throwOnStatusError is true (tanstack)", async () => {
459503
const mutation = tanstack.mutation("get", "/pet/{petId}", { withResponse: true });
460504
let err: unknown;

packages/typed-openapi/tests/tanstack-query.generator.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ describe("generator", () => {
299299
params: (TEndpoint extends { parameters: infer Parameters } ? Parameters : {}) & {
300300
withResponse?: TLocalWithResponse;
301301
throwOnStatusError?: boolean;
302+
overrides?: RequestInit;
302303
},
303304
): Promise<TLocalSelection> => {
304305
const withResponse = params.withResponse ?? options?.withResponse ?? false;

0 commit comments

Comments
 (0)