fix(mcp): keep the getDocuments timeout when a caller passes a signal

Same pattern as forgetMemoryRequest: `options?.signal ?? AbortSignal.timeout(...)`
dropped the 30s bound whenever a caller supplied its own signal. Compose the
two with AbortSignal.any so the caller signal adds cancellation instead of
replacing the timeout.
This commit is contained in:
Agnik47 2026-10-02 09:46:52 +05:30
parent 9381369eee
commit 014e1e924d
2 changed files with 74 additions and 1 deletions

View file

@ -0,0 +1,66 @@
import { afterEach, describe, expect, it, vi } from "vitest"
import { SupermemoryClient } from "./index"
const API_URL = "https://api.example.com"
describe("SupermemoryClient.getDocuments", () => {
afterEach(() => {
vi.restoreAllMocks()
vi.unstubAllGlobals()
})
function stubFetch() {
const fetchMock = vi.fn().mockResolvedValue(
Response.json({
documents: [],
pagination: {
currentPage: 1,
limit: 200,
totalItems: 0,
totalPages: 0,
},
}),
)
vi.stubGlobal("fetch", fetchMock)
return fetchMock
}
it("cancels through a caller-provided signal", async () => {
const fetchMock = stubFetch()
const controller = new AbortController()
await new SupermemoryClient("sm_test_key", "user_1", API_URL).getDocuments(
["user_1"],
1,
200,
{ signal: controller.signal },
)
const [, init] = fetchMock.mock.calls[0] as [string, RequestInit]
controller.abort()
expect(init.signal?.aborted).toBe(true)
})
it("keeps the timeout when a caller-provided signal is present", async () => {
const timeoutController = new AbortController()
const timeoutSpy = vi
.spyOn(AbortSignal, "timeout")
.mockReturnValue(timeoutController.signal)
const fetchMock = stubFetch()
await new SupermemoryClient("sm_test_key", "user_1", API_URL).getDocuments(
["user_1"],
1,
200,
{ signal: new AbortController().signal },
)
expect(timeoutSpy).toHaveBeenCalledWith(30_000)
const [, init] = fetchMock.mock.calls[0] as [string, RequestInit]
// Firing only the timeout leg aborts the request: a caller signal adds
// cancellation, it does not remove the 30s bound.
timeoutController.abort()
expect(init.signal?.aborted).toBe(true)
})
})

View file

@ -336,7 +336,14 @@ export class SupermemoryClient {
options?: { signal?: AbortSignal },
): Promise<DocumentsApiResponse> {
try {
const signal = options?.signal ?? AbortSignal.timeout(FETCH_TIMEOUT_MS)
// Compose rather than choose: a caller-supplied signal must add
// cancellation on top of the timeout, not replace it.
const signal = options?.signal
? AbortSignal.any([
options.signal,
AbortSignal.timeout(FETCH_TIMEOUT_MS),
])
: AbortSignal.timeout(FETCH_TIMEOUT_MS)
const response = await fetch(`${this.apiUrl}/v3/documents/documents`, {
method: "POST",
headers: {