mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-10-10 03:28:14 +00:00
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:
parent
9381369eee
commit
014e1e924d
2 changed files with 74 additions and 1 deletions
66
apps/mcp/src/server/client/index.test.ts
Normal file
66
apps/mcp/src/server/client/index.test.ts
Normal 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)
|
||||
})
|
||||
})
|
||||
|
|
@ -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: {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue