diff --git a/src/utils/axios-client.ts b/src/utils/axios-client.ts index 957835c..b8c9ab6 100644 --- a/src/utils/axios-client.ts +++ b/src/utils/axios-client.ts @@ -174,6 +174,18 @@ export function createAxiosClient({ // Add origin URL in browser environment client.interceptors.request.use((config) => { + // Axios selects its Node HTTP adapter in Deno, where DELETE requests with + // a body fail in the node:http compatibility layer. deleteMany relies on + // that request shape, while Deno's native fetch handles it correctly. + if ( + typeof (globalThis as typeof globalThis & { Deno?: unknown }).Deno !== + "undefined" && + config.method?.toLowerCase() === "delete" && + config.data !== undefined + ) { + config.adapter = "fetch"; + } + if (typeof window !== "undefined") { config.headers.set("X-Origin-URL", window.location.href); } diff --git a/tests/unit/axios-client.test.ts b/tests/unit/axios-client.test.ts new file mode 100644 index 0000000..aece9e1 --- /dev/null +++ b/tests/unit/axios-client.test.ts @@ -0,0 +1,70 @@ +import axios from "axios"; +import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; + +import { createAxiosClient } from "../../src/utils/axios-client.ts"; + +vi.mock("axios", () => ({ + default: { + create: vi.fn(), + }, +})); + +function createMockClient() { + return { + defaults: { headers: { common: {} } }, + interceptors: { + request: { use: vi.fn() }, + response: { use: vi.fn() }, + }, + }; +} + +describe("createAxiosClient Deno adapter selection", () => { + let mockClient: ReturnType; + + beforeEach(() => { + mockClient = createMockClient(); + vi.mocked(axios.create).mockReturnValue(mockClient as any); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + vi.clearAllMocks(); + }); + + function getRequestInterceptor() { + createAxiosClient({ baseURL: "https://example.com/api" }); + return mockClient.interceptors.request.use.mock.calls[0][0]; + } + + test("uses the fetch adapter for DELETE requests with a body in Deno", () => { + vi.stubGlobal("Deno", {}); + const intercept = getRequestInterceptor(); + const config = { method: "delete", data: { status: "done" }, headers: new Headers() }; + + expect(intercept(config).adapter).toBe("fetch"); + }); + + test("keeps the default adapter for bodyless DELETE requests in Deno", () => { + vi.stubGlobal("Deno", {}); + const intercept = getRequestInterceptor(); + const config = { method: "delete", headers: new Headers() }; + + expect(intercept(config).adapter).toBeUndefined(); + }); + + test("keeps the default adapter for other request methods in Deno", () => { + vi.stubGlobal("Deno", {}); + const intercept = getRequestInterceptor(); + const config = { method: "post", data: { title: "new" }, headers: new Headers() }; + + expect(intercept(config).adapter).toBeUndefined(); + }); + + test("keeps the default adapter outside Deno", () => { + const intercept = getRequestInterceptor(); + const config = { method: "delete", data: { status: "done" }, headers: new Headers() }; + + expect(intercept(config).adapter).toBeUndefined(); + }); +});