feat(server): add timeout handler plugin - #1858
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
More templates
@orpc/ai-sdk
@orpc/arktype
@orpc/bun
@orpc/client
@orpc/cloudflare
@orpc/contract
@orpc/experimental-effect
@orpc/evlog
@orpc/hibernation
@orpc/json-schema
@orpc/nest
@orpc/next
@orpc/node
@orpc/openapi
@orpc/opentelemetry
@orpc/pinia-colada
@orpc/pino
@orpc/publisher
@orpc/ratelimit
@orpc/server
@orpc/shared
@orpc/swr
@orpc/tanstack-query
@orpc/trpc
@orpc/valibot
@orpc/zod
commit: |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
ℹ️ Minor suggestions only — the plugin is solid and well-tested; one ergonomic nit inline.
Reviewed changes
TimeoutHandlerPluginimplementation — newpackages/server/src/plugins/timeout.tsthat aborts the merged request signal with anAbortErroraftertimeout(response production) orstreamingTimeout(full streaming body), never preempting the procedure. Exported from@orpc/server/plugins.- Handler tests — 15 fake-timer tests with an
afterEachasserting zero leftover timers, covering abort propagation, no-preemption, unmasked errors, dynamic per-request values, streaming cleanup for both async-iterator andReadableStreambodies. - Registration — added to the
tests/plugins/all-plugins.test.tscompatibility suite. - Docs —
apps/content/docs/plugins/timeout.mdxsplit into Client/Server sections with streaming and dynamic-timeout coverage.
I traced the abort path end to end: the interceptor forwards request.signal = anyAbortSignal([request.signal, controller.signal]), and the standard handler relays request.signal into the procedure signal, so aborting the plugin's controller does reach signal-honoring procedures. The timeout timer is cleared in finally once next() resolves, and streamingTimeout is cleared via the wrapped body's onFinish (both on completion and cancellation). The tests are meaningful — each assertion would fail if the corresponding behavior regressed (e.g. a buggy timer that fires would trip the getTimerCount() === 0 guard).
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ The new
14339e0e"fix" commit is clean (generated-file version bump only); not approving because the prior review'stimeout-required nit thread is still open ontimeout.ts.
Reviewed changes (delta since previous review, commit 14339e0e "fix")
- Wrangler types regeneration —
packages/cloudflare/worker-configuration.d.tsandplaygrounds/cloudflare/worker-configuration.d.tsonly change the generatedworkerd@1.20260730.1 → workerd@1.20260804.1header line; the hash line and all actual content are byte-identical, so this is pure generated-file cosmetics with no behavioral surface. - The
TimeoutHandlerPluginsource (packages/server/src/plugins/timeout.ts), its tests, and the docs are unchanged from the previously reviewed version.
One non-blocking observation: this commit bundles an unrelated wrangler types regeneration (generated files) into a feature PR. It's harmless, but dropping it would keep the PR focused on the timeout plugin.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Adds
TimeoutHandlerPluginto@orpc/server/plugins, the server-side counterpart ofTimeoutLinkPlugin. When handling exceeds the configured timeout, the plugin aborts the request signal with anAbortError. It never preempts the procedure: abort-aware procedures stop early and logging records the timeout as a cancellation, while a procedure that ignores the signal still delivers its real result or error, so nothing is masked.Features
timeoutlimits producing the response; the separatestreamingTimeout(usually higher) limits the full duration of streaming bodies, both async iterator objects and readable streams.null/undefineddisables the timeout for that request.Testing
pnpm type:check, eslint, andpnpm docs:validatepass.Docs
/docs/plugins/timeoutnow covers both the link and handler plugins, streaming timeouts, and dynamic per-request values.