feat(world-vercel): allow a custom dispatcher#2235
Conversation
🦋 Changeset detectedLatest commit: 9949659 The changes in this PR will be included in the next version bump. This PR includes changesets to release 17 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
🧪 E2E Test Results✅ All tests passed Summary
Details by Category✅ ▲ Vercel Production
✅ 💻 Local Development
✅ 📦 Local Production
✅ 🐘 Local Postgres
✅ 🪟 Windows
✅ 📋 Other
|
📊 Benchmark Results
workflow with no steps💻 Local Development
workflow with 1 step💻 Local Development
workflow with 10 sequential steps💻 Local Development
workflow with 25 sequential steps💻 Local Development
workflow with 50 sequential steps💻 Local Development
Promise.all with 10 concurrent steps💻 Local Development
Promise.all with 25 concurrent steps💻 Local Development
Promise.all with 50 concurrent steps💻 Local Development
Promise.race with 10 concurrent steps💻 Local Development
Promise.race with 25 concurrent steps💻 Local Development
Promise.race with 50 concurrent steps💻 Local Development
workflow with 10 sequential data payload steps (10KB)💻 Local Development
workflow with 25 sequential data payload steps (10KB)💻 Local Development
workflow with 50 sequential data payload steps (10KB)💻 Local Development
workflow with 10 concurrent data payload steps (10KB)💻 Local Development
workflow with 25 concurrent data payload steps (10KB)💻 Local Development
workflow with 50 concurrent data payload steps (10KB)💻 Local Development
Stream Benchmarks (includes TTFB metrics)workflow with stream💻 Local Development
stream pipeline with 5 transform steps (1MB)💻 Local Development
10 parallel streams (1MB each)💻 Local Development
fan-out fan-in 10 streams (1MB each)💻 Local Development
SummaryFastest Framework by WorldWinner determined by most benchmark wins
Fastest World by FrameworkWinner determined by most benchmark wins
Column Definitions
Worlds:
❌ Some benchmark jobs failed:
Check the workflow run for details. |
karthikscale3
left a comment
There was a problem hiding this comment.
Reviewed for correctness and regression risk — backward-compatible and safe to merge, defaults are unchanged and every request path threads config consistently. A few non-blocking notes inline.
| import { createVercelWorld } from '@workflow/world-vercel'; | ||
| import { setWorld } from '@workflow/core/runtime'; | ||
|
|
||
| setWorld(createVercelWorld({ dispatcher: new Agent({ connections: 16 }) })); |
There was a problem hiding this comment.
Injection timing caveat worth documenting here: at Vercel runtime createWorld() calls createVercelWorld() with no config, so setWorld(...) is the only injection path. If the runtime has already created/cached the world before this setWorld call runs, the custom dispatcher silently won't take effect. Worth a one-line note that setWorld must run before the world is first used (i.e. before the first workflow request).
| * dispatcher from a different undici version. Callers may pass any undici | ||
| * version's dispatcher, or any object implementing the dispatcher contract. | ||
| */ | ||
| dispatcher?: unknown; |
There was a problem hiding this comment.
unknown is the right call given undici's version-specific Dispatcher type (the doc comment explains it well). Just flagging the trade-off: callers get zero type safety, so a malformed/incompatible dispatcher fails only at runtime inside fetch/@vercel/queue rather than at compile time. Acceptable here, but worth keeping in mind for the error story.
| * shared default agent. | ||
| */ | ||
| export function getDispatcher(config?: APIConfig): unknown { | ||
| return config?.dispatcher ?? getDefaultDispatcher(); |
There was a problem hiding this comment.
Nullish coalescing means an explicit dispatcher: null (or undefined) falls back to the shared default — sensible and probably the desired behavior. Only note: a caller who passes a falsy-but-intentional value expecting it to be used would be surprised, but for a dispatcher that's the correct semantics. No change needed; documenting the intent.
|
Backport PR opened against |
What
Adds a
dispatcheroption toAPIConfig(consumed bycreateVercelWorld) so callers can supply a custom undici dispatcher. It is threaded through every request path — HTTP API calls, refs, encryption key fetch, deployment resolution, and the@vercel/queueclient — and defaults to the shared undiciRetryAgent.Why
The Vercel world relies on Node's built-in fetch being undici-backed and pins a specific undici version. Both the HTTP and queue paths already issue requests via global
fetchwith an undicidispatcher, so exposing the dispatcher gives callers a single, uniform knob to bring their own undiciAgent/RetryAgent(e.g. to tune pooling/retries on newer Node runtimes) without us changing defaults.How
APIConfig.dispatcher?: unknown(mirrors@vercel/queue's owndispatcher?: unknown).getDispatcher(config?)now returns the caller's dispatcher when set, else the shared default agent (the previous singleton, renamed togetDefaultDispatcher).configthrough; default behavior is unchanged.Usage
At Vercel runtime
createWorld()callscreateVercelWorld()with no config, so injection is via the existingsetWorldescape hatch (documented in the README):Tests
http-client.test.tscoveringgetDispatcherdefault vs. override.No public API removed;
dispatcheris purely additive and defaults to current undici behavior.