[world-vercel] Unify HTTP request handling into a shared core - #2631

Merged
VaguelySerious merged 1 commit into
mainfrom
peter/world-vercel-http-cleanup
Jun 28, 2026
Merged

[world-vercel] Unify HTTP request handling into a shared core#2631
VaguelySerious merged 1 commit into
mainfrom
peter/world-vercel-http-cleanup

Conversation

@VaguelySerious

Copy link
Copy Markdown
Member

What

Follow-up to #2573. world-vercel had five separate HTTP request paths, each re-implementing the same cross-cutting concerns inconsistently. This collapses the duplication into one shared core (http-core.ts) and routes every path through it, so the v4 / stream / Vercel-API paths gain the OTEL client spans + DEBUG logging the v3 path already had.

Before

PathOTEL spanDEBUG logcurl-reprodiag headerstyped errorstrace-ctx
v3 makeRequest
v4 events(2nd copy)
stream write/close(2nd copy)
stream get/list
Vercel API (run-key, resolve-deploy)(plain Error)

The v4 events path — now the hottest path — had no client span and no debug logging.

How

New http-core.ts is the single source of truth for the request envelope:

  • errorForResponse — the status → typed-error map (409→EntityConflictError, 410→RunExpiredError, 425→TooEarlyError, 429→ThrottleError, else→WorkflowWorldError). Was implemented twice (v3 makeRequest + v4 throwForErrorResponse).
  • getVercelDiagnostics / parseRetryAfter / httpLog / logCurlRepro — one copy each (were duplicated across utils.ts and streamer.ts).
  • resolveVercelApiToken — shared token → VERCEL_TOKEN → OIDC precedence (was byte-identical in encryption.ts and resolve-latest-deployment.ts).
  • instrumentedFetch — shared OTEL span + trace-context injection + cache-bust header + timeout + DEBUG logging + error envelope. The v4 events, stream, and Vercel-API paths now route through it.

Also: stream writes now inject W3C trace context (previously missing, contrary to the documented requirement that every world→server request must), and the three dispatcher singletons in http-client.ts collapse behind one makeRetryDispatcher factory.

Behavior preserved

No behavior change beyond the added observability:

  • Per-path error-message strings are unchanged (the v3/v4/stream message formats feed log tooling); only the type mapping, Retry-After parsing, and diagnostics are unified.
  • The non-idempotent stream retry policy (transient + 429 only, never 5xx) is untouched.
  • The no-timeout semantics of the v4 (streamed LIST), stream (large buffered writes / long-lived live read), and Vercel-API paths are preserved (timeoutMs: null); only the v3 path keeps its 60s timeout.

Tests

  • New http-core.test.ts (error mapping per status, parseRetryAfter, diagnostics, token precedence).
  • Extended trace-propagation.test.ts: v4 now parents to its own http GET client span (matching v3), and a new case asserts stream writes inject traceparent.
  • Full world-vercel suite green (187 tests); build + typecheck + lint clean.

Changeset

@workflow/world-vercel patch — internal transport refactor + added observability, no API surface change.

Collapse the duplicated cross-cutting request logic across world-vercel's
request paths into one shared module (http-core.ts):
- errorForResponse: single status -> typed-error map (409/410/425/429/else),
shared by the v3 makeRequest and v4 throwForErrorResponse paths.
- getVercelDiagnostics / parseRetryAfter / httpLog / logCurlRepro: one copy
each (were duplicated across utils.ts and streamer.ts).
- resolveVercelApiToken: shared token precedence for the direct Vercel-API
calls (was byte-identical in encryption.ts and resolve-latest-deployment.ts).
- instrumentedFetch: the shared OTEL-span + trace-injection + cache-bust +
timeout + DEBUG-logging + error envelope.
Route the v4 events, stream write/read, and Vercel-API (run-key /
resolve-latest-deployment) paths through instrumentedFetch so they gain the
OTEL client spans and DEBUG logging the v3 path already had. Stream writes now
also inject W3C trace context (previously missing). Collapse the three
dispatcher singletons in http-client.ts behind a single makeRetryDispatcher
factory.
No behavior change beyond the added observability: per-path error-message
formats, the non-idempotent stream retry policy, and the no-timeout semantics
of the v4/stream/Vercel-API paths are preserved.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9263886

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 17 packages
NameType
@workflow/world-vercelPatch
@workflow/cliPatch
@workflow/corePatch
@workflow/webPatch
workflowPatch
@workflow/world-testingPatch
@workflow/buildersPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch
@workflow/nuxtPatch

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

@github-actions

github-actionsBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.035s (-26.0% 🟢)1.005s (~)0.970s101.00x
💻 LocalExpress0.048s (+1.7%)1.006s (~)0.958s101.39x
💻 LocalNext.js (Turbopack)0.050s (-4.8%)1.006s (~)0.956s101.44x
🐘 PostgresNext.js (Turbopack)0.061s (+9.8% 🔺)1.012s (~)0.951s101.74x
🐘 PostgresExpress0.063s (-9.9% 🟢)1.012s (~)0.949s101.80x
🐘 PostgresNitro0.071s (+9.2% 🔺)1.012s (~)0.940s102.05x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express0.332s (+43.7% 🔺)2.094s (~)1.762s101.00x
▲ VercelNitro0.343s (+36.1% 🔺)2.314s (+6.6% 🔺)1.971s101.03x
▲ VercelNext.js (Turbopack)0.931s (+244.5% 🔺)3.051s (+49.2% 🔺)2.120s102.80x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.062s (-2.2%)2.005s (~)0.943s101.00x
💻 LocalNext.js (Turbopack)1.084s (~)2.007s (~)0.923s101.02x
💻 LocalExpress1.084s (~)2.006s (~)0.922s101.02x
🐘 PostgresNitro1.088s (-1.3%)2.010s (~)0.922s101.02x
🐘 PostgresNext.js (Turbopack)1.103s (+1.8%)2.010s (~)0.907s101.04x
🐘 PostgresExpress1.104s (+1.1%)2.009s (~)0.905s101.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.499s (+6.2% 🔺)3.381s (+6.1% 🔺)1.881s101.00x
▲ VercelExpress1.584s (+14.5% 🔺)3.601s (+27.2% 🔺)2.017s101.06x
▲ VercelNext.js (Turbopack)2.692s (+87.7% 🔺)4.407s (+31.5% 🔺)1.715s101.80x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro10.335s (-1.2%)11.020s (~)0.685s31.00x
🐘 PostgresNitro10.443s (-0.6%)11.018s (~)0.575s31.01x
💻 LocalExpress10.449s (~)11.023s (~)0.574s31.01x
💻 LocalNext.js (Turbopack)10.523s (~)11.024s (~)0.501s31.02x
🐘 PostgresExpress10.529s (+0.8%)11.018s (~)0.489s31.02x
🐘 PostgresNext.js (Turbopack)10.549s (+0.9%)11.019s (~)0.470s31.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro13.474s (+15.0% 🔺)15.455s (+15.7% 🔺)1.981s31.00x
▲ VercelExpress13.947s (+13.3% 🔺)16.641s (+20.0% 🔺)2.694s21.04x
▲ VercelNext.js (Turbopack)14.162s (+20.0% 🔺)16.396s (+17.6% 🔺)2.234s21.05x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro13.443s (-1.4%)14.025s (~)0.582s51.00x
🐘 PostgresNitro13.632s (-0.6%)14.020s (~)0.388s51.01x
💻 LocalNext.js (Turbopack)13.637s (~)14.026s (~)0.389s51.01x
🐘 PostgresNext.js (Turbopack)13.650s (~)14.021s (~)0.371s51.02x
💻 LocalExpress13.656s (~)14.028s (~)0.372s51.02x
🐘 PostgresExpress13.824s (+1.8%)14.019s (~)0.194s51.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro20.368s (+24.4% 🔺)22.630s (+27.7% 🔺)2.262s31.00x
▲ VercelExpress20.918s (+27.2% 🔺)22.656s (+25.9% 🔺)1.738s31.03x
▲ VercelNext.js (Turbopack)23.709s (+36.7% 🔺)25.789s (+34.7% 🔺)2.079s31.16x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro11.628s (-4.5%)12.022s (-7.7% 🟢)0.394s81.00x
🐘 PostgresNitro12.094s (-1.5%)13.016s (~)0.922s71.04x
💻 LocalExpress12.171s (-1.4%)13.025s (~)0.853s71.05x
🐘 PostgresExpress12.282s (+1.5%)13.018s (~)0.736s71.06x
🐘 PostgresNext.js (Turbopack)12.323s (+1.8%)13.017s (~)0.694s71.06x
💻 LocalNext.js (Turbopack)12.389s (+1.1%)13.025s (~)0.636s71.07x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro22.441s (+18.2% 🔺)25.257s (+22.4% 🔺)2.816s41.00x
▲ VercelNext.js (Turbopack)24.710s (+27.7% 🔺)27.172s (+28.0% 🔺)2.462s41.10x
▲ VercelExpress24.776s (+34.8% 🔺)27.682s (+37.3% 🔺)2.906s41.10x

🔍 Observability: Nitro | Next.js (Turbopack) | Express

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.181s (~)2.008s (~)0.828s151.00x
🐘 PostgresNext.js (Turbopack)1.198s (+1.8%)2.008s (~)0.810s151.01x
🐘 PostgresExpress1.242s (+6.3% 🔺)2.009s (~)0.768s151.05x
💻 LocalNitro1.300s (-9.6% 🟢)2.006s (~)0.706s151.10x
💻 LocalExpress1.421s (+2.3%)2.007s (~)0.585s151.20x
💻 LocalNext.js (Turbopack)1.466s (-2.1%)2.007s (~)0.541s151.24x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.247s (+5.6% 🔺)4.214s (+22.4% 🔺)1.966s81.00x
▲ VercelExpress2.843s (+40.1% 🔺)4.746s (+35.6% 🔺)1.904s71.26x
▲ VercelNext.js (Turbopack)5.555s (+158.4% 🔺)7.589s (+100.3% 🔺)2.034s42.47x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.295s (-7.0% 🟢)2.736s (+2.3%)1.441s111.00x
🐘 PostgresNitro1.318s (-1.6%)2.591s (+3.3%)1.273s121.02x
🐘 PostgresNext.js (Turbopack)1.379s (+5.0% 🔺)3.011s (+3.2%)1.632s101.06x
💻 LocalNitro2.078s (-13.7% 🟢)2.826s (-3.2%)0.748s111.60x
💻 LocalExpress2.416s (+2.5%)3.009s (+3.1%)0.593s101.87x
💻 LocalNext.js (Turbopack)2.632s (-3.8%)3.010s (-3.2%)0.377s102.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.352s (+8.9% 🔺)5.327s (+2.8%)1.976s61.00x
▲ VercelExpress3.378s (-7.0% 🟢)5.318s (+3.0%)1.940s61.01x
▲ VercelNext.js (Turbopack)4.672s (+39.8% 🔺)6.725s (+33.3% 🔺)2.053s51.39x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.610s (-1.9%)4.137s (-3.0%)2.527s81.00x
🐘 PostgresExpress1.619s (+2.1%)4.012s (-6.7% 🟢)2.393s81.01x
🐘 PostgresNext.js (Turbopack)3.104s (+10.3% 🔺)6.015s (+9.0% 🔺)2.911s51.93x
💻 LocalNitro4.859s (-24.6% 🟢)5.347s (-25.9% 🟢)0.488s63.02x
💻 LocalNext.js (Turbopack)6.248s (+14.2% 🔺)7.518s (+25.0% 🔺)1.270s43.88x
💻 LocalExpress6.275s (-0.8%)6.614s (-5.8% 🟢)0.338s53.90x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express4.354s (+42.1% 🔺)6.876s (+36.3% 🔺)2.522s51.00x
▲ VercelNitro4.370s (+30.3% 🔺)6.765s (+32.1% 🔺)2.395s51.00x
▲ VercelNext.js (Turbopack)4.926s (+41.5% 🔺)7.542s (+45.0% 🔺)2.616s41.13x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.174s (-2.7%)2.007s (~)0.833s151.00x
🐘 PostgresNext.js (Turbopack)1.199s (+2.5%)2.008s (~)0.809s151.02x
🐘 PostgresExpress1.243s (+5.5% 🔺)2.074s (+3.3%)0.831s151.06x
💻 LocalNitro1.344s (-5.5% 🟢)2.006s (~)0.662s151.14x
💻 LocalExpress1.428s (-0.6%)2.007s (~)0.579s151.22x
💻 LocalNext.js (Turbopack)1.495s (+6.6% 🔺)2.073s (+3.3%)0.578s151.27x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.542s (+23.2% 🔺)4.657s (+33.1% 🔺)2.115s71.00x
▲ VercelNitro2.571s (+19.9% 🔺)4.516s (+21.5% 🔺)1.945s71.01x
▲ VercelNext.js (Turbopack)4.063s (-29.8% 🟢)6.178s (-17.0% 🟢)2.115s51.60x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.290s (-6.0% 🟢)2.075s (-16.1% 🟢)0.785s151.00x
🐘 PostgresExpress1.341s (~)2.393s (-3.2%)1.052s131.04x
🐘 PostgresNext.js (Turbopack)1.361s (+5.5% 🔺)3.010s (+3.2%)1.650s101.05x
💻 LocalNitro2.207s (-14.5% 🟢)2.917s (-6.2% 🟢)0.710s111.71x
💻 LocalExpress2.483s (+2.5%)2.919s (-6.1% 🟢)0.435s111.92x
💻 LocalNext.js (Turbopack)2.866s (+8.7% 🔺)3.209s (+6.7% 🔺)0.343s102.22x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.557s (-46.6% 🟢)4.443s (-33.1% 🟢)1.886s71.00x
▲ VercelNitro2.768s (+1.9%)4.662s (+8.0% 🔺)1.894s71.08x
▲ VercelNext.js (Turbopack)4.820s (+1.6%)7.047s (+1.4%)2.226s51.88x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.612s (-2.9%)4.012s (~)2.400s81.00x
🐘 PostgresExpress1.616s (+1.9%)4.012s (-3.0%)2.396s81.00x
🐘 PostgresNext.js (Turbopack)2.989s (+4.6%)6.013s (+2.8%)3.024s51.85x
💻 LocalNitro5.245s (-22.7% 🟢)5.680s (-21.3% 🟢)0.436s63.25x
💻 LocalNext.js (Turbopack)5.971s (+4.4%)6.614s (+9.9% 🔺)0.642s53.71x
💻 LocalExpress8.009s (+19.5% 🔺)8.518s (+18.0% 🔺)0.509s44.97x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.839s (-18.1% 🟢)6.037s (-4.1%)2.198s51.00x
▲ VercelNext.js (Turbopack)4.185s (+34.0% 🔺)6.365s (+23.3% 🔺)2.180s51.09x
▲ VercelNitro4.362s (+26.2% 🔺)6.526s (+29.7% 🔺)2.165s51.14x

🔍 Observability: Express | Next.js (Turbopack) | Nitro

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.508s (-13.3% 🟢)1.004s (~)0.496s601.00x
🐘 PostgresNitro0.518s (-12.9% 🟢)1.006s (-3.4%)0.488s601.02x
🐘 PostgresExpress0.565s (+5.9% 🔺)1.023s (~)0.458s591.11x
💻 LocalExpress0.596s (+4.0%)1.022s (+1.7%)0.426s591.17x
🐘 PostgresNext.js (Turbopack)0.608s (+18.9% 🔺)1.024s (+1.9%)0.417s591.20x
💻 LocalNext.js (Turbopack)0.608s (+0.8%)1.022s (~)0.414s591.20x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.261s (+16.5% 🔺)5.059s (+12.8% 🔺)1.799s121.00x
▲ VercelNitro3.490s (+31.9% 🔺)5.594s (+30.1% 🔺)2.104s111.07x
▲ VercelNext.js (Turbopack)4.224s (+39.0% 🔺)6.144s (+23.0% 🔺)1.920s101.30x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.240s (-12.3% 🟢)2.030s (-1.2%)0.789s451.00x
💻 LocalNitro1.256s (-15.3% 🟢)2.005s (~)0.749s451.01x
🐘 PostgresExpress1.331s (+6.4% 🔺)2.029s (+1.1%)0.699s451.07x
🐘 PostgresNext.js (Turbopack)1.389s (+11.6% 🔺)2.008s (-1.1%)0.619s451.12x
💻 LocalExpress1.472s (+1.2%)2.006s (~)0.534s451.19x
💻 LocalNext.js (Turbopack)1.532s (+4.4%)2.006s (~)0.474s451.24x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express7.933s (+22.7% 🔺)9.951s (+23.8% 🔺)2.018s101.00x
▲ VercelNitro8.413s (+31.5% 🔺)10.388s (+33.8% 🔺)1.975s91.06x
▲ VercelNext.js (Turbopack)10.501s (+47.0% 🔺)12.769s (+43.6% 🔺)2.268s81.32x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.512s (-7.5% 🟢)3.086s (+0.9%)0.574s391.00x
💻 LocalNitro2.760s (-14.3% 🟢)3.165s (-21.1% 🟢)0.405s381.10x
🐘 PostgresExpress2.763s (+6.5% 🔺)3.167s (+2.6%)0.404s381.10x
🐘 PostgresNext.js (Turbopack)2.793s (+12.8% 🔺)3.009s (-1.6%)0.216s401.11x
💻 LocalExpress3.242s (~)4.010s (+0.8%)0.768s301.29x
💻 LocalNext.js (Turbopack)3.305s (+1.4%)4.043s (~)0.738s301.32x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express13.918s (+1.0%)17.102s (+10.6% 🔺)3.184s81.00x
▲ VercelNitro14.974s (+6.6% 🔺)17.975s (+15.0% 🔺)3.000s71.08x
▲ VercelNext.js (Turbopack)20.700s (+26.1% 🔺)23.054s (+24.7% 🔺)2.354s61.49x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.212s (~)1.006s (~)0.794s601.00x
🐘 PostgresNext.js (Turbopack)0.218s (+30.2% 🔺)1.007s (~)0.788s601.03x
🐘 PostgresNitro0.223s (-1.8%)1.006s (~)0.783s601.05x
💻 LocalNitro0.399s (-14.8% 🟢)1.021s (+1.6%)0.622s591.89x
💻 LocalExpress0.504s (+16.8% 🔺)1.005s (~)0.501s602.38x
💻 LocalNext.js (Turbopack)0.644s (+10.1% 🔺)1.022s (+1.7%)0.378s593.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.221s (-43.8% 🟢)3.077s (-20.6% 🟢)1.857s201.00x
▲ VercelExpress1.345s (-35.1% 🟢)3.022s (-21.3% 🟢)1.677s201.10x
▲ VercelNext.js (Turbopack)2.809s (+54.4% 🔺)4.638s (+30.7% 🔺)1.830s132.30x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.308s (+15.0% 🔺)1.041s (+3.5%)0.733s871.00x
🐘 PostgresNitro0.319s (-5.4% 🟢)1.006s (~)0.687s901.03x
🐘 PostgresExpress0.326s (+1.0%)1.007s (-1.1%)0.680s901.06x
💻 LocalNitro1.815s (-14.9% 🟢)2.285s (-14.9% 🟢)0.470s405.89x
💻 LocalExpress2.221s (+2.8%)2.797s (+2.2%)0.576s337.21x
💻 LocalNext.js (Turbopack)2.826s (+1.3%)3.295s (+1.1%)0.469s289.17x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.920s (-22.0% 🟢)3.816s (-5.9% 🟢)1.896s241.00x
▲ VercelNitro1.964s (+3.0%)3.918s (+12.3% 🔺)1.954s231.02x
▲ VercelNext.js (Turbopack)3.338s (+53.8% 🔺)5.492s (+39.8% 🔺)2.155s171.74x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.507s (-1.7%)1.068s (~)0.561s1131.00x
🐘 PostgresNitro0.513s (-2.9%)1.068s (+3.5%)0.555s1141.01x
🐘 PostgresNext.js (Turbopack)0.580s (+23.0% 🔺)3.254s (+8.1% 🔺)2.674s371.14x
💻 LocalNitro8.193s (-17.4% 🟢)9.094s (-17.5% 🟢)0.901s1416.15x
💻 LocalExpress9.881s (-2.0%)11.117s (~)1.235s1119.48x
💻 LocalNext.js (Turbopack)10.366s (+6.0% 🔺)11.576s (+7.4% 🔺)1.210s1120.44x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.428s (+34.8% 🔺)5.645s (+36.8% 🔺)2.217s221.00x
▲ VercelExpress3.617s (+51.3% 🔺)6.034s (+48.8% 🔺)2.417s201.06x
▲ VercelNext.js (Turbopack)4.887s (+4.3%)7.315s (+11.8% 🔺)2.428s171.43x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.128s (-2.6%)2.004s (~)0.008s (-33.9% 🟢)2.015s (~)0.887s101.00x
💻 LocalNext.js (Turbopack)1.145s (-2.1%)1.967s (~)0.012s (-2.4%)2.019s (~)0.875s101.01x
🐘 PostgresExpress1.159s (+0.7%)1.996s (~)0.001s (+10.0% 🔺)2.010s (~)0.852s101.03x
🐘 PostgresNitro1.159s (-0.7%)1.999s (~)0.001s (+9.1% 🔺)2.011s (~)0.852s101.03x
💻 LocalExpress1.160s (+0.7%)2.005s (~)0.013s (+5.8% 🔺)2.020s (~)0.860s101.03x
🐘 PostgresNext.js (Turbopack)1.165s (+1.7%)2.001s (~)0.001s (+18.2% 🔺)2.011s (~)0.846s101.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.120s (+3.6%)3.629s (+18.3% 🔺)2.424s (+38.4% 🔺)6.604s (+22.2% 🔺)4.484s101.00x
▲ VercelNitro2.340s (+5.5% 🔺)3.692s (+12.7% 🔺)2.806s (+50.6% 🔺)7.080s (+24.4% 🔺)4.740s101.10x
▲ VercelNext.js (Turbopack)3.746s (+81.4% 🔺)4.139s (+20.0% 🔺)2.700s (+91.6% 🔺)8.319s (+53.9% 🔺)4.573s101.77x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.426s (-8.2% 🟢)2.006s (~)0.011s (-7.8% 🟢)2.020s (~)0.594s301.00x
🐘 PostgresNitro1.544s (-3.8%)1.999s (~)0.005s (+6.6% 🔺)2.029s (~)0.485s301.08x
🐘 PostgresExpress1.551s (~)2.001s (~)0.005s (-10.2% 🟢)2.024s (~)0.473s301.09x
💻 LocalExpress1.556s (+0.5%)2.011s (~)0.012s (-4.1%)2.027s (~)0.471s301.09x
💻 LocalNext.js (Turbopack)1.608s (+1.9%)2.000s (+1.6%)0.013s (-3.1%)2.058s (+1.6%)0.450s301.13x
🐘 PostgresNext.js (Turbopack)1.675s (+8.7% 🔺)2.010s (~)0.005s (+0.6%)2.027s (~)0.352s301.17x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro7.027s (+15.7% 🔺)8.939s (+26.3% 🔺)0.304s (+34.3% 🔺)9.893s (+26.4% 🔺)2.866s71.00x
▲ VercelExpress7.058s (+19.9% 🔺)8.708s (+20.8% 🔺)0.283s (+31.1% 🔺)9.595s (+21.7% 🔺)2.537s71.00x
▲ VercelNext.js (Turbopack)10.646s (+79.2% 🔺)12.028s (+63.2% 🔺)0.377s (+18.4% 🔺)13.403s (+62.4% 🔺)2.757s51.52x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.781s (+0.9%)1.062s (+1.3%)0.000s (+Infinity% 🔺)1.079s (+1.4%)0.298s561.00x
🐘 PostgresNitro0.799s (-2.8%)1.044s (-5.0% 🟢)0.000s (-100.0% 🟢)1.059s (-5.3% 🟢)0.260s581.02x
💻 LocalNitro1.004s (-23.9% 🟢)1.475s (-25.5% 🟢)0.000s (-30.7% 🟢)1.478s (-25.5% 🟢)0.473s411.29x
🐘 PostgresNext.js (Turbopack)1.041s (+13.6% 🔺)1.538s (+20.5% 🔺)0.000s (+141.0% 🔺)1.550s (+20.8% 🔺)0.509s391.33x
💻 LocalExpress1.256s (-0.9%)2.013s (~)0.000s (+50.0% 🔺)2.016s (~)0.760s301.61x
💻 LocalNext.js (Turbopack)1.375s (+3.0%)1.979s (~)0.001s (+283.3% 🔺)2.016s (~)0.642s301.76x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.783s (+21.3% 🔺)5.122s (+16.1% 🔺)0.000s (NaN%)5.698s (+16.8% 🔺)1.915s111.00x
▲ VercelExpress3.945s (+1.0%)5.443s (+8.3% 🔺)0.000s (-100.0% 🟢)5.941s (+7.3% 🔺)1.996s111.04x
▲ VercelNext.js (Turbopack)5.317s (+61.5% 🔺)6.785s (+44.3% 🔺)0.000s (+50.0% 🔺)7.685s (+46.3% 🔺)2.368s81.41x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.685s (-12.1% 🟢)2.301s (-7.8% 🟢)0.000s (+84.6% 🔺)2.344s (-7.0% 🟢)0.658s261.00x
🐘 PostgresExpress1.748s (+3.1%)2.345s (+4.0%)0.000s (-100.0% 🟢)2.366s (+4.2%)0.618s261.04x
💻 LocalNitro2.653s (-25.6% 🟢)3.073s (-23.7% 🟢)0.000s (-64.7% 🟢)3.081s (-23.6% 🟢)0.427s201.57x
🐘 PostgresNext.js (Turbopack)2.662s (+31.0% 🔺)3.336s (+27.8% 🔺)0.000s (-100.0% 🟢)3.345s (+26.6% 🔺)0.683s181.58x
💻 LocalExpress3.715s (+2.2%)4.313s (+2.0%)0.001s (+7.1% 🔺)4.319s (+2.1%)0.604s142.20x
💻 LocalNext.js (Turbopack)3.837s (+4.7%)4.277s (+2.0%)0.001s (-19.6% 🟢)4.319s (+2.0%)0.482s142.28x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.405s (+14.2% 🔺)7.099s (+23.6% 🔺)0.002s (+1400.0% 🔺)7.612s (+21.7% 🔺)2.207s81.00x
▲ VercelNitro5.848s (+19.5% 🔺)7.481s (+30.8% 🔺)0.000s (+Infinity% 🔺)8.026s (+29.8% 🔺)2.178s81.08x
▲ VercelNext.js (Turbopack)7.707s (+66.4% 🔺)9.533s (+58.4% 🔺)0.000s (+66.7% 🔺)10.735s (+63.2% 🔺)3.027s61.43x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNitro21/21
🐘 PostgresNitro14/21
▲ VercelExpress11/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres16/21
Next.js (Turbopack)🐘 Postgres15/21
Nitro🐘 Postgres13/21
Column Definitions
  • Workflow Time: Runtime reported by workflow (completedAt - createdAt) - primary metric
  • TTFB: Time to First Byte - time from workflow start until first stream byte received (stream benchmarks only)
  • Slurp: Time from first byte to complete stream consumption (stream benchmarks only)
  • Wall Time: Total testbench time (trigger workflow + poll for result)
  • Overhead: Testbench overhead (Wall Time - Workflow Time)
  • Samples: Number of benchmark iterations run
  • vs Fastest: How much slower compared to the fastest configuration for this benchmark

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Redis + BullMQ: Community world (local development)
  • 🌐 Cloudflare: Community world (local development)
  • 🌐 MySQL: Community world (local development)
  • 🌐 Azure: Community world (local development)
  • 🌐 NATS JetStream: Community world (local development)
  • 🌐 Upstash: Community world (local development)
  • 🌐 Platformatic: Community world (local development)

📋 View full workflow run

@github-actions

github-actionsBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production144202301672
✅ 💻 Local Development160502191824
✅ 📦 Local Production160502191824
✅ 🐘 Local Postgres159302311824
✅ 🪟 Windows15200152
✅ 📋 Other88501791064
Total7282010788360

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro125027
✅ example125027
✅ express125027
✅ fastify125027
✅ hono125027
✅ nextjs-turbopack14903
✅ nextjs-webpack14903
✅ nitro125027
✅ nuxt125027
✅ sveltekit14408
✅ vite125027
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable126026
✅ express-stable126026
✅ fastify-stable126026
✅ hono-stable126026
✅ nextjs-turbopack-canary132020
✅ nextjs-turbopack-stable15101
✅ nextjs-webpack-canary132020
✅ nextjs-webpack-stable15101
✅ nitro-stable126026
✅ nuxt-stable126026
✅ sveltekit-stable14507
✅ vite-stable126026
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack15200
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable127025
✅ e2e-local-dev-tanstack-start-127025
✅ e2e-local-postgres-nest-stable126026
✅ e2e-local-postgres-tanstack-start-126026
✅ e2e-local-prod-nest-stable127025
✅ e2e-local-prod-tanstack-start-127025
✅ e2e-vercel-prod-tanstack-start125027

📋 View full workflow run

@vercel

vercelBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

@VaguelySerious
VaguelySerious marked this pull request as ready for review June 25, 2026 22:55
@VaguelySerious
VaguelySerious requested review from a team and ijjk as code ownersJune 25, 2026 22:55

@karthikscale3karthikscale3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving — reviewed against the merged OTEL trace-correlation work (linked-mode #2363, route/execution cohesion #2527, v4 injection #2533). No regressions; this is a net improvement.

What I verified:

  • Injection is centralized + default-on: injectTraceContext = true in instrumentedFetch, inject runs inside the http <method> CLIENT span, and no caller passes false — so every path injects.
  • No bypass remains: the only raw fetch( is inside instrumentedFetch; all call sites (v4 events, makeRequest, streamer, both Vercel-API callers) route through it. Stronger guarantee than before.
  • v4 is fixed, not broken: #2533's inline injection moves into the core and v4 now gets a real client span (it had none), so workflow-server parents to it like v3. Stream + Vercel-API paths gain propagation.
  • Span name + parenting preserved: still http <method> / CLIENT with PeerService/RpcService → service-map edges and name-based queries intact; workflow-server still parents into the invocation trace (parent/child, not links).
  • Cohesion/linked mode untouched (only world-vercel/* changed; runtime.ts/step-handler.ts/telemetry.ts are not in the diff).
  • Tests strengthened: trace-propagation.test.ts asserts the v4 request's traceparent matches its client span and adds a stream-write propagation test.

One thing to confirm (see inline comment) + a doc follow-up.

// Explicitly propagate trace context so the receiving server can parent
// its spans to this client span — the custom undici dispatcher bypasses
// ambient auto-instrumentation. No-ops when no OTEL SDK is registered.
if (injectTraceContext) await injectTraceContextIntoHeaders(headers);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since instrumentedFetch is now the single trace-context injection chokepoint, please update the AGENTS.md guardrail (Architecture Notes → "Trace context propagation (world-vercel HTTP requests)") before merge: it currently tells contributors to call injectTraceContextIntoHeaders directly, but the rule should now be "route new request paths through instrumentedFetch (injects W3C context by default)". That keeps the guidance pointing at this chokepoint so future paths inherit propagation automatically.

Also worth a conscious decision: the peerService: 'vercel-api' callers default to injectTraceContext: true, so they now send traceparentand baggage (incl. workflow.run_id/name) to vercel.com. Likely fine since it's first-party, but if that propagation isn't intended, pass injectTraceContext: false for those callers.

@VaguelySerious
VaguelySerious merged commit 1dcdafd into mainJun 28, 2026
179 of 183 checks passed
@VaguelySerious
VaguelySerious deleted the peter/world-vercel-http-cleanup branch June 28, 2026 21:59
@github-actionsgithub-actionsBot mentioned this pull request Jun 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for 1dcdafd (AI decision).

This PR is an explicit follow-up to #2573 and the unified http-core.ts it introduces depends on main-only infrastructure: the events/stream dispatcher functions (getEventsDispatcher, getStreamDispatcher, EVENTS_AGENT_OPTIONS, STREAM_RETRY_OPTIONS) and the fetch-based v4 path with trace-context injection do not exist on stable (verified — stable's events-v4.ts still uses undici.request() directly and streamer.ts uses plain fetch() without dispatchers). Backporting would pull in code referencing dispatchers absent on stable, so it builds on main-only behavior.

To override, re-run the Backport to stable workflow manually via workflow_dispatch and paste this commit SHA into the ref input:

1dcdafd422c870ec1b7dcbba8b0e733c1b6bbb14

VaguelySerious added a commit that referenced this pull request Jul 31, 2026
Backport of #2631, adapted to stable. The v3 makeRequest path, the v4
events client, the stream write/close path and the direct Vercel API
calls each had their own request envelope; the 412 -> PreconditionFailed
and firewall-challenge-429 -> retryable TRANSPORT mappings were
duplicated between utils.ts and events-v4.ts.
http-core.ts now owns that envelope: the OTEL CLIENT span and its
attributes, W3C trace-context injection, the X-Request-Time cache bust,
the per-request deadline (mapped to a typed retryable WorkflowWorldError
instead of an opaque AbortError), DEBUG logging with a curl repro, and
the status -> typed-error mapping. utils.ts, events-v4.ts, streamer.ts,
encryption.ts and resolve-latest-deployment.ts delegate to it.
Behavioral gains on stable: the v4 events path and stream writes now
emit client spans and propagate traceparent, so the backend can parent
its spans to the caller without relying on the app having undici
auto-instrumentation.
Adapted rather than cherry-picked: stable keeps #3169's bounded
headers/body timeouts (documented as firing ahead of the outer
REQUEST_TIMEOUT_MS backstop), its body-parse retry loop, and #3038's
write-vs-close stream dispatcher split. Only injectTraceContextIntoHeaders
is taken from #2363, not its WORKFLOW_TRACE_MODE machinery.
Routing v4 through the global fetch makes the H2 interceptor's
re-buffering gate live on this path (fetch streamifies request bodies),
so the new http-client test drives createWorkflowRunEventV4 concurrently
against a local H2 server: 16 writes on one session with the
interceptor, 8 without it.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@VaguelySerious@karthikscale3
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

[world-vercel] Unify HTTP request handling into a shared core - #2631

Merged
VaguelySerious merged 1 commit into
mainfrom
peter/world-vercel-http-cleanup
Jun 28, 2026
Merged

[world-vercel] Unify HTTP request handling into a shared core#2631
VaguelySerious merged 1 commit into
mainfrom
peter/world-vercel-http-cleanup

Conversation

@VaguelySerious

Copy link
Copy Markdown
Member

What

Follow-up to #2573. world-vercel had five separate HTTP request paths, each re-implementing the same cross-cutting concerns inconsistently. This collapses the duplication into one shared core (http-core.ts) and routes every path through it, so the v4 / stream / Vercel-API paths gain the OTEL client spans + DEBUG logging the v3 path already had.

Before

PathOTEL spanDEBUG logcurl-reprodiag headerstyped errorstrace-ctx
v3 makeRequest
v4 events(2nd copy)
stream write/close(2nd copy)
stream get/list
Vercel API (run-key, resolve-deploy)(plain Error)

The v4 events path — now the hottest path — had no client span and no debug logging.

How

New http-core.ts is the single source of truth for the request envelope:

  • errorForResponse — the status → typed-error map (409→EntityConflictError, 410→RunExpiredError, 425→TooEarlyError, 429→ThrottleError, else→WorkflowWorldError). Was implemented twice (v3 makeRequest + v4 throwForErrorResponse).
  • getVercelDiagnostics / parseRetryAfter / httpLog / logCurlRepro — one copy each (were duplicated across utils.ts and streamer.ts).
  • resolveVercelApiToken — shared token → VERCEL_TOKEN → OIDC precedence (was byte-identical in encryption.ts and resolve-latest-deployment.ts).
  • instrumentedFetch — shared OTEL span + trace-context injection + cache-bust header + timeout + DEBUG logging + error envelope. The v4 events, stream, and Vercel-API paths now route through it.

Also: stream writes now inject W3C trace context (previously missing, contrary to the documented requirement that every world→server request must), and the three dispatcher singletons in http-client.ts collapse behind one makeRetryDispatcher factory.

Behavior preserved

No behavior change beyond the added observability:

  • Per-path error-message strings are unchanged (the v3/v4/stream message formats feed log tooling); only the type mapping, Retry-After parsing, and diagnostics are unified.
  • The non-idempotent stream retry policy (transient + 429 only, never 5xx) is untouched.
  • The no-timeout semantics of the v4 (streamed LIST), stream (large buffered writes / long-lived live read), and Vercel-API paths are preserved (timeoutMs: null); only the v3 path keeps its 60s timeout.

Tests

  • New http-core.test.ts (error mapping per status, parseRetryAfter, diagnostics, token precedence).
  • Extended trace-propagation.test.ts: v4 now parents to its own http GET client span (matching v3), and a new case asserts stream writes inject traceparent.
  • Full world-vercel suite green (187 tests); build + typecheck + lint clean.

Changeset

@workflow/world-vercel patch — internal transport refactor + added observability, no API surface change.

Collapse the duplicated cross-cutting request logic across world-vercel's
request paths into one shared module (http-core.ts):
- errorForResponse: single status -> typed-error map (409/410/425/429/else),
shared by the v3 makeRequest and v4 throwForErrorResponse paths.
- getVercelDiagnostics / parseRetryAfter / httpLog / logCurlRepro: one copy
each (were duplicated across utils.ts and streamer.ts).
- resolveVercelApiToken: shared token precedence for the direct Vercel-API
calls (was byte-identical in encryption.ts and resolve-latest-deployment.ts).
- instrumentedFetch: the shared OTEL-span + trace-injection + cache-bust +
timeout + DEBUG-logging + error envelope.
Route the v4 events, stream write/read, and Vercel-API (run-key /
resolve-latest-deployment) paths through instrumentedFetch so they gain the
OTEL client spans and DEBUG logging the v3 path already had. Stream writes now
also inject W3C trace context (previously missing). Collapse the three
dispatcher singletons in http-client.ts behind a single makeRetryDispatcher
factory.
No behavior change beyond the added observability: per-path error-message
formats, the non-idempotent stream retry policy, and the no-timeout semantics
of the v4/stream/Vercel-API paths are preserved.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9263886

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 17 packages
NameType
@workflow/world-vercelPatch
@workflow/cliPatch
@workflow/corePatch
@workflow/webPatch
workflowPatch
@workflow/world-testingPatch
@workflow/buildersPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch
@workflow/nuxtPatch

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

@github-actions

github-actionsBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.035s (-26.0% 🟢)1.005s (~)0.970s101.00x
💻 LocalExpress0.048s (+1.7%)1.006s (~)0.958s101.39x
💻 LocalNext.js (Turbopack)0.050s (-4.8%)1.006s (~)0.956s101.44x
🐘 PostgresNext.js (Turbopack)0.061s (+9.8% 🔺)1.012s (~)0.951s101.74x
🐘 PostgresExpress0.063s (-9.9% 🟢)1.012s (~)0.949s101.80x
🐘 PostgresNitro0.071s (+9.2% 🔺)1.012s (~)0.940s102.05x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express0.332s (+43.7% 🔺)2.094s (~)1.762s101.00x
▲ VercelNitro0.343s (+36.1% 🔺)2.314s (+6.6% 🔺)1.971s101.03x
▲ VercelNext.js (Turbopack)0.931s (+244.5% 🔺)3.051s (+49.2% 🔺)2.120s102.80x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.062s (-2.2%)2.005s (~)0.943s101.00x
💻 LocalNext.js (Turbopack)1.084s (~)2.007s (~)0.923s101.02x
💻 LocalExpress1.084s (~)2.006s (~)0.922s101.02x
🐘 PostgresNitro1.088s (-1.3%)2.010s (~)0.922s101.02x
🐘 PostgresNext.js (Turbopack)1.103s (+1.8%)2.010s (~)0.907s101.04x
🐘 PostgresExpress1.104s (+1.1%)2.009s (~)0.905s101.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.499s (+6.2% 🔺)3.381s (+6.1% 🔺)1.881s101.00x
▲ VercelExpress1.584s (+14.5% 🔺)3.601s (+27.2% 🔺)2.017s101.06x
▲ VercelNext.js (Turbopack)2.692s (+87.7% 🔺)4.407s (+31.5% 🔺)1.715s101.80x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro10.335s (-1.2%)11.020s (~)0.685s31.00x
🐘 PostgresNitro10.443s (-0.6%)11.018s (~)0.575s31.01x
💻 LocalExpress10.449s (~)11.023s (~)0.574s31.01x
💻 LocalNext.js (Turbopack)10.523s (~)11.024s (~)0.501s31.02x
🐘 PostgresExpress10.529s (+0.8%)11.018s (~)0.489s31.02x
🐘 PostgresNext.js (Turbopack)10.549s (+0.9%)11.019s (~)0.470s31.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro13.474s (+15.0% 🔺)15.455s (+15.7% 🔺)1.981s31.00x
▲ VercelExpress13.947s (+13.3% 🔺)16.641s (+20.0% 🔺)2.694s21.04x
▲ VercelNext.js (Turbopack)14.162s (+20.0% 🔺)16.396s (+17.6% 🔺)2.234s21.05x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro13.443s (-1.4%)14.025s (~)0.582s51.00x
🐘 PostgresNitro13.632s (-0.6%)14.020s (~)0.388s51.01x
💻 LocalNext.js (Turbopack)13.637s (~)14.026s (~)0.389s51.01x
🐘 PostgresNext.js (Turbopack)13.650s (~)14.021s (~)0.371s51.02x
💻 LocalExpress13.656s (~)14.028s (~)0.372s51.02x
🐘 PostgresExpress13.824s (+1.8%)14.019s (~)0.194s51.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro20.368s (+24.4% 🔺)22.630s (+27.7% 🔺)2.262s31.00x
▲ VercelExpress20.918s (+27.2% 🔺)22.656s (+25.9% 🔺)1.738s31.03x
▲ VercelNext.js (Turbopack)23.709s (+36.7% 🔺)25.789s (+34.7% 🔺)2.079s31.16x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro11.628s (-4.5%)12.022s (-7.7% 🟢)0.394s81.00x
🐘 PostgresNitro12.094s (-1.5%)13.016s (~)0.922s71.04x
💻 LocalExpress12.171s (-1.4%)13.025s (~)0.853s71.05x
🐘 PostgresExpress12.282s (+1.5%)13.018s (~)0.736s71.06x
🐘 PostgresNext.js (Turbopack)12.323s (+1.8%)13.017s (~)0.694s71.06x
💻 LocalNext.js (Turbopack)12.389s (+1.1%)13.025s (~)0.636s71.07x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro22.441s (+18.2% 🔺)25.257s (+22.4% 🔺)2.816s41.00x
▲ VercelNext.js (Turbopack)24.710s (+27.7% 🔺)27.172s (+28.0% 🔺)2.462s41.10x
▲ VercelExpress24.776s (+34.8% 🔺)27.682s (+37.3% 🔺)2.906s41.10x

🔍 Observability: Nitro | Next.js (Turbopack) | Express

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.181s (~)2.008s (~)0.828s151.00x
🐘 PostgresNext.js (Turbopack)1.198s (+1.8%)2.008s (~)0.810s151.01x
🐘 PostgresExpress1.242s (+6.3% 🔺)2.009s (~)0.768s151.05x
💻 LocalNitro1.300s (-9.6% 🟢)2.006s (~)0.706s151.10x
💻 LocalExpress1.421s (+2.3%)2.007s (~)0.585s151.20x
💻 LocalNext.js (Turbopack)1.466s (-2.1%)2.007s (~)0.541s151.24x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.247s (+5.6% 🔺)4.214s (+22.4% 🔺)1.966s81.00x
▲ VercelExpress2.843s (+40.1% 🔺)4.746s (+35.6% 🔺)1.904s71.26x
▲ VercelNext.js (Turbopack)5.555s (+158.4% 🔺)7.589s (+100.3% 🔺)2.034s42.47x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.295s (-7.0% 🟢)2.736s (+2.3%)1.441s111.00x
🐘 PostgresNitro1.318s (-1.6%)2.591s (+3.3%)1.273s121.02x
🐘 PostgresNext.js (Turbopack)1.379s (+5.0% 🔺)3.011s (+3.2%)1.632s101.06x
💻 LocalNitro2.078s (-13.7% 🟢)2.826s (-3.2%)0.748s111.60x
💻 LocalExpress2.416s (+2.5%)3.009s (+3.1%)0.593s101.87x
💻 LocalNext.js (Turbopack)2.632s (-3.8%)3.010s (-3.2%)0.377s102.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.352s (+8.9% 🔺)5.327s (+2.8%)1.976s61.00x
▲ VercelExpress3.378s (-7.0% 🟢)5.318s (+3.0%)1.940s61.01x
▲ VercelNext.js (Turbopack)4.672s (+39.8% 🔺)6.725s (+33.3% 🔺)2.053s51.39x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.610s (-1.9%)4.137s (-3.0%)2.527s81.00x
🐘 PostgresExpress1.619s (+2.1%)4.012s (-6.7% 🟢)2.393s81.01x
🐘 PostgresNext.js (Turbopack)3.104s (+10.3% 🔺)6.015s (+9.0% 🔺)2.911s51.93x
💻 LocalNitro4.859s (-24.6% 🟢)5.347s (-25.9% 🟢)0.488s63.02x
💻 LocalNext.js (Turbopack)6.248s (+14.2% 🔺)7.518s (+25.0% 🔺)1.270s43.88x
💻 LocalExpress6.275s (-0.8%)6.614s (-5.8% 🟢)0.338s53.90x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express4.354s (+42.1% 🔺)6.876s (+36.3% 🔺)2.522s51.00x
▲ VercelNitro4.370s (+30.3% 🔺)6.765s (+32.1% 🔺)2.395s51.00x
▲ VercelNext.js (Turbopack)4.926s (+41.5% 🔺)7.542s (+45.0% 🔺)2.616s41.13x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.174s (-2.7%)2.007s (~)0.833s151.00x
🐘 PostgresNext.js (Turbopack)1.199s (+2.5%)2.008s (~)0.809s151.02x
🐘 PostgresExpress1.243s (+5.5% 🔺)2.074s (+3.3%)0.831s151.06x
💻 LocalNitro1.344s (-5.5% 🟢)2.006s (~)0.662s151.14x
💻 LocalExpress1.428s (-0.6%)2.007s (~)0.579s151.22x
💻 LocalNext.js (Turbopack)1.495s (+6.6% 🔺)2.073s (+3.3%)0.578s151.27x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.542s (+23.2% 🔺)4.657s (+33.1% 🔺)2.115s71.00x
▲ VercelNitro2.571s (+19.9% 🔺)4.516s (+21.5% 🔺)1.945s71.01x
▲ VercelNext.js (Turbopack)4.063s (-29.8% 🟢)6.178s (-17.0% 🟢)2.115s51.60x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.290s (-6.0% 🟢)2.075s (-16.1% 🟢)0.785s151.00x
🐘 PostgresExpress1.341s (~)2.393s (-3.2%)1.052s131.04x
🐘 PostgresNext.js (Turbopack)1.361s (+5.5% 🔺)3.010s (+3.2%)1.650s101.05x
💻 LocalNitro2.207s (-14.5% 🟢)2.917s (-6.2% 🟢)0.710s111.71x
💻 LocalExpress2.483s (+2.5%)2.919s (-6.1% 🟢)0.435s111.92x
💻 LocalNext.js (Turbopack)2.866s (+8.7% 🔺)3.209s (+6.7% 🔺)0.343s102.22x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.557s (-46.6% 🟢)4.443s (-33.1% 🟢)1.886s71.00x
▲ VercelNitro2.768s (+1.9%)4.662s (+8.0% 🔺)1.894s71.08x
▲ VercelNext.js (Turbopack)4.820s (+1.6%)7.047s (+1.4%)2.226s51.88x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.612s (-2.9%)4.012s (~)2.400s81.00x
🐘 PostgresExpress1.616s (+1.9%)4.012s (-3.0%)2.396s81.00x
🐘 PostgresNext.js (Turbopack)2.989s (+4.6%)6.013s (+2.8%)3.024s51.85x
💻 LocalNitro5.245s (-22.7% 🟢)5.680s (-21.3% 🟢)0.436s63.25x
💻 LocalNext.js (Turbopack)5.971s (+4.4%)6.614s (+9.9% 🔺)0.642s53.71x
💻 LocalExpress8.009s (+19.5% 🔺)8.518s (+18.0% 🔺)0.509s44.97x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.839s (-18.1% 🟢)6.037s (-4.1%)2.198s51.00x
▲ VercelNext.js (Turbopack)4.185s (+34.0% 🔺)6.365s (+23.3% 🔺)2.180s51.09x
▲ VercelNitro4.362s (+26.2% 🔺)6.526s (+29.7% 🔺)2.165s51.14x

🔍 Observability: Express | Next.js (Turbopack) | Nitro

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.508s (-13.3% 🟢)1.004s (~)0.496s601.00x
🐘 PostgresNitro0.518s (-12.9% 🟢)1.006s (-3.4%)0.488s601.02x
🐘 PostgresExpress0.565s (+5.9% 🔺)1.023s (~)0.458s591.11x
💻 LocalExpress0.596s (+4.0%)1.022s (+1.7%)0.426s591.17x
🐘 PostgresNext.js (Turbopack)0.608s (+18.9% 🔺)1.024s (+1.9%)0.417s591.20x
💻 LocalNext.js (Turbopack)0.608s (+0.8%)1.022s (~)0.414s591.20x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.261s (+16.5% 🔺)5.059s (+12.8% 🔺)1.799s121.00x
▲ VercelNitro3.490s (+31.9% 🔺)5.594s (+30.1% 🔺)2.104s111.07x
▲ VercelNext.js (Turbopack)4.224s (+39.0% 🔺)6.144s (+23.0% 🔺)1.920s101.30x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.240s (-12.3% 🟢)2.030s (-1.2%)0.789s451.00x
💻 LocalNitro1.256s (-15.3% 🟢)2.005s (~)0.749s451.01x
🐘 PostgresExpress1.331s (+6.4% 🔺)2.029s (+1.1%)0.699s451.07x
🐘 PostgresNext.js (Turbopack)1.389s (+11.6% 🔺)2.008s (-1.1%)0.619s451.12x
💻 LocalExpress1.472s (+1.2%)2.006s (~)0.534s451.19x
💻 LocalNext.js (Turbopack)1.532s (+4.4%)2.006s (~)0.474s451.24x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express7.933s (+22.7% 🔺)9.951s (+23.8% 🔺)2.018s101.00x
▲ VercelNitro8.413s (+31.5% 🔺)10.388s (+33.8% 🔺)1.975s91.06x
▲ VercelNext.js (Turbopack)10.501s (+47.0% 🔺)12.769s (+43.6% 🔺)2.268s81.32x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.512s (-7.5% 🟢)3.086s (+0.9%)0.574s391.00x
💻 LocalNitro2.760s (-14.3% 🟢)3.165s (-21.1% 🟢)0.405s381.10x
🐘 PostgresExpress2.763s (+6.5% 🔺)3.167s (+2.6%)0.404s381.10x
🐘 PostgresNext.js (Turbopack)2.793s (+12.8% 🔺)3.009s (-1.6%)0.216s401.11x
💻 LocalExpress3.242s (~)4.010s (+0.8%)0.768s301.29x
💻 LocalNext.js (Turbopack)3.305s (+1.4%)4.043s (~)0.738s301.32x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express13.918s (+1.0%)17.102s (+10.6% 🔺)3.184s81.00x
▲ VercelNitro14.974s (+6.6% 🔺)17.975s (+15.0% 🔺)3.000s71.08x
▲ VercelNext.js (Turbopack)20.700s (+26.1% 🔺)23.054s (+24.7% 🔺)2.354s61.49x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.212s (~)1.006s (~)0.794s601.00x
🐘 PostgresNext.js (Turbopack)0.218s (+30.2% 🔺)1.007s (~)0.788s601.03x
🐘 PostgresNitro0.223s (-1.8%)1.006s (~)0.783s601.05x
💻 LocalNitro0.399s (-14.8% 🟢)1.021s (+1.6%)0.622s591.89x
💻 LocalExpress0.504s (+16.8% 🔺)1.005s (~)0.501s602.38x
💻 LocalNext.js (Turbopack)0.644s (+10.1% 🔺)1.022s (+1.7%)0.378s593.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.221s (-43.8% 🟢)3.077s (-20.6% 🟢)1.857s201.00x
▲ VercelExpress1.345s (-35.1% 🟢)3.022s (-21.3% 🟢)1.677s201.10x
▲ VercelNext.js (Turbopack)2.809s (+54.4% 🔺)4.638s (+30.7% 🔺)1.830s132.30x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.308s (+15.0% 🔺)1.041s (+3.5%)0.733s871.00x
🐘 PostgresNitro0.319s (-5.4% 🟢)1.006s (~)0.687s901.03x
🐘 PostgresExpress0.326s (+1.0%)1.007s (-1.1%)0.680s901.06x
💻 LocalNitro1.815s (-14.9% 🟢)2.285s (-14.9% 🟢)0.470s405.89x
💻 LocalExpress2.221s (+2.8%)2.797s (+2.2%)0.576s337.21x
💻 LocalNext.js (Turbopack)2.826s (+1.3%)3.295s (+1.1%)0.469s289.17x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.920s (-22.0% 🟢)3.816s (-5.9% 🟢)1.896s241.00x
▲ VercelNitro1.964s (+3.0%)3.918s (+12.3% 🔺)1.954s231.02x
▲ VercelNext.js (Turbopack)3.338s (+53.8% 🔺)5.492s (+39.8% 🔺)2.155s171.74x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.507s (-1.7%)1.068s (~)0.561s1131.00x
🐘 PostgresNitro0.513s (-2.9%)1.068s (+3.5%)0.555s1141.01x
🐘 PostgresNext.js (Turbopack)0.580s (+23.0% 🔺)3.254s (+8.1% 🔺)2.674s371.14x
💻 LocalNitro8.193s (-17.4% 🟢)9.094s (-17.5% 🟢)0.901s1416.15x
💻 LocalExpress9.881s (-2.0%)11.117s (~)1.235s1119.48x
💻 LocalNext.js (Turbopack)10.366s (+6.0% 🔺)11.576s (+7.4% 🔺)1.210s1120.44x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.428s (+34.8% 🔺)5.645s (+36.8% 🔺)2.217s221.00x
▲ VercelExpress3.617s (+51.3% 🔺)6.034s (+48.8% 🔺)2.417s201.06x
▲ VercelNext.js (Turbopack)4.887s (+4.3%)7.315s (+11.8% 🔺)2.428s171.43x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.128s (-2.6%)2.004s (~)0.008s (-33.9% 🟢)2.015s (~)0.887s101.00x
💻 LocalNext.js (Turbopack)1.145s (-2.1%)1.967s (~)0.012s (-2.4%)2.019s (~)0.875s101.01x
🐘 PostgresExpress1.159s (+0.7%)1.996s (~)0.001s (+10.0% 🔺)2.010s (~)0.852s101.03x
🐘 PostgresNitro1.159s (-0.7%)1.999s (~)0.001s (+9.1% 🔺)2.011s (~)0.852s101.03x
💻 LocalExpress1.160s (+0.7%)2.005s (~)0.013s (+5.8% 🔺)2.020s (~)0.860s101.03x
🐘 PostgresNext.js (Turbopack)1.165s (+1.7%)2.001s (~)0.001s (+18.2% 🔺)2.011s (~)0.846s101.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.120s (+3.6%)3.629s (+18.3% 🔺)2.424s (+38.4% 🔺)6.604s (+22.2% 🔺)4.484s101.00x
▲ VercelNitro2.340s (+5.5% 🔺)3.692s (+12.7% 🔺)2.806s (+50.6% 🔺)7.080s (+24.4% 🔺)4.740s101.10x
▲ VercelNext.js (Turbopack)3.746s (+81.4% 🔺)4.139s (+20.0% 🔺)2.700s (+91.6% 🔺)8.319s (+53.9% 🔺)4.573s101.77x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.426s (-8.2% 🟢)2.006s (~)0.011s (-7.8% 🟢)2.020s (~)0.594s301.00x
🐘 PostgresNitro1.544s (-3.8%)1.999s (~)0.005s (+6.6% 🔺)2.029s (~)0.485s301.08x
🐘 PostgresExpress1.551s (~)2.001s (~)0.005s (-10.2% 🟢)2.024s (~)0.473s301.09x
💻 LocalExpress1.556s (+0.5%)2.011s (~)0.012s (-4.1%)2.027s (~)0.471s301.09x
💻 LocalNext.js (Turbopack)1.608s (+1.9%)2.000s (+1.6%)0.013s (-3.1%)2.058s (+1.6%)0.450s301.13x
🐘 PostgresNext.js (Turbopack)1.675s (+8.7% 🔺)2.010s (~)0.005s (+0.6%)2.027s (~)0.352s301.17x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro7.027s (+15.7% 🔺)8.939s (+26.3% 🔺)0.304s (+34.3% 🔺)9.893s (+26.4% 🔺)2.866s71.00x
▲ VercelExpress7.058s (+19.9% 🔺)8.708s (+20.8% 🔺)0.283s (+31.1% 🔺)9.595s (+21.7% 🔺)2.537s71.00x
▲ VercelNext.js (Turbopack)10.646s (+79.2% 🔺)12.028s (+63.2% 🔺)0.377s (+18.4% 🔺)13.403s (+62.4% 🔺)2.757s51.52x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.781s (+0.9%)1.062s (+1.3%)0.000s (+Infinity% 🔺)1.079s (+1.4%)0.298s561.00x
🐘 PostgresNitro0.799s (-2.8%)1.044s (-5.0% 🟢)0.000s (-100.0% 🟢)1.059s (-5.3% 🟢)0.260s581.02x
💻 LocalNitro1.004s (-23.9% 🟢)1.475s (-25.5% 🟢)0.000s (-30.7% 🟢)1.478s (-25.5% 🟢)0.473s411.29x
🐘 PostgresNext.js (Turbopack)1.041s (+13.6% 🔺)1.538s (+20.5% 🔺)0.000s (+141.0% 🔺)1.550s (+20.8% 🔺)0.509s391.33x
💻 LocalExpress1.256s (-0.9%)2.013s (~)0.000s (+50.0% 🔺)2.016s (~)0.760s301.61x
💻 LocalNext.js (Turbopack)1.375s (+3.0%)1.979s (~)0.001s (+283.3% 🔺)2.016s (~)0.642s301.76x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.783s (+21.3% 🔺)5.122s (+16.1% 🔺)0.000s (NaN%)5.698s (+16.8% 🔺)1.915s111.00x
▲ VercelExpress3.945s (+1.0%)5.443s (+8.3% 🔺)0.000s (-100.0% 🟢)5.941s (+7.3% 🔺)1.996s111.04x
▲ VercelNext.js (Turbopack)5.317s (+61.5% 🔺)6.785s (+44.3% 🔺)0.000s (+50.0% 🔺)7.685s (+46.3% 🔺)2.368s81.41x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.685s (-12.1% 🟢)2.301s (-7.8% 🟢)0.000s (+84.6% 🔺)2.344s (-7.0% 🟢)0.658s261.00x
🐘 PostgresExpress1.748s (+3.1%)2.345s (+4.0%)0.000s (-100.0% 🟢)2.366s (+4.2%)0.618s261.04x
💻 LocalNitro2.653s (-25.6% 🟢)3.073s (-23.7% 🟢)0.000s (-64.7% 🟢)3.081s (-23.6% 🟢)0.427s201.57x
🐘 PostgresNext.js (Turbopack)2.662s (+31.0% 🔺)3.336s (+27.8% 🔺)0.000s (-100.0% 🟢)3.345s (+26.6% 🔺)0.683s181.58x
💻 LocalExpress3.715s (+2.2%)4.313s (+2.0%)0.001s (+7.1% 🔺)4.319s (+2.1%)0.604s142.20x
💻 LocalNext.js (Turbopack)3.837s (+4.7%)4.277s (+2.0%)0.001s (-19.6% 🟢)4.319s (+2.0%)0.482s142.28x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.405s (+14.2% 🔺)7.099s (+23.6% 🔺)0.002s (+1400.0% 🔺)7.612s (+21.7% 🔺)2.207s81.00x
▲ VercelNitro5.848s (+19.5% 🔺)7.481s (+30.8% 🔺)0.000s (+Infinity% 🔺)8.026s (+29.8% 🔺)2.178s81.08x
▲ VercelNext.js (Turbopack)7.707s (+66.4% 🔺)9.533s (+58.4% 🔺)0.000s (+66.7% 🔺)10.735s (+63.2% 🔺)3.027s61.43x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNitro21/21
🐘 PostgresNitro14/21
▲ VercelExpress11/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres16/21
Next.js (Turbopack)🐘 Postgres15/21
Nitro🐘 Postgres13/21
Column Definitions
  • Workflow Time: Runtime reported by workflow (completedAt - createdAt) - primary metric
  • TTFB: Time to First Byte - time from workflow start until first stream byte received (stream benchmarks only)
  • Slurp: Time from first byte to complete stream consumption (stream benchmarks only)
  • Wall Time: Total testbench time (trigger workflow + poll for result)
  • Overhead: Testbench overhead (Wall Time - Workflow Time)
  • Samples: Number of benchmark iterations run
  • vs Fastest: How much slower compared to the fastest configuration for this benchmark

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Redis + BullMQ: Community world (local development)
  • 🌐 Cloudflare: Community world (local development)
  • 🌐 MySQL: Community world (local development)
  • 🌐 Azure: Community world (local development)
  • 🌐 NATS JetStream: Community world (local development)
  • 🌐 Upstash: Community world (local development)
  • 🌐 Platformatic: Community world (local development)

📋 View full workflow run

@github-actions

github-actionsBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production144202301672
✅ 💻 Local Development160502191824
✅ 📦 Local Production160502191824
✅ 🐘 Local Postgres159302311824
✅ 🪟 Windows15200152
✅ 📋 Other88501791064
Total7282010788360

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro125027
✅ example125027
✅ express125027
✅ fastify125027
✅ hono125027
✅ nextjs-turbopack14903
✅ nextjs-webpack14903
✅ nitro125027
✅ nuxt125027
✅ sveltekit14408
✅ vite125027
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable126026
✅ express-stable126026
✅ fastify-stable126026
✅ hono-stable126026
✅ nextjs-turbopack-canary132020
✅ nextjs-turbopack-stable15101
✅ nextjs-webpack-canary132020
✅ nextjs-webpack-stable15101
✅ nitro-stable126026
✅ nuxt-stable126026
✅ sveltekit-stable14507
✅ vite-stable126026
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack15200
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable127025
✅ e2e-local-dev-tanstack-start-127025
✅ e2e-local-postgres-nest-stable126026
✅ e2e-local-postgres-tanstack-start-126026
✅ e2e-local-prod-nest-stable127025
✅ e2e-local-prod-tanstack-start-127025
✅ e2e-vercel-prod-tanstack-start125027

📋 View full workflow run

@vercel

vercelBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

@VaguelySerious
VaguelySerious marked this pull request as ready for review June 25, 2026 22:55
@VaguelySerious
VaguelySerious requested review from a team and ijjk as code ownersJune 25, 2026 22:55

@karthikscale3karthikscale3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving — reviewed against the merged OTEL trace-correlation work (linked-mode #2363, route/execution cohesion #2527, v4 injection #2533). No regressions; this is a net improvement.

What I verified:

  • Injection is centralized + default-on: injectTraceContext = true in instrumentedFetch, inject runs inside the http <method> CLIENT span, and no caller passes false — so every path injects.
  • No bypass remains: the only raw fetch( is inside instrumentedFetch; all call sites (v4 events, makeRequest, streamer, both Vercel-API callers) route through it. Stronger guarantee than before.
  • v4 is fixed, not broken: #2533's inline injection moves into the core and v4 now gets a real client span (it had none), so workflow-server parents to it like v3. Stream + Vercel-API paths gain propagation.
  • Span name + parenting preserved: still http <method> / CLIENT with PeerService/RpcService → service-map edges and name-based queries intact; workflow-server still parents into the invocation trace (parent/child, not links).
  • Cohesion/linked mode untouched (only world-vercel/* changed; runtime.ts/step-handler.ts/telemetry.ts are not in the diff).
  • Tests strengthened: trace-propagation.test.ts asserts the v4 request's traceparent matches its client span and adds a stream-write propagation test.

One thing to confirm (see inline comment) + a doc follow-up.

// Explicitly propagate trace context so the receiving server can parent
// its spans to this client span — the custom undici dispatcher bypasses
// ambient auto-instrumentation. No-ops when no OTEL SDK is registered.
if (injectTraceContext) await injectTraceContextIntoHeaders(headers);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since instrumentedFetch is now the single trace-context injection chokepoint, please update the AGENTS.md guardrail (Architecture Notes → "Trace context propagation (world-vercel HTTP requests)") before merge: it currently tells contributors to call injectTraceContextIntoHeaders directly, but the rule should now be "route new request paths through instrumentedFetch (injects W3C context by default)". That keeps the guidance pointing at this chokepoint so future paths inherit propagation automatically.

Also worth a conscious decision: the peerService: 'vercel-api' callers default to injectTraceContext: true, so they now send traceparentand baggage (incl. workflow.run_id/name) to vercel.com. Likely fine since it's first-party, but if that propagation isn't intended, pass injectTraceContext: false for those callers.

@VaguelySerious
VaguelySerious merged commit 1dcdafd into mainJun 28, 2026
179 of 183 checks passed
@VaguelySerious
VaguelySerious deleted the peter/world-vercel-http-cleanup branch June 28, 2026 21:59
@github-actionsgithub-actionsBot mentioned this pull request Jun 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for 1dcdafd (AI decision).

This PR is an explicit follow-up to #2573 and the unified http-core.ts it introduces depends on main-only infrastructure: the events/stream dispatcher functions (getEventsDispatcher, getStreamDispatcher, EVENTS_AGENT_OPTIONS, STREAM_RETRY_OPTIONS) and the fetch-based v4 path with trace-context injection do not exist on stable (verified — stable's events-v4.ts still uses undici.request() directly and streamer.ts uses plain fetch() without dispatchers). Backporting would pull in code referencing dispatchers absent on stable, so it builds on main-only behavior.

To override, re-run the Backport to stable workflow manually via workflow_dispatch and paste this commit SHA into the ref input:

1dcdafd422c870ec1b7dcbba8b0e733c1b6bbb14

VaguelySerious added a commit that referenced this pull request Jul 31, 2026
Backport of #2631, adapted to stable. The v3 makeRequest path, the v4
events client, the stream write/close path and the direct Vercel API
calls each had their own request envelope; the 412 -> PreconditionFailed
and firewall-challenge-429 -> retryable TRANSPORT mappings were
duplicated between utils.ts and events-v4.ts.
http-core.ts now owns that envelope: the OTEL CLIENT span and its
attributes, W3C trace-context injection, the X-Request-Time cache bust,
the per-request deadline (mapped to a typed retryable WorkflowWorldError
instead of an opaque AbortError), DEBUG logging with a curl repro, and
the status -> typed-error mapping. utils.ts, events-v4.ts, streamer.ts,
encryption.ts and resolve-latest-deployment.ts delegate to it.
Behavioral gains on stable: the v4 events path and stream writes now
emit client spans and propagate traceparent, so the backend can parent
its spans to the caller without relying on the app having undici
auto-instrumentation.
Adapted rather than cherry-picked: stable keeps #3169's bounded
headers/body timeouts (documented as firing ahead of the outer
REQUEST_TIMEOUT_MS backstop), its body-parse retry loop, and #3038's
write-vs-close stream dispatcher split. Only injectTraceContextIntoHeaders
is taken from #2363, not its WORKFLOW_TRACE_MODE machinery.
Routing v4 through the global fetch makes the H2 interceptor's
re-buffering gate live on this path (fetch streamifies request bodies),
so the new http-client test drives createWorkflowRunEventV4 concurrently
against a local H2 server: 16 writes on one session with the
interceptor, 8 without it.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@VaguelySerious@karthikscale3
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[world-vercel] Unify HTTP request handling into a shared core - #2631

Merged
VaguelySerious merged 1 commit into
mainfrom
peter/world-vercel-http-cleanup
Jun 28, 2026
Merged

[world-vercel] Unify HTTP request handling into a shared core#2631
VaguelySerious merged 1 commit into
mainfrom
peter/world-vercel-http-cleanup

Conversation

@VaguelySerious

Copy link
Copy Markdown
Member

What

Follow-up to #2573. world-vercel had five separate HTTP request paths, each re-implementing the same cross-cutting concerns inconsistently. This collapses the duplication into one shared core (http-core.ts) and routes every path through it, so the v4 / stream / Vercel-API paths gain the OTEL client spans + DEBUG logging the v3 path already had.

Before

PathOTEL spanDEBUG logcurl-reprodiag headerstyped errorstrace-ctx
v3 makeRequest
v4 events(2nd copy)
stream write/close(2nd copy)
stream get/list
Vercel API (run-key, resolve-deploy)(plain Error)

The v4 events path — now the hottest path — had no client span and no debug logging.

How

New http-core.ts is the single source of truth for the request envelope:

  • errorForResponse — the status → typed-error map (409→EntityConflictError, 410→RunExpiredError, 425→TooEarlyError, 429→ThrottleError, else→WorkflowWorldError). Was implemented twice (v3 makeRequest + v4 throwForErrorResponse).
  • getVercelDiagnostics / parseRetryAfter / httpLog / logCurlRepro — one copy each (were duplicated across utils.ts and streamer.ts).
  • resolveVercelApiToken — shared token → VERCEL_TOKEN → OIDC precedence (was byte-identical in encryption.ts and resolve-latest-deployment.ts).
  • instrumentedFetch — shared OTEL span + trace-context injection + cache-bust header + timeout + DEBUG logging + error envelope. The v4 events, stream, and Vercel-API paths now route through it.

Also: stream writes now inject W3C trace context (previously missing, contrary to the documented requirement that every world→server request must), and the three dispatcher singletons in http-client.ts collapse behind one makeRetryDispatcher factory.

Behavior preserved

No behavior change beyond the added observability:

  • Per-path error-message strings are unchanged (the v3/v4/stream message formats feed log tooling); only the type mapping, Retry-After parsing, and diagnostics are unified.
  • The non-idempotent stream retry policy (transient + 429 only, never 5xx) is untouched.
  • The no-timeout semantics of the v4 (streamed LIST), stream (large buffered writes / long-lived live read), and Vercel-API paths are preserved (timeoutMs: null); only the v3 path keeps its 60s timeout.

Tests

  • New http-core.test.ts (error mapping per status, parseRetryAfter, diagnostics, token precedence).
  • Extended trace-propagation.test.ts: v4 now parents to its own http GET client span (matching v3), and a new case asserts stream writes inject traceparent.
  • Full world-vercel suite green (187 tests); build + typecheck + lint clean.

Changeset

@workflow/world-vercel patch — internal transport refactor + added observability, no API surface change.

Collapse the duplicated cross-cutting request logic across world-vercel's
request paths into one shared module (http-core.ts):
- errorForResponse: single status -> typed-error map (409/410/425/429/else),
shared by the v3 makeRequest and v4 throwForErrorResponse paths.
- getVercelDiagnostics / parseRetryAfter / httpLog / logCurlRepro: one copy
each (were duplicated across utils.ts and streamer.ts).
- resolveVercelApiToken: shared token precedence for the direct Vercel-API
calls (was byte-identical in encryption.ts and resolve-latest-deployment.ts).
- instrumentedFetch: the shared OTEL-span + trace-injection + cache-bust +
timeout + DEBUG-logging + error envelope.
Route the v4 events, stream write/read, and Vercel-API (run-key /
resolve-latest-deployment) paths through instrumentedFetch so they gain the
OTEL client spans and DEBUG logging the v3 path already had. Stream writes now
also inject W3C trace context (previously missing). Collapse the three
dispatcher singletons in http-client.ts behind a single makeRetryDispatcher
factory.
No behavior change beyond the added observability: per-path error-message
formats, the non-idempotent stream retry policy, and the no-timeout semantics
of the v4/stream/Vercel-API paths are preserved.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9263886

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 17 packages
NameType
@workflow/world-vercelPatch
@workflow/cliPatch
@workflow/corePatch
@workflow/webPatch
workflowPatch
@workflow/world-testingPatch
@workflow/buildersPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch
@workflow/nuxtPatch

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

@github-actions

github-actionsBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.035s (-26.0% 🟢)1.005s (~)0.970s101.00x
💻 LocalExpress0.048s (+1.7%)1.006s (~)0.958s101.39x
💻 LocalNext.js (Turbopack)0.050s (-4.8%)1.006s (~)0.956s101.44x
🐘 PostgresNext.js (Turbopack)0.061s (+9.8% 🔺)1.012s (~)0.951s101.74x
🐘 PostgresExpress0.063s (-9.9% 🟢)1.012s (~)0.949s101.80x
🐘 PostgresNitro0.071s (+9.2% 🔺)1.012s (~)0.940s102.05x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express0.332s (+43.7% 🔺)2.094s (~)1.762s101.00x
▲ VercelNitro0.343s (+36.1% 🔺)2.314s (+6.6% 🔺)1.971s101.03x
▲ VercelNext.js (Turbopack)0.931s (+244.5% 🔺)3.051s (+49.2% 🔺)2.120s102.80x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.062s (-2.2%)2.005s (~)0.943s101.00x
💻 LocalNext.js (Turbopack)1.084s (~)2.007s (~)0.923s101.02x
💻 LocalExpress1.084s (~)2.006s (~)0.922s101.02x
🐘 PostgresNitro1.088s (-1.3%)2.010s (~)0.922s101.02x
🐘 PostgresNext.js (Turbopack)1.103s (+1.8%)2.010s (~)0.907s101.04x
🐘 PostgresExpress1.104s (+1.1%)2.009s (~)0.905s101.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.499s (+6.2% 🔺)3.381s (+6.1% 🔺)1.881s101.00x
▲ VercelExpress1.584s (+14.5% 🔺)3.601s (+27.2% 🔺)2.017s101.06x
▲ VercelNext.js (Turbopack)2.692s (+87.7% 🔺)4.407s (+31.5% 🔺)1.715s101.80x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro10.335s (-1.2%)11.020s (~)0.685s31.00x
🐘 PostgresNitro10.443s (-0.6%)11.018s (~)0.575s31.01x
💻 LocalExpress10.449s (~)11.023s (~)0.574s31.01x
💻 LocalNext.js (Turbopack)10.523s (~)11.024s (~)0.501s31.02x
🐘 PostgresExpress10.529s (+0.8%)11.018s (~)0.489s31.02x
🐘 PostgresNext.js (Turbopack)10.549s (+0.9%)11.019s (~)0.470s31.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro13.474s (+15.0% 🔺)15.455s (+15.7% 🔺)1.981s31.00x
▲ VercelExpress13.947s (+13.3% 🔺)16.641s (+20.0% 🔺)2.694s21.04x
▲ VercelNext.js (Turbopack)14.162s (+20.0% 🔺)16.396s (+17.6% 🔺)2.234s21.05x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro13.443s (-1.4%)14.025s (~)0.582s51.00x
🐘 PostgresNitro13.632s (-0.6%)14.020s (~)0.388s51.01x
💻 LocalNext.js (Turbopack)13.637s (~)14.026s (~)0.389s51.01x
🐘 PostgresNext.js (Turbopack)13.650s (~)14.021s (~)0.371s51.02x
💻 LocalExpress13.656s (~)14.028s (~)0.372s51.02x
🐘 PostgresExpress13.824s (+1.8%)14.019s (~)0.194s51.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro20.368s (+24.4% 🔺)22.630s (+27.7% 🔺)2.262s31.00x
▲ VercelExpress20.918s (+27.2% 🔺)22.656s (+25.9% 🔺)1.738s31.03x
▲ VercelNext.js (Turbopack)23.709s (+36.7% 🔺)25.789s (+34.7% 🔺)2.079s31.16x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro11.628s (-4.5%)12.022s (-7.7% 🟢)0.394s81.00x
🐘 PostgresNitro12.094s (-1.5%)13.016s (~)0.922s71.04x
💻 LocalExpress12.171s (-1.4%)13.025s (~)0.853s71.05x
🐘 PostgresExpress12.282s (+1.5%)13.018s (~)0.736s71.06x
🐘 PostgresNext.js (Turbopack)12.323s (+1.8%)13.017s (~)0.694s71.06x
💻 LocalNext.js (Turbopack)12.389s (+1.1%)13.025s (~)0.636s71.07x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro22.441s (+18.2% 🔺)25.257s (+22.4% 🔺)2.816s41.00x
▲ VercelNext.js (Turbopack)24.710s (+27.7% 🔺)27.172s (+28.0% 🔺)2.462s41.10x
▲ VercelExpress24.776s (+34.8% 🔺)27.682s (+37.3% 🔺)2.906s41.10x

🔍 Observability: Nitro | Next.js (Turbopack) | Express

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.181s (~)2.008s (~)0.828s151.00x
🐘 PostgresNext.js (Turbopack)1.198s (+1.8%)2.008s (~)0.810s151.01x
🐘 PostgresExpress1.242s (+6.3% 🔺)2.009s (~)0.768s151.05x
💻 LocalNitro1.300s (-9.6% 🟢)2.006s (~)0.706s151.10x
💻 LocalExpress1.421s (+2.3%)2.007s (~)0.585s151.20x
💻 LocalNext.js (Turbopack)1.466s (-2.1%)2.007s (~)0.541s151.24x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.247s (+5.6% 🔺)4.214s (+22.4% 🔺)1.966s81.00x
▲ VercelExpress2.843s (+40.1% 🔺)4.746s (+35.6% 🔺)1.904s71.26x
▲ VercelNext.js (Turbopack)5.555s (+158.4% 🔺)7.589s (+100.3% 🔺)2.034s42.47x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.295s (-7.0% 🟢)2.736s (+2.3%)1.441s111.00x
🐘 PostgresNitro1.318s (-1.6%)2.591s (+3.3%)1.273s121.02x
🐘 PostgresNext.js (Turbopack)1.379s (+5.0% 🔺)3.011s (+3.2%)1.632s101.06x
💻 LocalNitro2.078s (-13.7% 🟢)2.826s (-3.2%)0.748s111.60x
💻 LocalExpress2.416s (+2.5%)3.009s (+3.1%)0.593s101.87x
💻 LocalNext.js (Turbopack)2.632s (-3.8%)3.010s (-3.2%)0.377s102.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.352s (+8.9% 🔺)5.327s (+2.8%)1.976s61.00x
▲ VercelExpress3.378s (-7.0% 🟢)5.318s (+3.0%)1.940s61.01x
▲ VercelNext.js (Turbopack)4.672s (+39.8% 🔺)6.725s (+33.3% 🔺)2.053s51.39x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.610s (-1.9%)4.137s (-3.0%)2.527s81.00x
🐘 PostgresExpress1.619s (+2.1%)4.012s (-6.7% 🟢)2.393s81.01x
🐘 PostgresNext.js (Turbopack)3.104s (+10.3% 🔺)6.015s (+9.0% 🔺)2.911s51.93x
💻 LocalNitro4.859s (-24.6% 🟢)5.347s (-25.9% 🟢)0.488s63.02x
💻 LocalNext.js (Turbopack)6.248s (+14.2% 🔺)7.518s (+25.0% 🔺)1.270s43.88x
💻 LocalExpress6.275s (-0.8%)6.614s (-5.8% 🟢)0.338s53.90x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express4.354s (+42.1% 🔺)6.876s (+36.3% 🔺)2.522s51.00x
▲ VercelNitro4.370s (+30.3% 🔺)6.765s (+32.1% 🔺)2.395s51.00x
▲ VercelNext.js (Turbopack)4.926s (+41.5% 🔺)7.542s (+45.0% 🔺)2.616s41.13x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.174s (-2.7%)2.007s (~)0.833s151.00x
🐘 PostgresNext.js (Turbopack)1.199s (+2.5%)2.008s (~)0.809s151.02x
🐘 PostgresExpress1.243s (+5.5% 🔺)2.074s (+3.3%)0.831s151.06x
💻 LocalNitro1.344s (-5.5% 🟢)2.006s (~)0.662s151.14x
💻 LocalExpress1.428s (-0.6%)2.007s (~)0.579s151.22x
💻 LocalNext.js (Turbopack)1.495s (+6.6% 🔺)2.073s (+3.3%)0.578s151.27x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.542s (+23.2% 🔺)4.657s (+33.1% 🔺)2.115s71.00x
▲ VercelNitro2.571s (+19.9% 🔺)4.516s (+21.5% 🔺)1.945s71.01x
▲ VercelNext.js (Turbopack)4.063s (-29.8% 🟢)6.178s (-17.0% 🟢)2.115s51.60x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.290s (-6.0% 🟢)2.075s (-16.1% 🟢)0.785s151.00x
🐘 PostgresExpress1.341s (~)2.393s (-3.2%)1.052s131.04x
🐘 PostgresNext.js (Turbopack)1.361s (+5.5% 🔺)3.010s (+3.2%)1.650s101.05x
💻 LocalNitro2.207s (-14.5% 🟢)2.917s (-6.2% 🟢)0.710s111.71x
💻 LocalExpress2.483s (+2.5%)2.919s (-6.1% 🟢)0.435s111.92x
💻 LocalNext.js (Turbopack)2.866s (+8.7% 🔺)3.209s (+6.7% 🔺)0.343s102.22x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.557s (-46.6% 🟢)4.443s (-33.1% 🟢)1.886s71.00x
▲ VercelNitro2.768s (+1.9%)4.662s (+8.0% 🔺)1.894s71.08x
▲ VercelNext.js (Turbopack)4.820s (+1.6%)7.047s (+1.4%)2.226s51.88x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.612s (-2.9%)4.012s (~)2.400s81.00x
🐘 PostgresExpress1.616s (+1.9%)4.012s (-3.0%)2.396s81.00x
🐘 PostgresNext.js (Turbopack)2.989s (+4.6%)6.013s (+2.8%)3.024s51.85x
💻 LocalNitro5.245s (-22.7% 🟢)5.680s (-21.3% 🟢)0.436s63.25x
💻 LocalNext.js (Turbopack)5.971s (+4.4%)6.614s (+9.9% 🔺)0.642s53.71x
💻 LocalExpress8.009s (+19.5% 🔺)8.518s (+18.0% 🔺)0.509s44.97x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.839s (-18.1% 🟢)6.037s (-4.1%)2.198s51.00x
▲ VercelNext.js (Turbopack)4.185s (+34.0% 🔺)6.365s (+23.3% 🔺)2.180s51.09x
▲ VercelNitro4.362s (+26.2% 🔺)6.526s (+29.7% 🔺)2.165s51.14x

🔍 Observability: Express | Next.js (Turbopack) | Nitro

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.508s (-13.3% 🟢)1.004s (~)0.496s601.00x
🐘 PostgresNitro0.518s (-12.9% 🟢)1.006s (-3.4%)0.488s601.02x
🐘 PostgresExpress0.565s (+5.9% 🔺)1.023s (~)0.458s591.11x
💻 LocalExpress0.596s (+4.0%)1.022s (+1.7%)0.426s591.17x
🐘 PostgresNext.js (Turbopack)0.608s (+18.9% 🔺)1.024s (+1.9%)0.417s591.20x
💻 LocalNext.js (Turbopack)0.608s (+0.8%)1.022s (~)0.414s591.20x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.261s (+16.5% 🔺)5.059s (+12.8% 🔺)1.799s121.00x
▲ VercelNitro3.490s (+31.9% 🔺)5.594s (+30.1% 🔺)2.104s111.07x
▲ VercelNext.js (Turbopack)4.224s (+39.0% 🔺)6.144s (+23.0% 🔺)1.920s101.30x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.240s (-12.3% 🟢)2.030s (-1.2%)0.789s451.00x
💻 LocalNitro1.256s (-15.3% 🟢)2.005s (~)0.749s451.01x
🐘 PostgresExpress1.331s (+6.4% 🔺)2.029s (+1.1%)0.699s451.07x
🐘 PostgresNext.js (Turbopack)1.389s (+11.6% 🔺)2.008s (-1.1%)0.619s451.12x
💻 LocalExpress1.472s (+1.2%)2.006s (~)0.534s451.19x
💻 LocalNext.js (Turbopack)1.532s (+4.4%)2.006s (~)0.474s451.24x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express7.933s (+22.7% 🔺)9.951s (+23.8% 🔺)2.018s101.00x
▲ VercelNitro8.413s (+31.5% 🔺)10.388s (+33.8% 🔺)1.975s91.06x
▲ VercelNext.js (Turbopack)10.501s (+47.0% 🔺)12.769s (+43.6% 🔺)2.268s81.32x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.512s (-7.5% 🟢)3.086s (+0.9%)0.574s391.00x
💻 LocalNitro2.760s (-14.3% 🟢)3.165s (-21.1% 🟢)0.405s381.10x
🐘 PostgresExpress2.763s (+6.5% 🔺)3.167s (+2.6%)0.404s381.10x
🐘 PostgresNext.js (Turbopack)2.793s (+12.8% 🔺)3.009s (-1.6%)0.216s401.11x
💻 LocalExpress3.242s (~)4.010s (+0.8%)0.768s301.29x
💻 LocalNext.js (Turbopack)3.305s (+1.4%)4.043s (~)0.738s301.32x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express13.918s (+1.0%)17.102s (+10.6% 🔺)3.184s81.00x
▲ VercelNitro14.974s (+6.6% 🔺)17.975s (+15.0% 🔺)3.000s71.08x
▲ VercelNext.js (Turbopack)20.700s (+26.1% 🔺)23.054s (+24.7% 🔺)2.354s61.49x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.212s (~)1.006s (~)0.794s601.00x
🐘 PostgresNext.js (Turbopack)0.218s (+30.2% 🔺)1.007s (~)0.788s601.03x
🐘 PostgresNitro0.223s (-1.8%)1.006s (~)0.783s601.05x
💻 LocalNitro0.399s (-14.8% 🟢)1.021s (+1.6%)0.622s591.89x
💻 LocalExpress0.504s (+16.8% 🔺)1.005s (~)0.501s602.38x
💻 LocalNext.js (Turbopack)0.644s (+10.1% 🔺)1.022s (+1.7%)0.378s593.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.221s (-43.8% 🟢)3.077s (-20.6% 🟢)1.857s201.00x
▲ VercelExpress1.345s (-35.1% 🟢)3.022s (-21.3% 🟢)1.677s201.10x
▲ VercelNext.js (Turbopack)2.809s (+54.4% 🔺)4.638s (+30.7% 🔺)1.830s132.30x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.308s (+15.0% 🔺)1.041s (+3.5%)0.733s871.00x
🐘 PostgresNitro0.319s (-5.4% 🟢)1.006s (~)0.687s901.03x
🐘 PostgresExpress0.326s (+1.0%)1.007s (-1.1%)0.680s901.06x
💻 LocalNitro1.815s (-14.9% 🟢)2.285s (-14.9% 🟢)0.470s405.89x
💻 LocalExpress2.221s (+2.8%)2.797s (+2.2%)0.576s337.21x
💻 LocalNext.js (Turbopack)2.826s (+1.3%)3.295s (+1.1%)0.469s289.17x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.920s (-22.0% 🟢)3.816s (-5.9% 🟢)1.896s241.00x
▲ VercelNitro1.964s (+3.0%)3.918s (+12.3% 🔺)1.954s231.02x
▲ VercelNext.js (Turbopack)3.338s (+53.8% 🔺)5.492s (+39.8% 🔺)2.155s171.74x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.507s (-1.7%)1.068s (~)0.561s1131.00x
🐘 PostgresNitro0.513s (-2.9%)1.068s (+3.5%)0.555s1141.01x
🐘 PostgresNext.js (Turbopack)0.580s (+23.0% 🔺)3.254s (+8.1% 🔺)2.674s371.14x
💻 LocalNitro8.193s (-17.4% 🟢)9.094s (-17.5% 🟢)0.901s1416.15x
💻 LocalExpress9.881s (-2.0%)11.117s (~)1.235s1119.48x
💻 LocalNext.js (Turbopack)10.366s (+6.0% 🔺)11.576s (+7.4% 🔺)1.210s1120.44x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.428s (+34.8% 🔺)5.645s (+36.8% 🔺)2.217s221.00x
▲ VercelExpress3.617s (+51.3% 🔺)6.034s (+48.8% 🔺)2.417s201.06x
▲ VercelNext.js (Turbopack)4.887s (+4.3%)7.315s (+11.8% 🔺)2.428s171.43x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.128s (-2.6%)2.004s (~)0.008s (-33.9% 🟢)2.015s (~)0.887s101.00x
💻 LocalNext.js (Turbopack)1.145s (-2.1%)1.967s (~)0.012s (-2.4%)2.019s (~)0.875s101.01x
🐘 PostgresExpress1.159s (+0.7%)1.996s (~)0.001s (+10.0% 🔺)2.010s (~)0.852s101.03x
🐘 PostgresNitro1.159s (-0.7%)1.999s (~)0.001s (+9.1% 🔺)2.011s (~)0.852s101.03x
💻 LocalExpress1.160s (+0.7%)2.005s (~)0.013s (+5.8% 🔺)2.020s (~)0.860s101.03x
🐘 PostgresNext.js (Turbopack)1.165s (+1.7%)2.001s (~)0.001s (+18.2% 🔺)2.011s (~)0.846s101.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.120s (+3.6%)3.629s (+18.3% 🔺)2.424s (+38.4% 🔺)6.604s (+22.2% 🔺)4.484s101.00x
▲ VercelNitro2.340s (+5.5% 🔺)3.692s (+12.7% 🔺)2.806s (+50.6% 🔺)7.080s (+24.4% 🔺)4.740s101.10x
▲ VercelNext.js (Turbopack)3.746s (+81.4% 🔺)4.139s (+20.0% 🔺)2.700s (+91.6% 🔺)8.319s (+53.9% 🔺)4.573s101.77x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.426s (-8.2% 🟢)2.006s (~)0.011s (-7.8% 🟢)2.020s (~)0.594s301.00x
🐘 PostgresNitro1.544s (-3.8%)1.999s (~)0.005s (+6.6% 🔺)2.029s (~)0.485s301.08x
🐘 PostgresExpress1.551s (~)2.001s (~)0.005s (-10.2% 🟢)2.024s (~)0.473s301.09x
💻 LocalExpress1.556s (+0.5%)2.011s (~)0.012s (-4.1%)2.027s (~)0.471s301.09x
💻 LocalNext.js (Turbopack)1.608s (+1.9%)2.000s (+1.6%)0.013s (-3.1%)2.058s (+1.6%)0.450s301.13x
🐘 PostgresNext.js (Turbopack)1.675s (+8.7% 🔺)2.010s (~)0.005s (+0.6%)2.027s (~)0.352s301.17x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro7.027s (+15.7% 🔺)8.939s (+26.3% 🔺)0.304s (+34.3% 🔺)9.893s (+26.4% 🔺)2.866s71.00x
▲ VercelExpress7.058s (+19.9% 🔺)8.708s (+20.8% 🔺)0.283s (+31.1% 🔺)9.595s (+21.7% 🔺)2.537s71.00x
▲ VercelNext.js (Turbopack)10.646s (+79.2% 🔺)12.028s (+63.2% 🔺)0.377s (+18.4% 🔺)13.403s (+62.4% 🔺)2.757s51.52x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.781s (+0.9%)1.062s (+1.3%)0.000s (+Infinity% 🔺)1.079s (+1.4%)0.298s561.00x
🐘 PostgresNitro0.799s (-2.8%)1.044s (-5.0% 🟢)0.000s (-100.0% 🟢)1.059s (-5.3% 🟢)0.260s581.02x
💻 LocalNitro1.004s (-23.9% 🟢)1.475s (-25.5% 🟢)0.000s (-30.7% 🟢)1.478s (-25.5% 🟢)0.473s411.29x
🐘 PostgresNext.js (Turbopack)1.041s (+13.6% 🔺)1.538s (+20.5% 🔺)0.000s (+141.0% 🔺)1.550s (+20.8% 🔺)0.509s391.33x
💻 LocalExpress1.256s (-0.9%)2.013s (~)0.000s (+50.0% 🔺)2.016s (~)0.760s301.61x
💻 LocalNext.js (Turbopack)1.375s (+3.0%)1.979s (~)0.001s (+283.3% 🔺)2.016s (~)0.642s301.76x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.783s (+21.3% 🔺)5.122s (+16.1% 🔺)0.000s (NaN%)5.698s (+16.8% 🔺)1.915s111.00x
▲ VercelExpress3.945s (+1.0%)5.443s (+8.3% 🔺)0.000s (-100.0% 🟢)5.941s (+7.3% 🔺)1.996s111.04x
▲ VercelNext.js (Turbopack)5.317s (+61.5% 🔺)6.785s (+44.3% 🔺)0.000s (+50.0% 🔺)7.685s (+46.3% 🔺)2.368s81.41x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.685s (-12.1% 🟢)2.301s (-7.8% 🟢)0.000s (+84.6% 🔺)2.344s (-7.0% 🟢)0.658s261.00x
🐘 PostgresExpress1.748s (+3.1%)2.345s (+4.0%)0.000s (-100.0% 🟢)2.366s (+4.2%)0.618s261.04x
💻 LocalNitro2.653s (-25.6% 🟢)3.073s (-23.7% 🟢)0.000s (-64.7% 🟢)3.081s (-23.6% 🟢)0.427s201.57x
🐘 PostgresNext.js (Turbopack)2.662s (+31.0% 🔺)3.336s (+27.8% 🔺)0.000s (-100.0% 🟢)3.345s (+26.6% 🔺)0.683s181.58x
💻 LocalExpress3.715s (+2.2%)4.313s (+2.0%)0.001s (+7.1% 🔺)4.319s (+2.1%)0.604s142.20x
💻 LocalNext.js (Turbopack)3.837s (+4.7%)4.277s (+2.0%)0.001s (-19.6% 🟢)4.319s (+2.0%)0.482s142.28x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.405s (+14.2% 🔺)7.099s (+23.6% 🔺)0.002s (+1400.0% 🔺)7.612s (+21.7% 🔺)2.207s81.00x
▲ VercelNitro5.848s (+19.5% 🔺)7.481s (+30.8% 🔺)0.000s (+Infinity% 🔺)8.026s (+29.8% 🔺)2.178s81.08x
▲ VercelNext.js (Turbopack)7.707s (+66.4% 🔺)9.533s (+58.4% 🔺)0.000s (+66.7% 🔺)10.735s (+63.2% 🔺)3.027s61.43x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNitro21/21
🐘 PostgresNitro14/21
▲ VercelExpress11/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres16/21
Next.js (Turbopack)🐘 Postgres15/21
Nitro🐘 Postgres13/21
Column Definitions
  • Workflow Time: Runtime reported by workflow (completedAt - createdAt) - primary metric
  • TTFB: Time to First Byte - time from workflow start until first stream byte received (stream benchmarks only)
  • Slurp: Time from first byte to complete stream consumption (stream benchmarks only)
  • Wall Time: Total testbench time (trigger workflow + poll for result)
  • Overhead: Testbench overhead (Wall Time - Workflow Time)
  • Samples: Number of benchmark iterations run
  • vs Fastest: How much slower compared to the fastest configuration for this benchmark

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Redis + BullMQ: Community world (local development)
  • 🌐 Cloudflare: Community world (local development)
  • 🌐 MySQL: Community world (local development)
  • 🌐 Azure: Community world (local development)
  • 🌐 NATS JetStream: Community world (local development)
  • 🌐 Upstash: Community world (local development)
  • 🌐 Platformatic: Community world (local development)

📋 View full workflow run

@github-actions

github-actionsBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production144202301672
✅ 💻 Local Development160502191824
✅ 📦 Local Production160502191824
✅ 🐘 Local Postgres159302311824
✅ 🪟 Windows15200152
✅ 📋 Other88501791064
Total7282010788360

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro125027
✅ example125027
✅ express125027
✅ fastify125027
✅ hono125027
✅ nextjs-turbopack14903
✅ nextjs-webpack14903
✅ nitro125027
✅ nuxt125027
✅ sveltekit14408
✅ vite125027
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable126026
✅ express-stable126026
✅ fastify-stable126026
✅ hono-stable126026
✅ nextjs-turbopack-canary132020
✅ nextjs-turbopack-stable15101
✅ nextjs-webpack-canary132020
✅ nextjs-webpack-stable15101
✅ nitro-stable126026
✅ nuxt-stable126026
✅ sveltekit-stable14507
✅ vite-stable126026
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack15200
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable127025
✅ e2e-local-dev-tanstack-start-127025
✅ e2e-local-postgres-nest-stable126026
✅ e2e-local-postgres-tanstack-start-126026
✅ e2e-local-prod-nest-stable127025
✅ e2e-local-prod-tanstack-start-127025
✅ e2e-vercel-prod-tanstack-start125027

📋 View full workflow run

@vercel

vercelBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

@VaguelySerious
VaguelySerious marked this pull request as ready for review June 25, 2026 22:55
@VaguelySerious
VaguelySerious requested review from a team and ijjk as code ownersJune 25, 2026 22:55

@karthikscale3karthikscale3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving — reviewed against the merged OTEL trace-correlation work (linked-mode #2363, route/execution cohesion #2527, v4 injection #2533). No regressions; this is a net improvement.

What I verified:

  • Injection is centralized + default-on: injectTraceContext = true in instrumentedFetch, inject runs inside the http <method> CLIENT span, and no caller passes false — so every path injects.
  • No bypass remains: the only raw fetch( is inside instrumentedFetch; all call sites (v4 events, makeRequest, streamer, both Vercel-API callers) route through it. Stronger guarantee than before.
  • v4 is fixed, not broken: #2533's inline injection moves into the core and v4 now gets a real client span (it had none), so workflow-server parents to it like v3. Stream + Vercel-API paths gain propagation.
  • Span name + parenting preserved: still http <method> / CLIENT with PeerService/RpcService → service-map edges and name-based queries intact; workflow-server still parents into the invocation trace (parent/child, not links).
  • Cohesion/linked mode untouched (only world-vercel/* changed; runtime.ts/step-handler.ts/telemetry.ts are not in the diff).
  • Tests strengthened: trace-propagation.test.ts asserts the v4 request's traceparent matches its client span and adds a stream-write propagation test.

One thing to confirm (see inline comment) + a doc follow-up.

// Explicitly propagate trace context so the receiving server can parent
// its spans to this client span — the custom undici dispatcher bypasses
// ambient auto-instrumentation. No-ops when no OTEL SDK is registered.
if (injectTraceContext) await injectTraceContextIntoHeaders(headers);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since instrumentedFetch is now the single trace-context injection chokepoint, please update the AGENTS.md guardrail (Architecture Notes → "Trace context propagation (world-vercel HTTP requests)") before merge: it currently tells contributors to call injectTraceContextIntoHeaders directly, but the rule should now be "route new request paths through instrumentedFetch (injects W3C context by default)". That keeps the guidance pointing at this chokepoint so future paths inherit propagation automatically.

Also worth a conscious decision: the peerService: 'vercel-api' callers default to injectTraceContext: true, so they now send traceparentand baggage (incl. workflow.run_id/name) to vercel.com. Likely fine since it's first-party, but if that propagation isn't intended, pass injectTraceContext: false for those callers.

@VaguelySerious
VaguelySerious merged commit 1dcdafd into mainJun 28, 2026
179 of 183 checks passed
@VaguelySerious
VaguelySerious deleted the peter/world-vercel-http-cleanup branch June 28, 2026 21:59
@github-actionsgithub-actionsBot mentioned this pull request Jun 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for 1dcdafd (AI decision).

This PR is an explicit follow-up to #2573 and the unified http-core.ts it introduces depends on main-only infrastructure: the events/stream dispatcher functions (getEventsDispatcher, getStreamDispatcher, EVENTS_AGENT_OPTIONS, STREAM_RETRY_OPTIONS) and the fetch-based v4 path with trace-context injection do not exist on stable (verified — stable's events-v4.ts still uses undici.request() directly and streamer.ts uses plain fetch() without dispatchers). Backporting would pull in code referencing dispatchers absent on stable, so it builds on main-only behavior.

To override, re-run the Backport to stable workflow manually via workflow_dispatch and paste this commit SHA into the ref input:

1dcdafd422c870ec1b7dcbba8b0e733c1b6bbb14

VaguelySerious added a commit that referenced this pull request Jul 31, 2026
Backport of #2631, adapted to stable. The v3 makeRequest path, the v4
events client, the stream write/close path and the direct Vercel API
calls each had their own request envelope; the 412 -> PreconditionFailed
and firewall-challenge-429 -> retryable TRANSPORT mappings were
duplicated between utils.ts and events-v4.ts.
http-core.ts now owns that envelope: the OTEL CLIENT span and its
attributes, W3C trace-context injection, the X-Request-Time cache bust,
the per-request deadline (mapped to a typed retryable WorkflowWorldError
instead of an opaque AbortError), DEBUG logging with a curl repro, and
the status -> typed-error mapping. utils.ts, events-v4.ts, streamer.ts,
encryption.ts and resolve-latest-deployment.ts delegate to it.
Behavioral gains on stable: the v4 events path and stream writes now
emit client spans and propagate traceparent, so the backend can parent
its spans to the caller without relying on the app having undici
auto-instrumentation.
Adapted rather than cherry-picked: stable keeps #3169's bounded
headers/body timeouts (documented as firing ahead of the outer
REQUEST_TIMEOUT_MS backstop), its body-parse retry loop, and #3038's
write-vs-close stream dispatcher split. Only injectTraceContextIntoHeaders
is taken from #2363, not its WORKFLOW_TRACE_MODE machinery.
Routing v4 through the global fetch makes the H2 interceptor's
re-buffering gate live on this path (fetch streamifies request bodies),
so the new http-client test drives createWorkflowRunEventV4 concurrently
against a local H2 server: 16 writes on one session with the
interceptor, 8 without it.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@VaguelySerious@karthikscale3
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[world-vercel] Unify HTTP request handling into a shared core - #2631

Merged
VaguelySerious merged 1 commit into
mainfrom
peter/world-vercel-http-cleanup
Jun 28, 2026
Merged

[world-vercel] Unify HTTP request handling into a shared core#2631
VaguelySerious merged 1 commit into
mainfrom
peter/world-vercel-http-cleanup

Conversation

@VaguelySerious

Copy link
Copy Markdown
Member

What

Follow-up to #2573. world-vercel had five separate HTTP request paths, each re-implementing the same cross-cutting concerns inconsistently. This collapses the duplication into one shared core (http-core.ts) and routes every path through it, so the v4 / stream / Vercel-API paths gain the OTEL client spans + DEBUG logging the v3 path already had.

Before

PathOTEL spanDEBUG logcurl-reprodiag headerstyped errorstrace-ctx
v3 makeRequest
v4 events(2nd copy)
stream write/close(2nd copy)
stream get/list
Vercel API (run-key, resolve-deploy)(plain Error)

The v4 events path — now the hottest path — had no client span and no debug logging.

How

New http-core.ts is the single source of truth for the request envelope:

  • errorForResponse — the status → typed-error map (409→EntityConflictError, 410→RunExpiredError, 425→TooEarlyError, 429→ThrottleError, else→WorkflowWorldError). Was implemented twice (v3 makeRequest + v4 throwForErrorResponse).
  • getVercelDiagnostics / parseRetryAfter / httpLog / logCurlRepro — one copy each (were duplicated across utils.ts and streamer.ts).
  • resolveVercelApiToken — shared token → VERCEL_TOKEN → OIDC precedence (was byte-identical in encryption.ts and resolve-latest-deployment.ts).
  • instrumentedFetch — shared OTEL span + trace-context injection + cache-bust header + timeout + DEBUG logging + error envelope. The v4 events, stream, and Vercel-API paths now route through it.

Also: stream writes now inject W3C trace context (previously missing, contrary to the documented requirement that every world→server request must), and the three dispatcher singletons in http-client.ts collapse behind one makeRetryDispatcher factory.

Behavior preserved

No behavior change beyond the added observability:

  • Per-path error-message strings are unchanged (the v3/v4/stream message formats feed log tooling); only the type mapping, Retry-After parsing, and diagnostics are unified.
  • The non-idempotent stream retry policy (transient + 429 only, never 5xx) is untouched.
  • The no-timeout semantics of the v4 (streamed LIST), stream (large buffered writes / long-lived live read), and Vercel-API paths are preserved (timeoutMs: null); only the v3 path keeps its 60s timeout.

Tests

  • New http-core.test.ts (error mapping per status, parseRetryAfter, diagnostics, token precedence).
  • Extended trace-propagation.test.ts: v4 now parents to its own http GET client span (matching v3), and a new case asserts stream writes inject traceparent.
  • Full world-vercel suite green (187 tests); build + typecheck + lint clean.

Changeset

@workflow/world-vercel patch — internal transport refactor + added observability, no API surface change.

Collapse the duplicated cross-cutting request logic across world-vercel's
request paths into one shared module (http-core.ts):
- errorForResponse: single status -> typed-error map (409/410/425/429/else),
shared by the v3 makeRequest and v4 throwForErrorResponse paths.
- getVercelDiagnostics / parseRetryAfter / httpLog / logCurlRepro: one copy
each (were duplicated across utils.ts and streamer.ts).
- resolveVercelApiToken: shared token precedence for the direct Vercel-API
calls (was byte-identical in encryption.ts and resolve-latest-deployment.ts).
- instrumentedFetch: the shared OTEL-span + trace-injection + cache-bust +
timeout + DEBUG-logging + error envelope.
Route the v4 events, stream write/read, and Vercel-API (run-key /
resolve-latest-deployment) paths through instrumentedFetch so they gain the
OTEL client spans and DEBUG logging the v3 path already had. Stream writes now
also inject W3C trace context (previously missing). Collapse the three
dispatcher singletons in http-client.ts behind a single makeRetryDispatcher
factory.
No behavior change beyond the added observability: per-path error-message
formats, the non-idempotent stream retry policy, and the no-timeout semantics
of the v4/stream/Vercel-API paths are preserved.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9263886

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 17 packages
NameType
@workflow/world-vercelPatch
@workflow/cliPatch
@workflow/corePatch
@workflow/webPatch
workflowPatch
@workflow/world-testingPatch
@workflow/buildersPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch
@workflow/nuxtPatch

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

@github-actions

github-actionsBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.035s (-26.0% 🟢)1.005s (~)0.970s101.00x
💻 LocalExpress0.048s (+1.7%)1.006s (~)0.958s101.39x
💻 LocalNext.js (Turbopack)0.050s (-4.8%)1.006s (~)0.956s101.44x
🐘 PostgresNext.js (Turbopack)0.061s (+9.8% 🔺)1.012s (~)0.951s101.74x
🐘 PostgresExpress0.063s (-9.9% 🟢)1.012s (~)0.949s101.80x
🐘 PostgresNitro0.071s (+9.2% 🔺)1.012s (~)0.940s102.05x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express0.332s (+43.7% 🔺)2.094s (~)1.762s101.00x
▲ VercelNitro0.343s (+36.1% 🔺)2.314s (+6.6% 🔺)1.971s101.03x
▲ VercelNext.js (Turbopack)0.931s (+244.5% 🔺)3.051s (+49.2% 🔺)2.120s102.80x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.062s (-2.2%)2.005s (~)0.943s101.00x
💻 LocalNext.js (Turbopack)1.084s (~)2.007s (~)0.923s101.02x
💻 LocalExpress1.084s (~)2.006s (~)0.922s101.02x
🐘 PostgresNitro1.088s (-1.3%)2.010s (~)0.922s101.02x
🐘 PostgresNext.js (Turbopack)1.103s (+1.8%)2.010s (~)0.907s101.04x
🐘 PostgresExpress1.104s (+1.1%)2.009s (~)0.905s101.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.499s (+6.2% 🔺)3.381s (+6.1% 🔺)1.881s101.00x
▲ VercelExpress1.584s (+14.5% 🔺)3.601s (+27.2% 🔺)2.017s101.06x
▲ VercelNext.js (Turbopack)2.692s (+87.7% 🔺)4.407s (+31.5% 🔺)1.715s101.80x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro10.335s (-1.2%)11.020s (~)0.685s31.00x
🐘 PostgresNitro10.443s (-0.6%)11.018s (~)0.575s31.01x
💻 LocalExpress10.449s (~)11.023s (~)0.574s31.01x
💻 LocalNext.js (Turbopack)10.523s (~)11.024s (~)0.501s31.02x
🐘 PostgresExpress10.529s (+0.8%)11.018s (~)0.489s31.02x
🐘 PostgresNext.js (Turbopack)10.549s (+0.9%)11.019s (~)0.470s31.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro13.474s (+15.0% 🔺)15.455s (+15.7% 🔺)1.981s31.00x
▲ VercelExpress13.947s (+13.3% 🔺)16.641s (+20.0% 🔺)2.694s21.04x
▲ VercelNext.js (Turbopack)14.162s (+20.0% 🔺)16.396s (+17.6% 🔺)2.234s21.05x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro13.443s (-1.4%)14.025s (~)0.582s51.00x
🐘 PostgresNitro13.632s (-0.6%)14.020s (~)0.388s51.01x
💻 LocalNext.js (Turbopack)13.637s (~)14.026s (~)0.389s51.01x
🐘 PostgresNext.js (Turbopack)13.650s (~)14.021s (~)0.371s51.02x
💻 LocalExpress13.656s (~)14.028s (~)0.372s51.02x
🐘 PostgresExpress13.824s (+1.8%)14.019s (~)0.194s51.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro20.368s (+24.4% 🔺)22.630s (+27.7% 🔺)2.262s31.00x
▲ VercelExpress20.918s (+27.2% 🔺)22.656s (+25.9% 🔺)1.738s31.03x
▲ VercelNext.js (Turbopack)23.709s (+36.7% 🔺)25.789s (+34.7% 🔺)2.079s31.16x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro11.628s (-4.5%)12.022s (-7.7% 🟢)0.394s81.00x
🐘 PostgresNitro12.094s (-1.5%)13.016s (~)0.922s71.04x
💻 LocalExpress12.171s (-1.4%)13.025s (~)0.853s71.05x
🐘 PostgresExpress12.282s (+1.5%)13.018s (~)0.736s71.06x
🐘 PostgresNext.js (Turbopack)12.323s (+1.8%)13.017s (~)0.694s71.06x
💻 LocalNext.js (Turbopack)12.389s (+1.1%)13.025s (~)0.636s71.07x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro22.441s (+18.2% 🔺)25.257s (+22.4% 🔺)2.816s41.00x
▲ VercelNext.js (Turbopack)24.710s (+27.7% 🔺)27.172s (+28.0% 🔺)2.462s41.10x
▲ VercelExpress24.776s (+34.8% 🔺)27.682s (+37.3% 🔺)2.906s41.10x

🔍 Observability: Nitro | Next.js (Turbopack) | Express

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.181s (~)2.008s (~)0.828s151.00x
🐘 PostgresNext.js (Turbopack)1.198s (+1.8%)2.008s (~)0.810s151.01x
🐘 PostgresExpress1.242s (+6.3% 🔺)2.009s (~)0.768s151.05x
💻 LocalNitro1.300s (-9.6% 🟢)2.006s (~)0.706s151.10x
💻 LocalExpress1.421s (+2.3%)2.007s (~)0.585s151.20x
💻 LocalNext.js (Turbopack)1.466s (-2.1%)2.007s (~)0.541s151.24x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.247s (+5.6% 🔺)4.214s (+22.4% 🔺)1.966s81.00x
▲ VercelExpress2.843s (+40.1% 🔺)4.746s (+35.6% 🔺)1.904s71.26x
▲ VercelNext.js (Turbopack)5.555s (+158.4% 🔺)7.589s (+100.3% 🔺)2.034s42.47x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.295s (-7.0% 🟢)2.736s (+2.3%)1.441s111.00x
🐘 PostgresNitro1.318s (-1.6%)2.591s (+3.3%)1.273s121.02x
🐘 PostgresNext.js (Turbopack)1.379s (+5.0% 🔺)3.011s (+3.2%)1.632s101.06x
💻 LocalNitro2.078s (-13.7% 🟢)2.826s (-3.2%)0.748s111.60x
💻 LocalExpress2.416s (+2.5%)3.009s (+3.1%)0.593s101.87x
💻 LocalNext.js (Turbopack)2.632s (-3.8%)3.010s (-3.2%)0.377s102.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.352s (+8.9% 🔺)5.327s (+2.8%)1.976s61.00x
▲ VercelExpress3.378s (-7.0% 🟢)5.318s (+3.0%)1.940s61.01x
▲ VercelNext.js (Turbopack)4.672s (+39.8% 🔺)6.725s (+33.3% 🔺)2.053s51.39x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.610s (-1.9%)4.137s (-3.0%)2.527s81.00x
🐘 PostgresExpress1.619s (+2.1%)4.012s (-6.7% 🟢)2.393s81.01x
🐘 PostgresNext.js (Turbopack)3.104s (+10.3% 🔺)6.015s (+9.0% 🔺)2.911s51.93x
💻 LocalNitro4.859s (-24.6% 🟢)5.347s (-25.9% 🟢)0.488s63.02x
💻 LocalNext.js (Turbopack)6.248s (+14.2% 🔺)7.518s (+25.0% 🔺)1.270s43.88x
💻 LocalExpress6.275s (-0.8%)6.614s (-5.8% 🟢)0.338s53.90x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express4.354s (+42.1% 🔺)6.876s (+36.3% 🔺)2.522s51.00x
▲ VercelNitro4.370s (+30.3% 🔺)6.765s (+32.1% 🔺)2.395s51.00x
▲ VercelNext.js (Turbopack)4.926s (+41.5% 🔺)7.542s (+45.0% 🔺)2.616s41.13x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.174s (-2.7%)2.007s (~)0.833s151.00x
🐘 PostgresNext.js (Turbopack)1.199s (+2.5%)2.008s (~)0.809s151.02x
🐘 PostgresExpress1.243s (+5.5% 🔺)2.074s (+3.3%)0.831s151.06x
💻 LocalNitro1.344s (-5.5% 🟢)2.006s (~)0.662s151.14x
💻 LocalExpress1.428s (-0.6%)2.007s (~)0.579s151.22x
💻 LocalNext.js (Turbopack)1.495s (+6.6% 🔺)2.073s (+3.3%)0.578s151.27x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.542s (+23.2% 🔺)4.657s (+33.1% 🔺)2.115s71.00x
▲ VercelNitro2.571s (+19.9% 🔺)4.516s (+21.5% 🔺)1.945s71.01x
▲ VercelNext.js (Turbopack)4.063s (-29.8% 🟢)6.178s (-17.0% 🟢)2.115s51.60x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.290s (-6.0% 🟢)2.075s (-16.1% 🟢)0.785s151.00x
🐘 PostgresExpress1.341s (~)2.393s (-3.2%)1.052s131.04x
🐘 PostgresNext.js (Turbopack)1.361s (+5.5% 🔺)3.010s (+3.2%)1.650s101.05x
💻 LocalNitro2.207s (-14.5% 🟢)2.917s (-6.2% 🟢)0.710s111.71x
💻 LocalExpress2.483s (+2.5%)2.919s (-6.1% 🟢)0.435s111.92x
💻 LocalNext.js (Turbopack)2.866s (+8.7% 🔺)3.209s (+6.7% 🔺)0.343s102.22x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.557s (-46.6% 🟢)4.443s (-33.1% 🟢)1.886s71.00x
▲ VercelNitro2.768s (+1.9%)4.662s (+8.0% 🔺)1.894s71.08x
▲ VercelNext.js (Turbopack)4.820s (+1.6%)7.047s (+1.4%)2.226s51.88x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.612s (-2.9%)4.012s (~)2.400s81.00x
🐘 PostgresExpress1.616s (+1.9%)4.012s (-3.0%)2.396s81.00x
🐘 PostgresNext.js (Turbopack)2.989s (+4.6%)6.013s (+2.8%)3.024s51.85x
💻 LocalNitro5.245s (-22.7% 🟢)5.680s (-21.3% 🟢)0.436s63.25x
💻 LocalNext.js (Turbopack)5.971s (+4.4%)6.614s (+9.9% 🔺)0.642s53.71x
💻 LocalExpress8.009s (+19.5% 🔺)8.518s (+18.0% 🔺)0.509s44.97x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.839s (-18.1% 🟢)6.037s (-4.1%)2.198s51.00x
▲ VercelNext.js (Turbopack)4.185s (+34.0% 🔺)6.365s (+23.3% 🔺)2.180s51.09x
▲ VercelNitro4.362s (+26.2% 🔺)6.526s (+29.7% 🔺)2.165s51.14x

🔍 Observability: Express | Next.js (Turbopack) | Nitro

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.508s (-13.3% 🟢)1.004s (~)0.496s601.00x
🐘 PostgresNitro0.518s (-12.9% 🟢)1.006s (-3.4%)0.488s601.02x
🐘 PostgresExpress0.565s (+5.9% 🔺)1.023s (~)0.458s591.11x
💻 LocalExpress0.596s (+4.0%)1.022s (+1.7%)0.426s591.17x
🐘 PostgresNext.js (Turbopack)0.608s (+18.9% 🔺)1.024s (+1.9%)0.417s591.20x
💻 LocalNext.js (Turbopack)0.608s (+0.8%)1.022s (~)0.414s591.20x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.261s (+16.5% 🔺)5.059s (+12.8% 🔺)1.799s121.00x
▲ VercelNitro3.490s (+31.9% 🔺)5.594s (+30.1% 🔺)2.104s111.07x
▲ VercelNext.js (Turbopack)4.224s (+39.0% 🔺)6.144s (+23.0% 🔺)1.920s101.30x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.240s (-12.3% 🟢)2.030s (-1.2%)0.789s451.00x
💻 LocalNitro1.256s (-15.3% 🟢)2.005s (~)0.749s451.01x
🐘 PostgresExpress1.331s (+6.4% 🔺)2.029s (+1.1%)0.699s451.07x
🐘 PostgresNext.js (Turbopack)1.389s (+11.6% 🔺)2.008s (-1.1%)0.619s451.12x
💻 LocalExpress1.472s (+1.2%)2.006s (~)0.534s451.19x
💻 LocalNext.js (Turbopack)1.532s (+4.4%)2.006s (~)0.474s451.24x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express7.933s (+22.7% 🔺)9.951s (+23.8% 🔺)2.018s101.00x
▲ VercelNitro8.413s (+31.5% 🔺)10.388s (+33.8% 🔺)1.975s91.06x
▲ VercelNext.js (Turbopack)10.501s (+47.0% 🔺)12.769s (+43.6% 🔺)2.268s81.32x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.512s (-7.5% 🟢)3.086s (+0.9%)0.574s391.00x
💻 LocalNitro2.760s (-14.3% 🟢)3.165s (-21.1% 🟢)0.405s381.10x
🐘 PostgresExpress2.763s (+6.5% 🔺)3.167s (+2.6%)0.404s381.10x
🐘 PostgresNext.js (Turbopack)2.793s (+12.8% 🔺)3.009s (-1.6%)0.216s401.11x
💻 LocalExpress3.242s (~)4.010s (+0.8%)0.768s301.29x
💻 LocalNext.js (Turbopack)3.305s (+1.4%)4.043s (~)0.738s301.32x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express13.918s (+1.0%)17.102s (+10.6% 🔺)3.184s81.00x
▲ VercelNitro14.974s (+6.6% 🔺)17.975s (+15.0% 🔺)3.000s71.08x
▲ VercelNext.js (Turbopack)20.700s (+26.1% 🔺)23.054s (+24.7% 🔺)2.354s61.49x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.212s (~)1.006s (~)0.794s601.00x
🐘 PostgresNext.js (Turbopack)0.218s (+30.2% 🔺)1.007s (~)0.788s601.03x
🐘 PostgresNitro0.223s (-1.8%)1.006s (~)0.783s601.05x
💻 LocalNitro0.399s (-14.8% 🟢)1.021s (+1.6%)0.622s591.89x
💻 LocalExpress0.504s (+16.8% 🔺)1.005s (~)0.501s602.38x
💻 LocalNext.js (Turbopack)0.644s (+10.1% 🔺)1.022s (+1.7%)0.378s593.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.221s (-43.8% 🟢)3.077s (-20.6% 🟢)1.857s201.00x
▲ VercelExpress1.345s (-35.1% 🟢)3.022s (-21.3% 🟢)1.677s201.10x
▲ VercelNext.js (Turbopack)2.809s (+54.4% 🔺)4.638s (+30.7% 🔺)1.830s132.30x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.308s (+15.0% 🔺)1.041s (+3.5%)0.733s871.00x
🐘 PostgresNitro0.319s (-5.4% 🟢)1.006s (~)0.687s901.03x
🐘 PostgresExpress0.326s (+1.0%)1.007s (-1.1%)0.680s901.06x
💻 LocalNitro1.815s (-14.9% 🟢)2.285s (-14.9% 🟢)0.470s405.89x
💻 LocalExpress2.221s (+2.8%)2.797s (+2.2%)0.576s337.21x
💻 LocalNext.js (Turbopack)2.826s (+1.3%)3.295s (+1.1%)0.469s289.17x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.920s (-22.0% 🟢)3.816s (-5.9% 🟢)1.896s241.00x
▲ VercelNitro1.964s (+3.0%)3.918s (+12.3% 🔺)1.954s231.02x
▲ VercelNext.js (Turbopack)3.338s (+53.8% 🔺)5.492s (+39.8% 🔺)2.155s171.74x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.507s (-1.7%)1.068s (~)0.561s1131.00x
🐘 PostgresNitro0.513s (-2.9%)1.068s (+3.5%)0.555s1141.01x
🐘 PostgresNext.js (Turbopack)0.580s (+23.0% 🔺)3.254s (+8.1% 🔺)2.674s371.14x
💻 LocalNitro8.193s (-17.4% 🟢)9.094s (-17.5% 🟢)0.901s1416.15x
💻 LocalExpress9.881s (-2.0%)11.117s (~)1.235s1119.48x
💻 LocalNext.js (Turbopack)10.366s (+6.0% 🔺)11.576s (+7.4% 🔺)1.210s1120.44x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.428s (+34.8% 🔺)5.645s (+36.8% 🔺)2.217s221.00x
▲ VercelExpress3.617s (+51.3% 🔺)6.034s (+48.8% 🔺)2.417s201.06x
▲ VercelNext.js (Turbopack)4.887s (+4.3%)7.315s (+11.8% 🔺)2.428s171.43x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.128s (-2.6%)2.004s (~)0.008s (-33.9% 🟢)2.015s (~)0.887s101.00x
💻 LocalNext.js (Turbopack)1.145s (-2.1%)1.967s (~)0.012s (-2.4%)2.019s (~)0.875s101.01x
🐘 PostgresExpress1.159s (+0.7%)1.996s (~)0.001s (+10.0% 🔺)2.010s (~)0.852s101.03x
🐘 PostgresNitro1.159s (-0.7%)1.999s (~)0.001s (+9.1% 🔺)2.011s (~)0.852s101.03x
💻 LocalExpress1.160s (+0.7%)2.005s (~)0.013s (+5.8% 🔺)2.020s (~)0.860s101.03x
🐘 PostgresNext.js (Turbopack)1.165s (+1.7%)2.001s (~)0.001s (+18.2% 🔺)2.011s (~)0.846s101.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.120s (+3.6%)3.629s (+18.3% 🔺)2.424s (+38.4% 🔺)6.604s (+22.2% 🔺)4.484s101.00x
▲ VercelNitro2.340s (+5.5% 🔺)3.692s (+12.7% 🔺)2.806s (+50.6% 🔺)7.080s (+24.4% 🔺)4.740s101.10x
▲ VercelNext.js (Turbopack)3.746s (+81.4% 🔺)4.139s (+20.0% 🔺)2.700s (+91.6% 🔺)8.319s (+53.9% 🔺)4.573s101.77x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.426s (-8.2% 🟢)2.006s (~)0.011s (-7.8% 🟢)2.020s (~)0.594s301.00x
🐘 PostgresNitro1.544s (-3.8%)1.999s (~)0.005s (+6.6% 🔺)2.029s (~)0.485s301.08x
🐘 PostgresExpress1.551s (~)2.001s (~)0.005s (-10.2% 🟢)2.024s (~)0.473s301.09x
💻 LocalExpress1.556s (+0.5%)2.011s (~)0.012s (-4.1%)2.027s (~)0.471s301.09x
💻 LocalNext.js (Turbopack)1.608s (+1.9%)2.000s (+1.6%)0.013s (-3.1%)2.058s (+1.6%)0.450s301.13x
🐘 PostgresNext.js (Turbopack)1.675s (+8.7% 🔺)2.010s (~)0.005s (+0.6%)2.027s (~)0.352s301.17x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro7.027s (+15.7% 🔺)8.939s (+26.3% 🔺)0.304s (+34.3% 🔺)9.893s (+26.4% 🔺)2.866s71.00x
▲ VercelExpress7.058s (+19.9% 🔺)8.708s (+20.8% 🔺)0.283s (+31.1% 🔺)9.595s (+21.7% 🔺)2.537s71.00x
▲ VercelNext.js (Turbopack)10.646s (+79.2% 🔺)12.028s (+63.2% 🔺)0.377s (+18.4% 🔺)13.403s (+62.4% 🔺)2.757s51.52x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.781s (+0.9%)1.062s (+1.3%)0.000s (+Infinity% 🔺)1.079s (+1.4%)0.298s561.00x
🐘 PostgresNitro0.799s (-2.8%)1.044s (-5.0% 🟢)0.000s (-100.0% 🟢)1.059s (-5.3% 🟢)0.260s581.02x
💻 LocalNitro1.004s (-23.9% 🟢)1.475s (-25.5% 🟢)0.000s (-30.7% 🟢)1.478s (-25.5% 🟢)0.473s411.29x
🐘 PostgresNext.js (Turbopack)1.041s (+13.6% 🔺)1.538s (+20.5% 🔺)0.000s (+141.0% 🔺)1.550s (+20.8% 🔺)0.509s391.33x
💻 LocalExpress1.256s (-0.9%)2.013s (~)0.000s (+50.0% 🔺)2.016s (~)0.760s301.61x
💻 LocalNext.js (Turbopack)1.375s (+3.0%)1.979s (~)0.001s (+283.3% 🔺)2.016s (~)0.642s301.76x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.783s (+21.3% 🔺)5.122s (+16.1% 🔺)0.000s (NaN%)5.698s (+16.8% 🔺)1.915s111.00x
▲ VercelExpress3.945s (+1.0%)5.443s (+8.3% 🔺)0.000s (-100.0% 🟢)5.941s (+7.3% 🔺)1.996s111.04x
▲ VercelNext.js (Turbopack)5.317s (+61.5% 🔺)6.785s (+44.3% 🔺)0.000s (+50.0% 🔺)7.685s (+46.3% 🔺)2.368s81.41x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.685s (-12.1% 🟢)2.301s (-7.8% 🟢)0.000s (+84.6% 🔺)2.344s (-7.0% 🟢)0.658s261.00x
🐘 PostgresExpress1.748s (+3.1%)2.345s (+4.0%)0.000s (-100.0% 🟢)2.366s (+4.2%)0.618s261.04x
💻 LocalNitro2.653s (-25.6% 🟢)3.073s (-23.7% 🟢)0.000s (-64.7% 🟢)3.081s (-23.6% 🟢)0.427s201.57x
🐘 PostgresNext.js (Turbopack)2.662s (+31.0% 🔺)3.336s (+27.8% 🔺)0.000s (-100.0% 🟢)3.345s (+26.6% 🔺)0.683s181.58x
💻 LocalExpress3.715s (+2.2%)4.313s (+2.0%)0.001s (+7.1% 🔺)4.319s (+2.1%)0.604s142.20x
💻 LocalNext.js (Turbopack)3.837s (+4.7%)4.277s (+2.0%)0.001s (-19.6% 🟢)4.319s (+2.0%)0.482s142.28x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.405s (+14.2% 🔺)7.099s (+23.6% 🔺)0.002s (+1400.0% 🔺)7.612s (+21.7% 🔺)2.207s81.00x
▲ VercelNitro5.848s (+19.5% 🔺)7.481s (+30.8% 🔺)0.000s (+Infinity% 🔺)8.026s (+29.8% 🔺)2.178s81.08x
▲ VercelNext.js (Turbopack)7.707s (+66.4% 🔺)9.533s (+58.4% 🔺)0.000s (+66.7% 🔺)10.735s (+63.2% 🔺)3.027s61.43x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNitro21/21
🐘 PostgresNitro14/21
▲ VercelExpress11/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres16/21
Next.js (Turbopack)🐘 Postgres15/21
Nitro🐘 Postgres13/21
Column Definitions
  • Workflow Time: Runtime reported by workflow (completedAt - createdAt) - primary metric
  • TTFB: Time to First Byte - time from workflow start until first stream byte received (stream benchmarks only)
  • Slurp: Time from first byte to complete stream consumption (stream benchmarks only)
  • Wall Time: Total testbench time (trigger workflow + poll for result)
  • Overhead: Testbench overhead (Wall Time - Workflow Time)
  • Samples: Number of benchmark iterations run
  • vs Fastest: How much slower compared to the fastest configuration for this benchmark

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Redis + BullMQ: Community world (local development)
  • 🌐 Cloudflare: Community world (local development)
  • 🌐 MySQL: Community world (local development)
  • 🌐 Azure: Community world (local development)
  • 🌐 NATS JetStream: Community world (local development)
  • 🌐 Upstash: Community world (local development)
  • 🌐 Platformatic: Community world (local development)

📋 View full workflow run

@github-actions

github-actionsBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production144202301672
✅ 💻 Local Development160502191824
✅ 📦 Local Production160502191824
✅ 🐘 Local Postgres159302311824
✅ 🪟 Windows15200152
✅ 📋 Other88501791064
Total7282010788360

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro125027
✅ example125027
✅ express125027
✅ fastify125027
✅ hono125027
✅ nextjs-turbopack14903
✅ nextjs-webpack14903
✅ nitro125027
✅ nuxt125027
✅ sveltekit14408
✅ vite125027
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable126026
✅ express-stable126026
✅ fastify-stable126026
✅ hono-stable126026
✅ nextjs-turbopack-canary132020
✅ nextjs-turbopack-stable15101
✅ nextjs-webpack-canary132020
✅ nextjs-webpack-stable15101
✅ nitro-stable126026
✅ nuxt-stable126026
✅ sveltekit-stable14507
✅ vite-stable126026
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack15200
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable127025
✅ e2e-local-dev-tanstack-start-127025
✅ e2e-local-postgres-nest-stable126026
✅ e2e-local-postgres-tanstack-start-126026
✅ e2e-local-prod-nest-stable127025
✅ e2e-local-prod-tanstack-start-127025
✅ e2e-vercel-prod-tanstack-start125027

📋 View full workflow run

@vercel

vercelBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

@VaguelySerious
VaguelySerious marked this pull request as ready for review June 25, 2026 22:55
@VaguelySerious
VaguelySerious requested review from a team and ijjk as code ownersJune 25, 2026 22:55

@karthikscale3karthikscale3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving — reviewed against the merged OTEL trace-correlation work (linked-mode #2363, route/execution cohesion #2527, v4 injection #2533). No regressions; this is a net improvement.

What I verified:

  • Injection is centralized + default-on: injectTraceContext = true in instrumentedFetch, inject runs inside the http <method> CLIENT span, and no caller passes false — so every path injects.
  • No bypass remains: the only raw fetch( is inside instrumentedFetch; all call sites (v4 events, makeRequest, streamer, both Vercel-API callers) route through it. Stronger guarantee than before.
  • v4 is fixed, not broken: #2533's inline injection moves into the core and v4 now gets a real client span (it had none), so workflow-server parents to it like v3. Stream + Vercel-API paths gain propagation.
  • Span name + parenting preserved: still http <method> / CLIENT with PeerService/RpcService → service-map edges and name-based queries intact; workflow-server still parents into the invocation trace (parent/child, not links).
  • Cohesion/linked mode untouched (only world-vercel/* changed; runtime.ts/step-handler.ts/telemetry.ts are not in the diff).
  • Tests strengthened: trace-propagation.test.ts asserts the v4 request's traceparent matches its client span and adds a stream-write propagation test.

One thing to confirm (see inline comment) + a doc follow-up.

// Explicitly propagate trace context so the receiving server can parent
// its spans to this client span — the custom undici dispatcher bypasses
// ambient auto-instrumentation. No-ops when no OTEL SDK is registered.
if (injectTraceContext) await injectTraceContextIntoHeaders(headers);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since instrumentedFetch is now the single trace-context injection chokepoint, please update the AGENTS.md guardrail (Architecture Notes → "Trace context propagation (world-vercel HTTP requests)") before merge: it currently tells contributors to call injectTraceContextIntoHeaders directly, but the rule should now be "route new request paths through instrumentedFetch (injects W3C context by default)". That keeps the guidance pointing at this chokepoint so future paths inherit propagation automatically.

Also worth a conscious decision: the peerService: 'vercel-api' callers default to injectTraceContext: true, so they now send traceparentand baggage (incl. workflow.run_id/name) to vercel.com. Likely fine since it's first-party, but if that propagation isn't intended, pass injectTraceContext: false for those callers.

@VaguelySerious
VaguelySerious merged commit 1dcdafd into mainJun 28, 2026
179 of 183 checks passed
@VaguelySerious
VaguelySerious deleted the peter/world-vercel-http-cleanup branch June 28, 2026 21:59
@github-actionsgithub-actionsBot mentioned this pull request Jun 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for 1dcdafd (AI decision).

This PR is an explicit follow-up to #2573 and the unified http-core.ts it introduces depends on main-only infrastructure: the events/stream dispatcher functions (getEventsDispatcher, getStreamDispatcher, EVENTS_AGENT_OPTIONS, STREAM_RETRY_OPTIONS) and the fetch-based v4 path with trace-context injection do not exist on stable (verified — stable's events-v4.ts still uses undici.request() directly and streamer.ts uses plain fetch() without dispatchers). Backporting would pull in code referencing dispatchers absent on stable, so it builds on main-only behavior.

To override, re-run the Backport to stable workflow manually via workflow_dispatch and paste this commit SHA into the ref input:

1dcdafd422c870ec1b7dcbba8b0e733c1b6bbb14

VaguelySerious added a commit that referenced this pull request Jul 31, 2026
Backport of #2631, adapted to stable. The v3 makeRequest path, the v4
events client, the stream write/close path and the direct Vercel API
calls each had their own request envelope; the 412 -> PreconditionFailed
and firewall-challenge-429 -> retryable TRANSPORT mappings were
duplicated between utils.ts and events-v4.ts.
http-core.ts now owns that envelope: the OTEL CLIENT span and its
attributes, W3C trace-context injection, the X-Request-Time cache bust,
the per-request deadline (mapped to a typed retryable WorkflowWorldError
instead of an opaque AbortError), DEBUG logging with a curl repro, and
the status -> typed-error mapping. utils.ts, events-v4.ts, streamer.ts,
encryption.ts and resolve-latest-deployment.ts delegate to it.
Behavioral gains on stable: the v4 events path and stream writes now
emit client spans and propagate traceparent, so the backend can parent
its spans to the caller without relying on the app having undici
auto-instrumentation.
Adapted rather than cherry-picked: stable keeps #3169's bounded
headers/body timeouts (documented as firing ahead of the outer
REQUEST_TIMEOUT_MS backstop), its body-parse retry loop, and #3038's
write-vs-close stream dispatcher split. Only injectTraceContextIntoHeaders
is taken from #2363, not its WORKFLOW_TRACE_MODE machinery.
Routing v4 through the global fetch makes the H2 interceptor's
re-buffering gate live on this path (fetch streamifies request bodies),
so the new http-client test drives createWorkflowRunEventV4 concurrently
against a local H2 server: 16 writes on one session with the
interceptor, 8 without it.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@VaguelySerious@karthikscale3
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

[world-vercel] Unify HTTP request handling into a shared core - #2631

Merged
VaguelySerious merged 1 commit into
mainfrom
peter/world-vercel-http-cleanup
Jun 28, 2026
Merged

[world-vercel] Unify HTTP request handling into a shared core#2631
VaguelySerious merged 1 commit into
mainfrom
peter/world-vercel-http-cleanup

Conversation

@VaguelySerious

Copy link
Copy Markdown
Member

What

Follow-up to #2573. world-vercel had five separate HTTP request paths, each re-implementing the same cross-cutting concerns inconsistently. This collapses the duplication into one shared core (http-core.ts) and routes every path through it, so the v4 / stream / Vercel-API paths gain the OTEL client spans + DEBUG logging the v3 path already had.

Before

PathOTEL spanDEBUG logcurl-reprodiag headerstyped errorstrace-ctx
v3 makeRequest
v4 events(2nd copy)
stream write/close(2nd copy)
stream get/list
Vercel API (run-key, resolve-deploy)(plain Error)

The v4 events path — now the hottest path — had no client span and no debug logging.

How

New http-core.ts is the single source of truth for the request envelope:

  • errorForResponse — the status → typed-error map (409→EntityConflictError, 410→RunExpiredError, 425→TooEarlyError, 429→ThrottleError, else→WorkflowWorldError). Was implemented twice (v3 makeRequest + v4 throwForErrorResponse).
  • getVercelDiagnostics / parseRetryAfter / httpLog / logCurlRepro — one copy each (were duplicated across utils.ts and streamer.ts).
  • resolveVercelApiToken — shared token → VERCEL_TOKEN → OIDC precedence (was byte-identical in encryption.ts and resolve-latest-deployment.ts).
  • instrumentedFetch — shared OTEL span + trace-context injection + cache-bust header + timeout + DEBUG logging + error envelope. The v4 events, stream, and Vercel-API paths now route through it.

Also: stream writes now inject W3C trace context (previously missing, contrary to the documented requirement that every world→server request must), and the three dispatcher singletons in http-client.ts collapse behind one makeRetryDispatcher factory.

Behavior preserved

No behavior change beyond the added observability:

  • Per-path error-message strings are unchanged (the v3/v4/stream message formats feed log tooling); only the type mapping, Retry-After parsing, and diagnostics are unified.
  • The non-idempotent stream retry policy (transient + 429 only, never 5xx) is untouched.
  • The no-timeout semantics of the v4 (streamed LIST), stream (large buffered writes / long-lived live read), and Vercel-API paths are preserved (timeoutMs: null); only the v3 path keeps its 60s timeout.

Tests

  • New http-core.test.ts (error mapping per status, parseRetryAfter, diagnostics, token precedence).
  • Extended trace-propagation.test.ts: v4 now parents to its own http GET client span (matching v3), and a new case asserts stream writes inject traceparent.
  • Full world-vercel suite green (187 tests); build + typecheck + lint clean.

Changeset

@workflow/world-vercel patch — internal transport refactor + added observability, no API surface change.

Collapse the duplicated cross-cutting request logic across world-vercel's
request paths into one shared module (http-core.ts):
- errorForResponse: single status -> typed-error map (409/410/425/429/else),
shared by the v3 makeRequest and v4 throwForErrorResponse paths.
- getVercelDiagnostics / parseRetryAfter / httpLog / logCurlRepro: one copy
each (were duplicated across utils.ts and streamer.ts).
- resolveVercelApiToken: shared token precedence for the direct Vercel-API
calls (was byte-identical in encryption.ts and resolve-latest-deployment.ts).
- instrumentedFetch: the shared OTEL-span + trace-injection + cache-bust +
timeout + DEBUG-logging + error envelope.
Route the v4 events, stream write/read, and Vercel-API (run-key /
resolve-latest-deployment) paths through instrumentedFetch so they gain the
OTEL client spans and DEBUG logging the v3 path already had. Stream writes now
also inject W3C trace context (previously missing). Collapse the three
dispatcher singletons in http-client.ts behind a single makeRetryDispatcher
factory.
No behavior change beyond the added observability: per-path error-message
formats, the non-idempotent stream retry policy, and the no-timeout semantics
of the v4/stream/Vercel-API paths are preserved.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9263886

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 17 packages
NameType
@workflow/world-vercelPatch
@workflow/cliPatch
@workflow/corePatch
@workflow/webPatch
workflowPatch
@workflow/world-testingPatch
@workflow/buildersPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch
@workflow/nuxtPatch

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

@github-actions

github-actionsBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.035s (-26.0% 🟢)1.005s (~)0.970s101.00x
💻 LocalExpress0.048s (+1.7%)1.006s (~)0.958s101.39x
💻 LocalNext.js (Turbopack)0.050s (-4.8%)1.006s (~)0.956s101.44x
🐘 PostgresNext.js (Turbopack)0.061s (+9.8% 🔺)1.012s (~)0.951s101.74x
🐘 PostgresExpress0.063s (-9.9% 🟢)1.012s (~)0.949s101.80x
🐘 PostgresNitro0.071s (+9.2% 🔺)1.012s (~)0.940s102.05x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express0.332s (+43.7% 🔺)2.094s (~)1.762s101.00x
▲ VercelNitro0.343s (+36.1% 🔺)2.314s (+6.6% 🔺)1.971s101.03x
▲ VercelNext.js (Turbopack)0.931s (+244.5% 🔺)3.051s (+49.2% 🔺)2.120s102.80x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.062s (-2.2%)2.005s (~)0.943s101.00x
💻 LocalNext.js (Turbopack)1.084s (~)2.007s (~)0.923s101.02x
💻 LocalExpress1.084s (~)2.006s (~)0.922s101.02x
🐘 PostgresNitro1.088s (-1.3%)2.010s (~)0.922s101.02x
🐘 PostgresNext.js (Turbopack)1.103s (+1.8%)2.010s (~)0.907s101.04x
🐘 PostgresExpress1.104s (+1.1%)2.009s (~)0.905s101.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.499s (+6.2% 🔺)3.381s (+6.1% 🔺)1.881s101.00x
▲ VercelExpress1.584s (+14.5% 🔺)3.601s (+27.2% 🔺)2.017s101.06x
▲ VercelNext.js (Turbopack)2.692s (+87.7% 🔺)4.407s (+31.5% 🔺)1.715s101.80x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro10.335s (-1.2%)11.020s (~)0.685s31.00x
🐘 PostgresNitro10.443s (-0.6%)11.018s (~)0.575s31.01x
💻 LocalExpress10.449s (~)11.023s (~)0.574s31.01x
💻 LocalNext.js (Turbopack)10.523s (~)11.024s (~)0.501s31.02x
🐘 PostgresExpress10.529s (+0.8%)11.018s (~)0.489s31.02x
🐘 PostgresNext.js (Turbopack)10.549s (+0.9%)11.019s (~)0.470s31.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro13.474s (+15.0% 🔺)15.455s (+15.7% 🔺)1.981s31.00x
▲ VercelExpress13.947s (+13.3% 🔺)16.641s (+20.0% 🔺)2.694s21.04x
▲ VercelNext.js (Turbopack)14.162s (+20.0% 🔺)16.396s (+17.6% 🔺)2.234s21.05x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro13.443s (-1.4%)14.025s (~)0.582s51.00x
🐘 PostgresNitro13.632s (-0.6%)14.020s (~)0.388s51.01x
💻 LocalNext.js (Turbopack)13.637s (~)14.026s (~)0.389s51.01x
🐘 PostgresNext.js (Turbopack)13.650s (~)14.021s (~)0.371s51.02x
💻 LocalExpress13.656s (~)14.028s (~)0.372s51.02x
🐘 PostgresExpress13.824s (+1.8%)14.019s (~)0.194s51.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro20.368s (+24.4% 🔺)22.630s (+27.7% 🔺)2.262s31.00x
▲ VercelExpress20.918s (+27.2% 🔺)22.656s (+25.9% 🔺)1.738s31.03x
▲ VercelNext.js (Turbopack)23.709s (+36.7% 🔺)25.789s (+34.7% 🔺)2.079s31.16x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro11.628s (-4.5%)12.022s (-7.7% 🟢)0.394s81.00x
🐘 PostgresNitro12.094s (-1.5%)13.016s (~)0.922s71.04x
💻 LocalExpress12.171s (-1.4%)13.025s (~)0.853s71.05x
🐘 PostgresExpress12.282s (+1.5%)13.018s (~)0.736s71.06x
🐘 PostgresNext.js (Turbopack)12.323s (+1.8%)13.017s (~)0.694s71.06x
💻 LocalNext.js (Turbopack)12.389s (+1.1%)13.025s (~)0.636s71.07x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro22.441s (+18.2% 🔺)25.257s (+22.4% 🔺)2.816s41.00x
▲ VercelNext.js (Turbopack)24.710s (+27.7% 🔺)27.172s (+28.0% 🔺)2.462s41.10x
▲ VercelExpress24.776s (+34.8% 🔺)27.682s (+37.3% 🔺)2.906s41.10x

🔍 Observability: Nitro | Next.js (Turbopack) | Express

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.181s (~)2.008s (~)0.828s151.00x
🐘 PostgresNext.js (Turbopack)1.198s (+1.8%)2.008s (~)0.810s151.01x
🐘 PostgresExpress1.242s (+6.3% 🔺)2.009s (~)0.768s151.05x
💻 LocalNitro1.300s (-9.6% 🟢)2.006s (~)0.706s151.10x
💻 LocalExpress1.421s (+2.3%)2.007s (~)0.585s151.20x
💻 LocalNext.js (Turbopack)1.466s (-2.1%)2.007s (~)0.541s151.24x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.247s (+5.6% 🔺)4.214s (+22.4% 🔺)1.966s81.00x
▲ VercelExpress2.843s (+40.1% 🔺)4.746s (+35.6% 🔺)1.904s71.26x
▲ VercelNext.js (Turbopack)5.555s (+158.4% 🔺)7.589s (+100.3% 🔺)2.034s42.47x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.295s (-7.0% 🟢)2.736s (+2.3%)1.441s111.00x
🐘 PostgresNitro1.318s (-1.6%)2.591s (+3.3%)1.273s121.02x
🐘 PostgresNext.js (Turbopack)1.379s (+5.0% 🔺)3.011s (+3.2%)1.632s101.06x
💻 LocalNitro2.078s (-13.7% 🟢)2.826s (-3.2%)0.748s111.60x
💻 LocalExpress2.416s (+2.5%)3.009s (+3.1%)0.593s101.87x
💻 LocalNext.js (Turbopack)2.632s (-3.8%)3.010s (-3.2%)0.377s102.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.352s (+8.9% 🔺)5.327s (+2.8%)1.976s61.00x
▲ VercelExpress3.378s (-7.0% 🟢)5.318s (+3.0%)1.940s61.01x
▲ VercelNext.js (Turbopack)4.672s (+39.8% 🔺)6.725s (+33.3% 🔺)2.053s51.39x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.610s (-1.9%)4.137s (-3.0%)2.527s81.00x
🐘 PostgresExpress1.619s (+2.1%)4.012s (-6.7% 🟢)2.393s81.01x
🐘 PostgresNext.js (Turbopack)3.104s (+10.3% 🔺)6.015s (+9.0% 🔺)2.911s51.93x
💻 LocalNitro4.859s (-24.6% 🟢)5.347s (-25.9% 🟢)0.488s63.02x
💻 LocalNext.js (Turbopack)6.248s (+14.2% 🔺)7.518s (+25.0% 🔺)1.270s43.88x
💻 LocalExpress6.275s (-0.8%)6.614s (-5.8% 🟢)0.338s53.90x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express4.354s (+42.1% 🔺)6.876s (+36.3% 🔺)2.522s51.00x
▲ VercelNitro4.370s (+30.3% 🔺)6.765s (+32.1% 🔺)2.395s51.00x
▲ VercelNext.js (Turbopack)4.926s (+41.5% 🔺)7.542s (+45.0% 🔺)2.616s41.13x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.174s (-2.7%)2.007s (~)0.833s151.00x
🐘 PostgresNext.js (Turbopack)1.199s (+2.5%)2.008s (~)0.809s151.02x
🐘 PostgresExpress1.243s (+5.5% 🔺)2.074s (+3.3%)0.831s151.06x
💻 LocalNitro1.344s (-5.5% 🟢)2.006s (~)0.662s151.14x
💻 LocalExpress1.428s (-0.6%)2.007s (~)0.579s151.22x
💻 LocalNext.js (Turbopack)1.495s (+6.6% 🔺)2.073s (+3.3%)0.578s151.27x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.542s (+23.2% 🔺)4.657s (+33.1% 🔺)2.115s71.00x
▲ VercelNitro2.571s (+19.9% 🔺)4.516s (+21.5% 🔺)1.945s71.01x
▲ VercelNext.js (Turbopack)4.063s (-29.8% 🟢)6.178s (-17.0% 🟢)2.115s51.60x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.290s (-6.0% 🟢)2.075s (-16.1% 🟢)0.785s151.00x
🐘 PostgresExpress1.341s (~)2.393s (-3.2%)1.052s131.04x
🐘 PostgresNext.js (Turbopack)1.361s (+5.5% 🔺)3.010s (+3.2%)1.650s101.05x
💻 LocalNitro2.207s (-14.5% 🟢)2.917s (-6.2% 🟢)0.710s111.71x
💻 LocalExpress2.483s (+2.5%)2.919s (-6.1% 🟢)0.435s111.92x
💻 LocalNext.js (Turbopack)2.866s (+8.7% 🔺)3.209s (+6.7% 🔺)0.343s102.22x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.557s (-46.6% 🟢)4.443s (-33.1% 🟢)1.886s71.00x
▲ VercelNitro2.768s (+1.9%)4.662s (+8.0% 🔺)1.894s71.08x
▲ VercelNext.js (Turbopack)4.820s (+1.6%)7.047s (+1.4%)2.226s51.88x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.612s (-2.9%)4.012s (~)2.400s81.00x
🐘 PostgresExpress1.616s (+1.9%)4.012s (-3.0%)2.396s81.00x
🐘 PostgresNext.js (Turbopack)2.989s (+4.6%)6.013s (+2.8%)3.024s51.85x
💻 LocalNitro5.245s (-22.7% 🟢)5.680s (-21.3% 🟢)0.436s63.25x
💻 LocalNext.js (Turbopack)5.971s (+4.4%)6.614s (+9.9% 🔺)0.642s53.71x
💻 LocalExpress8.009s (+19.5% 🔺)8.518s (+18.0% 🔺)0.509s44.97x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.839s (-18.1% 🟢)6.037s (-4.1%)2.198s51.00x
▲ VercelNext.js (Turbopack)4.185s (+34.0% 🔺)6.365s (+23.3% 🔺)2.180s51.09x
▲ VercelNitro4.362s (+26.2% 🔺)6.526s (+29.7% 🔺)2.165s51.14x

🔍 Observability: Express | Next.js (Turbopack) | Nitro

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.508s (-13.3% 🟢)1.004s (~)0.496s601.00x
🐘 PostgresNitro0.518s (-12.9% 🟢)1.006s (-3.4%)0.488s601.02x
🐘 PostgresExpress0.565s (+5.9% 🔺)1.023s (~)0.458s591.11x
💻 LocalExpress0.596s (+4.0%)1.022s (+1.7%)0.426s591.17x
🐘 PostgresNext.js (Turbopack)0.608s (+18.9% 🔺)1.024s (+1.9%)0.417s591.20x
💻 LocalNext.js (Turbopack)0.608s (+0.8%)1.022s (~)0.414s591.20x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.261s (+16.5% 🔺)5.059s (+12.8% 🔺)1.799s121.00x
▲ VercelNitro3.490s (+31.9% 🔺)5.594s (+30.1% 🔺)2.104s111.07x
▲ VercelNext.js (Turbopack)4.224s (+39.0% 🔺)6.144s (+23.0% 🔺)1.920s101.30x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.240s (-12.3% 🟢)2.030s (-1.2%)0.789s451.00x
💻 LocalNitro1.256s (-15.3% 🟢)2.005s (~)0.749s451.01x
🐘 PostgresExpress1.331s (+6.4% 🔺)2.029s (+1.1%)0.699s451.07x
🐘 PostgresNext.js (Turbopack)1.389s (+11.6% 🔺)2.008s (-1.1%)0.619s451.12x
💻 LocalExpress1.472s (+1.2%)2.006s (~)0.534s451.19x
💻 LocalNext.js (Turbopack)1.532s (+4.4%)2.006s (~)0.474s451.24x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express7.933s (+22.7% 🔺)9.951s (+23.8% 🔺)2.018s101.00x
▲ VercelNitro8.413s (+31.5% 🔺)10.388s (+33.8% 🔺)1.975s91.06x
▲ VercelNext.js (Turbopack)10.501s (+47.0% 🔺)12.769s (+43.6% 🔺)2.268s81.32x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.512s (-7.5% 🟢)3.086s (+0.9%)0.574s391.00x
💻 LocalNitro2.760s (-14.3% 🟢)3.165s (-21.1% 🟢)0.405s381.10x
🐘 PostgresExpress2.763s (+6.5% 🔺)3.167s (+2.6%)0.404s381.10x
🐘 PostgresNext.js (Turbopack)2.793s (+12.8% 🔺)3.009s (-1.6%)0.216s401.11x
💻 LocalExpress3.242s (~)4.010s (+0.8%)0.768s301.29x
💻 LocalNext.js (Turbopack)3.305s (+1.4%)4.043s (~)0.738s301.32x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express13.918s (+1.0%)17.102s (+10.6% 🔺)3.184s81.00x
▲ VercelNitro14.974s (+6.6% 🔺)17.975s (+15.0% 🔺)3.000s71.08x
▲ VercelNext.js (Turbopack)20.700s (+26.1% 🔺)23.054s (+24.7% 🔺)2.354s61.49x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.212s (~)1.006s (~)0.794s601.00x
🐘 PostgresNext.js (Turbopack)0.218s (+30.2% 🔺)1.007s (~)0.788s601.03x
🐘 PostgresNitro0.223s (-1.8%)1.006s (~)0.783s601.05x
💻 LocalNitro0.399s (-14.8% 🟢)1.021s (+1.6%)0.622s591.89x
💻 LocalExpress0.504s (+16.8% 🔺)1.005s (~)0.501s602.38x
💻 LocalNext.js (Turbopack)0.644s (+10.1% 🔺)1.022s (+1.7%)0.378s593.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.221s (-43.8% 🟢)3.077s (-20.6% 🟢)1.857s201.00x
▲ VercelExpress1.345s (-35.1% 🟢)3.022s (-21.3% 🟢)1.677s201.10x
▲ VercelNext.js (Turbopack)2.809s (+54.4% 🔺)4.638s (+30.7% 🔺)1.830s132.30x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.308s (+15.0% 🔺)1.041s (+3.5%)0.733s871.00x
🐘 PostgresNitro0.319s (-5.4% 🟢)1.006s (~)0.687s901.03x
🐘 PostgresExpress0.326s (+1.0%)1.007s (-1.1%)0.680s901.06x
💻 LocalNitro1.815s (-14.9% 🟢)2.285s (-14.9% 🟢)0.470s405.89x
💻 LocalExpress2.221s (+2.8%)2.797s (+2.2%)0.576s337.21x
💻 LocalNext.js (Turbopack)2.826s (+1.3%)3.295s (+1.1%)0.469s289.17x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.920s (-22.0% 🟢)3.816s (-5.9% 🟢)1.896s241.00x
▲ VercelNitro1.964s (+3.0%)3.918s (+12.3% 🔺)1.954s231.02x
▲ VercelNext.js (Turbopack)3.338s (+53.8% 🔺)5.492s (+39.8% 🔺)2.155s171.74x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.507s (-1.7%)1.068s (~)0.561s1131.00x
🐘 PostgresNitro0.513s (-2.9%)1.068s (+3.5%)0.555s1141.01x
🐘 PostgresNext.js (Turbopack)0.580s (+23.0% 🔺)3.254s (+8.1% 🔺)2.674s371.14x
💻 LocalNitro8.193s (-17.4% 🟢)9.094s (-17.5% 🟢)0.901s1416.15x
💻 LocalExpress9.881s (-2.0%)11.117s (~)1.235s1119.48x
💻 LocalNext.js (Turbopack)10.366s (+6.0% 🔺)11.576s (+7.4% 🔺)1.210s1120.44x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.428s (+34.8% 🔺)5.645s (+36.8% 🔺)2.217s221.00x
▲ VercelExpress3.617s (+51.3% 🔺)6.034s (+48.8% 🔺)2.417s201.06x
▲ VercelNext.js (Turbopack)4.887s (+4.3%)7.315s (+11.8% 🔺)2.428s171.43x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.128s (-2.6%)2.004s (~)0.008s (-33.9% 🟢)2.015s (~)0.887s101.00x
💻 LocalNext.js (Turbopack)1.145s (-2.1%)1.967s (~)0.012s (-2.4%)2.019s (~)0.875s101.01x
🐘 PostgresExpress1.159s (+0.7%)1.996s (~)0.001s (+10.0% 🔺)2.010s (~)0.852s101.03x
🐘 PostgresNitro1.159s (-0.7%)1.999s (~)0.001s (+9.1% 🔺)2.011s (~)0.852s101.03x
💻 LocalExpress1.160s (+0.7%)2.005s (~)0.013s (+5.8% 🔺)2.020s (~)0.860s101.03x
🐘 PostgresNext.js (Turbopack)1.165s (+1.7%)2.001s (~)0.001s (+18.2% 🔺)2.011s (~)0.846s101.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.120s (+3.6%)3.629s (+18.3% 🔺)2.424s (+38.4% 🔺)6.604s (+22.2% 🔺)4.484s101.00x
▲ VercelNitro2.340s (+5.5% 🔺)3.692s (+12.7% 🔺)2.806s (+50.6% 🔺)7.080s (+24.4% 🔺)4.740s101.10x
▲ VercelNext.js (Turbopack)3.746s (+81.4% 🔺)4.139s (+20.0% 🔺)2.700s (+91.6% 🔺)8.319s (+53.9% 🔺)4.573s101.77x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.426s (-8.2% 🟢)2.006s (~)0.011s (-7.8% 🟢)2.020s (~)0.594s301.00x
🐘 PostgresNitro1.544s (-3.8%)1.999s (~)0.005s (+6.6% 🔺)2.029s (~)0.485s301.08x
🐘 PostgresExpress1.551s (~)2.001s (~)0.005s (-10.2% 🟢)2.024s (~)0.473s301.09x
💻 LocalExpress1.556s (+0.5%)2.011s (~)0.012s (-4.1%)2.027s (~)0.471s301.09x
💻 LocalNext.js (Turbopack)1.608s (+1.9%)2.000s (+1.6%)0.013s (-3.1%)2.058s (+1.6%)0.450s301.13x
🐘 PostgresNext.js (Turbopack)1.675s (+8.7% 🔺)2.010s (~)0.005s (+0.6%)2.027s (~)0.352s301.17x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro7.027s (+15.7% 🔺)8.939s (+26.3% 🔺)0.304s (+34.3% 🔺)9.893s (+26.4% 🔺)2.866s71.00x
▲ VercelExpress7.058s (+19.9% 🔺)8.708s (+20.8% 🔺)0.283s (+31.1% 🔺)9.595s (+21.7% 🔺)2.537s71.00x
▲ VercelNext.js (Turbopack)10.646s (+79.2% 🔺)12.028s (+63.2% 🔺)0.377s (+18.4% 🔺)13.403s (+62.4% 🔺)2.757s51.52x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.781s (+0.9%)1.062s (+1.3%)0.000s (+Infinity% 🔺)1.079s (+1.4%)0.298s561.00x
🐘 PostgresNitro0.799s (-2.8%)1.044s (-5.0% 🟢)0.000s (-100.0% 🟢)1.059s (-5.3% 🟢)0.260s581.02x
💻 LocalNitro1.004s (-23.9% 🟢)1.475s (-25.5% 🟢)0.000s (-30.7% 🟢)1.478s (-25.5% 🟢)0.473s411.29x
🐘 PostgresNext.js (Turbopack)1.041s (+13.6% 🔺)1.538s (+20.5% 🔺)0.000s (+141.0% 🔺)1.550s (+20.8% 🔺)0.509s391.33x
💻 LocalExpress1.256s (-0.9%)2.013s (~)0.000s (+50.0% 🔺)2.016s (~)0.760s301.61x
💻 LocalNext.js (Turbopack)1.375s (+3.0%)1.979s (~)0.001s (+283.3% 🔺)2.016s (~)0.642s301.76x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.783s (+21.3% 🔺)5.122s (+16.1% 🔺)0.000s (NaN%)5.698s (+16.8% 🔺)1.915s111.00x
▲ VercelExpress3.945s (+1.0%)5.443s (+8.3% 🔺)0.000s (-100.0% 🟢)5.941s (+7.3% 🔺)1.996s111.04x
▲ VercelNext.js (Turbopack)5.317s (+61.5% 🔺)6.785s (+44.3% 🔺)0.000s (+50.0% 🔺)7.685s (+46.3% 🔺)2.368s81.41x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.685s (-12.1% 🟢)2.301s (-7.8% 🟢)0.000s (+84.6% 🔺)2.344s (-7.0% 🟢)0.658s261.00x
🐘 PostgresExpress1.748s (+3.1%)2.345s (+4.0%)0.000s (-100.0% 🟢)2.366s (+4.2%)0.618s261.04x
💻 LocalNitro2.653s (-25.6% 🟢)3.073s (-23.7% 🟢)0.000s (-64.7% 🟢)3.081s (-23.6% 🟢)0.427s201.57x
🐘 PostgresNext.js (Turbopack)2.662s (+31.0% 🔺)3.336s (+27.8% 🔺)0.000s (-100.0% 🟢)3.345s (+26.6% 🔺)0.683s181.58x
💻 LocalExpress3.715s (+2.2%)4.313s (+2.0%)0.001s (+7.1% 🔺)4.319s (+2.1%)0.604s142.20x
💻 LocalNext.js (Turbopack)3.837s (+4.7%)4.277s (+2.0%)0.001s (-19.6% 🟢)4.319s (+2.0%)0.482s142.28x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.405s (+14.2% 🔺)7.099s (+23.6% 🔺)0.002s (+1400.0% 🔺)7.612s (+21.7% 🔺)2.207s81.00x
▲ VercelNitro5.848s (+19.5% 🔺)7.481s (+30.8% 🔺)0.000s (+Infinity% 🔺)8.026s (+29.8% 🔺)2.178s81.08x
▲ VercelNext.js (Turbopack)7.707s (+66.4% 🔺)9.533s (+58.4% 🔺)0.000s (+66.7% 🔺)10.735s (+63.2% 🔺)3.027s61.43x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNitro21/21
🐘 PostgresNitro14/21
▲ VercelExpress11/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres16/21
Next.js (Turbopack)🐘 Postgres15/21
Nitro🐘 Postgres13/21
Column Definitions
  • Workflow Time: Runtime reported by workflow (completedAt - createdAt) - primary metric
  • TTFB: Time to First Byte - time from workflow start until first stream byte received (stream benchmarks only)
  • Slurp: Time from first byte to complete stream consumption (stream benchmarks only)
  • Wall Time: Total testbench time (trigger workflow + poll for result)
  • Overhead: Testbench overhead (Wall Time - Workflow Time)
  • Samples: Number of benchmark iterations run
  • vs Fastest: How much slower compared to the fastest configuration for this benchmark

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Redis + BullMQ: Community world (local development)
  • 🌐 Cloudflare: Community world (local development)
  • 🌐 MySQL: Community world (local development)
  • 🌐 Azure: Community world (local development)
  • 🌐 NATS JetStream: Community world (local development)
  • 🌐 Upstash: Community world (local development)
  • 🌐 Platformatic: Community world (local development)

📋 View full workflow run

@github-actions

github-actionsBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production144202301672
✅ 💻 Local Development160502191824
✅ 📦 Local Production160502191824
✅ 🐘 Local Postgres159302311824
✅ 🪟 Windows15200152
✅ 📋 Other88501791064
Total7282010788360

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro125027
✅ example125027
✅ express125027
✅ fastify125027
✅ hono125027
✅ nextjs-turbopack14903
✅ nextjs-webpack14903
✅ nitro125027
✅ nuxt125027
✅ sveltekit14408
✅ vite125027
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable126026
✅ express-stable126026
✅ fastify-stable126026
✅ hono-stable126026
✅ nextjs-turbopack-canary132020
✅ nextjs-turbopack-stable15101
✅ nextjs-webpack-canary132020
✅ nextjs-webpack-stable15101
✅ nitro-stable126026
✅ nuxt-stable126026
✅ sveltekit-stable14507
✅ vite-stable126026
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack15200
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable127025
✅ e2e-local-dev-tanstack-start-127025
✅ e2e-local-postgres-nest-stable126026
✅ e2e-local-postgres-tanstack-start-126026
✅ e2e-local-prod-nest-stable127025
✅ e2e-local-prod-tanstack-start-127025
✅ e2e-vercel-prod-tanstack-start125027

📋 View full workflow run

@vercel

vercelBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

@VaguelySerious
VaguelySerious marked this pull request as ready for review June 25, 2026 22:55
@VaguelySerious
VaguelySerious requested review from a team and ijjk as code ownersJune 25, 2026 22:55

@karthikscale3karthikscale3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving — reviewed against the merged OTEL trace-correlation work (linked-mode #2363, route/execution cohesion #2527, v4 injection #2533). No regressions; this is a net improvement.

What I verified:

  • Injection is centralized + default-on: injectTraceContext = true in instrumentedFetch, inject runs inside the http <method> CLIENT span, and no caller passes false — so every path injects.
  • No bypass remains: the only raw fetch( is inside instrumentedFetch; all call sites (v4 events, makeRequest, streamer, both Vercel-API callers) route through it. Stronger guarantee than before.
  • v4 is fixed, not broken: #2533's inline injection moves into the core and v4 now gets a real client span (it had none), so workflow-server parents to it like v3. Stream + Vercel-API paths gain propagation.
  • Span name + parenting preserved: still http <method> / CLIENT with PeerService/RpcService → service-map edges and name-based queries intact; workflow-server still parents into the invocation trace (parent/child, not links).
  • Cohesion/linked mode untouched (only world-vercel/* changed; runtime.ts/step-handler.ts/telemetry.ts are not in the diff).
  • Tests strengthened: trace-propagation.test.ts asserts the v4 request's traceparent matches its client span and adds a stream-write propagation test.

One thing to confirm (see inline comment) + a doc follow-up.

// Explicitly propagate trace context so the receiving server can parent
// its spans to this client span — the custom undici dispatcher bypasses
// ambient auto-instrumentation. No-ops when no OTEL SDK is registered.
if (injectTraceContext) await injectTraceContextIntoHeaders(headers);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since instrumentedFetch is now the single trace-context injection chokepoint, please update the AGENTS.md guardrail (Architecture Notes → "Trace context propagation (world-vercel HTTP requests)") before merge: it currently tells contributors to call injectTraceContextIntoHeaders directly, but the rule should now be "route new request paths through instrumentedFetch (injects W3C context by default)". That keeps the guidance pointing at this chokepoint so future paths inherit propagation automatically.

Also worth a conscious decision: the peerService: 'vercel-api' callers default to injectTraceContext: true, so they now send traceparentand baggage (incl. workflow.run_id/name) to vercel.com. Likely fine since it's first-party, but if that propagation isn't intended, pass injectTraceContext: false for those callers.

@VaguelySerious
VaguelySerious merged commit 1dcdafd into mainJun 28, 2026
179 of 183 checks passed
@VaguelySerious
VaguelySerious deleted the peter/world-vercel-http-cleanup branch June 28, 2026 21:59
@github-actionsgithub-actionsBot mentioned this pull request Jun 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for 1dcdafd (AI decision).

This PR is an explicit follow-up to #2573 and the unified http-core.ts it introduces depends on main-only infrastructure: the events/stream dispatcher functions (getEventsDispatcher, getStreamDispatcher, EVENTS_AGENT_OPTIONS, STREAM_RETRY_OPTIONS) and the fetch-based v4 path with trace-context injection do not exist on stable (verified — stable's events-v4.ts still uses undici.request() directly and streamer.ts uses plain fetch() without dispatchers). Backporting would pull in code referencing dispatchers absent on stable, so it builds on main-only behavior.

To override, re-run the Backport to stable workflow manually via workflow_dispatch and paste this commit SHA into the ref input:

1dcdafd422c870ec1b7dcbba8b0e733c1b6bbb14

VaguelySerious added a commit that referenced this pull request Jul 31, 2026
Backport of #2631, adapted to stable. The v3 makeRequest path, the v4
events client, the stream write/close path and the direct Vercel API
calls each had their own request envelope; the 412 -> PreconditionFailed
and firewall-challenge-429 -> retryable TRANSPORT mappings were
duplicated between utils.ts and events-v4.ts.
http-core.ts now owns that envelope: the OTEL CLIENT span and its
attributes, W3C trace-context injection, the X-Request-Time cache bust,
the per-request deadline (mapped to a typed retryable WorkflowWorldError
instead of an opaque AbortError), DEBUG logging with a curl repro, and
the status -> typed-error mapping. utils.ts, events-v4.ts, streamer.ts,
encryption.ts and resolve-latest-deployment.ts delegate to it.
Behavioral gains on stable: the v4 events path and stream writes now
emit client spans and propagate traceparent, so the backend can parent
its spans to the caller without relying on the app having undici
auto-instrumentation.
Adapted rather than cherry-picked: stable keeps #3169's bounded
headers/body timeouts (documented as firing ahead of the outer
REQUEST_TIMEOUT_MS backstop), its body-parse retry loop, and #3038's
write-vs-close stream dispatcher split. Only injectTraceContextIntoHeaders
is taken from #2363, not its WORKFLOW_TRACE_MODE machinery.
Routing v4 through the global fetch makes the H2 interceptor's
re-buffering gate live on this path (fetch streamifies request bodies),
so the new http-client test drives createWorkflowRunEventV4 concurrently
against a local H2 server: 16 writes on one session with the
interceptor, 8 without it.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@VaguelySerious@karthikscale3
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[world-vercel] Unify HTTP request handling into a shared core - #2631

Merged
VaguelySerious merged 1 commit into
mainfrom
peter/world-vercel-http-cleanup
Jun 28, 2026
Merged

[world-vercel] Unify HTTP request handling into a shared core#2631
VaguelySerious merged 1 commit into
mainfrom
peter/world-vercel-http-cleanup

Conversation

@VaguelySerious

Copy link
Copy Markdown
Member

What

Follow-up to #2573. world-vercel had five separate HTTP request paths, each re-implementing the same cross-cutting concerns inconsistently. This collapses the duplication into one shared core (http-core.ts) and routes every path through it, so the v4 / stream / Vercel-API paths gain the OTEL client spans + DEBUG logging the v3 path already had.

Before

PathOTEL spanDEBUG logcurl-reprodiag headerstyped errorstrace-ctx
v3 makeRequest
v4 events(2nd copy)
stream write/close(2nd copy)
stream get/list
Vercel API (run-key, resolve-deploy)(plain Error)

The v4 events path — now the hottest path — had no client span and no debug logging.

How

New http-core.ts is the single source of truth for the request envelope:

  • errorForResponse — the status → typed-error map (409→EntityConflictError, 410→RunExpiredError, 425→TooEarlyError, 429→ThrottleError, else→WorkflowWorldError). Was implemented twice (v3 makeRequest + v4 throwForErrorResponse).
  • getVercelDiagnostics / parseRetryAfter / httpLog / logCurlRepro — one copy each (were duplicated across utils.ts and streamer.ts).
  • resolveVercelApiToken — shared token → VERCEL_TOKEN → OIDC precedence (was byte-identical in encryption.ts and resolve-latest-deployment.ts).
  • instrumentedFetch — shared OTEL span + trace-context injection + cache-bust header + timeout + DEBUG logging + error envelope. The v4 events, stream, and Vercel-API paths now route through it.

Also: stream writes now inject W3C trace context (previously missing, contrary to the documented requirement that every world→server request must), and the three dispatcher singletons in http-client.ts collapse behind one makeRetryDispatcher factory.

Behavior preserved

No behavior change beyond the added observability:

  • Per-path error-message strings are unchanged (the v3/v4/stream message formats feed log tooling); only the type mapping, Retry-After parsing, and diagnostics are unified.
  • The non-idempotent stream retry policy (transient + 429 only, never 5xx) is untouched.
  • The no-timeout semantics of the v4 (streamed LIST), stream (large buffered writes / long-lived live read), and Vercel-API paths are preserved (timeoutMs: null); only the v3 path keeps its 60s timeout.

Tests

  • New http-core.test.ts (error mapping per status, parseRetryAfter, diagnostics, token precedence).
  • Extended trace-propagation.test.ts: v4 now parents to its own http GET client span (matching v3), and a new case asserts stream writes inject traceparent.
  • Full world-vercel suite green (187 tests); build + typecheck + lint clean.

Changeset

@workflow/world-vercel patch — internal transport refactor + added observability, no API surface change.

Collapse the duplicated cross-cutting request logic across world-vercel's
request paths into one shared module (http-core.ts):
- errorForResponse: single status -> typed-error map (409/410/425/429/else),
shared by the v3 makeRequest and v4 throwForErrorResponse paths.
- getVercelDiagnostics / parseRetryAfter / httpLog / logCurlRepro: one copy
each (were duplicated across utils.ts and streamer.ts).
- resolveVercelApiToken: shared token precedence for the direct Vercel-API
calls (was byte-identical in encryption.ts and resolve-latest-deployment.ts).
- instrumentedFetch: the shared OTEL-span + trace-injection + cache-bust +
timeout + DEBUG-logging + error envelope.
Route the v4 events, stream write/read, and Vercel-API (run-key /
resolve-latest-deployment) paths through instrumentedFetch so they gain the
OTEL client spans and DEBUG logging the v3 path already had. Stream writes now
also inject W3C trace context (previously missing). Collapse the three
dispatcher singletons in http-client.ts behind a single makeRetryDispatcher
factory.
No behavior change beyond the added observability: per-path error-message
formats, the non-idempotent stream retry policy, and the no-timeout semantics
of the v4/stream/Vercel-API paths are preserved.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9263886

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 17 packages
NameType
@workflow/world-vercelPatch
@workflow/cliPatch
@workflow/corePatch
@workflow/webPatch
workflowPatch
@workflow/world-testingPatch
@workflow/buildersPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch
@workflow/nuxtPatch

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

@github-actions

github-actionsBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.035s (-26.0% 🟢)1.005s (~)0.970s101.00x
💻 LocalExpress0.048s (+1.7%)1.006s (~)0.958s101.39x
💻 LocalNext.js (Turbopack)0.050s (-4.8%)1.006s (~)0.956s101.44x
🐘 PostgresNext.js (Turbopack)0.061s (+9.8% 🔺)1.012s (~)0.951s101.74x
🐘 PostgresExpress0.063s (-9.9% 🟢)1.012s (~)0.949s101.80x
🐘 PostgresNitro0.071s (+9.2% 🔺)1.012s (~)0.940s102.05x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express0.332s (+43.7% 🔺)2.094s (~)1.762s101.00x
▲ VercelNitro0.343s (+36.1% 🔺)2.314s (+6.6% 🔺)1.971s101.03x
▲ VercelNext.js (Turbopack)0.931s (+244.5% 🔺)3.051s (+49.2% 🔺)2.120s102.80x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.062s (-2.2%)2.005s (~)0.943s101.00x
💻 LocalNext.js (Turbopack)1.084s (~)2.007s (~)0.923s101.02x
💻 LocalExpress1.084s (~)2.006s (~)0.922s101.02x
🐘 PostgresNitro1.088s (-1.3%)2.010s (~)0.922s101.02x
🐘 PostgresNext.js (Turbopack)1.103s (+1.8%)2.010s (~)0.907s101.04x
🐘 PostgresExpress1.104s (+1.1%)2.009s (~)0.905s101.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.499s (+6.2% 🔺)3.381s (+6.1% 🔺)1.881s101.00x
▲ VercelExpress1.584s (+14.5% 🔺)3.601s (+27.2% 🔺)2.017s101.06x
▲ VercelNext.js (Turbopack)2.692s (+87.7% 🔺)4.407s (+31.5% 🔺)1.715s101.80x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro10.335s (-1.2%)11.020s (~)0.685s31.00x
🐘 PostgresNitro10.443s (-0.6%)11.018s (~)0.575s31.01x
💻 LocalExpress10.449s (~)11.023s (~)0.574s31.01x
💻 LocalNext.js (Turbopack)10.523s (~)11.024s (~)0.501s31.02x
🐘 PostgresExpress10.529s (+0.8%)11.018s (~)0.489s31.02x
🐘 PostgresNext.js (Turbopack)10.549s (+0.9%)11.019s (~)0.470s31.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro13.474s (+15.0% 🔺)15.455s (+15.7% 🔺)1.981s31.00x
▲ VercelExpress13.947s (+13.3% 🔺)16.641s (+20.0% 🔺)2.694s21.04x
▲ VercelNext.js (Turbopack)14.162s (+20.0% 🔺)16.396s (+17.6% 🔺)2.234s21.05x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro13.443s (-1.4%)14.025s (~)0.582s51.00x
🐘 PostgresNitro13.632s (-0.6%)14.020s (~)0.388s51.01x
💻 LocalNext.js (Turbopack)13.637s (~)14.026s (~)0.389s51.01x
🐘 PostgresNext.js (Turbopack)13.650s (~)14.021s (~)0.371s51.02x
💻 LocalExpress13.656s (~)14.028s (~)0.372s51.02x
🐘 PostgresExpress13.824s (+1.8%)14.019s (~)0.194s51.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro20.368s (+24.4% 🔺)22.630s (+27.7% 🔺)2.262s31.00x
▲ VercelExpress20.918s (+27.2% 🔺)22.656s (+25.9% 🔺)1.738s31.03x
▲ VercelNext.js (Turbopack)23.709s (+36.7% 🔺)25.789s (+34.7% 🔺)2.079s31.16x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro11.628s (-4.5%)12.022s (-7.7% 🟢)0.394s81.00x
🐘 PostgresNitro12.094s (-1.5%)13.016s (~)0.922s71.04x
💻 LocalExpress12.171s (-1.4%)13.025s (~)0.853s71.05x
🐘 PostgresExpress12.282s (+1.5%)13.018s (~)0.736s71.06x
🐘 PostgresNext.js (Turbopack)12.323s (+1.8%)13.017s (~)0.694s71.06x
💻 LocalNext.js (Turbopack)12.389s (+1.1%)13.025s (~)0.636s71.07x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro22.441s (+18.2% 🔺)25.257s (+22.4% 🔺)2.816s41.00x
▲ VercelNext.js (Turbopack)24.710s (+27.7% 🔺)27.172s (+28.0% 🔺)2.462s41.10x
▲ VercelExpress24.776s (+34.8% 🔺)27.682s (+37.3% 🔺)2.906s41.10x

🔍 Observability: Nitro | Next.js (Turbopack) | Express

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.181s (~)2.008s (~)0.828s151.00x
🐘 PostgresNext.js (Turbopack)1.198s (+1.8%)2.008s (~)0.810s151.01x
🐘 PostgresExpress1.242s (+6.3% 🔺)2.009s (~)0.768s151.05x
💻 LocalNitro1.300s (-9.6% 🟢)2.006s (~)0.706s151.10x
💻 LocalExpress1.421s (+2.3%)2.007s (~)0.585s151.20x
💻 LocalNext.js (Turbopack)1.466s (-2.1%)2.007s (~)0.541s151.24x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.247s (+5.6% 🔺)4.214s (+22.4% 🔺)1.966s81.00x
▲ VercelExpress2.843s (+40.1% 🔺)4.746s (+35.6% 🔺)1.904s71.26x
▲ VercelNext.js (Turbopack)5.555s (+158.4% 🔺)7.589s (+100.3% 🔺)2.034s42.47x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.295s (-7.0% 🟢)2.736s (+2.3%)1.441s111.00x
🐘 PostgresNitro1.318s (-1.6%)2.591s (+3.3%)1.273s121.02x
🐘 PostgresNext.js (Turbopack)1.379s (+5.0% 🔺)3.011s (+3.2%)1.632s101.06x
💻 LocalNitro2.078s (-13.7% 🟢)2.826s (-3.2%)0.748s111.60x
💻 LocalExpress2.416s (+2.5%)3.009s (+3.1%)0.593s101.87x
💻 LocalNext.js (Turbopack)2.632s (-3.8%)3.010s (-3.2%)0.377s102.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.352s (+8.9% 🔺)5.327s (+2.8%)1.976s61.00x
▲ VercelExpress3.378s (-7.0% 🟢)5.318s (+3.0%)1.940s61.01x
▲ VercelNext.js (Turbopack)4.672s (+39.8% 🔺)6.725s (+33.3% 🔺)2.053s51.39x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.610s (-1.9%)4.137s (-3.0%)2.527s81.00x
🐘 PostgresExpress1.619s (+2.1%)4.012s (-6.7% 🟢)2.393s81.01x
🐘 PostgresNext.js (Turbopack)3.104s (+10.3% 🔺)6.015s (+9.0% 🔺)2.911s51.93x
💻 LocalNitro4.859s (-24.6% 🟢)5.347s (-25.9% 🟢)0.488s63.02x
💻 LocalNext.js (Turbopack)6.248s (+14.2% 🔺)7.518s (+25.0% 🔺)1.270s43.88x
💻 LocalExpress6.275s (-0.8%)6.614s (-5.8% 🟢)0.338s53.90x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express4.354s (+42.1% 🔺)6.876s (+36.3% 🔺)2.522s51.00x
▲ VercelNitro4.370s (+30.3% 🔺)6.765s (+32.1% 🔺)2.395s51.00x
▲ VercelNext.js (Turbopack)4.926s (+41.5% 🔺)7.542s (+45.0% 🔺)2.616s41.13x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.174s (-2.7%)2.007s (~)0.833s151.00x
🐘 PostgresNext.js (Turbopack)1.199s (+2.5%)2.008s (~)0.809s151.02x
🐘 PostgresExpress1.243s (+5.5% 🔺)2.074s (+3.3%)0.831s151.06x
💻 LocalNitro1.344s (-5.5% 🟢)2.006s (~)0.662s151.14x
💻 LocalExpress1.428s (-0.6%)2.007s (~)0.579s151.22x
💻 LocalNext.js (Turbopack)1.495s (+6.6% 🔺)2.073s (+3.3%)0.578s151.27x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.542s (+23.2% 🔺)4.657s (+33.1% 🔺)2.115s71.00x
▲ VercelNitro2.571s (+19.9% 🔺)4.516s (+21.5% 🔺)1.945s71.01x
▲ VercelNext.js (Turbopack)4.063s (-29.8% 🟢)6.178s (-17.0% 🟢)2.115s51.60x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.290s (-6.0% 🟢)2.075s (-16.1% 🟢)0.785s151.00x
🐘 PostgresExpress1.341s (~)2.393s (-3.2%)1.052s131.04x
🐘 PostgresNext.js (Turbopack)1.361s (+5.5% 🔺)3.010s (+3.2%)1.650s101.05x
💻 LocalNitro2.207s (-14.5% 🟢)2.917s (-6.2% 🟢)0.710s111.71x
💻 LocalExpress2.483s (+2.5%)2.919s (-6.1% 🟢)0.435s111.92x
💻 LocalNext.js (Turbopack)2.866s (+8.7% 🔺)3.209s (+6.7% 🔺)0.343s102.22x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.557s (-46.6% 🟢)4.443s (-33.1% 🟢)1.886s71.00x
▲ VercelNitro2.768s (+1.9%)4.662s (+8.0% 🔺)1.894s71.08x
▲ VercelNext.js (Turbopack)4.820s (+1.6%)7.047s (+1.4%)2.226s51.88x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.612s (-2.9%)4.012s (~)2.400s81.00x
🐘 PostgresExpress1.616s (+1.9%)4.012s (-3.0%)2.396s81.00x
🐘 PostgresNext.js (Turbopack)2.989s (+4.6%)6.013s (+2.8%)3.024s51.85x
💻 LocalNitro5.245s (-22.7% 🟢)5.680s (-21.3% 🟢)0.436s63.25x
💻 LocalNext.js (Turbopack)5.971s (+4.4%)6.614s (+9.9% 🔺)0.642s53.71x
💻 LocalExpress8.009s (+19.5% 🔺)8.518s (+18.0% 🔺)0.509s44.97x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.839s (-18.1% 🟢)6.037s (-4.1%)2.198s51.00x
▲ VercelNext.js (Turbopack)4.185s (+34.0% 🔺)6.365s (+23.3% 🔺)2.180s51.09x
▲ VercelNitro4.362s (+26.2% 🔺)6.526s (+29.7% 🔺)2.165s51.14x

🔍 Observability: Express | Next.js (Turbopack) | Nitro

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.508s (-13.3% 🟢)1.004s (~)0.496s601.00x
🐘 PostgresNitro0.518s (-12.9% 🟢)1.006s (-3.4%)0.488s601.02x
🐘 PostgresExpress0.565s (+5.9% 🔺)1.023s (~)0.458s591.11x
💻 LocalExpress0.596s (+4.0%)1.022s (+1.7%)0.426s591.17x
🐘 PostgresNext.js (Turbopack)0.608s (+18.9% 🔺)1.024s (+1.9%)0.417s591.20x
💻 LocalNext.js (Turbopack)0.608s (+0.8%)1.022s (~)0.414s591.20x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.261s (+16.5% 🔺)5.059s (+12.8% 🔺)1.799s121.00x
▲ VercelNitro3.490s (+31.9% 🔺)5.594s (+30.1% 🔺)2.104s111.07x
▲ VercelNext.js (Turbopack)4.224s (+39.0% 🔺)6.144s (+23.0% 🔺)1.920s101.30x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.240s (-12.3% 🟢)2.030s (-1.2%)0.789s451.00x
💻 LocalNitro1.256s (-15.3% 🟢)2.005s (~)0.749s451.01x
🐘 PostgresExpress1.331s (+6.4% 🔺)2.029s (+1.1%)0.699s451.07x
🐘 PostgresNext.js (Turbopack)1.389s (+11.6% 🔺)2.008s (-1.1%)0.619s451.12x
💻 LocalExpress1.472s (+1.2%)2.006s (~)0.534s451.19x
💻 LocalNext.js (Turbopack)1.532s (+4.4%)2.006s (~)0.474s451.24x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express7.933s (+22.7% 🔺)9.951s (+23.8% 🔺)2.018s101.00x
▲ VercelNitro8.413s (+31.5% 🔺)10.388s (+33.8% 🔺)1.975s91.06x
▲ VercelNext.js (Turbopack)10.501s (+47.0% 🔺)12.769s (+43.6% 🔺)2.268s81.32x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.512s (-7.5% 🟢)3.086s (+0.9%)0.574s391.00x
💻 LocalNitro2.760s (-14.3% 🟢)3.165s (-21.1% 🟢)0.405s381.10x
🐘 PostgresExpress2.763s (+6.5% 🔺)3.167s (+2.6%)0.404s381.10x
🐘 PostgresNext.js (Turbopack)2.793s (+12.8% 🔺)3.009s (-1.6%)0.216s401.11x
💻 LocalExpress3.242s (~)4.010s (+0.8%)0.768s301.29x
💻 LocalNext.js (Turbopack)3.305s (+1.4%)4.043s (~)0.738s301.32x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express13.918s (+1.0%)17.102s (+10.6% 🔺)3.184s81.00x
▲ VercelNitro14.974s (+6.6% 🔺)17.975s (+15.0% 🔺)3.000s71.08x
▲ VercelNext.js (Turbopack)20.700s (+26.1% 🔺)23.054s (+24.7% 🔺)2.354s61.49x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.212s (~)1.006s (~)0.794s601.00x
🐘 PostgresNext.js (Turbopack)0.218s (+30.2% 🔺)1.007s (~)0.788s601.03x
🐘 PostgresNitro0.223s (-1.8%)1.006s (~)0.783s601.05x
💻 LocalNitro0.399s (-14.8% 🟢)1.021s (+1.6%)0.622s591.89x
💻 LocalExpress0.504s (+16.8% 🔺)1.005s (~)0.501s602.38x
💻 LocalNext.js (Turbopack)0.644s (+10.1% 🔺)1.022s (+1.7%)0.378s593.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.221s (-43.8% 🟢)3.077s (-20.6% 🟢)1.857s201.00x
▲ VercelExpress1.345s (-35.1% 🟢)3.022s (-21.3% 🟢)1.677s201.10x
▲ VercelNext.js (Turbopack)2.809s (+54.4% 🔺)4.638s (+30.7% 🔺)1.830s132.30x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.308s (+15.0% 🔺)1.041s (+3.5%)0.733s871.00x
🐘 PostgresNitro0.319s (-5.4% 🟢)1.006s (~)0.687s901.03x
🐘 PostgresExpress0.326s (+1.0%)1.007s (-1.1%)0.680s901.06x
💻 LocalNitro1.815s (-14.9% 🟢)2.285s (-14.9% 🟢)0.470s405.89x
💻 LocalExpress2.221s (+2.8%)2.797s (+2.2%)0.576s337.21x
💻 LocalNext.js (Turbopack)2.826s (+1.3%)3.295s (+1.1%)0.469s289.17x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.920s (-22.0% 🟢)3.816s (-5.9% 🟢)1.896s241.00x
▲ VercelNitro1.964s (+3.0%)3.918s (+12.3% 🔺)1.954s231.02x
▲ VercelNext.js (Turbopack)3.338s (+53.8% 🔺)5.492s (+39.8% 🔺)2.155s171.74x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.507s (-1.7%)1.068s (~)0.561s1131.00x
🐘 PostgresNitro0.513s (-2.9%)1.068s (+3.5%)0.555s1141.01x
🐘 PostgresNext.js (Turbopack)0.580s (+23.0% 🔺)3.254s (+8.1% 🔺)2.674s371.14x
💻 LocalNitro8.193s (-17.4% 🟢)9.094s (-17.5% 🟢)0.901s1416.15x
💻 LocalExpress9.881s (-2.0%)11.117s (~)1.235s1119.48x
💻 LocalNext.js (Turbopack)10.366s (+6.0% 🔺)11.576s (+7.4% 🔺)1.210s1120.44x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.428s (+34.8% 🔺)5.645s (+36.8% 🔺)2.217s221.00x
▲ VercelExpress3.617s (+51.3% 🔺)6.034s (+48.8% 🔺)2.417s201.06x
▲ VercelNext.js (Turbopack)4.887s (+4.3%)7.315s (+11.8% 🔺)2.428s171.43x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.128s (-2.6%)2.004s (~)0.008s (-33.9% 🟢)2.015s (~)0.887s101.00x
💻 LocalNext.js (Turbopack)1.145s (-2.1%)1.967s (~)0.012s (-2.4%)2.019s (~)0.875s101.01x
🐘 PostgresExpress1.159s (+0.7%)1.996s (~)0.001s (+10.0% 🔺)2.010s (~)0.852s101.03x
🐘 PostgresNitro1.159s (-0.7%)1.999s (~)0.001s (+9.1% 🔺)2.011s (~)0.852s101.03x
💻 LocalExpress1.160s (+0.7%)2.005s (~)0.013s (+5.8% 🔺)2.020s (~)0.860s101.03x
🐘 PostgresNext.js (Turbopack)1.165s (+1.7%)2.001s (~)0.001s (+18.2% 🔺)2.011s (~)0.846s101.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.120s (+3.6%)3.629s (+18.3% 🔺)2.424s (+38.4% 🔺)6.604s (+22.2% 🔺)4.484s101.00x
▲ VercelNitro2.340s (+5.5% 🔺)3.692s (+12.7% 🔺)2.806s (+50.6% 🔺)7.080s (+24.4% 🔺)4.740s101.10x
▲ VercelNext.js (Turbopack)3.746s (+81.4% 🔺)4.139s (+20.0% 🔺)2.700s (+91.6% 🔺)8.319s (+53.9% 🔺)4.573s101.77x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.426s (-8.2% 🟢)2.006s (~)0.011s (-7.8% 🟢)2.020s (~)0.594s301.00x
🐘 PostgresNitro1.544s (-3.8%)1.999s (~)0.005s (+6.6% 🔺)2.029s (~)0.485s301.08x
🐘 PostgresExpress1.551s (~)2.001s (~)0.005s (-10.2% 🟢)2.024s (~)0.473s301.09x
💻 LocalExpress1.556s (+0.5%)2.011s (~)0.012s (-4.1%)2.027s (~)0.471s301.09x
💻 LocalNext.js (Turbopack)1.608s (+1.9%)2.000s (+1.6%)0.013s (-3.1%)2.058s (+1.6%)0.450s301.13x
🐘 PostgresNext.js (Turbopack)1.675s (+8.7% 🔺)2.010s (~)0.005s (+0.6%)2.027s (~)0.352s301.17x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro7.027s (+15.7% 🔺)8.939s (+26.3% 🔺)0.304s (+34.3% 🔺)9.893s (+26.4% 🔺)2.866s71.00x
▲ VercelExpress7.058s (+19.9% 🔺)8.708s (+20.8% 🔺)0.283s (+31.1% 🔺)9.595s (+21.7% 🔺)2.537s71.00x
▲ VercelNext.js (Turbopack)10.646s (+79.2% 🔺)12.028s (+63.2% 🔺)0.377s (+18.4% 🔺)13.403s (+62.4% 🔺)2.757s51.52x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.781s (+0.9%)1.062s (+1.3%)0.000s (+Infinity% 🔺)1.079s (+1.4%)0.298s561.00x
🐘 PostgresNitro0.799s (-2.8%)1.044s (-5.0% 🟢)0.000s (-100.0% 🟢)1.059s (-5.3% 🟢)0.260s581.02x
💻 LocalNitro1.004s (-23.9% 🟢)1.475s (-25.5% 🟢)0.000s (-30.7% 🟢)1.478s (-25.5% 🟢)0.473s411.29x
🐘 PostgresNext.js (Turbopack)1.041s (+13.6% 🔺)1.538s (+20.5% 🔺)0.000s (+141.0% 🔺)1.550s (+20.8% 🔺)0.509s391.33x
💻 LocalExpress1.256s (-0.9%)2.013s (~)0.000s (+50.0% 🔺)2.016s (~)0.760s301.61x
💻 LocalNext.js (Turbopack)1.375s (+3.0%)1.979s (~)0.001s (+283.3% 🔺)2.016s (~)0.642s301.76x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.783s (+21.3% 🔺)5.122s (+16.1% 🔺)0.000s (NaN%)5.698s (+16.8% 🔺)1.915s111.00x
▲ VercelExpress3.945s (+1.0%)5.443s (+8.3% 🔺)0.000s (-100.0% 🟢)5.941s (+7.3% 🔺)1.996s111.04x
▲ VercelNext.js (Turbopack)5.317s (+61.5% 🔺)6.785s (+44.3% 🔺)0.000s (+50.0% 🔺)7.685s (+46.3% 🔺)2.368s81.41x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.685s (-12.1% 🟢)2.301s (-7.8% 🟢)0.000s (+84.6% 🔺)2.344s (-7.0% 🟢)0.658s261.00x
🐘 PostgresExpress1.748s (+3.1%)2.345s (+4.0%)0.000s (-100.0% 🟢)2.366s (+4.2%)0.618s261.04x
💻 LocalNitro2.653s (-25.6% 🟢)3.073s (-23.7% 🟢)0.000s (-64.7% 🟢)3.081s (-23.6% 🟢)0.427s201.57x
🐘 PostgresNext.js (Turbopack)2.662s (+31.0% 🔺)3.336s (+27.8% 🔺)0.000s (-100.0% 🟢)3.345s (+26.6% 🔺)0.683s181.58x
💻 LocalExpress3.715s (+2.2%)4.313s (+2.0%)0.001s (+7.1% 🔺)4.319s (+2.1%)0.604s142.20x
💻 LocalNext.js (Turbopack)3.837s (+4.7%)4.277s (+2.0%)0.001s (-19.6% 🟢)4.319s (+2.0%)0.482s142.28x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.405s (+14.2% 🔺)7.099s (+23.6% 🔺)0.002s (+1400.0% 🔺)7.612s (+21.7% 🔺)2.207s81.00x
▲ VercelNitro5.848s (+19.5% 🔺)7.481s (+30.8% 🔺)0.000s (+Infinity% 🔺)8.026s (+29.8% 🔺)2.178s81.08x
▲ VercelNext.js (Turbopack)7.707s (+66.4% 🔺)9.533s (+58.4% 🔺)0.000s (+66.7% 🔺)10.735s (+63.2% 🔺)3.027s61.43x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNitro21/21
🐘 PostgresNitro14/21
▲ VercelExpress11/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres16/21
Next.js (Turbopack)🐘 Postgres15/21
Nitro🐘 Postgres13/21
Column Definitions
  • Workflow Time: Runtime reported by workflow (completedAt - createdAt) - primary metric
  • TTFB: Time to First Byte - time from workflow start until first stream byte received (stream benchmarks only)
  • Slurp: Time from first byte to complete stream consumption (stream benchmarks only)
  • Wall Time: Total testbench time (trigger workflow + poll for result)
  • Overhead: Testbench overhead (Wall Time - Workflow Time)
  • Samples: Number of benchmark iterations run
  • vs Fastest: How much slower compared to the fastest configuration for this benchmark

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Redis + BullMQ: Community world (local development)
  • 🌐 Cloudflare: Community world (local development)
  • 🌐 MySQL: Community world (local development)
  • 🌐 Azure: Community world (local development)
  • 🌐 NATS JetStream: Community world (local development)
  • 🌐 Upstash: Community world (local development)
  • 🌐 Platformatic: Community world (local development)

📋 View full workflow run

@github-actions

github-actionsBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production144202301672
✅ 💻 Local Development160502191824
✅ 📦 Local Production160502191824
✅ 🐘 Local Postgres159302311824
✅ 🪟 Windows15200152
✅ 📋 Other88501791064
Total7282010788360

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro125027
✅ example125027
✅ express125027
✅ fastify125027
✅ hono125027
✅ nextjs-turbopack14903
✅ nextjs-webpack14903
✅ nitro125027
✅ nuxt125027
✅ sveltekit14408
✅ vite125027
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable126026
✅ express-stable126026
✅ fastify-stable126026
✅ hono-stable126026
✅ nextjs-turbopack-canary132020
✅ nextjs-turbopack-stable15101
✅ nextjs-webpack-canary132020
✅ nextjs-webpack-stable15101
✅ nitro-stable126026
✅ nuxt-stable126026
✅ sveltekit-stable14507
✅ vite-stable126026
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack15200
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable127025
✅ e2e-local-dev-tanstack-start-127025
✅ e2e-local-postgres-nest-stable126026
✅ e2e-local-postgres-tanstack-start-126026
✅ e2e-local-prod-nest-stable127025
✅ e2e-local-prod-tanstack-start-127025
✅ e2e-vercel-prod-tanstack-start125027

📋 View full workflow run

@vercel

vercelBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

@VaguelySerious
VaguelySerious marked this pull request as ready for review June 25, 2026 22:55
@VaguelySerious
VaguelySerious requested review from a team and ijjk as code ownersJune 25, 2026 22:55

@karthikscale3karthikscale3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving — reviewed against the merged OTEL trace-correlation work (linked-mode #2363, route/execution cohesion #2527, v4 injection #2533). No regressions; this is a net improvement.

What I verified:

  • Injection is centralized + default-on: injectTraceContext = true in instrumentedFetch, inject runs inside the http <method> CLIENT span, and no caller passes false — so every path injects.
  • No bypass remains: the only raw fetch( is inside instrumentedFetch; all call sites (v4 events, makeRequest, streamer, both Vercel-API callers) route through it. Stronger guarantee than before.
  • v4 is fixed, not broken: #2533's inline injection moves into the core and v4 now gets a real client span (it had none), so workflow-server parents to it like v3. Stream + Vercel-API paths gain propagation.
  • Span name + parenting preserved: still http <method> / CLIENT with PeerService/RpcService → service-map edges and name-based queries intact; workflow-server still parents into the invocation trace (parent/child, not links).
  • Cohesion/linked mode untouched (only world-vercel/* changed; runtime.ts/step-handler.ts/telemetry.ts are not in the diff).
  • Tests strengthened: trace-propagation.test.ts asserts the v4 request's traceparent matches its client span and adds a stream-write propagation test.

One thing to confirm (see inline comment) + a doc follow-up.

// Explicitly propagate trace context so the receiving server can parent
// its spans to this client span — the custom undici dispatcher bypasses
// ambient auto-instrumentation. No-ops when no OTEL SDK is registered.
if (injectTraceContext) await injectTraceContextIntoHeaders(headers);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since instrumentedFetch is now the single trace-context injection chokepoint, please update the AGENTS.md guardrail (Architecture Notes → "Trace context propagation (world-vercel HTTP requests)") before merge: it currently tells contributors to call injectTraceContextIntoHeaders directly, but the rule should now be "route new request paths through instrumentedFetch (injects W3C context by default)". That keeps the guidance pointing at this chokepoint so future paths inherit propagation automatically.

Also worth a conscious decision: the peerService: 'vercel-api' callers default to injectTraceContext: true, so they now send traceparentand baggage (incl. workflow.run_id/name) to vercel.com. Likely fine since it's first-party, but if that propagation isn't intended, pass injectTraceContext: false for those callers.

@VaguelySerious
VaguelySerious merged commit 1dcdafd into mainJun 28, 2026
179 of 183 checks passed
@VaguelySerious
VaguelySerious deleted the peter/world-vercel-http-cleanup branch June 28, 2026 21:59
@github-actionsgithub-actionsBot mentioned this pull request Jun 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for 1dcdafd (AI decision).

This PR is an explicit follow-up to #2573 and the unified http-core.ts it introduces depends on main-only infrastructure: the events/stream dispatcher functions (getEventsDispatcher, getStreamDispatcher, EVENTS_AGENT_OPTIONS, STREAM_RETRY_OPTIONS) and the fetch-based v4 path with trace-context injection do not exist on stable (verified — stable's events-v4.ts still uses undici.request() directly and streamer.ts uses plain fetch() without dispatchers). Backporting would pull in code referencing dispatchers absent on stable, so it builds on main-only behavior.

To override, re-run the Backport to stable workflow manually via workflow_dispatch and paste this commit SHA into the ref input:

1dcdafd422c870ec1b7dcbba8b0e733c1b6bbb14

VaguelySerious added a commit that referenced this pull request Jul 31, 2026
Backport of #2631, adapted to stable. The v3 makeRequest path, the v4
events client, the stream write/close path and the direct Vercel API
calls each had their own request envelope; the 412 -> PreconditionFailed
and firewall-challenge-429 -> retryable TRANSPORT mappings were
duplicated between utils.ts and events-v4.ts.
http-core.ts now owns that envelope: the OTEL CLIENT span and its
attributes, W3C trace-context injection, the X-Request-Time cache bust,
the per-request deadline (mapped to a typed retryable WorkflowWorldError
instead of an opaque AbortError), DEBUG logging with a curl repro, and
the status -> typed-error mapping. utils.ts, events-v4.ts, streamer.ts,
encryption.ts and resolve-latest-deployment.ts delegate to it.
Behavioral gains on stable: the v4 events path and stream writes now
emit client spans and propagate traceparent, so the backend can parent
its spans to the caller without relying on the app having undici
auto-instrumentation.
Adapted rather than cherry-picked: stable keeps #3169's bounded
headers/body timeouts (documented as firing ahead of the outer
REQUEST_TIMEOUT_MS backstop), its body-parse retry loop, and #3038's
write-vs-close stream dispatcher split. Only injectTraceContextIntoHeaders
is taken from #2363, not its WORKFLOW_TRACE_MODE machinery.
Routing v4 through the global fetch makes the H2 interceptor's
re-buffering gate live on this path (fetch streamifies request bodies),
so the new http-client test drives createWorkflowRunEventV4 concurrently
against a local H2 server: 16 writes on one session with the
interceptor, 8 without it.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@VaguelySerious@karthikscale3
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[world-vercel] Unify HTTP request handling into a shared core - #2631

Merged
VaguelySerious merged 1 commit into
mainfrom
peter/world-vercel-http-cleanup
Jun 28, 2026
Merged

[world-vercel] Unify HTTP request handling into a shared core#2631
VaguelySerious merged 1 commit into
mainfrom
peter/world-vercel-http-cleanup

Conversation

@VaguelySerious

Copy link
Copy Markdown
Member

What

Follow-up to #2573. world-vercel had five separate HTTP request paths, each re-implementing the same cross-cutting concerns inconsistently. This collapses the duplication into one shared core (http-core.ts) and routes every path through it, so the v4 / stream / Vercel-API paths gain the OTEL client spans + DEBUG logging the v3 path already had.

Before

PathOTEL spanDEBUG logcurl-reprodiag headerstyped errorstrace-ctx
v3 makeRequest
v4 events(2nd copy)
stream write/close(2nd copy)
stream get/list
Vercel API (run-key, resolve-deploy)(plain Error)

The v4 events path — now the hottest path — had no client span and no debug logging.

How

New http-core.ts is the single source of truth for the request envelope:

  • errorForResponse — the status → typed-error map (409→EntityConflictError, 410→RunExpiredError, 425→TooEarlyError, 429→ThrottleError, else→WorkflowWorldError). Was implemented twice (v3 makeRequest + v4 throwForErrorResponse).
  • getVercelDiagnostics / parseRetryAfter / httpLog / logCurlRepro — one copy each (were duplicated across utils.ts and streamer.ts).
  • resolveVercelApiToken — shared token → VERCEL_TOKEN → OIDC precedence (was byte-identical in encryption.ts and resolve-latest-deployment.ts).
  • instrumentedFetch — shared OTEL span + trace-context injection + cache-bust header + timeout + DEBUG logging + error envelope. The v4 events, stream, and Vercel-API paths now route through it.

Also: stream writes now inject W3C trace context (previously missing, contrary to the documented requirement that every world→server request must), and the three dispatcher singletons in http-client.ts collapse behind one makeRetryDispatcher factory.

Behavior preserved

No behavior change beyond the added observability:

  • Per-path error-message strings are unchanged (the v3/v4/stream message formats feed log tooling); only the type mapping, Retry-After parsing, and diagnostics are unified.
  • The non-idempotent stream retry policy (transient + 429 only, never 5xx) is untouched.
  • The no-timeout semantics of the v4 (streamed LIST), stream (large buffered writes / long-lived live read), and Vercel-API paths are preserved (timeoutMs: null); only the v3 path keeps its 60s timeout.

Tests

  • New http-core.test.ts (error mapping per status, parseRetryAfter, diagnostics, token precedence).
  • Extended trace-propagation.test.ts: v4 now parents to its own http GET client span (matching v3), and a new case asserts stream writes inject traceparent.
  • Full world-vercel suite green (187 tests); build + typecheck + lint clean.

Changeset

@workflow/world-vercel patch — internal transport refactor + added observability, no API surface change.

Collapse the duplicated cross-cutting request logic across world-vercel's
request paths into one shared module (http-core.ts):
- errorForResponse: single status -> typed-error map (409/410/425/429/else),
shared by the v3 makeRequest and v4 throwForErrorResponse paths.
- getVercelDiagnostics / parseRetryAfter / httpLog / logCurlRepro: one copy
each (were duplicated across utils.ts and streamer.ts).
- resolveVercelApiToken: shared token precedence for the direct Vercel-API
calls (was byte-identical in encryption.ts and resolve-latest-deployment.ts).
- instrumentedFetch: the shared OTEL-span + trace-injection + cache-bust +
timeout + DEBUG-logging + error envelope.
Route the v4 events, stream write/read, and Vercel-API (run-key /
resolve-latest-deployment) paths through instrumentedFetch so they gain the
OTEL client spans and DEBUG logging the v3 path already had. Stream writes now
also inject W3C trace context (previously missing). Collapse the three
dispatcher singletons in http-client.ts behind a single makeRetryDispatcher
factory.
No behavior change beyond the added observability: per-path error-message
formats, the non-idempotent stream retry policy, and the no-timeout semantics
of the v4/stream/Vercel-API paths are preserved.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9263886

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 17 packages
NameType
@workflow/world-vercelPatch
@workflow/cliPatch
@workflow/corePatch
@workflow/webPatch
workflowPatch
@workflow/world-testingPatch
@workflow/buildersPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch
@workflow/nuxtPatch

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

@github-actions

github-actionsBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.035s (-26.0% 🟢)1.005s (~)0.970s101.00x
💻 LocalExpress0.048s (+1.7%)1.006s (~)0.958s101.39x
💻 LocalNext.js (Turbopack)0.050s (-4.8%)1.006s (~)0.956s101.44x
🐘 PostgresNext.js (Turbopack)0.061s (+9.8% 🔺)1.012s (~)0.951s101.74x
🐘 PostgresExpress0.063s (-9.9% 🟢)1.012s (~)0.949s101.80x
🐘 PostgresNitro0.071s (+9.2% 🔺)1.012s (~)0.940s102.05x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express0.332s (+43.7% 🔺)2.094s (~)1.762s101.00x
▲ VercelNitro0.343s (+36.1% 🔺)2.314s (+6.6% 🔺)1.971s101.03x
▲ VercelNext.js (Turbopack)0.931s (+244.5% 🔺)3.051s (+49.2% 🔺)2.120s102.80x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.062s (-2.2%)2.005s (~)0.943s101.00x
💻 LocalNext.js (Turbopack)1.084s (~)2.007s (~)0.923s101.02x
💻 LocalExpress1.084s (~)2.006s (~)0.922s101.02x
🐘 PostgresNitro1.088s (-1.3%)2.010s (~)0.922s101.02x
🐘 PostgresNext.js (Turbopack)1.103s (+1.8%)2.010s (~)0.907s101.04x
🐘 PostgresExpress1.104s (+1.1%)2.009s (~)0.905s101.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.499s (+6.2% 🔺)3.381s (+6.1% 🔺)1.881s101.00x
▲ VercelExpress1.584s (+14.5% 🔺)3.601s (+27.2% 🔺)2.017s101.06x
▲ VercelNext.js (Turbopack)2.692s (+87.7% 🔺)4.407s (+31.5% 🔺)1.715s101.80x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro10.335s (-1.2%)11.020s (~)0.685s31.00x
🐘 PostgresNitro10.443s (-0.6%)11.018s (~)0.575s31.01x
💻 LocalExpress10.449s (~)11.023s (~)0.574s31.01x
💻 LocalNext.js (Turbopack)10.523s (~)11.024s (~)0.501s31.02x
🐘 PostgresExpress10.529s (+0.8%)11.018s (~)0.489s31.02x
🐘 PostgresNext.js (Turbopack)10.549s (+0.9%)11.019s (~)0.470s31.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro13.474s (+15.0% 🔺)15.455s (+15.7% 🔺)1.981s31.00x
▲ VercelExpress13.947s (+13.3% 🔺)16.641s (+20.0% 🔺)2.694s21.04x
▲ VercelNext.js (Turbopack)14.162s (+20.0% 🔺)16.396s (+17.6% 🔺)2.234s21.05x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro13.443s (-1.4%)14.025s (~)0.582s51.00x
🐘 PostgresNitro13.632s (-0.6%)14.020s (~)0.388s51.01x
💻 LocalNext.js (Turbopack)13.637s (~)14.026s (~)0.389s51.01x
🐘 PostgresNext.js (Turbopack)13.650s (~)14.021s (~)0.371s51.02x
💻 LocalExpress13.656s (~)14.028s (~)0.372s51.02x
🐘 PostgresExpress13.824s (+1.8%)14.019s (~)0.194s51.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro20.368s (+24.4% 🔺)22.630s (+27.7% 🔺)2.262s31.00x
▲ VercelExpress20.918s (+27.2% 🔺)22.656s (+25.9% 🔺)1.738s31.03x
▲ VercelNext.js (Turbopack)23.709s (+36.7% 🔺)25.789s (+34.7% 🔺)2.079s31.16x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro11.628s (-4.5%)12.022s (-7.7% 🟢)0.394s81.00x
🐘 PostgresNitro12.094s (-1.5%)13.016s (~)0.922s71.04x
💻 LocalExpress12.171s (-1.4%)13.025s (~)0.853s71.05x
🐘 PostgresExpress12.282s (+1.5%)13.018s (~)0.736s71.06x
🐘 PostgresNext.js (Turbopack)12.323s (+1.8%)13.017s (~)0.694s71.06x
💻 LocalNext.js (Turbopack)12.389s (+1.1%)13.025s (~)0.636s71.07x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro22.441s (+18.2% 🔺)25.257s (+22.4% 🔺)2.816s41.00x
▲ VercelNext.js (Turbopack)24.710s (+27.7% 🔺)27.172s (+28.0% 🔺)2.462s41.10x
▲ VercelExpress24.776s (+34.8% 🔺)27.682s (+37.3% 🔺)2.906s41.10x

🔍 Observability: Nitro | Next.js (Turbopack) | Express

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.181s (~)2.008s (~)0.828s151.00x
🐘 PostgresNext.js (Turbopack)1.198s (+1.8%)2.008s (~)0.810s151.01x
🐘 PostgresExpress1.242s (+6.3% 🔺)2.009s (~)0.768s151.05x
💻 LocalNitro1.300s (-9.6% 🟢)2.006s (~)0.706s151.10x
💻 LocalExpress1.421s (+2.3%)2.007s (~)0.585s151.20x
💻 LocalNext.js (Turbopack)1.466s (-2.1%)2.007s (~)0.541s151.24x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.247s (+5.6% 🔺)4.214s (+22.4% 🔺)1.966s81.00x
▲ VercelExpress2.843s (+40.1% 🔺)4.746s (+35.6% 🔺)1.904s71.26x
▲ VercelNext.js (Turbopack)5.555s (+158.4% 🔺)7.589s (+100.3% 🔺)2.034s42.47x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.295s (-7.0% 🟢)2.736s (+2.3%)1.441s111.00x
🐘 PostgresNitro1.318s (-1.6%)2.591s (+3.3%)1.273s121.02x
🐘 PostgresNext.js (Turbopack)1.379s (+5.0% 🔺)3.011s (+3.2%)1.632s101.06x
💻 LocalNitro2.078s (-13.7% 🟢)2.826s (-3.2%)0.748s111.60x
💻 LocalExpress2.416s (+2.5%)3.009s (+3.1%)0.593s101.87x
💻 LocalNext.js (Turbopack)2.632s (-3.8%)3.010s (-3.2%)0.377s102.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.352s (+8.9% 🔺)5.327s (+2.8%)1.976s61.00x
▲ VercelExpress3.378s (-7.0% 🟢)5.318s (+3.0%)1.940s61.01x
▲ VercelNext.js (Turbopack)4.672s (+39.8% 🔺)6.725s (+33.3% 🔺)2.053s51.39x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.610s (-1.9%)4.137s (-3.0%)2.527s81.00x
🐘 PostgresExpress1.619s (+2.1%)4.012s (-6.7% 🟢)2.393s81.01x
🐘 PostgresNext.js (Turbopack)3.104s (+10.3% 🔺)6.015s (+9.0% 🔺)2.911s51.93x
💻 LocalNitro4.859s (-24.6% 🟢)5.347s (-25.9% 🟢)0.488s63.02x
💻 LocalNext.js (Turbopack)6.248s (+14.2% 🔺)7.518s (+25.0% 🔺)1.270s43.88x
💻 LocalExpress6.275s (-0.8%)6.614s (-5.8% 🟢)0.338s53.90x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express4.354s (+42.1% 🔺)6.876s (+36.3% 🔺)2.522s51.00x
▲ VercelNitro4.370s (+30.3% 🔺)6.765s (+32.1% 🔺)2.395s51.00x
▲ VercelNext.js (Turbopack)4.926s (+41.5% 🔺)7.542s (+45.0% 🔺)2.616s41.13x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.174s (-2.7%)2.007s (~)0.833s151.00x
🐘 PostgresNext.js (Turbopack)1.199s (+2.5%)2.008s (~)0.809s151.02x
🐘 PostgresExpress1.243s (+5.5% 🔺)2.074s (+3.3%)0.831s151.06x
💻 LocalNitro1.344s (-5.5% 🟢)2.006s (~)0.662s151.14x
💻 LocalExpress1.428s (-0.6%)2.007s (~)0.579s151.22x
💻 LocalNext.js (Turbopack)1.495s (+6.6% 🔺)2.073s (+3.3%)0.578s151.27x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.542s (+23.2% 🔺)4.657s (+33.1% 🔺)2.115s71.00x
▲ VercelNitro2.571s (+19.9% 🔺)4.516s (+21.5% 🔺)1.945s71.01x
▲ VercelNext.js (Turbopack)4.063s (-29.8% 🟢)6.178s (-17.0% 🟢)2.115s51.60x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.290s (-6.0% 🟢)2.075s (-16.1% 🟢)0.785s151.00x
🐘 PostgresExpress1.341s (~)2.393s (-3.2%)1.052s131.04x
🐘 PostgresNext.js (Turbopack)1.361s (+5.5% 🔺)3.010s (+3.2%)1.650s101.05x
💻 LocalNitro2.207s (-14.5% 🟢)2.917s (-6.2% 🟢)0.710s111.71x
💻 LocalExpress2.483s (+2.5%)2.919s (-6.1% 🟢)0.435s111.92x
💻 LocalNext.js (Turbopack)2.866s (+8.7% 🔺)3.209s (+6.7% 🔺)0.343s102.22x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.557s (-46.6% 🟢)4.443s (-33.1% 🟢)1.886s71.00x
▲ VercelNitro2.768s (+1.9%)4.662s (+8.0% 🔺)1.894s71.08x
▲ VercelNext.js (Turbopack)4.820s (+1.6%)7.047s (+1.4%)2.226s51.88x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.612s (-2.9%)4.012s (~)2.400s81.00x
🐘 PostgresExpress1.616s (+1.9%)4.012s (-3.0%)2.396s81.00x
🐘 PostgresNext.js (Turbopack)2.989s (+4.6%)6.013s (+2.8%)3.024s51.85x
💻 LocalNitro5.245s (-22.7% 🟢)5.680s (-21.3% 🟢)0.436s63.25x
💻 LocalNext.js (Turbopack)5.971s (+4.4%)6.614s (+9.9% 🔺)0.642s53.71x
💻 LocalExpress8.009s (+19.5% 🔺)8.518s (+18.0% 🔺)0.509s44.97x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.839s (-18.1% 🟢)6.037s (-4.1%)2.198s51.00x
▲ VercelNext.js (Turbopack)4.185s (+34.0% 🔺)6.365s (+23.3% 🔺)2.180s51.09x
▲ VercelNitro4.362s (+26.2% 🔺)6.526s (+29.7% 🔺)2.165s51.14x

🔍 Observability: Express | Next.js (Turbopack) | Nitro

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.508s (-13.3% 🟢)1.004s (~)0.496s601.00x
🐘 PostgresNitro0.518s (-12.9% 🟢)1.006s (-3.4%)0.488s601.02x
🐘 PostgresExpress0.565s (+5.9% 🔺)1.023s (~)0.458s591.11x
💻 LocalExpress0.596s (+4.0%)1.022s (+1.7%)0.426s591.17x
🐘 PostgresNext.js (Turbopack)0.608s (+18.9% 🔺)1.024s (+1.9%)0.417s591.20x
💻 LocalNext.js (Turbopack)0.608s (+0.8%)1.022s (~)0.414s591.20x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.261s (+16.5% 🔺)5.059s (+12.8% 🔺)1.799s121.00x
▲ VercelNitro3.490s (+31.9% 🔺)5.594s (+30.1% 🔺)2.104s111.07x
▲ VercelNext.js (Turbopack)4.224s (+39.0% 🔺)6.144s (+23.0% 🔺)1.920s101.30x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.240s (-12.3% 🟢)2.030s (-1.2%)0.789s451.00x
💻 LocalNitro1.256s (-15.3% 🟢)2.005s (~)0.749s451.01x
🐘 PostgresExpress1.331s (+6.4% 🔺)2.029s (+1.1%)0.699s451.07x
🐘 PostgresNext.js (Turbopack)1.389s (+11.6% 🔺)2.008s (-1.1%)0.619s451.12x
💻 LocalExpress1.472s (+1.2%)2.006s (~)0.534s451.19x
💻 LocalNext.js (Turbopack)1.532s (+4.4%)2.006s (~)0.474s451.24x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express7.933s (+22.7% 🔺)9.951s (+23.8% 🔺)2.018s101.00x
▲ VercelNitro8.413s (+31.5% 🔺)10.388s (+33.8% 🔺)1.975s91.06x
▲ VercelNext.js (Turbopack)10.501s (+47.0% 🔺)12.769s (+43.6% 🔺)2.268s81.32x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.512s (-7.5% 🟢)3.086s (+0.9%)0.574s391.00x
💻 LocalNitro2.760s (-14.3% 🟢)3.165s (-21.1% 🟢)0.405s381.10x
🐘 PostgresExpress2.763s (+6.5% 🔺)3.167s (+2.6%)0.404s381.10x
🐘 PostgresNext.js (Turbopack)2.793s (+12.8% 🔺)3.009s (-1.6%)0.216s401.11x
💻 LocalExpress3.242s (~)4.010s (+0.8%)0.768s301.29x
💻 LocalNext.js (Turbopack)3.305s (+1.4%)4.043s (~)0.738s301.32x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express13.918s (+1.0%)17.102s (+10.6% 🔺)3.184s81.00x
▲ VercelNitro14.974s (+6.6% 🔺)17.975s (+15.0% 🔺)3.000s71.08x
▲ VercelNext.js (Turbopack)20.700s (+26.1% 🔺)23.054s (+24.7% 🔺)2.354s61.49x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.212s (~)1.006s (~)0.794s601.00x
🐘 PostgresNext.js (Turbopack)0.218s (+30.2% 🔺)1.007s (~)0.788s601.03x
🐘 PostgresNitro0.223s (-1.8%)1.006s (~)0.783s601.05x
💻 LocalNitro0.399s (-14.8% 🟢)1.021s (+1.6%)0.622s591.89x
💻 LocalExpress0.504s (+16.8% 🔺)1.005s (~)0.501s602.38x
💻 LocalNext.js (Turbopack)0.644s (+10.1% 🔺)1.022s (+1.7%)0.378s593.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.221s (-43.8% 🟢)3.077s (-20.6% 🟢)1.857s201.00x
▲ VercelExpress1.345s (-35.1% 🟢)3.022s (-21.3% 🟢)1.677s201.10x
▲ VercelNext.js (Turbopack)2.809s (+54.4% 🔺)4.638s (+30.7% 🔺)1.830s132.30x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.308s (+15.0% 🔺)1.041s (+3.5%)0.733s871.00x
🐘 PostgresNitro0.319s (-5.4% 🟢)1.006s (~)0.687s901.03x
🐘 PostgresExpress0.326s (+1.0%)1.007s (-1.1%)0.680s901.06x
💻 LocalNitro1.815s (-14.9% 🟢)2.285s (-14.9% 🟢)0.470s405.89x
💻 LocalExpress2.221s (+2.8%)2.797s (+2.2%)0.576s337.21x
💻 LocalNext.js (Turbopack)2.826s (+1.3%)3.295s (+1.1%)0.469s289.17x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.920s (-22.0% 🟢)3.816s (-5.9% 🟢)1.896s241.00x
▲ VercelNitro1.964s (+3.0%)3.918s (+12.3% 🔺)1.954s231.02x
▲ VercelNext.js (Turbopack)3.338s (+53.8% 🔺)5.492s (+39.8% 🔺)2.155s171.74x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.507s (-1.7%)1.068s (~)0.561s1131.00x
🐘 PostgresNitro0.513s (-2.9%)1.068s (+3.5%)0.555s1141.01x
🐘 PostgresNext.js (Turbopack)0.580s (+23.0% 🔺)3.254s (+8.1% 🔺)2.674s371.14x
💻 LocalNitro8.193s (-17.4% 🟢)9.094s (-17.5% 🟢)0.901s1416.15x
💻 LocalExpress9.881s (-2.0%)11.117s (~)1.235s1119.48x
💻 LocalNext.js (Turbopack)10.366s (+6.0% 🔺)11.576s (+7.4% 🔺)1.210s1120.44x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.428s (+34.8% 🔺)5.645s (+36.8% 🔺)2.217s221.00x
▲ VercelExpress3.617s (+51.3% 🔺)6.034s (+48.8% 🔺)2.417s201.06x
▲ VercelNext.js (Turbopack)4.887s (+4.3%)7.315s (+11.8% 🔺)2.428s171.43x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.128s (-2.6%)2.004s (~)0.008s (-33.9% 🟢)2.015s (~)0.887s101.00x
💻 LocalNext.js (Turbopack)1.145s (-2.1%)1.967s (~)0.012s (-2.4%)2.019s (~)0.875s101.01x
🐘 PostgresExpress1.159s (+0.7%)1.996s (~)0.001s (+10.0% 🔺)2.010s (~)0.852s101.03x
🐘 PostgresNitro1.159s (-0.7%)1.999s (~)0.001s (+9.1% 🔺)2.011s (~)0.852s101.03x
💻 LocalExpress1.160s (+0.7%)2.005s (~)0.013s (+5.8% 🔺)2.020s (~)0.860s101.03x
🐘 PostgresNext.js (Turbopack)1.165s (+1.7%)2.001s (~)0.001s (+18.2% 🔺)2.011s (~)0.846s101.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.120s (+3.6%)3.629s (+18.3% 🔺)2.424s (+38.4% 🔺)6.604s (+22.2% 🔺)4.484s101.00x
▲ VercelNitro2.340s (+5.5% 🔺)3.692s (+12.7% 🔺)2.806s (+50.6% 🔺)7.080s (+24.4% 🔺)4.740s101.10x
▲ VercelNext.js (Turbopack)3.746s (+81.4% 🔺)4.139s (+20.0% 🔺)2.700s (+91.6% 🔺)8.319s (+53.9% 🔺)4.573s101.77x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.426s (-8.2% 🟢)2.006s (~)0.011s (-7.8% 🟢)2.020s (~)0.594s301.00x
🐘 PostgresNitro1.544s (-3.8%)1.999s (~)0.005s (+6.6% 🔺)2.029s (~)0.485s301.08x
🐘 PostgresExpress1.551s (~)2.001s (~)0.005s (-10.2% 🟢)2.024s (~)0.473s301.09x
💻 LocalExpress1.556s (+0.5%)2.011s (~)0.012s (-4.1%)2.027s (~)0.471s301.09x
💻 LocalNext.js (Turbopack)1.608s (+1.9%)2.000s (+1.6%)0.013s (-3.1%)2.058s (+1.6%)0.450s301.13x
🐘 PostgresNext.js (Turbopack)1.675s (+8.7% 🔺)2.010s (~)0.005s (+0.6%)2.027s (~)0.352s301.17x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro7.027s (+15.7% 🔺)8.939s (+26.3% 🔺)0.304s (+34.3% 🔺)9.893s (+26.4% 🔺)2.866s71.00x
▲ VercelExpress7.058s (+19.9% 🔺)8.708s (+20.8% 🔺)0.283s (+31.1% 🔺)9.595s (+21.7% 🔺)2.537s71.00x
▲ VercelNext.js (Turbopack)10.646s (+79.2% 🔺)12.028s (+63.2% 🔺)0.377s (+18.4% 🔺)13.403s (+62.4% 🔺)2.757s51.52x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.781s (+0.9%)1.062s (+1.3%)0.000s (+Infinity% 🔺)1.079s (+1.4%)0.298s561.00x
🐘 PostgresNitro0.799s (-2.8%)1.044s (-5.0% 🟢)0.000s (-100.0% 🟢)1.059s (-5.3% 🟢)0.260s581.02x
💻 LocalNitro1.004s (-23.9% 🟢)1.475s (-25.5% 🟢)0.000s (-30.7% 🟢)1.478s (-25.5% 🟢)0.473s411.29x
🐘 PostgresNext.js (Turbopack)1.041s (+13.6% 🔺)1.538s (+20.5% 🔺)0.000s (+141.0% 🔺)1.550s (+20.8% 🔺)0.509s391.33x
💻 LocalExpress1.256s (-0.9%)2.013s (~)0.000s (+50.0% 🔺)2.016s (~)0.760s301.61x
💻 LocalNext.js (Turbopack)1.375s (+3.0%)1.979s (~)0.001s (+283.3% 🔺)2.016s (~)0.642s301.76x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.783s (+21.3% 🔺)5.122s (+16.1% 🔺)0.000s (NaN%)5.698s (+16.8% 🔺)1.915s111.00x
▲ VercelExpress3.945s (+1.0%)5.443s (+8.3% 🔺)0.000s (-100.0% 🟢)5.941s (+7.3% 🔺)1.996s111.04x
▲ VercelNext.js (Turbopack)5.317s (+61.5% 🔺)6.785s (+44.3% 🔺)0.000s (+50.0% 🔺)7.685s (+46.3% 🔺)2.368s81.41x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.685s (-12.1% 🟢)2.301s (-7.8% 🟢)0.000s (+84.6% 🔺)2.344s (-7.0% 🟢)0.658s261.00x
🐘 PostgresExpress1.748s (+3.1%)2.345s (+4.0%)0.000s (-100.0% 🟢)2.366s (+4.2%)0.618s261.04x
💻 LocalNitro2.653s (-25.6% 🟢)3.073s (-23.7% 🟢)0.000s (-64.7% 🟢)3.081s (-23.6% 🟢)0.427s201.57x
🐘 PostgresNext.js (Turbopack)2.662s (+31.0% 🔺)3.336s (+27.8% 🔺)0.000s (-100.0% 🟢)3.345s (+26.6% 🔺)0.683s181.58x
💻 LocalExpress3.715s (+2.2%)4.313s (+2.0%)0.001s (+7.1% 🔺)4.319s (+2.1%)0.604s142.20x
💻 LocalNext.js (Turbopack)3.837s (+4.7%)4.277s (+2.0%)0.001s (-19.6% 🟢)4.319s (+2.0%)0.482s142.28x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.405s (+14.2% 🔺)7.099s (+23.6% 🔺)0.002s (+1400.0% 🔺)7.612s (+21.7% 🔺)2.207s81.00x
▲ VercelNitro5.848s (+19.5% 🔺)7.481s (+30.8% 🔺)0.000s (+Infinity% 🔺)8.026s (+29.8% 🔺)2.178s81.08x
▲ VercelNext.js (Turbopack)7.707s (+66.4% 🔺)9.533s (+58.4% 🔺)0.000s (+66.7% 🔺)10.735s (+63.2% 🔺)3.027s61.43x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNitro21/21
🐘 PostgresNitro14/21
▲ VercelExpress11/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres16/21
Next.js (Turbopack)🐘 Postgres15/21
Nitro🐘 Postgres13/21
Column Definitions
  • Workflow Time: Runtime reported by workflow (completedAt - createdAt) - primary metric
  • TTFB: Time to First Byte - time from workflow start until first stream byte received (stream benchmarks only)
  • Slurp: Time from first byte to complete stream consumption (stream benchmarks only)
  • Wall Time: Total testbench time (trigger workflow + poll for result)
  • Overhead: Testbench overhead (Wall Time - Workflow Time)
  • Samples: Number of benchmark iterations run
  • vs Fastest: How much slower compared to the fastest configuration for this benchmark

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Redis + BullMQ: Community world (local development)
  • 🌐 Cloudflare: Community world (local development)
  • 🌐 MySQL: Community world (local development)
  • 🌐 Azure: Community world (local development)
  • 🌐 NATS JetStream: Community world (local development)
  • 🌐 Upstash: Community world (local development)
  • 🌐 Platformatic: Community world (local development)

📋 View full workflow run

@github-actions

github-actionsBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production144202301672
✅ 💻 Local Development160502191824
✅ 📦 Local Production160502191824
✅ 🐘 Local Postgres159302311824
✅ 🪟 Windows15200152
✅ 📋 Other88501791064
Total7282010788360

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro125027
✅ example125027
✅ express125027
✅ fastify125027
✅ hono125027
✅ nextjs-turbopack14903
✅ nextjs-webpack14903
✅ nitro125027
✅ nuxt125027
✅ sveltekit14408
✅ vite125027
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable126026
✅ express-stable126026
✅ fastify-stable126026
✅ hono-stable126026
✅ nextjs-turbopack-canary132020
✅ nextjs-turbopack-stable15101
✅ nextjs-webpack-canary132020
✅ nextjs-webpack-stable15101
✅ nitro-stable126026
✅ nuxt-stable126026
✅ sveltekit-stable14507
✅ vite-stable126026
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack15200
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable127025
✅ e2e-local-dev-tanstack-start-127025
✅ e2e-local-postgres-nest-stable126026
✅ e2e-local-postgres-tanstack-start-126026
✅ e2e-local-prod-nest-stable127025
✅ e2e-local-prod-tanstack-start-127025
✅ e2e-vercel-prod-tanstack-start125027

📋 View full workflow run

@vercel

vercelBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

@VaguelySerious
VaguelySerious marked this pull request as ready for review June 25, 2026 22:55
@VaguelySerious
VaguelySerious requested review from a team and ijjk as code ownersJune 25, 2026 22:55

@karthikscale3karthikscale3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving — reviewed against the merged OTEL trace-correlation work (linked-mode #2363, route/execution cohesion #2527, v4 injection #2533). No regressions; this is a net improvement.

What I verified:

  • Injection is centralized + default-on: injectTraceContext = true in instrumentedFetch, inject runs inside the http <method> CLIENT span, and no caller passes false — so every path injects.
  • No bypass remains: the only raw fetch( is inside instrumentedFetch; all call sites (v4 events, makeRequest, streamer, both Vercel-API callers) route through it. Stronger guarantee than before.
  • v4 is fixed, not broken: #2533's inline injection moves into the core and v4 now gets a real client span (it had none), so workflow-server parents to it like v3. Stream + Vercel-API paths gain propagation.
  • Span name + parenting preserved: still http <method> / CLIENT with PeerService/RpcService → service-map edges and name-based queries intact; workflow-server still parents into the invocation trace (parent/child, not links).
  • Cohesion/linked mode untouched (only world-vercel/* changed; runtime.ts/step-handler.ts/telemetry.ts are not in the diff).
  • Tests strengthened: trace-propagation.test.ts asserts the v4 request's traceparent matches its client span and adds a stream-write propagation test.

One thing to confirm (see inline comment) + a doc follow-up.

// Explicitly propagate trace context so the receiving server can parent
// its spans to this client span — the custom undici dispatcher bypasses
// ambient auto-instrumentation. No-ops when no OTEL SDK is registered.
if (injectTraceContext) await injectTraceContextIntoHeaders(headers);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since instrumentedFetch is now the single trace-context injection chokepoint, please update the AGENTS.md guardrail (Architecture Notes → "Trace context propagation (world-vercel HTTP requests)") before merge: it currently tells contributors to call injectTraceContextIntoHeaders directly, but the rule should now be "route new request paths through instrumentedFetch (injects W3C context by default)". That keeps the guidance pointing at this chokepoint so future paths inherit propagation automatically.

Also worth a conscious decision: the peerService: 'vercel-api' callers default to injectTraceContext: true, so they now send traceparentand baggage (incl. workflow.run_id/name) to vercel.com. Likely fine since it's first-party, but if that propagation isn't intended, pass injectTraceContext: false for those callers.

@VaguelySerious
VaguelySerious merged commit 1dcdafd into mainJun 28, 2026
179 of 183 checks passed
@VaguelySerious
VaguelySerious deleted the peter/world-vercel-http-cleanup branch June 28, 2026 21:59
@github-actionsgithub-actionsBot mentioned this pull request Jun 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for 1dcdafd (AI decision).

This PR is an explicit follow-up to #2573 and the unified http-core.ts it introduces depends on main-only infrastructure: the events/stream dispatcher functions (getEventsDispatcher, getStreamDispatcher, EVENTS_AGENT_OPTIONS, STREAM_RETRY_OPTIONS) and the fetch-based v4 path with trace-context injection do not exist on stable (verified — stable's events-v4.ts still uses undici.request() directly and streamer.ts uses plain fetch() without dispatchers). Backporting would pull in code referencing dispatchers absent on stable, so it builds on main-only behavior.

To override, re-run the Backport to stable workflow manually via workflow_dispatch and paste this commit SHA into the ref input:

1dcdafd422c870ec1b7dcbba8b0e733c1b6bbb14

VaguelySerious added a commit that referenced this pull request Jul 31, 2026
Backport of #2631, adapted to stable. The v3 makeRequest path, the v4
events client, the stream write/close path and the direct Vercel API
calls each had their own request envelope; the 412 -> PreconditionFailed
and firewall-challenge-429 -> retryable TRANSPORT mappings were
duplicated between utils.ts and events-v4.ts.
http-core.ts now owns that envelope: the OTEL CLIENT span and its
attributes, W3C trace-context injection, the X-Request-Time cache bust,
the per-request deadline (mapped to a typed retryable WorkflowWorldError
instead of an opaque AbortError), DEBUG logging with a curl repro, and
the status -> typed-error mapping. utils.ts, events-v4.ts, streamer.ts,
encryption.ts and resolve-latest-deployment.ts delegate to it.
Behavioral gains on stable: the v4 events path and stream writes now
emit client spans and propagate traceparent, so the backend can parent
its spans to the caller without relying on the app having undici
auto-instrumentation.
Adapted rather than cherry-picked: stable keeps #3169's bounded
headers/body timeouts (documented as firing ahead of the outer
REQUEST_TIMEOUT_MS backstop), its body-parse retry loop, and #3038's
write-vs-close stream dispatcher split. Only injectTraceContextIntoHeaders
is taken from #2363, not its WORKFLOW_TRACE_MODE machinery.
Routing v4 through the global fetch makes the H2 interceptor's
re-buffering gate live on this path (fetch streamifies request bodies),
so the new http-client test drives createWorkflowRunEventV4 concurrently
against a local H2 server: 16 writes on one session with the
interceptor, 8 without it.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@VaguelySerious@karthikscale3
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

[world-vercel] Unify HTTP request handling into a shared core - #2631

Merged
VaguelySerious merged 1 commit into
mainfrom
peter/world-vercel-http-cleanup
Jun 28, 2026
Merged

[world-vercel] Unify HTTP request handling into a shared core#2631
VaguelySerious merged 1 commit into
mainfrom
peter/world-vercel-http-cleanup

Conversation

@VaguelySerious

Copy link
Copy Markdown
Member

What

Follow-up to #2573. world-vercel had five separate HTTP request paths, each re-implementing the same cross-cutting concerns inconsistently. This collapses the duplication into one shared core (http-core.ts) and routes every path through it, so the v4 / stream / Vercel-API paths gain the OTEL client spans + DEBUG logging the v3 path already had.

Before

PathOTEL spanDEBUG logcurl-reprodiag headerstyped errorstrace-ctx
v3 makeRequest
v4 events(2nd copy)
stream write/close(2nd copy)
stream get/list
Vercel API (run-key, resolve-deploy)(plain Error)

The v4 events path — now the hottest path — had no client span and no debug logging.

How

New http-core.ts is the single source of truth for the request envelope:

  • errorForResponse — the status → typed-error map (409→EntityConflictError, 410→RunExpiredError, 425→TooEarlyError, 429→ThrottleError, else→WorkflowWorldError). Was implemented twice (v3 makeRequest + v4 throwForErrorResponse).
  • getVercelDiagnostics / parseRetryAfter / httpLog / logCurlRepro — one copy each (were duplicated across utils.ts and streamer.ts).
  • resolveVercelApiToken — shared token → VERCEL_TOKEN → OIDC precedence (was byte-identical in encryption.ts and resolve-latest-deployment.ts).
  • instrumentedFetch — shared OTEL span + trace-context injection + cache-bust header + timeout + DEBUG logging + error envelope. The v4 events, stream, and Vercel-API paths now route through it.

Also: stream writes now inject W3C trace context (previously missing, contrary to the documented requirement that every world→server request must), and the three dispatcher singletons in http-client.ts collapse behind one makeRetryDispatcher factory.

Behavior preserved

No behavior change beyond the added observability:

  • Per-path error-message strings are unchanged (the v3/v4/stream message formats feed log tooling); only the type mapping, Retry-After parsing, and diagnostics are unified.
  • The non-idempotent stream retry policy (transient + 429 only, never 5xx) is untouched.
  • The no-timeout semantics of the v4 (streamed LIST), stream (large buffered writes / long-lived live read), and Vercel-API paths are preserved (timeoutMs: null); only the v3 path keeps its 60s timeout.

Tests

  • New http-core.test.ts (error mapping per status, parseRetryAfter, diagnostics, token precedence).
  • Extended trace-propagation.test.ts: v4 now parents to its own http GET client span (matching v3), and a new case asserts stream writes inject traceparent.
  • Full world-vercel suite green (187 tests); build + typecheck + lint clean.

Changeset

@workflow/world-vercel patch — internal transport refactor + added observability, no API surface change.

Collapse the duplicated cross-cutting request logic across world-vercel's
request paths into one shared module (http-core.ts):
- errorForResponse: single status -> typed-error map (409/410/425/429/else),
shared by the v3 makeRequest and v4 throwForErrorResponse paths.
- getVercelDiagnostics / parseRetryAfter / httpLog / logCurlRepro: one copy
each (were duplicated across utils.ts and streamer.ts).
- resolveVercelApiToken: shared token precedence for the direct Vercel-API
calls (was byte-identical in encryption.ts and resolve-latest-deployment.ts).
- instrumentedFetch: the shared OTEL-span + trace-injection + cache-bust +
timeout + DEBUG-logging + error envelope.
Route the v4 events, stream write/read, and Vercel-API (run-key /
resolve-latest-deployment) paths through instrumentedFetch so they gain the
OTEL client spans and DEBUG logging the v3 path already had. Stream writes now
also inject W3C trace context (previously missing). Collapse the three
dispatcher singletons in http-client.ts behind a single makeRetryDispatcher
factory.
No behavior change beyond the added observability: per-path error-message
formats, the non-idempotent stream retry policy, and the no-timeout semantics
of the v4/stream/Vercel-API paths are preserved.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9263886

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 17 packages
NameType
@workflow/world-vercelPatch
@workflow/cliPatch
@workflow/corePatch
@workflow/webPatch
workflowPatch
@workflow/world-testingPatch
@workflow/buildersPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch
@workflow/nuxtPatch

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

@github-actions

github-actionsBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.035s (-26.0% 🟢)1.005s (~)0.970s101.00x
💻 LocalExpress0.048s (+1.7%)1.006s (~)0.958s101.39x
💻 LocalNext.js (Turbopack)0.050s (-4.8%)1.006s (~)0.956s101.44x
🐘 PostgresNext.js (Turbopack)0.061s (+9.8% 🔺)1.012s (~)0.951s101.74x
🐘 PostgresExpress0.063s (-9.9% 🟢)1.012s (~)0.949s101.80x
🐘 PostgresNitro0.071s (+9.2% 🔺)1.012s (~)0.940s102.05x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express0.332s (+43.7% 🔺)2.094s (~)1.762s101.00x
▲ VercelNitro0.343s (+36.1% 🔺)2.314s (+6.6% 🔺)1.971s101.03x
▲ VercelNext.js (Turbopack)0.931s (+244.5% 🔺)3.051s (+49.2% 🔺)2.120s102.80x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.062s (-2.2%)2.005s (~)0.943s101.00x
💻 LocalNext.js (Turbopack)1.084s (~)2.007s (~)0.923s101.02x
💻 LocalExpress1.084s (~)2.006s (~)0.922s101.02x
🐘 PostgresNitro1.088s (-1.3%)2.010s (~)0.922s101.02x
🐘 PostgresNext.js (Turbopack)1.103s (+1.8%)2.010s (~)0.907s101.04x
🐘 PostgresExpress1.104s (+1.1%)2.009s (~)0.905s101.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.499s (+6.2% 🔺)3.381s (+6.1% 🔺)1.881s101.00x
▲ VercelExpress1.584s (+14.5% 🔺)3.601s (+27.2% 🔺)2.017s101.06x
▲ VercelNext.js (Turbopack)2.692s (+87.7% 🔺)4.407s (+31.5% 🔺)1.715s101.80x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro10.335s (-1.2%)11.020s (~)0.685s31.00x
🐘 PostgresNitro10.443s (-0.6%)11.018s (~)0.575s31.01x
💻 LocalExpress10.449s (~)11.023s (~)0.574s31.01x
💻 LocalNext.js (Turbopack)10.523s (~)11.024s (~)0.501s31.02x
🐘 PostgresExpress10.529s (+0.8%)11.018s (~)0.489s31.02x
🐘 PostgresNext.js (Turbopack)10.549s (+0.9%)11.019s (~)0.470s31.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro13.474s (+15.0% 🔺)15.455s (+15.7% 🔺)1.981s31.00x
▲ VercelExpress13.947s (+13.3% 🔺)16.641s (+20.0% 🔺)2.694s21.04x
▲ VercelNext.js (Turbopack)14.162s (+20.0% 🔺)16.396s (+17.6% 🔺)2.234s21.05x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro13.443s (-1.4%)14.025s (~)0.582s51.00x
🐘 PostgresNitro13.632s (-0.6%)14.020s (~)0.388s51.01x
💻 LocalNext.js (Turbopack)13.637s (~)14.026s (~)0.389s51.01x
🐘 PostgresNext.js (Turbopack)13.650s (~)14.021s (~)0.371s51.02x
💻 LocalExpress13.656s (~)14.028s (~)0.372s51.02x
🐘 PostgresExpress13.824s (+1.8%)14.019s (~)0.194s51.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro20.368s (+24.4% 🔺)22.630s (+27.7% 🔺)2.262s31.00x
▲ VercelExpress20.918s (+27.2% 🔺)22.656s (+25.9% 🔺)1.738s31.03x
▲ VercelNext.js (Turbopack)23.709s (+36.7% 🔺)25.789s (+34.7% 🔺)2.079s31.16x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro11.628s (-4.5%)12.022s (-7.7% 🟢)0.394s81.00x
🐘 PostgresNitro12.094s (-1.5%)13.016s (~)0.922s71.04x
💻 LocalExpress12.171s (-1.4%)13.025s (~)0.853s71.05x
🐘 PostgresExpress12.282s (+1.5%)13.018s (~)0.736s71.06x
🐘 PostgresNext.js (Turbopack)12.323s (+1.8%)13.017s (~)0.694s71.06x
💻 LocalNext.js (Turbopack)12.389s (+1.1%)13.025s (~)0.636s71.07x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro22.441s (+18.2% 🔺)25.257s (+22.4% 🔺)2.816s41.00x
▲ VercelNext.js (Turbopack)24.710s (+27.7% 🔺)27.172s (+28.0% 🔺)2.462s41.10x
▲ VercelExpress24.776s (+34.8% 🔺)27.682s (+37.3% 🔺)2.906s41.10x

🔍 Observability: Nitro | Next.js (Turbopack) | Express

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.181s (~)2.008s (~)0.828s151.00x
🐘 PostgresNext.js (Turbopack)1.198s (+1.8%)2.008s (~)0.810s151.01x
🐘 PostgresExpress1.242s (+6.3% 🔺)2.009s (~)0.768s151.05x
💻 LocalNitro1.300s (-9.6% 🟢)2.006s (~)0.706s151.10x
💻 LocalExpress1.421s (+2.3%)2.007s (~)0.585s151.20x
💻 LocalNext.js (Turbopack)1.466s (-2.1%)2.007s (~)0.541s151.24x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.247s (+5.6% 🔺)4.214s (+22.4% 🔺)1.966s81.00x
▲ VercelExpress2.843s (+40.1% 🔺)4.746s (+35.6% 🔺)1.904s71.26x
▲ VercelNext.js (Turbopack)5.555s (+158.4% 🔺)7.589s (+100.3% 🔺)2.034s42.47x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.295s (-7.0% 🟢)2.736s (+2.3%)1.441s111.00x
🐘 PostgresNitro1.318s (-1.6%)2.591s (+3.3%)1.273s121.02x
🐘 PostgresNext.js (Turbopack)1.379s (+5.0% 🔺)3.011s (+3.2%)1.632s101.06x
💻 LocalNitro2.078s (-13.7% 🟢)2.826s (-3.2%)0.748s111.60x
💻 LocalExpress2.416s (+2.5%)3.009s (+3.1%)0.593s101.87x
💻 LocalNext.js (Turbopack)2.632s (-3.8%)3.010s (-3.2%)0.377s102.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.352s (+8.9% 🔺)5.327s (+2.8%)1.976s61.00x
▲ VercelExpress3.378s (-7.0% 🟢)5.318s (+3.0%)1.940s61.01x
▲ VercelNext.js (Turbopack)4.672s (+39.8% 🔺)6.725s (+33.3% 🔺)2.053s51.39x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.610s (-1.9%)4.137s (-3.0%)2.527s81.00x
🐘 PostgresExpress1.619s (+2.1%)4.012s (-6.7% 🟢)2.393s81.01x
🐘 PostgresNext.js (Turbopack)3.104s (+10.3% 🔺)6.015s (+9.0% 🔺)2.911s51.93x
💻 LocalNitro4.859s (-24.6% 🟢)5.347s (-25.9% 🟢)0.488s63.02x
💻 LocalNext.js (Turbopack)6.248s (+14.2% 🔺)7.518s (+25.0% 🔺)1.270s43.88x
💻 LocalExpress6.275s (-0.8%)6.614s (-5.8% 🟢)0.338s53.90x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express4.354s (+42.1% 🔺)6.876s (+36.3% 🔺)2.522s51.00x
▲ VercelNitro4.370s (+30.3% 🔺)6.765s (+32.1% 🔺)2.395s51.00x
▲ VercelNext.js (Turbopack)4.926s (+41.5% 🔺)7.542s (+45.0% 🔺)2.616s41.13x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.174s (-2.7%)2.007s (~)0.833s151.00x
🐘 PostgresNext.js (Turbopack)1.199s (+2.5%)2.008s (~)0.809s151.02x
🐘 PostgresExpress1.243s (+5.5% 🔺)2.074s (+3.3%)0.831s151.06x
💻 LocalNitro1.344s (-5.5% 🟢)2.006s (~)0.662s151.14x
💻 LocalExpress1.428s (-0.6%)2.007s (~)0.579s151.22x
💻 LocalNext.js (Turbopack)1.495s (+6.6% 🔺)2.073s (+3.3%)0.578s151.27x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.542s (+23.2% 🔺)4.657s (+33.1% 🔺)2.115s71.00x
▲ VercelNitro2.571s (+19.9% 🔺)4.516s (+21.5% 🔺)1.945s71.01x
▲ VercelNext.js (Turbopack)4.063s (-29.8% 🟢)6.178s (-17.0% 🟢)2.115s51.60x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.290s (-6.0% 🟢)2.075s (-16.1% 🟢)0.785s151.00x
🐘 PostgresExpress1.341s (~)2.393s (-3.2%)1.052s131.04x
🐘 PostgresNext.js (Turbopack)1.361s (+5.5% 🔺)3.010s (+3.2%)1.650s101.05x
💻 LocalNitro2.207s (-14.5% 🟢)2.917s (-6.2% 🟢)0.710s111.71x
💻 LocalExpress2.483s (+2.5%)2.919s (-6.1% 🟢)0.435s111.92x
💻 LocalNext.js (Turbopack)2.866s (+8.7% 🔺)3.209s (+6.7% 🔺)0.343s102.22x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.557s (-46.6% 🟢)4.443s (-33.1% 🟢)1.886s71.00x
▲ VercelNitro2.768s (+1.9%)4.662s (+8.0% 🔺)1.894s71.08x
▲ VercelNext.js (Turbopack)4.820s (+1.6%)7.047s (+1.4%)2.226s51.88x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.612s (-2.9%)4.012s (~)2.400s81.00x
🐘 PostgresExpress1.616s (+1.9%)4.012s (-3.0%)2.396s81.00x
🐘 PostgresNext.js (Turbopack)2.989s (+4.6%)6.013s (+2.8%)3.024s51.85x
💻 LocalNitro5.245s (-22.7% 🟢)5.680s (-21.3% 🟢)0.436s63.25x
💻 LocalNext.js (Turbopack)5.971s (+4.4%)6.614s (+9.9% 🔺)0.642s53.71x
💻 LocalExpress8.009s (+19.5% 🔺)8.518s (+18.0% 🔺)0.509s44.97x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.839s (-18.1% 🟢)6.037s (-4.1%)2.198s51.00x
▲ VercelNext.js (Turbopack)4.185s (+34.0% 🔺)6.365s (+23.3% 🔺)2.180s51.09x
▲ VercelNitro4.362s (+26.2% 🔺)6.526s (+29.7% 🔺)2.165s51.14x

🔍 Observability: Express | Next.js (Turbopack) | Nitro

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.508s (-13.3% 🟢)1.004s (~)0.496s601.00x
🐘 PostgresNitro0.518s (-12.9% 🟢)1.006s (-3.4%)0.488s601.02x
🐘 PostgresExpress0.565s (+5.9% 🔺)1.023s (~)0.458s591.11x
💻 LocalExpress0.596s (+4.0%)1.022s (+1.7%)0.426s591.17x
🐘 PostgresNext.js (Turbopack)0.608s (+18.9% 🔺)1.024s (+1.9%)0.417s591.20x
💻 LocalNext.js (Turbopack)0.608s (+0.8%)1.022s (~)0.414s591.20x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.261s (+16.5% 🔺)5.059s (+12.8% 🔺)1.799s121.00x
▲ VercelNitro3.490s (+31.9% 🔺)5.594s (+30.1% 🔺)2.104s111.07x
▲ VercelNext.js (Turbopack)4.224s (+39.0% 🔺)6.144s (+23.0% 🔺)1.920s101.30x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.240s (-12.3% 🟢)2.030s (-1.2%)0.789s451.00x
💻 LocalNitro1.256s (-15.3% 🟢)2.005s (~)0.749s451.01x
🐘 PostgresExpress1.331s (+6.4% 🔺)2.029s (+1.1%)0.699s451.07x
🐘 PostgresNext.js (Turbopack)1.389s (+11.6% 🔺)2.008s (-1.1%)0.619s451.12x
💻 LocalExpress1.472s (+1.2%)2.006s (~)0.534s451.19x
💻 LocalNext.js (Turbopack)1.532s (+4.4%)2.006s (~)0.474s451.24x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express7.933s (+22.7% 🔺)9.951s (+23.8% 🔺)2.018s101.00x
▲ VercelNitro8.413s (+31.5% 🔺)10.388s (+33.8% 🔺)1.975s91.06x
▲ VercelNext.js (Turbopack)10.501s (+47.0% 🔺)12.769s (+43.6% 🔺)2.268s81.32x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.512s (-7.5% 🟢)3.086s (+0.9%)0.574s391.00x
💻 LocalNitro2.760s (-14.3% 🟢)3.165s (-21.1% 🟢)0.405s381.10x
🐘 PostgresExpress2.763s (+6.5% 🔺)3.167s (+2.6%)0.404s381.10x
🐘 PostgresNext.js (Turbopack)2.793s (+12.8% 🔺)3.009s (-1.6%)0.216s401.11x
💻 LocalExpress3.242s (~)4.010s (+0.8%)0.768s301.29x
💻 LocalNext.js (Turbopack)3.305s (+1.4%)4.043s (~)0.738s301.32x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express13.918s (+1.0%)17.102s (+10.6% 🔺)3.184s81.00x
▲ VercelNitro14.974s (+6.6% 🔺)17.975s (+15.0% 🔺)3.000s71.08x
▲ VercelNext.js (Turbopack)20.700s (+26.1% 🔺)23.054s (+24.7% 🔺)2.354s61.49x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.212s (~)1.006s (~)0.794s601.00x
🐘 PostgresNext.js (Turbopack)0.218s (+30.2% 🔺)1.007s (~)0.788s601.03x
🐘 PostgresNitro0.223s (-1.8%)1.006s (~)0.783s601.05x
💻 LocalNitro0.399s (-14.8% 🟢)1.021s (+1.6%)0.622s591.89x
💻 LocalExpress0.504s (+16.8% 🔺)1.005s (~)0.501s602.38x
💻 LocalNext.js (Turbopack)0.644s (+10.1% 🔺)1.022s (+1.7%)0.378s593.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.221s (-43.8% 🟢)3.077s (-20.6% 🟢)1.857s201.00x
▲ VercelExpress1.345s (-35.1% 🟢)3.022s (-21.3% 🟢)1.677s201.10x
▲ VercelNext.js (Turbopack)2.809s (+54.4% 🔺)4.638s (+30.7% 🔺)1.830s132.30x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.308s (+15.0% 🔺)1.041s (+3.5%)0.733s871.00x
🐘 PostgresNitro0.319s (-5.4% 🟢)1.006s (~)0.687s901.03x
🐘 PostgresExpress0.326s (+1.0%)1.007s (-1.1%)0.680s901.06x
💻 LocalNitro1.815s (-14.9% 🟢)2.285s (-14.9% 🟢)0.470s405.89x
💻 LocalExpress2.221s (+2.8%)2.797s (+2.2%)0.576s337.21x
💻 LocalNext.js (Turbopack)2.826s (+1.3%)3.295s (+1.1%)0.469s289.17x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.920s (-22.0% 🟢)3.816s (-5.9% 🟢)1.896s241.00x
▲ VercelNitro1.964s (+3.0%)3.918s (+12.3% 🔺)1.954s231.02x
▲ VercelNext.js (Turbopack)3.338s (+53.8% 🔺)5.492s (+39.8% 🔺)2.155s171.74x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.507s (-1.7%)1.068s (~)0.561s1131.00x
🐘 PostgresNitro0.513s (-2.9%)1.068s (+3.5%)0.555s1141.01x
🐘 PostgresNext.js (Turbopack)0.580s (+23.0% 🔺)3.254s (+8.1% 🔺)2.674s371.14x
💻 LocalNitro8.193s (-17.4% 🟢)9.094s (-17.5% 🟢)0.901s1416.15x
💻 LocalExpress9.881s (-2.0%)11.117s (~)1.235s1119.48x
💻 LocalNext.js (Turbopack)10.366s (+6.0% 🔺)11.576s (+7.4% 🔺)1.210s1120.44x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.428s (+34.8% 🔺)5.645s (+36.8% 🔺)2.217s221.00x
▲ VercelExpress3.617s (+51.3% 🔺)6.034s (+48.8% 🔺)2.417s201.06x
▲ VercelNext.js (Turbopack)4.887s (+4.3%)7.315s (+11.8% 🔺)2.428s171.43x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.128s (-2.6%)2.004s (~)0.008s (-33.9% 🟢)2.015s (~)0.887s101.00x
💻 LocalNext.js (Turbopack)1.145s (-2.1%)1.967s (~)0.012s (-2.4%)2.019s (~)0.875s101.01x
🐘 PostgresExpress1.159s (+0.7%)1.996s (~)0.001s (+10.0% 🔺)2.010s (~)0.852s101.03x
🐘 PostgresNitro1.159s (-0.7%)1.999s (~)0.001s (+9.1% 🔺)2.011s (~)0.852s101.03x
💻 LocalExpress1.160s (+0.7%)2.005s (~)0.013s (+5.8% 🔺)2.020s (~)0.860s101.03x
🐘 PostgresNext.js (Turbopack)1.165s (+1.7%)2.001s (~)0.001s (+18.2% 🔺)2.011s (~)0.846s101.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.120s (+3.6%)3.629s (+18.3% 🔺)2.424s (+38.4% 🔺)6.604s (+22.2% 🔺)4.484s101.00x
▲ VercelNitro2.340s (+5.5% 🔺)3.692s (+12.7% 🔺)2.806s (+50.6% 🔺)7.080s (+24.4% 🔺)4.740s101.10x
▲ VercelNext.js (Turbopack)3.746s (+81.4% 🔺)4.139s (+20.0% 🔺)2.700s (+91.6% 🔺)8.319s (+53.9% 🔺)4.573s101.77x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.426s (-8.2% 🟢)2.006s (~)0.011s (-7.8% 🟢)2.020s (~)0.594s301.00x
🐘 PostgresNitro1.544s (-3.8%)1.999s (~)0.005s (+6.6% 🔺)2.029s (~)0.485s301.08x
🐘 PostgresExpress1.551s (~)2.001s (~)0.005s (-10.2% 🟢)2.024s (~)0.473s301.09x
💻 LocalExpress1.556s (+0.5%)2.011s (~)0.012s (-4.1%)2.027s (~)0.471s301.09x
💻 LocalNext.js (Turbopack)1.608s (+1.9%)2.000s (+1.6%)0.013s (-3.1%)2.058s (+1.6%)0.450s301.13x
🐘 PostgresNext.js (Turbopack)1.675s (+8.7% 🔺)2.010s (~)0.005s (+0.6%)2.027s (~)0.352s301.17x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro7.027s (+15.7% 🔺)8.939s (+26.3% 🔺)0.304s (+34.3% 🔺)9.893s (+26.4% 🔺)2.866s71.00x
▲ VercelExpress7.058s (+19.9% 🔺)8.708s (+20.8% 🔺)0.283s (+31.1% 🔺)9.595s (+21.7% 🔺)2.537s71.00x
▲ VercelNext.js (Turbopack)10.646s (+79.2% 🔺)12.028s (+63.2% 🔺)0.377s (+18.4% 🔺)13.403s (+62.4% 🔺)2.757s51.52x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.781s (+0.9%)1.062s (+1.3%)0.000s (+Infinity% 🔺)1.079s (+1.4%)0.298s561.00x
🐘 PostgresNitro0.799s (-2.8%)1.044s (-5.0% 🟢)0.000s (-100.0% 🟢)1.059s (-5.3% 🟢)0.260s581.02x
💻 LocalNitro1.004s (-23.9% 🟢)1.475s (-25.5% 🟢)0.000s (-30.7% 🟢)1.478s (-25.5% 🟢)0.473s411.29x
🐘 PostgresNext.js (Turbopack)1.041s (+13.6% 🔺)1.538s (+20.5% 🔺)0.000s (+141.0% 🔺)1.550s (+20.8% 🔺)0.509s391.33x
💻 LocalExpress1.256s (-0.9%)2.013s (~)0.000s (+50.0% 🔺)2.016s (~)0.760s301.61x
💻 LocalNext.js (Turbopack)1.375s (+3.0%)1.979s (~)0.001s (+283.3% 🔺)2.016s (~)0.642s301.76x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.783s (+21.3% 🔺)5.122s (+16.1% 🔺)0.000s (NaN%)5.698s (+16.8% 🔺)1.915s111.00x
▲ VercelExpress3.945s (+1.0%)5.443s (+8.3% 🔺)0.000s (-100.0% 🟢)5.941s (+7.3% 🔺)1.996s111.04x
▲ VercelNext.js (Turbopack)5.317s (+61.5% 🔺)6.785s (+44.3% 🔺)0.000s (+50.0% 🔺)7.685s (+46.3% 🔺)2.368s81.41x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.685s (-12.1% 🟢)2.301s (-7.8% 🟢)0.000s (+84.6% 🔺)2.344s (-7.0% 🟢)0.658s261.00x
🐘 PostgresExpress1.748s (+3.1%)2.345s (+4.0%)0.000s (-100.0% 🟢)2.366s (+4.2%)0.618s261.04x
💻 LocalNitro2.653s (-25.6% 🟢)3.073s (-23.7% 🟢)0.000s (-64.7% 🟢)3.081s (-23.6% 🟢)0.427s201.57x
🐘 PostgresNext.js (Turbopack)2.662s (+31.0% 🔺)3.336s (+27.8% 🔺)0.000s (-100.0% 🟢)3.345s (+26.6% 🔺)0.683s181.58x
💻 LocalExpress3.715s (+2.2%)4.313s (+2.0%)0.001s (+7.1% 🔺)4.319s (+2.1%)0.604s142.20x
💻 LocalNext.js (Turbopack)3.837s (+4.7%)4.277s (+2.0%)0.001s (-19.6% 🟢)4.319s (+2.0%)0.482s142.28x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.405s (+14.2% 🔺)7.099s (+23.6% 🔺)0.002s (+1400.0% 🔺)7.612s (+21.7% 🔺)2.207s81.00x
▲ VercelNitro5.848s (+19.5% 🔺)7.481s (+30.8% 🔺)0.000s (+Infinity% 🔺)8.026s (+29.8% 🔺)2.178s81.08x
▲ VercelNext.js (Turbopack)7.707s (+66.4% 🔺)9.533s (+58.4% 🔺)0.000s (+66.7% 🔺)10.735s (+63.2% 🔺)3.027s61.43x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNitro21/21
🐘 PostgresNitro14/21
▲ VercelExpress11/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres16/21
Next.js (Turbopack)🐘 Postgres15/21
Nitro🐘 Postgres13/21
Column Definitions
  • Workflow Time: Runtime reported by workflow (completedAt - createdAt) - primary metric
  • TTFB: Time to First Byte - time from workflow start until first stream byte received (stream benchmarks only)
  • Slurp: Time from first byte to complete stream consumption (stream benchmarks only)
  • Wall Time: Total testbench time (trigger workflow + poll for result)
  • Overhead: Testbench overhead (Wall Time - Workflow Time)
  • Samples: Number of benchmark iterations run
  • vs Fastest: How much slower compared to the fastest configuration for this benchmark

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Redis + BullMQ: Community world (local development)
  • 🌐 Cloudflare: Community world (local development)
  • 🌐 MySQL: Community world (local development)
  • 🌐 Azure: Community world (local development)
  • 🌐 NATS JetStream: Community world (local development)
  • 🌐 Upstash: Community world (local development)
  • 🌐 Platformatic: Community world (local development)

📋 View full workflow run

@github-actions

github-actionsBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production144202301672
✅ 💻 Local Development160502191824
✅ 📦 Local Production160502191824
✅ 🐘 Local Postgres159302311824
✅ 🪟 Windows15200152
✅ 📋 Other88501791064
Total7282010788360

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro125027
✅ example125027
✅ express125027
✅ fastify125027
✅ hono125027
✅ nextjs-turbopack14903
✅ nextjs-webpack14903
✅ nitro125027
✅ nuxt125027
✅ sveltekit14408
✅ vite125027
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable126026
✅ express-stable126026
✅ fastify-stable126026
✅ hono-stable126026
✅ nextjs-turbopack-canary132020
✅ nextjs-turbopack-stable15101
✅ nextjs-webpack-canary132020
✅ nextjs-webpack-stable15101
✅ nitro-stable126026
✅ nuxt-stable126026
✅ sveltekit-stable14507
✅ vite-stable126026
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack15200
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable127025
✅ e2e-local-dev-tanstack-start-127025
✅ e2e-local-postgres-nest-stable126026
✅ e2e-local-postgres-tanstack-start-126026
✅ e2e-local-prod-nest-stable127025
✅ e2e-local-prod-tanstack-start-127025
✅ e2e-vercel-prod-tanstack-start125027

📋 View full workflow run

@vercel

vercelBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

@VaguelySerious
VaguelySerious marked this pull request as ready for review June 25, 2026 22:55
@VaguelySerious
VaguelySerious requested review from a team and ijjk as code ownersJune 25, 2026 22:55

@karthikscale3karthikscale3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving — reviewed against the merged OTEL trace-correlation work (linked-mode #2363, route/execution cohesion #2527, v4 injection #2533). No regressions; this is a net improvement.

What I verified:

  • Injection is centralized + default-on: injectTraceContext = true in instrumentedFetch, inject runs inside the http <method> CLIENT span, and no caller passes false — so every path injects.
  • No bypass remains: the only raw fetch( is inside instrumentedFetch; all call sites (v4 events, makeRequest, streamer, both Vercel-API callers) route through it. Stronger guarantee than before.
  • v4 is fixed, not broken: #2533's inline injection moves into the core and v4 now gets a real client span (it had none), so workflow-server parents to it like v3. Stream + Vercel-API paths gain propagation.
  • Span name + parenting preserved: still http <method> / CLIENT with PeerService/RpcService → service-map edges and name-based queries intact; workflow-server still parents into the invocation trace (parent/child, not links).
  • Cohesion/linked mode untouched (only world-vercel/* changed; runtime.ts/step-handler.ts/telemetry.ts are not in the diff).
  • Tests strengthened: trace-propagation.test.ts asserts the v4 request's traceparent matches its client span and adds a stream-write propagation test.

One thing to confirm (see inline comment) + a doc follow-up.

// Explicitly propagate trace context so the receiving server can parent
// its spans to this client span — the custom undici dispatcher bypasses
// ambient auto-instrumentation. No-ops when no OTEL SDK is registered.
if (injectTraceContext) await injectTraceContextIntoHeaders(headers);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since instrumentedFetch is now the single trace-context injection chokepoint, please update the AGENTS.md guardrail (Architecture Notes → "Trace context propagation (world-vercel HTTP requests)") before merge: it currently tells contributors to call injectTraceContextIntoHeaders directly, but the rule should now be "route new request paths through instrumentedFetch (injects W3C context by default)". That keeps the guidance pointing at this chokepoint so future paths inherit propagation automatically.

Also worth a conscious decision: the peerService: 'vercel-api' callers default to injectTraceContext: true, so they now send traceparentand baggage (incl. workflow.run_id/name) to vercel.com. Likely fine since it's first-party, but if that propagation isn't intended, pass injectTraceContext: false for those callers.

@VaguelySerious
VaguelySerious merged commit 1dcdafd into mainJun 28, 2026
179 of 183 checks passed
@VaguelySerious
VaguelySerious deleted the peter/world-vercel-http-cleanup branch June 28, 2026 21:59
@github-actionsgithub-actionsBot mentioned this pull request Jun 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for 1dcdafd (AI decision).

This PR is an explicit follow-up to #2573 and the unified http-core.ts it introduces depends on main-only infrastructure: the events/stream dispatcher functions (getEventsDispatcher, getStreamDispatcher, EVENTS_AGENT_OPTIONS, STREAM_RETRY_OPTIONS) and the fetch-based v4 path with trace-context injection do not exist on stable (verified — stable's events-v4.ts still uses undici.request() directly and streamer.ts uses plain fetch() without dispatchers). Backporting would pull in code referencing dispatchers absent on stable, so it builds on main-only behavior.

To override, re-run the Backport to stable workflow manually via workflow_dispatch and paste this commit SHA into the ref input:

1dcdafd422c870ec1b7dcbba8b0e733c1b6bbb14

VaguelySerious added a commit that referenced this pull request Jul 31, 2026
Backport of #2631, adapted to stable. The v3 makeRequest path, the v4
events client, the stream write/close path and the direct Vercel API
calls each had their own request envelope; the 412 -> PreconditionFailed
and firewall-challenge-429 -> retryable TRANSPORT mappings were
duplicated between utils.ts and events-v4.ts.
http-core.ts now owns that envelope: the OTEL CLIENT span and its
attributes, W3C trace-context injection, the X-Request-Time cache bust,
the per-request deadline (mapped to a typed retryable WorkflowWorldError
instead of an opaque AbortError), DEBUG logging with a curl repro, and
the status -> typed-error mapping. utils.ts, events-v4.ts, streamer.ts,
encryption.ts and resolve-latest-deployment.ts delegate to it.
Behavioral gains on stable: the v4 events path and stream writes now
emit client spans and propagate traceparent, so the backend can parent
its spans to the caller without relying on the app having undici
auto-instrumentation.
Adapted rather than cherry-picked: stable keeps #3169's bounded
headers/body timeouts (documented as firing ahead of the outer
REQUEST_TIMEOUT_MS backstop), its body-parse retry loop, and #3038's
write-vs-close stream dispatcher split. Only injectTraceContextIntoHeaders
is taken from #2363, not its WORKFLOW_TRACE_MODE machinery.
Routing v4 through the global fetch makes the H2 interceptor's
re-buffering gate live on this path (fetch streamifies request bodies),
so the new http-client test drives createWorkflowRunEventV4 concurrently
against a local H2 server: 16 writes on one session with the
interceptor, 8 without it.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@VaguelySerious@karthikscale3