Skip to content

Preserve AbortSignal across SDK fetch adapters #95

Open

Description

@betegon

Part of v1.0.0 (#93). The response/error foundation is merged in #104. This slice establishes cancellation before opt-in retries #92.

Contract and ownership

Use standard AbortSignal per operation on a reusable client. A signal belongs to an execution, not a process-global client or query key. Cancelling one operation must not cancel concurrent operations sharing that client.

The SDK constructs a native Request and calls the injected fetch(request). Adapters must preserve method, body, headers and signal, respecting native RequestInit overrides when accepting (input, init). In particular, an explicit signal: null removes an inherited signal; a blanket nullish fallback does not express that precedence correctly. Tests must check propagation, not identity of signal objects, because Request can create a dependent signal.

Preserve #104's error behavior: pre-dispatch/fetch cancellation uses the fields or throwOnError contract and existing error interceptors; generated pagination rejects with the resulting error. Exceptions while reading/decoding a received response reject unchanged. Preserve native abort reasons where the runtime supports them, including non-Error values. Do not invent an HTTP failure or a new cancellation error class. A caller cancellation and a consumer timeout remain distinct.

Cancellation is cooperative. The SDK can prevent its own subsequent dispatches but cannot force arbitrary custom fetch implementations or asynchronous hooks to stop, undo completed server effects, or cancel consumer-owned work without a connected signal.

Verified consumer findings

Read-only investigation used Toolkit main bc345ebf and Sentry frontend 1f6cfd5 on 2026-10-15. These are pre-existing integration gaps, not regressions introduced by #104 or the object-client DX change.

Consumer Current gap Delivery boundary
CLI Normal executeAttempt reads only init?.signal, dropping Request.signal. Redirect dispatch preserves it, but catch classification still loses it. Signal linking discards the reason, and backoff does not observe cancellation. Separate Toolkit adapter correction linked here. Preserve existing auth/cache/timeout/retry policy.
MCP Protocol extra.signal is not connected to tool context or API methods. Its SDK adapter preserves the signal it receives, but the real tool cancellation never reaches it. Some fetch wrappers relabel aborts; timeout/backoff/polling helpers also need composition. Separate MCP adoption follow-up, required for meaningful v1 cancellation evidence. Keep context per invocation; do not mutate shared server context.
Frontend apiFetch/apiFetchInfinite ignore TanStack's context.signal; the legacy client uses its own controller. Query-state cancellation can leave transport and later infinite-query pages running. Shared runtime integration in #103. #102 remains type-only and independent.

Local probes confirmed native SDK cancellation and concurrent-call isolation, the CLI Request adapter failure, continued MCP handler work after protocol cancellation, and TanStack 5.101.4 transport/page behavior. They did not validate production OAuth, Cloudflare deployment, or browser login/sudo. Node 22 and Bun can expose different native error values when cancellation occurs during response-body consumption; preserve the runtime failure rather than asserting universal reason identity.

SDK implementation and acceptance

  • Document per-operation signals and the injected-fetch contract, including Request/RequestInit precedence and consumer responsibility for in-flight cancellation.
  • Check cancellation immediately before REST dispatch, after request interception, without changing fix: stabilize SDK response and error contracts #104's fields, throwOnError, or intentional error mapping behavior.
  • Cover standalone operations, bound methods, injected clients and generated fetchPage/collection helpers. Prevent the next adapter dispatch when cancellation occurs between generated pages. Generic callback-based pagination still depends on its supplied fetcher.
  • Test pre-aborted and in-flight requests, cancellation after an async request interceptor, response-body consumption after headers, custom reasons, and native signal overrides.
  • Test independent concurrent calls on one client and composition with a consumer-owned timeout without adding SDK deadlines.
  • Verify the browser fetch adapter preserves method/body/headers and signal for both native fetch forms.
  • Keep the public API unchanged: no new cancellation class, composition helper, retry engine, response cache, or consumer migration in this SDK slice. Revisit shared utilities only if a demonstrated need remains.

Linked consumer work and closure

  • Link a Toolkit CLI correction using the effective caller signal consistently for dispatch, error classification, cache/auth continuation, and retry waits. Stop new attempts after cancellation while keeping existing retry eligibility and budgets.
  • Verify CLI Request and URL+init paths, redirects, reasons, internal timeout distinction, body cancellation, and backoff. A cancelled caller must not abort an OAuth refresh shared by other callers. The current public CLI-library signal is streaming-specific; this work must not claim universal command cancellation.
  • Link a separate MCP follow-up for extra.signal -> invocation context -> API service -> SDK/raw fetch, error preservation, and cancellation-aware loops. MCP cancellation reasons may be strings; checking only error.name === "AbortError" is insufficient.
  • Keep frontend acceptance in Integrate the SDK runtime with frontend apiOptions and RequestError #103: connect the actual TanStack signal centrally, preserve the real HTTP RequestError and cache shape, stop infinite-query work and post-sudo continuation after cancellation, and avoid cancellation toasts or new retries. Verify unmount behavior deliberately because consuming the signal changes TanStack's default cache-completion behavior. Do not reproduce permanently pending legacy promises as an SDK requirement.

Close this issue after the SDK contract and linked CLI adapter correction are verified, with MCP follow-up ownership and #103 acceptance recorded. Completing all consumer migrations is not part of this SDK PR; their integrated workflows remain v1 release gates in #93.

After this slice, #92 adds opt-in retry mechanics and per-call controls using the same cancellation contract. There must be one retry owner; retained consumer loops and shared auth refresh must compose deliberately. Generated SSE reconnect behavior is separate from this REST slice and must not be described as covered by these tests.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions