Skip to content

fix(swc-plugin): count destructuring-default references in DCE usage analysis - #2398

Merged
pranaygp merged 3 commits into
mainfrom
pgp/swc-plugin-fix
Jun 16, 2026
Merged

fix(swc-plugin): count destructuring-default references in DCE usage analysis#2398
pranaygp merged 3 commits into
mainfrom
pgp/swc-plugin-fix

Conversation

@pranaygp

Copy link
Copy Markdown
Contributor

Fixes#2396.

Problem

The SWC plugin's dead-code-elimination (DCE) usage analysis did not count identifier references that appear as default values inside a destructuring pattern (e.g. const { ttl = TTL } = options;). When a module-scope const was referenced only through such a destructuring default, the DCE pass treated it as unused and stripped it from the emitted bundle — while keeping the code that referenced it. The result was a runtime ReferenceError: <const> is not defined when the default fired.

This is mode-independent (reproduces in both step and workflow mode) and class-independent (a plain top-level function exhibits it too). It shipped as a worked-around bug in the kill-switch pattern (#1858) and remains latent in workbench/vitest/workflows/cookbook/distributed-abort-controller.ts.

Root cause

In ComprehensiveUsageCollector::visit_mut_var_declarator, the collector visited only the initializer and deliberately skipped the entire var_decl.name pattern (to avoid marking the binding name as "used"). For destructuring patterns, the default-value initializer expressions live insidevar_decl.name — so references like the TTL in { ttl = TTL } were never added to used_identifiers, and remove_dead_code then pruned the still-referenced declaration.

Fix

Added visit_pat_default_initializers, which walks a binding pattern and visits only the default-value initializer expressions (and computed keys) — ObjectPatProp::KeyValue values, ObjectPatProp::Assign.value, AssignPat.right, array element defaults, rest args — while still not marking the binding names themselves as used. visit_mut_var_declarator now calls it on var_decl.name.

Function-parameter destructuring defaults were already covered: visit_mut_fn_decl visits params in full (confirmed by the existing default-parameter-usage fixture), so no change was needed there.

Tests

  • New fixture destructuring-default-references-module-const/ asserts that two module-scope consts referenced only via destructuring defaults — one inside a class static method, one inside a plain exported function — survive in both step and workflow mode. A third, genuinely-unused const is still stripped, confirming DCE is not over-broadened.
  • Verified the fixture fails on main (consts stripped) and passes with the fix.
  • Full cargo test -p swc_workflow suite green (128 fixture tests + error tests).

Spec

Updated packages/swc-plugin-workflow/spec.md (both the step-mode and workflow-mode DCE sections) to note that a reference counts even when it appears only inside a destructuring-default initializer.

Relation to prior fixes

Same symptom class as #1944 (DCE removing a declaration that surviving code references) but a distinct root cause: #1944 was about DCE ordering relative to step hoisting; this is about the usage collector never traversing destructuring-default initializers at all, and reproduces with no nested/hoisted steps involved.

🤖 Generated with Claude Code

…analysis
The DCE usage collector skipped the entire variable name pattern when
visiting a `VarDeclarator` (to avoid marking the binding name as "used").
But default-value initializers inside destructuring patterns live in that
pattern — e.g. the `TTL` in `const { ttl = TTL } = options;` — so those
references were invisible to the collector. A module-scope `const`
referenced only through such a default was treated as unused and stripped,
while the surviving code kept reading it, producing a runtime
`ReferenceError` when the default fired.
Traverse the default-value initializer expressions (and computed keys)
within destructuring patterns while still not marking the binding names
themselves, so the referenced declaration is preserved. Function-parameter
defaults were already covered (params are visited in full).
Fixes#2396.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CopilotAI review requested due to automatic review settings June 13, 2026 06:29
@pranaygp
pranaygp requested a review from a team as a code ownerJune 13, 2026 06:29
@changeset-bot

changeset-botBot commented Jun 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9006ec3

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

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

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

@vercel

vercelBot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented Jun 13, 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🥇 Express0.034s (-30.4% 🟢)1.005s (~)0.971s101.00x
💻 LocalNitro0.051s (+22.4% 🔺)1.008s (~)0.957s101.52x
🐘 PostgresNitro0.060s (-7.3% 🟢)1.012s (~)0.952s101.78x
🐘 PostgresExpress0.062s (+2.0%)1.013s (~)0.951s101.85x
🐘 PostgresNext.js (Turbopack)0.067s (-8.4% 🟢)1.012s (~)0.945s101.98x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro0.339s (-43.5% 🟢)2.573s (-3.3%)2.234s101.00x
▲ VercelExpress0.455s (-59.0% 🟢)2.707s (-24.4% 🟢)2.252s101.34x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.079s (-1.9%)2.006s (~)0.927s101.00x
💻 LocalNitro1.110s (+1.2%)2.007s (~)0.897s101.03x
🐘 PostgresExpress1.114s (~)2.011s (~)0.897s101.03x
🐘 PostgresNitro1.119s (~)2.010s (~)0.891s101.04x
🐘 PostgresNext.js (Turbopack)1.140s (-1.6%)2.010s (~)0.871s101.06x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.805s (-32.7% 🟢)3.807s (-23.9% 🟢)2.003s101.00x
▲ VercelNitro1.900s (-32.7% 🟢)3.850s (-15.3% 🟢)1.951s101.05x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express10.432s (-1.1%)11.020s (~)0.588s31.00x
🐘 PostgresNitro10.583s (~)11.017s (~)0.434s31.01x
💻 LocalNitro10.585s (+0.6%)11.024s (~)0.439s31.01x
🐘 PostgresExpress10.598s (~)11.020s (~)0.422s31.02x
🐘 PostgresNext.js (Turbopack)10.819s (~)11.018s (~)0.199s31.04x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express14.863s (-8.6% 🟢)16.580s (-11.0% 🟢)1.718s21.00x
▲ VercelNitro14.872s (-18.1% 🟢)17.769s (-10.6% 🟢)2.897s21.00x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express13.524s (-2.3%)14.027s (~)0.503s51.00x
🐘 PostgresNitro13.752s (-0.8%)14.018s (~)0.266s51.02x
🐘 PostgresExpress13.773s (~)14.023s (~)0.250s51.02x
💻 LocalNitro13.861s (~)14.028s (~)0.167s51.02x
🐘 PostgresNext.js (Turbopack)14.473s (~)15.017s (~)0.544s41.07x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express22.452s (-35.7% 🟢)24.544s (-33.5% 🟢)2.092s31.00x
▲ VercelNitro22.733s (-31.0% 🟢)24.477s (-29.8% 🟢)1.744s31.01x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express12.003s (-4.8%)12.396s (-4.8%)0.394s81.00x
🐘 PostgresExpress12.613s (~)13.019s (~)0.405s71.05x
🐘 PostgresNitro12.668s (~)13.022s (~)0.353s71.06x
💻 LocalNitro12.685s (+0.9%)13.027s (~)0.342s71.06x
🐘 PostgresNext.js (Turbopack)13.947s (~)14.306s (+1.0%)0.358s71.16x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express31.375s (-9.7% 🟢)33.491s (-9.7% 🟢)2.116s31.00x
▲ VercelNitro31.721s (-16.3% 🟢)34.300s (-13.2% 🟢)2.578s31.01x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.202s (~)2.008s (~)0.805s151.00x
💻 LocalExpress1.202s (+0.9%)2.007s (~)0.805s151.00x
🐘 PostgresNitro1.216s (~)2.008s (~)0.792s151.01x
🐘 PostgresNext.js (Turbopack)1.280s (-5.6% 🟢)2.008s (-3.3%)0.727s151.06x
💻 LocalNitro1.317s (+13.2% 🔺)2.007s (~)0.690s151.10x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.599s (+1.9%)4.260s (+4.6%)1.662s81.00x
▲ VercelNitro2.787s (-9.8% 🟢)4.617s (+0.7%)1.830s71.07x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.332s (-1.9%)2.316s (-7.6% 🟢)0.984s131.00x
🐘 PostgresNitro1.368s (+2.0%)2.509s (~)1.141s121.03x
🐘 PostgresNext.js (Turbopack)1.571s (-4.1%)2.394s (-4.6%)0.824s131.18x
💻 LocalExpress1.614s (-14.8% 🟢)2.005s (-6.8% 🟢)0.391s151.21x
💻 LocalNitro1.723s (+5.8% 🔺)2.007s (~)0.284s151.29x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.005s (-20.3% 🟢)5.201s (-3.7%)2.196s61.00x
▲ VercelExpress3.112s (-24.4% 🟢)5.163s (-14.7% 🟢)2.051s61.04x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.605s (-1.1%)4.011s (+3.2%)2.406s81.00x
🐘 PostgresNitro1.717s (+5.7% 🔺)3.760s (-9.2% 🟢)2.043s81.07x
🐘 PostgresNext.js (Turbopack)3.515s (-8.3% 🟢)4.308s (-3.1%)0.793s72.19x
💻 LocalNitro4.578s (+13.9% 🔺)5.155s (+12.5% 🔺)0.577s72.85x
💻 LocalExpress4.611s (-11.9% 🟢)5.154s (-11.9% 🟢)0.543s72.87x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.454s (-18.7% 🟢)5.446s (-4.9%)1.992s61.00x
▲ VercelExpress3.755s (-17.9% 🟢)5.803s (-11.6% 🟢)2.048s61.09x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.206s (-1.3%)2.007s (~)0.802s151.00x
🐘 PostgresExpress1.212s (~)2.008s (~)0.795s151.01x
🐘 PostgresNext.js (Turbopack)1.301s (+1.2%)2.007s (~)0.706s151.08x
💻 LocalExpress1.467s (-9.1% 🟢)2.006s (~)0.539s151.22x
💻 LocalNitro1.614s (+6.8% 🔺)2.007s (~)0.393s151.34x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.317s (-64.3% 🟢)3.984s (-50.4% 🟢)1.667s81.00x
▲ VercelExpress2.358s (+3.7%)3.953s (+2.6%)1.594s81.02x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.355s (~)2.393s (-7.7% 🟢)1.037s131.00x
🐘 PostgresExpress1.385s (-0.7%)2.592s (+4.9%)1.206s121.02x
🐘 PostgresNext.js (Turbopack)1.550s (+0.9%)2.075s (-6.7% 🟢)0.525s151.14x
💻 LocalExpress1.760s (-31.7% 🟢)2.072s (-33.3% 🟢)0.313s151.30x
💻 LocalNitro2.052s (+14.6% 🔺)2.508s (+21.0% 🔺)0.456s121.51x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.782s (-16.5% 🟢)4.629s (-13.4% 🟢)1.847s71.00x
▲ VercelNitro3.219s (-14.0% 🟢)4.836s (-11.4% 🟢)1.617s71.16x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.589s (-9.4% 🟢)4.012s (-3.0%)2.423s81.00x
🐘 PostgresNitro1.691s (-2.2%)4.148s (+6.7% 🔺)2.457s81.06x
🐘 PostgresNext.js (Turbopack)3.574s (-6.9% 🟢)4.298s (-3.3%)0.723s72.25x
💻 LocalExpress4.386s (-30.8% 🟢)5.015s (-28.6% 🟢)0.629s62.76x
💻 LocalNitro5.079s (+6.6% 🔺)5.513s (+3.1%)0.434s63.20x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.500s (-46.9% 🟢)5.977s (-28.4% 🟢)2.477s61.00x
▲ VercelExpress3.732s (-28.7% 🟢)6.025s (-13.5% 🟢)2.293s51.07x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express0.574s (-12.9% 🟢)1.004s (-3.4%)0.430s601.00x
🐘 PostgresNitro0.576s (-10.6% 🟢)1.006s (-3.4%)0.430s601.00x
🐘 PostgresExpress0.582s (-1.7%)1.007s (~)0.425s601.01x
💻 LocalNitro0.675s (+15.7% 🔺)1.040s (+3.5%)0.365s581.17x
🐘 PostgresNext.js (Turbopack)0.826s (-2.9%)1.023s (~)0.198s591.44x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.107s (-27.8% 🟢)7.860s (-22.2% 🟢)1.753s81.00x
▲ VercelExpress6.449s (-2.0%)8.127s (-5.8% 🟢)1.678s81.06x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.328s (-16.2% 🟢)2.027s (+1.0%)0.699s451.00x
🐘 PostgresExpress1.439s (~)2.030s (~)0.591s451.08x
🐘 PostgresNitro1.476s (+2.1%)2.101s (+4.6%)0.625s431.11x
💻 LocalNitro1.548s (+4.8%)2.007s (~)0.459s451.17x
🐘 PostgresNext.js (Turbopack)1.998s (-1.9%)2.315s (-9.7% 🟢)0.318s391.50x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express14.175s (-29.6% 🟢)16.338s (-25.3% 🟢)2.163s61.00x
▲ VercelNitro14.565s (-22.4% 🟢)16.655s (-19.8% 🟢)2.090s61.03x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.704s (-4.1%)3.085s (-1.7%)0.381s391.00x
🐘 PostgresExpress2.768s (~)3.112s (-0.8%)0.344s391.02x
💻 LocalExpress2.895s (-13.4% 🟢)3.245s (-19.1% 🟢)0.350s381.07x
💻 LocalNitro3.354s (+5.9% 🔺)4.011s (+2.5%)0.657s301.24x
🐘 PostgresNext.js (Turbopack)3.954s (-1.8%)4.332s (-1.6%)0.379s281.46x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express27.701s (-28.4% 🟢)30.747s (-23.9% 🟢)3.046s41.00x
▲ VercelNitro28.386s (-21.7% 🟢)31.304s (-18.0% 🟢)2.917s41.02x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.247s (-5.5% 🟢)1.006s (~)0.760s601.00x
🐘 PostgresExpress0.247s (-5.2% 🟢)1.006s (~)0.759s601.00x
🐘 PostgresNext.js (Turbopack)0.313s (+1.6%)1.006s (~)0.693s601.27x
💻 LocalExpress0.418s (-2.4%)1.004s (~)0.586s601.69x
💻 LocalNitro0.457s (+5.8% 🔺)1.005s (~)0.549s601.85x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.829s (-36.6% 🟢)3.881s (-22.8% 🟢)2.052s161.00x
▲ VercelExpress1.907s (-46.6% 🟢)3.882s (-30.6% 🟢)1.975s161.04x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.413s (~)1.029s (+2.3%)0.616s881.00x
🐘 PostgresNitro0.413s (-9.6% 🟢)1.065s (-1.3%)0.652s851.00x
🐘 PostgresNext.js (Turbopack)0.611s (-9.9% 🟢)1.105s (-13.4% 🟢)0.494s821.48x
💻 LocalExpress2.017s (-8.0% 🟢)2.537s (-11.1% 🟢)0.521s364.89x
💻 LocalNitro2.119s (+5.4% 🔺)2.715s (+11.2% 🔺)0.596s345.13x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.194s (-8.4% 🟢)5.218s (+0.9%)2.024s181.00x
▲ VercelExpress3.290s (-36.2% 🟢)5.431s (-24.8% 🟢)2.141s171.03x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.777s (-7.9% 🟢)1.367s (-7.1% 🟢)0.590s891.00x
🐘 PostgresExpress0.799s (-5.8% 🟢)1.419s (-5.9% 🟢)0.621s851.03x
🐘 PostgresNext.js (Turbopack)3.176s (+2.8%)3.889s (+1.6%)0.713s314.09x
💻 LocalExpress8.590s (-12.9% 🟢)9.254s (-10.0% 🟢)0.664s1311.06x
💻 LocalNitro8.879s (+1.8%)9.566s (+3.4%)0.687s1311.43x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.698s (-16.6% 🟢)7.627s (-11.6% 🟢)1.929s161.00x
▲ VercelNitro5.845s (-13.4% 🟢)7.928s (-8.2% 🟢)2.083s161.03x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.145s (-2.2%)2.004s (~)0.009s (-30.3% 🟢)2.014s (~)0.869s101.00x
💻 LocalNitro1.165s (+0.7%)2.005s (~)0.011s (+5.0%)2.019s (~)0.854s101.02x
🐘 PostgresNitro1.170s (-2.3%)1.995s (~)0.001s (-8.3% 🟢)2.010s (~)0.840s101.02x
🐘 PostgresExpress1.176s (~)1.998s (~)0.001s (~)2.010s (~)0.835s101.03x
🐘 PostgresNext.js (Turbopack)1.238s (~)2.000s (~)0.001s (~)2.011s (~)0.773s101.08x
💻 LocalNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.330s (-1.4%)3.506s (-0.7%)0.786s (+11.8% 🔺)4.765s (-0.5%)2.435s101.00x
▲ VercelNitro2.442s (-4.2%)3.523s (-5.1% 🟢)1.169s (+57.9% 🔺)5.176s (+4.3%)2.734s101.05x
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Express | Nitro

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.518s (-5.7% 🟢)2.009s (~)0.010s (-19.8% 🟢)2.021s (~)0.503s301.00x
🐘 PostgresNitro1.573s (-4.0%)2.001s (~)0.005s (+3.8%)2.026s (~)0.453s301.04x
💻 LocalNitro1.586s (+1.3%)2.012s (~)0.013s (+12.0% 🔺)2.028s (~)0.441s301.04x
🐘 PostgresExpress1.627s (+1.3%)2.007s (~)0.005s (+10.1% 🔺)2.027s (~)0.400s301.07x
🐘 PostgresNext.js (Turbopack)1.838s (+2.6%)2.010s (~)0.006s (+1.2%)2.026s (~)0.188s301.21x
💻 LocalNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.321s (-6.1% 🟢)7.549s (-6.2% 🟢)0.291s (+32.8% 🔺)8.340s (-5.3% 🟢)2.019s81.00x
▲ VercelExpress6.677s (-3.2%)8.183s (-2.0%)0.202s (-41.3% 🟢)8.874s (-3.8%)2.197s71.06x
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro | Express

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.779s (-4.1%)1.050s (-1.4%)0.000s (+194.7% 🔺)1.069s (-1.0%)0.290s571.00x
🐘 PostgresExpress0.811s (+1.7%)1.028s (-1.8%)0.000s (+Infinity% 🔺)1.073s (+1.2%)0.263s561.04x
🐘 PostgresNext.js (Turbopack)1.044s (+1.6%)1.581s (-2.5%)0.000s (-2.6%)1.589s (-2.6%)0.544s381.34x
💻 LocalExpress1.238s (-12.8% 🟢)1.980s (-1.7%)0.000s (-30.9% 🟢)1.982s (-1.7%)0.744s311.59x
💻 LocalNitro1.428s (+5.7% 🔺)2.015s (~)0.001s (+171.4% 🔺)2.017s (~)0.590s301.83x
💻 LocalNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.002s (-27.0% 🟢)4.590s (-20.1% 🟢)0.001s (+Infinity% 🔺)5.105s (-18.2% 🟢)2.104s121.00x
▲ VercelExpress3.054s (-17.3% 🟢)4.589s (-6.4% 🟢)0.002s (+Infinity% 🔺)5.037s (-9.3% 🟢)1.983s121.02x
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro | Express

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

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.596s (-5.8% 🟢)2.176s (~)0.000s (-50.0% 🟢)2.189s (~)0.593s281.00x
🐘 PostgresExpress1.606s (-2.8%)2.137s (-1.9%)0.000s (NaN%)2.152s (-1.6%)0.546s281.01x
🐘 PostgresNext.js (Turbopack)2.147s (-4.8%)2.652s (-2.8%)0.000s (+Infinity% 🔺)2.671s (-2.4%)0.524s231.35x
💻 LocalExpress2.630s (-20.7% 🟢)3.121s (-22.5% 🟢)0.000s (-70.0% 🟢)3.126s (-22.5% 🟢)0.496s201.65x
💻 LocalNitro3.268s (+6.1% 🔺)3.965s (+1.7%)0.001s (+85.7% 🔺)3.968s (+1.7%)0.700s162.05x
💻 LocalNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.141s (-15.8% 🟢)5.935s (-3.7%)0.003s (+Infinity% 🔺)6.439s (-5.3% 🟢)2.298s101.00x
▲ VercelExpress4.539s (-25.2% 🟢)6.277s (-17.3% 🟢)0.001s (-11.1% 🟢)6.771s (-17.3% 🟢)2.232s91.10x
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro | Express

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress20/21
🐘 PostgresNitro13/21
▲ VercelNitro11/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres12/21
Next.js (Turbopack)🐘 Postgres21/21
Nitro🐘 Postgres18/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)

📋 View full workflow run


Some benchmark jobs failed:

  • Local: failure
  • Postgres: success
  • Vercel: failure

Check the workflow run for details.

@github-actions

github-actionsBot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production129302271520
✅ 💻 Local Development190902192128
✅ 📦 Local Production190902192128
✅ 🐘 Local Postgres189502332128
✅ 🪟 Windows15200152
✅ 📋 Other88501791064
Total8043010779120

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro125027
✅ example125027
✅ express125027
✅ fastify125027
✅ hono125027
✅ nextjs-webpack14903
✅ nitro125027
✅ nuxt125027
✅ sveltekit14408
✅ vite125027
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable-lazy-discovery-disabled15200
✅ nextjs-turbopack-stable-lazy-discovery-enabled15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable-lazy-discovery-disabled15200
✅ nextjs-webpack-stable-lazy-discovery-enabled15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable-lazy-discovery-disabled15200
✅ nextjs-turbopack-stable-lazy-discovery-enabled15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable-lazy-discovery-disabled15200
✅ nextjs-webpack-stable-lazy-discovery-enabled15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable126026
✅ express-stable126026
✅ fastify-stable126026
✅ hono-stable126026
✅ nextjs-turbopack-canary132020
✅ nextjs-turbopack-stable-lazy-discovery-disabled15101
✅ nextjs-turbopack-stable-lazy-discovery-enabled15101
✅ nextjs-webpack-canary132020
✅ nextjs-webpack-stable-lazy-discovery-disabled15101
✅ nextjs-webpack-stable-lazy-discovery-enabled15101
✅ 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


Some E2E test jobs failed:

  • Vercel Prod: failure
  • Local Dev: success
  • Local Prod: success
  • Local Postgres: success
  • Windows: success

Check the workflow run for details.

CopilotAI 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.

Pull request overview

Fixes the SWC plugin’s DCE usage analysis so module-scope declarations referenced only via destructuring default initializers (e.g. const { ttl = TTL } = options) are correctly counted as “used”, preventing runtime ReferenceErrors when defaults fire.

Changes:

  • Extend ComprehensiveUsageCollector to traverse destructuring patterns and visit only default-value initializers (and computed keys), without marking binding identifiers as used.
  • Add a regression fixture covering module-scope const preservation in both step and workflow modes (and confirming truly-unused consts are still removed).
  • Update the SWC plugin spec and add a changeset for the patch release.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
packages/swc-plugin-workflow/transform/src/lib.rsAdds visit_pat_default_initializers and wires it into visit_mut_var_declarator so destructuring-default references are counted for DCE.
packages/swc-plugin-workflow/transform/tests/fixture/destructuring-default-references-module-const/input.jsNew fixture input reproducing the destructuring-default-only reference pattern and an unused const.
packages/swc-plugin-workflow/transform/tests/fixture/destructuring-default-references-module-const/output-step.jsStep-mode snapshot asserting referenced consts survive and unused const is removed.
packages/swc-plugin-workflow/transform/tests/fixture/destructuring-default-references-module-const/output-workflow.jsWorkflow-mode snapshot asserting referenced consts survive and unused const is removed.
packages/swc-plugin-workflow/spec.mdDocuments that destructuring-default initializers count as references for DCE in both modes.
.changeset/swc-destructuring-default-dce.mdPatch changeset describing the DCE fix and the prevented runtime error.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

* origin/main:
docs: document run idempotency (#2011)
Render attr_set events and run attributes in observability UI (#2393)
[ci] Fix backport job model slug (#2403)
[ci] Comment on PR when backport fails, revert to use opus 4.8 (#2400)
Update queue client to 0.3.1 (#2399)
fix(deps): upgrade esbuild to 0.28.1 (GHSA-gv7w-rqvm-qjhr) (#2395)

@TooTallNateTooTallNate left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Approve — precise root-cause fix, exhaustive pattern coverage, and I reproduced the before/after

Targeted DCE fix: the usage collector deliberately skipped var_decl.name to avoid marking binding names as used, but destructuring-default initializers (const { ttl = TTL } = options;) live inside that pattern, so references like TTL were never counted and DCE stripped the still-referenced declaration → runtime ReferenceError. The diagnosis in the PR matches the code exactly.

What I verified

  • The new visit_pat_default_initializers is exhaustive over the binding-pattern AST and visits only value positions: object KeyValue values + computed keys, Assign.value defaults, array element defaults, AssignPat.right, rest args, and expression patterns — while never descending into binding names. I traced each arm against the Pat/ObjectPatProp variants; nothing is missed and the { ttl = TTL } case correctly skips the ttl key and visits only the TTL default.
  • It cannot over-broaden DCE. The collector's visit_mut_ident blindly adds to used_identifiers, and this helper only ever adds references — the safe direction for DCE (over-counting preserves declarations, never wrongly strips). The Pat::Expr arm visiting a member expression's property ident is the same conservative behavior the collector already has when walking initializers, so it introduces no new risk.
  • Built and ran the full Rust suite: 128 fixture tests + error tests, all green.
  • Reproduced the regression: reverted just the visit_pat_default_initializers call site to simulate main, and both the step-mode and workflow-mode fixtures FAIL — the emitted output strips const TTL/const RETRIES while keeping the code that references them. Restored the fix → both pass. This is the before/after the PR claims, confirmed locally.
  • The fixture's negative case earns its keep: const UNUSED = 'dead' is still stripped in both outputs, proving the fix preserves referenced declarations only, not everything in a pattern's vicinity.
  • The fix covers the real latent bug, not just the synthetic repro: workbench/vitest/workflows/cookbook/distributed-abort-controller.ts:126 has const { ttlMs = DEFAULT_TTL_MS, graceMs = DEFAULT_GRACE_MS } = options; referencing module consts at lines 16/18 — exactly this pattern. The fixture is a faithful minimal reduction of it.
  • spec.md updated per AGENTS.md — both the step-mode and workflow-mode DCE sections gain the destructuring-default note (correct; spec.md has no version frontmatter, the bump rule applies to skills/). Changeset is a correctly-scoped @workflow/swc-plugin patch.

Notes

  • The fixture covers a class static method and a plain exported function, deliberately demonstrating the bug is class- and mode-independent (matching the issue). Good instinct to prove both, since the worked-around precedent (#1858 kill-switch) might have suggested it was class-specific.
  • Function-parameter destructuring defaults were already handled (visit_mut_fn_decl visits params in full, per the existing default-parameter-usage fixture) — the PR correctly scopes the fix to var declarators only and says so. I'd have flagged it if param defaults regressed, but they're untouched and the existing fixture still passes.
  • Distinct from #1944 as the PR notes: that was DCE ordering vs step hoisting; this is the collector never traversing these initializers at all. No nested/hoisted steps involved, which the fixture confirms.

CI lanes that are red are the session's known baseline flakes (Windows dev-server / express benchmark), unrelated to a Rust-only change. LGTM.

@pranaygp
pranaygp merged commit 53ede30 into mainJun 16, 2026
112 of 116 checks passed
@pranaygp
pranaygp deleted the pgp/swc-plugin-fix branch June 16, 2026 23:16
@github-actionsgithub-actionsBot mentioned this pull request Jun 16, 2026
github-actionsBot added a commit that referenced this pull request Jun 16, 2026
…analysis (#2398)
The DCE usage collector skipped the entire variable name pattern when
visiting a `VarDeclarator` (to avoid marking the binding name as "used").
But default-value initializers inside destructuring patterns live in that
pattern — e.g. the `TTL` in `const { ttl = TTL } = options;` — so those
references were invisible to the collector. A module-scope `const`
referenced only through such a default was treated as unused and stripped,
while the surviving code kept reading it, producing a runtime
`ReferenceError` when the default fired.
Traverse the default-value initializer expressions (and computed keys)
within destructuring patterns while still not marking the binding names
themselves, so the referenced declaration is preserved. Function-parameter
defaults were already covered (params are visited in full).
Fixes#2396.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

Backport PR opened against stable: #2465. Merge conflicts were resolved by AI — please review carefully. (backport job run)

pranaygp pushed a commit that referenced this pull request Jun 17, 2026
…analysis (#2398) (#2465)
The DCE usage collector skipped the entire variable name pattern when
visiting a `VarDeclarator` (to avoid marking the binding name as "used").
But default-value initializers inside destructuring patterns live in that
pattern — e.g. the `TTL` in `const { ttl = TTL } = options;` — so those
references were invisible to the collector. A module-scope `const`
referenced only through such a default was treated as unused and stripped,
while the surviving code kept reading it, producing a runtime
`ReferenceError` when the default fired.
Traverse the default-value initializer expressions (and computed keys)
within destructuring patterns while still not marking the binding names
themselves, so the referenced declaration is preserved. Function-parameter
defaults were already covered (params are visited in full).
Fixes#2396.
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
TooTallNate added a commit that referenced this pull request Jun 17, 2026
…default DCE test
The #2398 backport (#2465) added the destructuring-default-references-module-const
fixture but committed output-step.js verbatim from main and omitted output-client.js
entirely. Stable's SWC plugin emits a different step-registration shape
(registerStepFunction import vs. inline IIFE) and has less aggressive DCE (the
truly-unused UNUSED const is not stripped), so the copied output-step.js did not
match stable's actual output and the required output-client.js was missing.
Regenerate both fixture outputs against stable's plugin so the client_mode and
step_mode fixture tests pass. The regression being tested still holds: TTL and
RETRIES (referenced only via destructuring defaults) are preserved in all modes.
Signed-off-by: Nathan Rajlich <n@n8.io>
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.

[swc-plugin] DCE strips module-scope declarations referenced only by destructuring defaults → ReferenceError

3 participants

@pranaygp@TooTallNate
, '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" + '
fix(swc-plugin): count destructuring-default references in DCE usage analysis by pranaygp · Pull Request #2398 · vercel/workflow · GitHub
Skip to content

fix(swc-plugin): count destructuring-default references in DCE usage analysis - #2398

Merged
pranaygp merged 3 commits into
mainfrom
pgp/swc-plugin-fix
Jun 16, 2026
Merged

fix(swc-plugin): count destructuring-default references in DCE usage analysis#2398
pranaygp merged 3 commits into
mainfrom
pgp/swc-plugin-fix

Conversation

@pranaygp

Copy link
Copy Markdown
Contributor

Fixes#2396.

Problem

The SWC plugin's dead-code-elimination (DCE) usage analysis did not count identifier references that appear as default values inside a destructuring pattern (e.g. const { ttl = TTL } = options;). When a module-scope const was referenced only through such a destructuring default, the DCE pass treated it as unused and stripped it from the emitted bundle — while keeping the code that referenced it. The result was a runtime ReferenceError: <const> is not defined when the default fired.

This is mode-independent (reproduces in both step and workflow mode) and class-independent (a plain top-level function exhibits it too). It shipped as a worked-around bug in the kill-switch pattern (#1858) and remains latent in workbench/vitest/workflows/cookbook/distributed-abort-controller.ts.

Root cause

In ComprehensiveUsageCollector::visit_mut_var_declarator, the collector visited only the initializer and deliberately skipped the entire var_decl.name pattern (to avoid marking the binding name as "used"). For destructuring patterns, the default-value initializer expressions live insidevar_decl.name — so references like the TTL in { ttl = TTL } were never added to used_identifiers, and remove_dead_code then pruned the still-referenced declaration.

Fix

Added visit_pat_default_initializers, which walks a binding pattern and visits only the default-value initializer expressions (and computed keys) — ObjectPatProp::KeyValue values, ObjectPatProp::Assign.value, AssignPat.right, array element defaults, rest args — while still not marking the binding names themselves as used. visit_mut_var_declarator now calls it on var_decl.name.

Function-parameter destructuring defaults were already covered: visit_mut_fn_decl visits params in full (confirmed by the existing default-parameter-usage fixture), so no change was needed there.

Tests

  • New fixture destructuring-default-references-module-const/ asserts that two module-scope consts referenced only via destructuring defaults — one inside a class static method, one inside a plain exported function — survive in both step and workflow mode. A third, genuinely-unused const is still stripped, confirming DCE is not over-broadened.
  • Verified the fixture fails on main (consts stripped) and passes with the fix.
  • Full cargo test -p swc_workflow suite green (128 fixture tests + error tests).

Spec

Updated packages/swc-plugin-workflow/spec.md (both the step-mode and workflow-mode DCE sections) to note that a reference counts even when it appears only inside a destructuring-default initializer.

Relation to prior fixes

Same symptom class as #1944 (DCE removing a declaration that surviving code references) but a distinct root cause: #1944 was about DCE ordering relative to step hoisting; this is about the usage collector never traversing destructuring-default initializers at all, and reproduces with no nested/hoisted steps involved.

🤖 Generated with Claude Code

…analysis
The DCE usage collector skipped the entire variable name pattern when
visiting a `VarDeclarator` (to avoid marking the binding name as "used").
But default-value initializers inside destructuring patterns live in that
pattern — e.g. the `TTL` in `const { ttl = TTL } = options;` — so those
references were invisible to the collector. A module-scope `const`
referenced only through such a default was treated as unused and stripped,
while the surviving code kept reading it, producing a runtime
`ReferenceError` when the default fired.
Traverse the default-value initializer expressions (and computed keys)
within destructuring patterns while still not marking the binding names
themselves, so the referenced declaration is preserved. Function-parameter
defaults were already covered (params are visited in full).
Fixes#2396.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CopilotAI review requested due to automatic review settings June 13, 2026 06:29
@pranaygp
pranaygp requested a review from a team as a code ownerJune 13, 2026 06:29
@changeset-bot

changeset-botBot commented Jun 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9006ec3

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

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

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

@vercel

vercelBot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented Jun 13, 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🥇 Express0.034s (-30.4% 🟢)1.005s (~)0.971s101.00x
💻 LocalNitro0.051s (+22.4% 🔺)1.008s (~)0.957s101.52x
🐘 PostgresNitro0.060s (-7.3% 🟢)1.012s (~)0.952s101.78x
🐘 PostgresExpress0.062s (+2.0%)1.013s (~)0.951s101.85x
🐘 PostgresNext.js (Turbopack)0.067s (-8.4% 🟢)1.012s (~)0.945s101.98x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro0.339s (-43.5% 🟢)2.573s (-3.3%)2.234s101.00x
▲ VercelExpress0.455s (-59.0% 🟢)2.707s (-24.4% 🟢)2.252s101.34x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.079s (-1.9%)2.006s (~)0.927s101.00x
💻 LocalNitro1.110s (+1.2%)2.007s (~)0.897s101.03x
🐘 PostgresExpress1.114s (~)2.011s (~)0.897s101.03x
🐘 PostgresNitro1.119s (~)2.010s (~)0.891s101.04x
🐘 PostgresNext.js (Turbopack)1.140s (-1.6%)2.010s (~)0.871s101.06x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.805s (-32.7% 🟢)3.807s (-23.9% 🟢)2.003s101.00x
▲ VercelNitro1.900s (-32.7% 🟢)3.850s (-15.3% 🟢)1.951s101.05x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express10.432s (-1.1%)11.020s (~)0.588s31.00x
🐘 PostgresNitro10.583s (~)11.017s (~)0.434s31.01x
💻 LocalNitro10.585s (+0.6%)11.024s (~)0.439s31.01x
🐘 PostgresExpress10.598s (~)11.020s (~)0.422s31.02x
🐘 PostgresNext.js (Turbopack)10.819s (~)11.018s (~)0.199s31.04x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express14.863s (-8.6% 🟢)16.580s (-11.0% 🟢)1.718s21.00x
▲ VercelNitro14.872s (-18.1% 🟢)17.769s (-10.6% 🟢)2.897s21.00x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express13.524s (-2.3%)14.027s (~)0.503s51.00x
🐘 PostgresNitro13.752s (-0.8%)14.018s (~)0.266s51.02x
🐘 PostgresExpress13.773s (~)14.023s (~)0.250s51.02x
💻 LocalNitro13.861s (~)14.028s (~)0.167s51.02x
🐘 PostgresNext.js (Turbopack)14.473s (~)15.017s (~)0.544s41.07x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express22.452s (-35.7% 🟢)24.544s (-33.5% 🟢)2.092s31.00x
▲ VercelNitro22.733s (-31.0% 🟢)24.477s (-29.8% 🟢)1.744s31.01x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express12.003s (-4.8%)12.396s (-4.8%)0.394s81.00x
🐘 PostgresExpress12.613s (~)13.019s (~)0.405s71.05x
🐘 PostgresNitro12.668s (~)13.022s (~)0.353s71.06x
💻 LocalNitro12.685s (+0.9%)13.027s (~)0.342s71.06x
🐘 PostgresNext.js (Turbopack)13.947s (~)14.306s (+1.0%)0.358s71.16x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express31.375s (-9.7% 🟢)33.491s (-9.7% 🟢)2.116s31.00x
▲ VercelNitro31.721s (-16.3% 🟢)34.300s (-13.2% 🟢)2.578s31.01x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.202s (~)2.008s (~)0.805s151.00x
💻 LocalExpress1.202s (+0.9%)2.007s (~)0.805s151.00x
🐘 PostgresNitro1.216s (~)2.008s (~)0.792s151.01x
🐘 PostgresNext.js (Turbopack)1.280s (-5.6% 🟢)2.008s (-3.3%)0.727s151.06x
💻 LocalNitro1.317s (+13.2% 🔺)2.007s (~)0.690s151.10x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.599s (+1.9%)4.260s (+4.6%)1.662s81.00x
▲ VercelNitro2.787s (-9.8% 🟢)4.617s (+0.7%)1.830s71.07x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.332s (-1.9%)2.316s (-7.6% 🟢)0.984s131.00x
🐘 PostgresNitro1.368s (+2.0%)2.509s (~)1.141s121.03x
🐘 PostgresNext.js (Turbopack)1.571s (-4.1%)2.394s (-4.6%)0.824s131.18x
💻 LocalExpress1.614s (-14.8% 🟢)2.005s (-6.8% 🟢)0.391s151.21x
💻 LocalNitro1.723s (+5.8% 🔺)2.007s (~)0.284s151.29x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.005s (-20.3% 🟢)5.201s (-3.7%)2.196s61.00x
▲ VercelExpress3.112s (-24.4% 🟢)5.163s (-14.7% 🟢)2.051s61.04x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.605s (-1.1%)4.011s (+3.2%)2.406s81.00x
🐘 PostgresNitro1.717s (+5.7% 🔺)3.760s (-9.2% 🟢)2.043s81.07x
🐘 PostgresNext.js (Turbopack)3.515s (-8.3% 🟢)4.308s (-3.1%)0.793s72.19x
💻 LocalNitro4.578s (+13.9% 🔺)5.155s (+12.5% 🔺)0.577s72.85x
💻 LocalExpress4.611s (-11.9% 🟢)5.154s (-11.9% 🟢)0.543s72.87x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.454s (-18.7% 🟢)5.446s (-4.9%)1.992s61.00x
▲ VercelExpress3.755s (-17.9% 🟢)5.803s (-11.6% 🟢)2.048s61.09x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.206s (-1.3%)2.007s (~)0.802s151.00x
🐘 PostgresExpress1.212s (~)2.008s (~)0.795s151.01x
🐘 PostgresNext.js (Turbopack)1.301s (+1.2%)2.007s (~)0.706s151.08x
💻 LocalExpress1.467s (-9.1% 🟢)2.006s (~)0.539s151.22x
💻 LocalNitro1.614s (+6.8% 🔺)2.007s (~)0.393s151.34x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.317s (-64.3% 🟢)3.984s (-50.4% 🟢)1.667s81.00x
▲ VercelExpress2.358s (+3.7%)3.953s (+2.6%)1.594s81.02x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.355s (~)2.393s (-7.7% 🟢)1.037s131.00x
🐘 PostgresExpress1.385s (-0.7%)2.592s (+4.9%)1.206s121.02x
🐘 PostgresNext.js (Turbopack)1.550s (+0.9%)2.075s (-6.7% 🟢)0.525s151.14x
💻 LocalExpress1.760s (-31.7% 🟢)2.072s (-33.3% 🟢)0.313s151.30x
💻 LocalNitro2.052s (+14.6% 🔺)2.508s (+21.0% 🔺)0.456s121.51x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.782s (-16.5% 🟢)4.629s (-13.4% 🟢)1.847s71.00x
▲ VercelNitro3.219s (-14.0% 🟢)4.836s (-11.4% 🟢)1.617s71.16x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.589s (-9.4% 🟢)4.012s (-3.0%)2.423s81.00x
🐘 PostgresNitro1.691s (-2.2%)4.148s (+6.7% 🔺)2.457s81.06x
🐘 PostgresNext.js (Turbopack)3.574s (-6.9% 🟢)4.298s (-3.3%)0.723s72.25x
💻 LocalExpress4.386s (-30.8% 🟢)5.015s (-28.6% 🟢)0.629s62.76x
💻 LocalNitro5.079s (+6.6% 🔺)5.513s (+3.1%)0.434s63.20x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.500s (-46.9% 🟢)5.977s (-28.4% 🟢)2.477s61.00x
▲ VercelExpress3.732s (-28.7% 🟢)6.025s (-13.5% 🟢)2.293s51.07x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express0.574s (-12.9% 🟢)1.004s (-3.4%)0.430s601.00x
🐘 PostgresNitro0.576s (-10.6% 🟢)1.006s (-3.4%)0.430s601.00x
🐘 PostgresExpress0.582s (-1.7%)1.007s (~)0.425s601.01x
💻 LocalNitro0.675s (+15.7% 🔺)1.040s (+3.5%)0.365s581.17x
🐘 PostgresNext.js (Turbopack)0.826s (-2.9%)1.023s (~)0.198s591.44x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.107s (-27.8% 🟢)7.860s (-22.2% 🟢)1.753s81.00x
▲ VercelExpress6.449s (-2.0%)8.127s (-5.8% 🟢)1.678s81.06x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.328s (-16.2% 🟢)2.027s (+1.0%)0.699s451.00x
🐘 PostgresExpress1.439s (~)2.030s (~)0.591s451.08x
🐘 PostgresNitro1.476s (+2.1%)2.101s (+4.6%)0.625s431.11x
💻 LocalNitro1.548s (+4.8%)2.007s (~)0.459s451.17x
🐘 PostgresNext.js (Turbopack)1.998s (-1.9%)2.315s (-9.7% 🟢)0.318s391.50x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express14.175s (-29.6% 🟢)16.338s (-25.3% 🟢)2.163s61.00x
▲ VercelNitro14.565s (-22.4% 🟢)16.655s (-19.8% 🟢)2.090s61.03x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.704s (-4.1%)3.085s (-1.7%)0.381s391.00x
🐘 PostgresExpress2.768s (~)3.112s (-0.8%)0.344s391.02x
💻 LocalExpress2.895s (-13.4% 🟢)3.245s (-19.1% 🟢)0.350s381.07x
💻 LocalNitro3.354s (+5.9% 🔺)4.011s (+2.5%)0.657s301.24x
🐘 PostgresNext.js (Turbopack)3.954s (-1.8%)4.332s (-1.6%)0.379s281.46x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express27.701s (-28.4% 🟢)30.747s (-23.9% 🟢)3.046s41.00x
▲ VercelNitro28.386s (-21.7% 🟢)31.304s (-18.0% 🟢)2.917s41.02x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.247s (-5.5% 🟢)1.006s (~)0.760s601.00x
🐘 PostgresExpress0.247s (-5.2% 🟢)1.006s (~)0.759s601.00x
🐘 PostgresNext.js (Turbopack)0.313s (+1.6%)1.006s (~)0.693s601.27x
💻 LocalExpress0.418s (-2.4%)1.004s (~)0.586s601.69x
💻 LocalNitro0.457s (+5.8% 🔺)1.005s (~)0.549s601.85x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.829s (-36.6% 🟢)3.881s (-22.8% 🟢)2.052s161.00x
▲ VercelExpress1.907s (-46.6% 🟢)3.882s (-30.6% 🟢)1.975s161.04x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.413s (~)1.029s (+2.3%)0.616s881.00x
🐘 PostgresNitro0.413s (-9.6% 🟢)1.065s (-1.3%)0.652s851.00x
🐘 PostgresNext.js (Turbopack)0.611s (-9.9% 🟢)1.105s (-13.4% 🟢)0.494s821.48x
💻 LocalExpress2.017s (-8.0% 🟢)2.537s (-11.1% 🟢)0.521s364.89x
💻 LocalNitro2.119s (+5.4% 🔺)2.715s (+11.2% 🔺)0.596s345.13x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.194s (-8.4% 🟢)5.218s (+0.9%)2.024s181.00x
▲ VercelExpress3.290s (-36.2% 🟢)5.431s (-24.8% 🟢)2.141s171.03x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.777s (-7.9% 🟢)1.367s (-7.1% 🟢)0.590s891.00x
🐘 PostgresExpress0.799s (-5.8% 🟢)1.419s (-5.9% 🟢)0.621s851.03x
🐘 PostgresNext.js (Turbopack)3.176s (+2.8%)3.889s (+1.6%)0.713s314.09x
💻 LocalExpress8.590s (-12.9% 🟢)9.254s (-10.0% 🟢)0.664s1311.06x
💻 LocalNitro8.879s (+1.8%)9.566s (+3.4%)0.687s1311.43x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.698s (-16.6% 🟢)7.627s (-11.6% 🟢)1.929s161.00x
▲ VercelNitro5.845s (-13.4% 🟢)7.928s (-8.2% 🟢)2.083s161.03x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.145s (-2.2%)2.004s (~)0.009s (-30.3% 🟢)2.014s (~)0.869s101.00x
💻 LocalNitro1.165s (+0.7%)2.005s (~)0.011s (+5.0%)2.019s (~)0.854s101.02x
🐘 PostgresNitro1.170s (-2.3%)1.995s (~)0.001s (-8.3% 🟢)2.010s (~)0.840s101.02x
🐘 PostgresExpress1.176s (~)1.998s (~)0.001s (~)2.010s (~)0.835s101.03x
🐘 PostgresNext.js (Turbopack)1.238s (~)2.000s (~)0.001s (~)2.011s (~)0.773s101.08x
💻 LocalNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.330s (-1.4%)3.506s (-0.7%)0.786s (+11.8% 🔺)4.765s (-0.5%)2.435s101.00x
▲ VercelNitro2.442s (-4.2%)3.523s (-5.1% 🟢)1.169s (+57.9% 🔺)5.176s (+4.3%)2.734s101.05x
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Express | Nitro

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.518s (-5.7% 🟢)2.009s (~)0.010s (-19.8% 🟢)2.021s (~)0.503s301.00x
🐘 PostgresNitro1.573s (-4.0%)2.001s (~)0.005s (+3.8%)2.026s (~)0.453s301.04x
💻 LocalNitro1.586s (+1.3%)2.012s (~)0.013s (+12.0% 🔺)2.028s (~)0.441s301.04x
🐘 PostgresExpress1.627s (+1.3%)2.007s (~)0.005s (+10.1% 🔺)2.027s (~)0.400s301.07x
🐘 PostgresNext.js (Turbopack)1.838s (+2.6%)2.010s (~)0.006s (+1.2%)2.026s (~)0.188s301.21x
💻 LocalNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.321s (-6.1% 🟢)7.549s (-6.2% 🟢)0.291s (+32.8% 🔺)8.340s (-5.3% 🟢)2.019s81.00x
▲ VercelExpress6.677s (-3.2%)8.183s (-2.0%)0.202s (-41.3% 🟢)8.874s (-3.8%)2.197s71.06x
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro | Express

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.779s (-4.1%)1.050s (-1.4%)0.000s (+194.7% 🔺)1.069s (-1.0%)0.290s571.00x
🐘 PostgresExpress0.811s (+1.7%)1.028s (-1.8%)0.000s (+Infinity% 🔺)1.073s (+1.2%)0.263s561.04x
🐘 PostgresNext.js (Turbopack)1.044s (+1.6%)1.581s (-2.5%)0.000s (-2.6%)1.589s (-2.6%)0.544s381.34x
💻 LocalExpress1.238s (-12.8% 🟢)1.980s (-1.7%)0.000s (-30.9% 🟢)1.982s (-1.7%)0.744s311.59x
💻 LocalNitro1.428s (+5.7% 🔺)2.015s (~)0.001s (+171.4% 🔺)2.017s (~)0.590s301.83x
💻 LocalNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.002s (-27.0% 🟢)4.590s (-20.1% 🟢)0.001s (+Infinity% 🔺)5.105s (-18.2% 🟢)2.104s121.00x
▲ VercelExpress3.054s (-17.3% 🟢)4.589s (-6.4% 🟢)0.002s (+Infinity% 🔺)5.037s (-9.3% 🟢)1.983s121.02x
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro | Express

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

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.596s (-5.8% 🟢)2.176s (~)0.000s (-50.0% 🟢)2.189s (~)0.593s281.00x
🐘 PostgresExpress1.606s (-2.8%)2.137s (-1.9%)0.000s (NaN%)2.152s (-1.6%)0.546s281.01x
🐘 PostgresNext.js (Turbopack)2.147s (-4.8%)2.652s (-2.8%)0.000s (+Infinity% 🔺)2.671s (-2.4%)0.524s231.35x
💻 LocalExpress2.630s (-20.7% 🟢)3.121s (-22.5% 🟢)0.000s (-70.0% 🟢)3.126s (-22.5% 🟢)0.496s201.65x
💻 LocalNitro3.268s (+6.1% 🔺)3.965s (+1.7%)0.001s (+85.7% 🔺)3.968s (+1.7%)0.700s162.05x
💻 LocalNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.141s (-15.8% 🟢)5.935s (-3.7%)0.003s (+Infinity% 🔺)6.439s (-5.3% 🟢)2.298s101.00x
▲ VercelExpress4.539s (-25.2% 🟢)6.277s (-17.3% 🟢)0.001s (-11.1% 🟢)6.771s (-17.3% 🟢)2.232s91.10x
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro | Express

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress20/21
🐘 PostgresNitro13/21
▲ VercelNitro11/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres12/21
Next.js (Turbopack)🐘 Postgres21/21
Nitro🐘 Postgres18/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)

📋 View full workflow run


Some benchmark jobs failed:

  • Local: failure
  • Postgres: success
  • Vercel: failure

Check the workflow run for details.

@github-actions

github-actionsBot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production129302271520
✅ 💻 Local Development190902192128
✅ 📦 Local Production190902192128
✅ 🐘 Local Postgres189502332128
✅ 🪟 Windows15200152
✅ 📋 Other88501791064
Total8043010779120

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro125027
✅ example125027
✅ express125027
✅ fastify125027
✅ hono125027
✅ nextjs-webpack14903
✅ nitro125027
✅ nuxt125027
✅ sveltekit14408
✅ vite125027
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable-lazy-discovery-disabled15200
✅ nextjs-turbopack-stable-lazy-discovery-enabled15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable-lazy-discovery-disabled15200
✅ nextjs-webpack-stable-lazy-discovery-enabled15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable-lazy-discovery-disabled15200
✅ nextjs-turbopack-stable-lazy-discovery-enabled15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable-lazy-discovery-disabled15200
✅ nextjs-webpack-stable-lazy-discovery-enabled15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable126026
✅ express-stable126026
✅ fastify-stable126026
✅ hono-stable126026
✅ nextjs-turbopack-canary132020
✅ nextjs-turbopack-stable-lazy-discovery-disabled15101
✅ nextjs-turbopack-stable-lazy-discovery-enabled15101
✅ nextjs-webpack-canary132020
✅ nextjs-webpack-stable-lazy-discovery-disabled15101
✅ nextjs-webpack-stable-lazy-discovery-enabled15101
✅ 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


Some E2E test jobs failed:

  • Vercel Prod: failure
  • Local Dev: success
  • Local Prod: success
  • Local Postgres: success
  • Windows: success

Check the workflow run for details.

CopilotAI 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.

Pull request overview

Fixes the SWC plugin’s DCE usage analysis so module-scope declarations referenced only via destructuring default initializers (e.g. const { ttl = TTL } = options) are correctly counted as “used”, preventing runtime ReferenceErrors when defaults fire.

Changes:

  • Extend ComprehensiveUsageCollector to traverse destructuring patterns and visit only default-value initializers (and computed keys), without marking binding identifiers as used.
  • Add a regression fixture covering module-scope const preservation in both step and workflow modes (and confirming truly-unused consts are still removed).
  • Update the SWC plugin spec and add a changeset for the patch release.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
packages/swc-plugin-workflow/transform/src/lib.rsAdds visit_pat_default_initializers and wires it into visit_mut_var_declarator so destructuring-default references are counted for DCE.
packages/swc-plugin-workflow/transform/tests/fixture/destructuring-default-references-module-const/input.jsNew fixture input reproducing the destructuring-default-only reference pattern and an unused const.
packages/swc-plugin-workflow/transform/tests/fixture/destructuring-default-references-module-const/output-step.jsStep-mode snapshot asserting referenced consts survive and unused const is removed.
packages/swc-plugin-workflow/transform/tests/fixture/destructuring-default-references-module-const/output-workflow.jsWorkflow-mode snapshot asserting referenced consts survive and unused const is removed.
packages/swc-plugin-workflow/spec.mdDocuments that destructuring-default initializers count as references for DCE in both modes.
.changeset/swc-destructuring-default-dce.mdPatch changeset describing the DCE fix and the prevented runtime error.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

* origin/main:
docs: document run idempotency (#2011)
Render attr_set events and run attributes in observability UI (#2393)
[ci] Fix backport job model slug (#2403)
[ci] Comment on PR when backport fails, revert to use opus 4.8 (#2400)
Update queue client to 0.3.1 (#2399)
fix(deps): upgrade esbuild to 0.28.1 (GHSA-gv7w-rqvm-qjhr) (#2395)

@TooTallNateTooTallNate left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Approve — precise root-cause fix, exhaustive pattern coverage, and I reproduced the before/after

Targeted DCE fix: the usage collector deliberately skipped var_decl.name to avoid marking binding names as used, but destructuring-default initializers (const { ttl = TTL } = options;) live inside that pattern, so references like TTL were never counted and DCE stripped the still-referenced declaration → runtime ReferenceError. The diagnosis in the PR matches the code exactly.

What I verified

  • The new visit_pat_default_initializers is exhaustive over the binding-pattern AST and visits only value positions: object KeyValue values + computed keys, Assign.value defaults, array element defaults, AssignPat.right, rest args, and expression patterns — while never descending into binding names. I traced each arm against the Pat/ObjectPatProp variants; nothing is missed and the { ttl = TTL } case correctly skips the ttl key and visits only the TTL default.
  • It cannot over-broaden DCE. The collector's visit_mut_ident blindly adds to used_identifiers, and this helper only ever adds references — the safe direction for DCE (over-counting preserves declarations, never wrongly strips). The Pat::Expr arm visiting a member expression's property ident is the same conservative behavior the collector already has when walking initializers, so it introduces no new risk.
  • Built and ran the full Rust suite: 128 fixture tests + error tests, all green.
  • Reproduced the regression: reverted just the visit_pat_default_initializers call site to simulate main, and both the step-mode and workflow-mode fixtures FAIL — the emitted output strips const TTL/const RETRIES while keeping the code that references them. Restored the fix → both pass. This is the before/after the PR claims, confirmed locally.
  • The fixture's negative case earns its keep: const UNUSED = 'dead' is still stripped in both outputs, proving the fix preserves referenced declarations only, not everything in a pattern's vicinity.
  • The fix covers the real latent bug, not just the synthetic repro: workbench/vitest/workflows/cookbook/distributed-abort-controller.ts:126 has const { ttlMs = DEFAULT_TTL_MS, graceMs = DEFAULT_GRACE_MS } = options; referencing module consts at lines 16/18 — exactly this pattern. The fixture is a faithful minimal reduction of it.
  • spec.md updated per AGENTS.md — both the step-mode and workflow-mode DCE sections gain the destructuring-default note (correct; spec.md has no version frontmatter, the bump rule applies to skills/). Changeset is a correctly-scoped @workflow/swc-plugin patch.

Notes

  • The fixture covers a class static method and a plain exported function, deliberately demonstrating the bug is class- and mode-independent (matching the issue). Good instinct to prove both, since the worked-around precedent (#1858 kill-switch) might have suggested it was class-specific.
  • Function-parameter destructuring defaults were already handled (visit_mut_fn_decl visits params in full, per the existing default-parameter-usage fixture) — the PR correctly scopes the fix to var declarators only and says so. I'd have flagged it if param defaults regressed, but they're untouched and the existing fixture still passes.
  • Distinct from #1944 as the PR notes: that was DCE ordering vs step hoisting; this is the collector never traversing these initializers at all. No nested/hoisted steps involved, which the fixture confirms.

CI lanes that are red are the session's known baseline flakes (Windows dev-server / express benchmark), unrelated to a Rust-only change. LGTM.

@pranaygp
pranaygp merged commit 53ede30 into mainJun 16, 2026
112 of 116 checks passed
@pranaygp
pranaygp deleted the pgp/swc-plugin-fix branch June 16, 2026 23:16
@github-actionsgithub-actionsBot mentioned this pull request Jun 16, 2026
github-actionsBot added a commit that referenced this pull request Jun 16, 2026
…analysis (#2398)
The DCE usage collector skipped the entire variable name pattern when
visiting a `VarDeclarator` (to avoid marking the binding name as "used").
But default-value initializers inside destructuring patterns live in that
pattern — e.g. the `TTL` in `const { ttl = TTL } = options;` — so those
references were invisible to the collector. A module-scope `const`
referenced only through such a default was treated as unused and stripped,
while the surviving code kept reading it, producing a runtime
`ReferenceError` when the default fired.
Traverse the default-value initializer expressions (and computed keys)
within destructuring patterns while still not marking the binding names
themselves, so the referenced declaration is preserved. Function-parameter
defaults were already covered (params are visited in full).
Fixes#2396.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

Backport PR opened against stable: #2465. Merge conflicts were resolved by AI — please review carefully. (backport job run)

pranaygp pushed a commit that referenced this pull request Jun 17, 2026
…analysis (#2398) (#2465)
The DCE usage collector skipped the entire variable name pattern when
visiting a `VarDeclarator` (to avoid marking the binding name as "used").
But default-value initializers inside destructuring patterns live in that
pattern — e.g. the `TTL` in `const { ttl = TTL } = options;` — so those
references were invisible to the collector. A module-scope `const`
referenced only through such a default was treated as unused and stripped,
while the surviving code kept reading it, producing a runtime
`ReferenceError` when the default fired.
Traverse the default-value initializer expressions (and computed keys)
within destructuring patterns while still not marking the binding names
themselves, so the referenced declaration is preserved. Function-parameter
defaults were already covered (params are visited in full).
Fixes#2396.
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
TooTallNate added a commit that referenced this pull request Jun 17, 2026
…default DCE test
The #2398 backport (#2465) added the destructuring-default-references-module-const
fixture but committed output-step.js verbatim from main and omitted output-client.js
entirely. Stable's SWC plugin emits a different step-registration shape
(registerStepFunction import vs. inline IIFE) and has less aggressive DCE (the
truly-unused UNUSED const is not stripped), so the copied output-step.js did not
match stable's actual output and the required output-client.js was missing.
Regenerate both fixture outputs against stable's plugin so the client_mode and
step_mode fixture tests pass. The regression being tested still holds: TTL and
RETRIES (referenced only via destructuring defaults) are preserved in all modes.
Signed-off-by: Nathan Rajlich <n@n8.io>
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.

[swc-plugin] DCE strips module-scope declarations referenced only by destructuring defaults → ReferenceError

3 participants

@pranaygp@TooTallNate
, '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('^' + ".*" + ' fix(swc-plugin): count destructuring-default references in DCE usage analysis by pranaygp · Pull Request #2398 · vercel/workflow · GitHub
Skip to content

fix(swc-plugin): count destructuring-default references in DCE usage analysis - #2398

Merged
pranaygp merged 3 commits into
mainfrom
pgp/swc-plugin-fix
Jun 16, 2026
Merged

fix(swc-plugin): count destructuring-default references in DCE usage analysis#2398
pranaygp merged 3 commits into
mainfrom
pgp/swc-plugin-fix

Conversation

@pranaygp

Copy link
Copy Markdown
Contributor

Fixes#2396.

Problem

The SWC plugin's dead-code-elimination (DCE) usage analysis did not count identifier references that appear as default values inside a destructuring pattern (e.g. const { ttl = TTL } = options;). When a module-scope const was referenced only through such a destructuring default, the DCE pass treated it as unused and stripped it from the emitted bundle — while keeping the code that referenced it. The result was a runtime ReferenceError: <const> is not defined when the default fired.

This is mode-independent (reproduces in both step and workflow mode) and class-independent (a plain top-level function exhibits it too). It shipped as a worked-around bug in the kill-switch pattern (#1858) and remains latent in workbench/vitest/workflows/cookbook/distributed-abort-controller.ts.

Root cause

In ComprehensiveUsageCollector::visit_mut_var_declarator, the collector visited only the initializer and deliberately skipped the entire var_decl.name pattern (to avoid marking the binding name as "used"). For destructuring patterns, the default-value initializer expressions live insidevar_decl.name — so references like the TTL in { ttl = TTL } were never added to used_identifiers, and remove_dead_code then pruned the still-referenced declaration.

Fix

Added visit_pat_default_initializers, which walks a binding pattern and visits only the default-value initializer expressions (and computed keys) — ObjectPatProp::KeyValue values, ObjectPatProp::Assign.value, AssignPat.right, array element defaults, rest args — while still not marking the binding names themselves as used. visit_mut_var_declarator now calls it on var_decl.name.

Function-parameter destructuring defaults were already covered: visit_mut_fn_decl visits params in full (confirmed by the existing default-parameter-usage fixture), so no change was needed there.

Tests

  • New fixture destructuring-default-references-module-const/ asserts that two module-scope consts referenced only via destructuring defaults — one inside a class static method, one inside a plain exported function — survive in both step and workflow mode. A third, genuinely-unused const is still stripped, confirming DCE is not over-broadened.
  • Verified the fixture fails on main (consts stripped) and passes with the fix.
  • Full cargo test -p swc_workflow suite green (128 fixture tests + error tests).

Spec

Updated packages/swc-plugin-workflow/spec.md (both the step-mode and workflow-mode DCE sections) to note that a reference counts even when it appears only inside a destructuring-default initializer.

Relation to prior fixes

Same symptom class as #1944 (DCE removing a declaration that surviving code references) but a distinct root cause: #1944 was about DCE ordering relative to step hoisting; this is about the usage collector never traversing destructuring-default initializers at all, and reproduces with no nested/hoisted steps involved.

🤖 Generated with Claude Code

…analysis
The DCE usage collector skipped the entire variable name pattern when
visiting a `VarDeclarator` (to avoid marking the binding name as "used").
But default-value initializers inside destructuring patterns live in that
pattern — e.g. the `TTL` in `const { ttl = TTL } = options;` — so those
references were invisible to the collector. A module-scope `const`
referenced only through such a default was treated as unused and stripped,
while the surviving code kept reading it, producing a runtime
`ReferenceError` when the default fired.
Traverse the default-value initializer expressions (and computed keys)
within destructuring patterns while still not marking the binding names
themselves, so the referenced declaration is preserved. Function-parameter
defaults were already covered (params are visited in full).
Fixes#2396.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CopilotAI review requested due to automatic review settings June 13, 2026 06:29
@pranaygp
pranaygp requested a review from a team as a code ownerJune 13, 2026 06:29
@changeset-bot

changeset-botBot commented Jun 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9006ec3

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

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

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

@vercel

vercelBot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented Jun 13, 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🥇 Express0.034s (-30.4% 🟢)1.005s (~)0.971s101.00x
💻 LocalNitro0.051s (+22.4% 🔺)1.008s (~)0.957s101.52x
🐘 PostgresNitro0.060s (-7.3% 🟢)1.012s (~)0.952s101.78x
🐘 PostgresExpress0.062s (+2.0%)1.013s (~)0.951s101.85x
🐘 PostgresNext.js (Turbopack)0.067s (-8.4% 🟢)1.012s (~)0.945s101.98x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro0.339s (-43.5% 🟢)2.573s (-3.3%)2.234s101.00x
▲ VercelExpress0.455s (-59.0% 🟢)2.707s (-24.4% 🟢)2.252s101.34x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.079s (-1.9%)2.006s (~)0.927s101.00x
💻 LocalNitro1.110s (+1.2%)2.007s (~)0.897s101.03x
🐘 PostgresExpress1.114s (~)2.011s (~)0.897s101.03x
🐘 PostgresNitro1.119s (~)2.010s (~)0.891s101.04x
🐘 PostgresNext.js (Turbopack)1.140s (-1.6%)2.010s (~)0.871s101.06x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.805s (-32.7% 🟢)3.807s (-23.9% 🟢)2.003s101.00x
▲ VercelNitro1.900s (-32.7% 🟢)3.850s (-15.3% 🟢)1.951s101.05x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express10.432s (-1.1%)11.020s (~)0.588s31.00x
🐘 PostgresNitro10.583s (~)11.017s (~)0.434s31.01x
💻 LocalNitro10.585s (+0.6%)11.024s (~)0.439s31.01x
🐘 PostgresExpress10.598s (~)11.020s (~)0.422s31.02x
🐘 PostgresNext.js (Turbopack)10.819s (~)11.018s (~)0.199s31.04x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express14.863s (-8.6% 🟢)16.580s (-11.0% 🟢)1.718s21.00x
▲ VercelNitro14.872s (-18.1% 🟢)17.769s (-10.6% 🟢)2.897s21.00x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express13.524s (-2.3%)14.027s (~)0.503s51.00x
🐘 PostgresNitro13.752s (-0.8%)14.018s (~)0.266s51.02x
🐘 PostgresExpress13.773s (~)14.023s (~)0.250s51.02x
💻 LocalNitro13.861s (~)14.028s (~)0.167s51.02x
🐘 PostgresNext.js (Turbopack)14.473s (~)15.017s (~)0.544s41.07x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express22.452s (-35.7% 🟢)24.544s (-33.5% 🟢)2.092s31.00x
▲ VercelNitro22.733s (-31.0% 🟢)24.477s (-29.8% 🟢)1.744s31.01x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express12.003s (-4.8%)12.396s (-4.8%)0.394s81.00x
🐘 PostgresExpress12.613s (~)13.019s (~)0.405s71.05x
🐘 PostgresNitro12.668s (~)13.022s (~)0.353s71.06x
💻 LocalNitro12.685s (+0.9%)13.027s (~)0.342s71.06x
🐘 PostgresNext.js (Turbopack)13.947s (~)14.306s (+1.0%)0.358s71.16x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express31.375s (-9.7% 🟢)33.491s (-9.7% 🟢)2.116s31.00x
▲ VercelNitro31.721s (-16.3% 🟢)34.300s (-13.2% 🟢)2.578s31.01x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.202s (~)2.008s (~)0.805s151.00x
💻 LocalExpress1.202s (+0.9%)2.007s (~)0.805s151.00x
🐘 PostgresNitro1.216s (~)2.008s (~)0.792s151.01x
🐘 PostgresNext.js (Turbopack)1.280s (-5.6% 🟢)2.008s (-3.3%)0.727s151.06x
💻 LocalNitro1.317s (+13.2% 🔺)2.007s (~)0.690s151.10x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.599s (+1.9%)4.260s (+4.6%)1.662s81.00x
▲ VercelNitro2.787s (-9.8% 🟢)4.617s (+0.7%)1.830s71.07x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.332s (-1.9%)2.316s (-7.6% 🟢)0.984s131.00x
🐘 PostgresNitro1.368s (+2.0%)2.509s (~)1.141s121.03x
🐘 PostgresNext.js (Turbopack)1.571s (-4.1%)2.394s (-4.6%)0.824s131.18x
💻 LocalExpress1.614s (-14.8% 🟢)2.005s (-6.8% 🟢)0.391s151.21x
💻 LocalNitro1.723s (+5.8% 🔺)2.007s (~)0.284s151.29x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.005s (-20.3% 🟢)5.201s (-3.7%)2.196s61.00x
▲ VercelExpress3.112s (-24.4% 🟢)5.163s (-14.7% 🟢)2.051s61.04x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.605s (-1.1%)4.011s (+3.2%)2.406s81.00x
🐘 PostgresNitro1.717s (+5.7% 🔺)3.760s (-9.2% 🟢)2.043s81.07x
🐘 PostgresNext.js (Turbopack)3.515s (-8.3% 🟢)4.308s (-3.1%)0.793s72.19x
💻 LocalNitro4.578s (+13.9% 🔺)5.155s (+12.5% 🔺)0.577s72.85x
💻 LocalExpress4.611s (-11.9% 🟢)5.154s (-11.9% 🟢)0.543s72.87x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.454s (-18.7% 🟢)5.446s (-4.9%)1.992s61.00x
▲ VercelExpress3.755s (-17.9% 🟢)5.803s (-11.6% 🟢)2.048s61.09x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.206s (-1.3%)2.007s (~)0.802s151.00x
🐘 PostgresExpress1.212s (~)2.008s (~)0.795s151.01x
🐘 PostgresNext.js (Turbopack)1.301s (+1.2%)2.007s (~)0.706s151.08x
💻 LocalExpress1.467s (-9.1% 🟢)2.006s (~)0.539s151.22x
💻 LocalNitro1.614s (+6.8% 🔺)2.007s (~)0.393s151.34x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.317s (-64.3% 🟢)3.984s (-50.4% 🟢)1.667s81.00x
▲ VercelExpress2.358s (+3.7%)3.953s (+2.6%)1.594s81.02x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.355s (~)2.393s (-7.7% 🟢)1.037s131.00x
🐘 PostgresExpress1.385s (-0.7%)2.592s (+4.9%)1.206s121.02x
🐘 PostgresNext.js (Turbopack)1.550s (+0.9%)2.075s (-6.7% 🟢)0.525s151.14x
💻 LocalExpress1.760s (-31.7% 🟢)2.072s (-33.3% 🟢)0.313s151.30x
💻 LocalNitro2.052s (+14.6% 🔺)2.508s (+21.0% 🔺)0.456s121.51x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.782s (-16.5% 🟢)4.629s (-13.4% 🟢)1.847s71.00x
▲ VercelNitro3.219s (-14.0% 🟢)4.836s (-11.4% 🟢)1.617s71.16x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.589s (-9.4% 🟢)4.012s (-3.0%)2.423s81.00x
🐘 PostgresNitro1.691s (-2.2%)4.148s (+6.7% 🔺)2.457s81.06x
🐘 PostgresNext.js (Turbopack)3.574s (-6.9% 🟢)4.298s (-3.3%)0.723s72.25x
💻 LocalExpress4.386s (-30.8% 🟢)5.015s (-28.6% 🟢)0.629s62.76x
💻 LocalNitro5.079s (+6.6% 🔺)5.513s (+3.1%)0.434s63.20x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.500s (-46.9% 🟢)5.977s (-28.4% 🟢)2.477s61.00x
▲ VercelExpress3.732s (-28.7% 🟢)6.025s (-13.5% 🟢)2.293s51.07x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express0.574s (-12.9% 🟢)1.004s (-3.4%)0.430s601.00x
🐘 PostgresNitro0.576s (-10.6% 🟢)1.006s (-3.4%)0.430s601.00x
🐘 PostgresExpress0.582s (-1.7%)1.007s (~)0.425s601.01x
💻 LocalNitro0.675s (+15.7% 🔺)1.040s (+3.5%)0.365s581.17x
🐘 PostgresNext.js (Turbopack)0.826s (-2.9%)1.023s (~)0.198s591.44x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.107s (-27.8% 🟢)7.860s (-22.2% 🟢)1.753s81.00x
▲ VercelExpress6.449s (-2.0%)8.127s (-5.8% 🟢)1.678s81.06x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.328s (-16.2% 🟢)2.027s (+1.0%)0.699s451.00x
🐘 PostgresExpress1.439s (~)2.030s (~)0.591s451.08x
🐘 PostgresNitro1.476s (+2.1%)2.101s (+4.6%)0.625s431.11x
💻 LocalNitro1.548s (+4.8%)2.007s (~)0.459s451.17x
🐘 PostgresNext.js (Turbopack)1.998s (-1.9%)2.315s (-9.7% 🟢)0.318s391.50x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express14.175s (-29.6% 🟢)16.338s (-25.3% 🟢)2.163s61.00x
▲ VercelNitro14.565s (-22.4% 🟢)16.655s (-19.8% 🟢)2.090s61.03x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.704s (-4.1%)3.085s (-1.7%)0.381s391.00x
🐘 PostgresExpress2.768s (~)3.112s (-0.8%)0.344s391.02x
💻 LocalExpress2.895s (-13.4% 🟢)3.245s (-19.1% 🟢)0.350s381.07x
💻 LocalNitro3.354s (+5.9% 🔺)4.011s (+2.5%)0.657s301.24x
🐘 PostgresNext.js (Turbopack)3.954s (-1.8%)4.332s (-1.6%)0.379s281.46x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express27.701s (-28.4% 🟢)30.747s (-23.9% 🟢)3.046s41.00x
▲ VercelNitro28.386s (-21.7% 🟢)31.304s (-18.0% 🟢)2.917s41.02x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.247s (-5.5% 🟢)1.006s (~)0.760s601.00x
🐘 PostgresExpress0.247s (-5.2% 🟢)1.006s (~)0.759s601.00x
🐘 PostgresNext.js (Turbopack)0.313s (+1.6%)1.006s (~)0.693s601.27x
💻 LocalExpress0.418s (-2.4%)1.004s (~)0.586s601.69x
💻 LocalNitro0.457s (+5.8% 🔺)1.005s (~)0.549s601.85x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.829s (-36.6% 🟢)3.881s (-22.8% 🟢)2.052s161.00x
▲ VercelExpress1.907s (-46.6% 🟢)3.882s (-30.6% 🟢)1.975s161.04x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.413s (~)1.029s (+2.3%)0.616s881.00x
🐘 PostgresNitro0.413s (-9.6% 🟢)1.065s (-1.3%)0.652s851.00x
🐘 PostgresNext.js (Turbopack)0.611s (-9.9% 🟢)1.105s (-13.4% 🟢)0.494s821.48x
💻 LocalExpress2.017s (-8.0% 🟢)2.537s (-11.1% 🟢)0.521s364.89x
💻 LocalNitro2.119s (+5.4% 🔺)2.715s (+11.2% 🔺)0.596s345.13x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.194s (-8.4% 🟢)5.218s (+0.9%)2.024s181.00x
▲ VercelExpress3.290s (-36.2% 🟢)5.431s (-24.8% 🟢)2.141s171.03x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.777s (-7.9% 🟢)1.367s (-7.1% 🟢)0.590s891.00x
🐘 PostgresExpress0.799s (-5.8% 🟢)1.419s (-5.9% 🟢)0.621s851.03x
🐘 PostgresNext.js (Turbopack)3.176s (+2.8%)3.889s (+1.6%)0.713s314.09x
💻 LocalExpress8.590s (-12.9% 🟢)9.254s (-10.0% 🟢)0.664s1311.06x
💻 LocalNitro8.879s (+1.8%)9.566s (+3.4%)0.687s1311.43x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.698s (-16.6% 🟢)7.627s (-11.6% 🟢)1.929s161.00x
▲ VercelNitro5.845s (-13.4% 🟢)7.928s (-8.2% 🟢)2.083s161.03x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.145s (-2.2%)2.004s (~)0.009s (-30.3% 🟢)2.014s (~)0.869s101.00x
💻 LocalNitro1.165s (+0.7%)2.005s (~)0.011s (+5.0%)2.019s (~)0.854s101.02x
🐘 PostgresNitro1.170s (-2.3%)1.995s (~)0.001s (-8.3% 🟢)2.010s (~)0.840s101.02x
🐘 PostgresExpress1.176s (~)1.998s (~)0.001s (~)2.010s (~)0.835s101.03x
🐘 PostgresNext.js (Turbopack)1.238s (~)2.000s (~)0.001s (~)2.011s (~)0.773s101.08x
💻 LocalNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.330s (-1.4%)3.506s (-0.7%)0.786s (+11.8% 🔺)4.765s (-0.5%)2.435s101.00x
▲ VercelNitro2.442s (-4.2%)3.523s (-5.1% 🟢)1.169s (+57.9% 🔺)5.176s (+4.3%)2.734s101.05x
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Express | Nitro

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.518s (-5.7% 🟢)2.009s (~)0.010s (-19.8% 🟢)2.021s (~)0.503s301.00x
🐘 PostgresNitro1.573s (-4.0%)2.001s (~)0.005s (+3.8%)2.026s (~)0.453s301.04x
💻 LocalNitro1.586s (+1.3%)2.012s (~)0.013s (+12.0% 🔺)2.028s (~)0.441s301.04x
🐘 PostgresExpress1.627s (+1.3%)2.007s (~)0.005s (+10.1% 🔺)2.027s (~)0.400s301.07x
🐘 PostgresNext.js (Turbopack)1.838s (+2.6%)2.010s (~)0.006s (+1.2%)2.026s (~)0.188s301.21x
💻 LocalNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.321s (-6.1% 🟢)7.549s (-6.2% 🟢)0.291s (+32.8% 🔺)8.340s (-5.3% 🟢)2.019s81.00x
▲ VercelExpress6.677s (-3.2%)8.183s (-2.0%)0.202s (-41.3% 🟢)8.874s (-3.8%)2.197s71.06x
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro | Express

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.779s (-4.1%)1.050s (-1.4%)0.000s (+194.7% 🔺)1.069s (-1.0%)0.290s571.00x
🐘 PostgresExpress0.811s (+1.7%)1.028s (-1.8%)0.000s (+Infinity% 🔺)1.073s (+1.2%)0.263s561.04x
🐘 PostgresNext.js (Turbopack)1.044s (+1.6%)1.581s (-2.5%)0.000s (-2.6%)1.589s (-2.6%)0.544s381.34x
💻 LocalExpress1.238s (-12.8% 🟢)1.980s (-1.7%)0.000s (-30.9% 🟢)1.982s (-1.7%)0.744s311.59x
💻 LocalNitro1.428s (+5.7% 🔺)2.015s (~)0.001s (+171.4% 🔺)2.017s (~)0.590s301.83x
💻 LocalNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.002s (-27.0% 🟢)4.590s (-20.1% 🟢)0.001s (+Infinity% 🔺)5.105s (-18.2% 🟢)2.104s121.00x
▲ VercelExpress3.054s (-17.3% 🟢)4.589s (-6.4% 🟢)0.002s (+Infinity% 🔺)5.037s (-9.3% 🟢)1.983s121.02x
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro | Express

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

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.596s (-5.8% 🟢)2.176s (~)0.000s (-50.0% 🟢)2.189s (~)0.593s281.00x
🐘 PostgresExpress1.606s (-2.8%)2.137s (-1.9%)0.000s (NaN%)2.152s (-1.6%)0.546s281.01x
🐘 PostgresNext.js (Turbopack)2.147s (-4.8%)2.652s (-2.8%)0.000s (+Infinity% 🔺)2.671s (-2.4%)0.524s231.35x
💻 LocalExpress2.630s (-20.7% 🟢)3.121s (-22.5% 🟢)0.000s (-70.0% 🟢)3.126s (-22.5% 🟢)0.496s201.65x
💻 LocalNitro3.268s (+6.1% 🔺)3.965s (+1.7%)0.001s (+85.7% 🔺)3.968s (+1.7%)0.700s162.05x
💻 LocalNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.141s (-15.8% 🟢)5.935s (-3.7%)0.003s (+Infinity% 🔺)6.439s (-5.3% 🟢)2.298s101.00x
▲ VercelExpress4.539s (-25.2% 🟢)6.277s (-17.3% 🟢)0.001s (-11.1% 🟢)6.771s (-17.3% 🟢)2.232s91.10x
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro | Express

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress20/21
🐘 PostgresNitro13/21
▲ VercelNitro11/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres12/21
Next.js (Turbopack)🐘 Postgres21/21
Nitro🐘 Postgres18/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)

📋 View full workflow run


Some benchmark jobs failed:

  • Local: failure
  • Postgres: success
  • Vercel: failure

Check the workflow run for details.

@github-actions

github-actionsBot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production129302271520
✅ 💻 Local Development190902192128
✅ 📦 Local Production190902192128
✅ 🐘 Local Postgres189502332128
✅ 🪟 Windows15200152
✅ 📋 Other88501791064
Total8043010779120

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro125027
✅ example125027
✅ express125027
✅ fastify125027
✅ hono125027
✅ nextjs-webpack14903
✅ nitro125027
✅ nuxt125027
✅ sveltekit14408
✅ vite125027
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable-lazy-discovery-disabled15200
✅ nextjs-turbopack-stable-lazy-discovery-enabled15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable-lazy-discovery-disabled15200
✅ nextjs-webpack-stable-lazy-discovery-enabled15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable-lazy-discovery-disabled15200
✅ nextjs-turbopack-stable-lazy-discovery-enabled15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable-lazy-discovery-disabled15200
✅ nextjs-webpack-stable-lazy-discovery-enabled15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable126026
✅ express-stable126026
✅ fastify-stable126026
✅ hono-stable126026
✅ nextjs-turbopack-canary132020
✅ nextjs-turbopack-stable-lazy-discovery-disabled15101
✅ nextjs-turbopack-stable-lazy-discovery-enabled15101
✅ nextjs-webpack-canary132020
✅ nextjs-webpack-stable-lazy-discovery-disabled15101
✅ nextjs-webpack-stable-lazy-discovery-enabled15101
✅ 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


Some E2E test jobs failed:

  • Vercel Prod: failure
  • Local Dev: success
  • Local Prod: success
  • Local Postgres: success
  • Windows: success

Check the workflow run for details.

CopilotAI 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.

Pull request overview

Fixes the SWC plugin’s DCE usage analysis so module-scope declarations referenced only via destructuring default initializers (e.g. const { ttl = TTL } = options) are correctly counted as “used”, preventing runtime ReferenceErrors when defaults fire.

Changes:

  • Extend ComprehensiveUsageCollector to traverse destructuring patterns and visit only default-value initializers (and computed keys), without marking binding identifiers as used.
  • Add a regression fixture covering module-scope const preservation in both step and workflow modes (and confirming truly-unused consts are still removed).
  • Update the SWC plugin spec and add a changeset for the patch release.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
packages/swc-plugin-workflow/transform/src/lib.rsAdds visit_pat_default_initializers and wires it into visit_mut_var_declarator so destructuring-default references are counted for DCE.
packages/swc-plugin-workflow/transform/tests/fixture/destructuring-default-references-module-const/input.jsNew fixture input reproducing the destructuring-default-only reference pattern and an unused const.
packages/swc-plugin-workflow/transform/tests/fixture/destructuring-default-references-module-const/output-step.jsStep-mode snapshot asserting referenced consts survive and unused const is removed.
packages/swc-plugin-workflow/transform/tests/fixture/destructuring-default-references-module-const/output-workflow.jsWorkflow-mode snapshot asserting referenced consts survive and unused const is removed.
packages/swc-plugin-workflow/spec.mdDocuments that destructuring-default initializers count as references for DCE in both modes.
.changeset/swc-destructuring-default-dce.mdPatch changeset describing the DCE fix and the prevented runtime error.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

* origin/main:
docs: document run idempotency (#2011)
Render attr_set events and run attributes in observability UI (#2393)
[ci] Fix backport job model slug (#2403)
[ci] Comment on PR when backport fails, revert to use opus 4.8 (#2400)
Update queue client to 0.3.1 (#2399)
fix(deps): upgrade esbuild to 0.28.1 (GHSA-gv7w-rqvm-qjhr) (#2395)

@TooTallNateTooTallNate left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Approve — precise root-cause fix, exhaustive pattern coverage, and I reproduced the before/after

Targeted DCE fix: the usage collector deliberately skipped var_decl.name to avoid marking binding names as used, but destructuring-default initializers (const { ttl = TTL } = options;) live inside that pattern, so references like TTL were never counted and DCE stripped the still-referenced declaration → runtime ReferenceError. The diagnosis in the PR matches the code exactly.

What I verified

  • The new visit_pat_default_initializers is exhaustive over the binding-pattern AST and visits only value positions: object KeyValue values + computed keys, Assign.value defaults, array element defaults, AssignPat.right, rest args, and expression patterns — while never descending into binding names. I traced each arm against the Pat/ObjectPatProp variants; nothing is missed and the { ttl = TTL } case correctly skips the ttl key and visits only the TTL default.
  • It cannot over-broaden DCE. The collector's visit_mut_ident blindly adds to used_identifiers, and this helper only ever adds references — the safe direction for DCE (over-counting preserves declarations, never wrongly strips). The Pat::Expr arm visiting a member expression's property ident is the same conservative behavior the collector already has when walking initializers, so it introduces no new risk.
  • Built and ran the full Rust suite: 128 fixture tests + error tests, all green.
  • Reproduced the regression: reverted just the visit_pat_default_initializers call site to simulate main, and both the step-mode and workflow-mode fixtures FAIL — the emitted output strips const TTL/const RETRIES while keeping the code that references them. Restored the fix → both pass. This is the before/after the PR claims, confirmed locally.
  • The fixture's negative case earns its keep: const UNUSED = 'dead' is still stripped in both outputs, proving the fix preserves referenced declarations only, not everything in a pattern's vicinity.
  • The fix covers the real latent bug, not just the synthetic repro: workbench/vitest/workflows/cookbook/distributed-abort-controller.ts:126 has const { ttlMs = DEFAULT_TTL_MS, graceMs = DEFAULT_GRACE_MS } = options; referencing module consts at lines 16/18 — exactly this pattern. The fixture is a faithful minimal reduction of it.
  • spec.md updated per AGENTS.md — both the step-mode and workflow-mode DCE sections gain the destructuring-default note (correct; spec.md has no version frontmatter, the bump rule applies to skills/). Changeset is a correctly-scoped @workflow/swc-plugin patch.

Notes

  • The fixture covers a class static method and a plain exported function, deliberately demonstrating the bug is class- and mode-independent (matching the issue). Good instinct to prove both, since the worked-around precedent (#1858 kill-switch) might have suggested it was class-specific.
  • Function-parameter destructuring defaults were already handled (visit_mut_fn_decl visits params in full, per the existing default-parameter-usage fixture) — the PR correctly scopes the fix to var declarators only and says so. I'd have flagged it if param defaults regressed, but they're untouched and the existing fixture still passes.
  • Distinct from #1944 as the PR notes: that was DCE ordering vs step hoisting; this is the collector never traversing these initializers at all. No nested/hoisted steps involved, which the fixture confirms.

CI lanes that are red are the session's known baseline flakes (Windows dev-server / express benchmark), unrelated to a Rust-only change. LGTM.

@pranaygp
pranaygp merged commit 53ede30 into mainJun 16, 2026
112 of 116 checks passed
@pranaygp
pranaygp deleted the pgp/swc-plugin-fix branch June 16, 2026 23:16
@github-actionsgithub-actionsBot mentioned this pull request Jun 16, 2026
github-actionsBot added a commit that referenced this pull request Jun 16, 2026
…analysis (#2398)
The DCE usage collector skipped the entire variable name pattern when
visiting a `VarDeclarator` (to avoid marking the binding name as "used").
But default-value initializers inside destructuring patterns live in that
pattern — e.g. the `TTL` in `const { ttl = TTL } = options;` — so those
references were invisible to the collector. A module-scope `const`
referenced only through such a default was treated as unused and stripped,
while the surviving code kept reading it, producing a runtime
`ReferenceError` when the default fired.
Traverse the default-value initializer expressions (and computed keys)
within destructuring patterns while still not marking the binding names
themselves, so the referenced declaration is preserved. Function-parameter
defaults were already covered (params are visited in full).
Fixes#2396.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

Backport PR opened against stable: #2465. Merge conflicts were resolved by AI — please review carefully. (backport job run)

pranaygp pushed a commit that referenced this pull request Jun 17, 2026
…analysis (#2398) (#2465)
The DCE usage collector skipped the entire variable name pattern when
visiting a `VarDeclarator` (to avoid marking the binding name as "used").
But default-value initializers inside destructuring patterns live in that
pattern — e.g. the `TTL` in `const { ttl = TTL } = options;` — so those
references were invisible to the collector. A module-scope `const`
referenced only through such a default was treated as unused and stripped,
while the surviving code kept reading it, producing a runtime
`ReferenceError` when the default fired.
Traverse the default-value initializer expressions (and computed keys)
within destructuring patterns while still not marking the binding names
themselves, so the referenced declaration is preserved. Function-parameter
defaults were already covered (params are visited in full).
Fixes#2396.
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
TooTallNate added a commit that referenced this pull request Jun 17, 2026
…default DCE test
The #2398 backport (#2465) added the destructuring-default-references-module-const
fixture but committed output-step.js verbatim from main and omitted output-client.js
entirely. Stable's SWC plugin emits a different step-registration shape
(registerStepFunction import vs. inline IIFE) and has less aggressive DCE (the
truly-unused UNUSED const is not stripped), so the copied output-step.js did not
match stable's actual output and the required output-client.js was missing.
Regenerate both fixture outputs against stable's plugin so the client_mode and
step_mode fixture tests pass. The regression being tested still holds: TTL and
RETRIES (referenced only via destructuring defaults) are preserved in all modes.
Signed-off-by: Nathan Rajlich <n@n8.io>
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.

[swc-plugin] DCE strips module-scope declarations referenced only by destructuring defaults → ReferenceError

3 participants

@pranaygp@TooTallNate
, '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('^' + ".*" + ' fix(swc-plugin): count destructuring-default references in DCE usage analysis by pranaygp · Pull Request #2398 · vercel/workflow · GitHub
Skip to content

fix(swc-plugin): count destructuring-default references in DCE usage analysis - #2398

Merged
pranaygp merged 3 commits into
mainfrom
pgp/swc-plugin-fix
Jun 16, 2026
Merged

fix(swc-plugin): count destructuring-default references in DCE usage analysis#2398
pranaygp merged 3 commits into
mainfrom
pgp/swc-plugin-fix

Conversation

@pranaygp

Copy link
Copy Markdown
Contributor

Fixes#2396.

Problem

The SWC plugin's dead-code-elimination (DCE) usage analysis did not count identifier references that appear as default values inside a destructuring pattern (e.g. const { ttl = TTL } = options;). When a module-scope const was referenced only through such a destructuring default, the DCE pass treated it as unused and stripped it from the emitted bundle — while keeping the code that referenced it. The result was a runtime ReferenceError: <const> is not defined when the default fired.

This is mode-independent (reproduces in both step and workflow mode) and class-independent (a plain top-level function exhibits it too). It shipped as a worked-around bug in the kill-switch pattern (#1858) and remains latent in workbench/vitest/workflows/cookbook/distributed-abort-controller.ts.

Root cause

In ComprehensiveUsageCollector::visit_mut_var_declarator, the collector visited only the initializer and deliberately skipped the entire var_decl.name pattern (to avoid marking the binding name as "used"). For destructuring patterns, the default-value initializer expressions live insidevar_decl.name — so references like the TTL in { ttl = TTL } were never added to used_identifiers, and remove_dead_code then pruned the still-referenced declaration.

Fix

Added visit_pat_default_initializers, which walks a binding pattern and visits only the default-value initializer expressions (and computed keys) — ObjectPatProp::KeyValue values, ObjectPatProp::Assign.value, AssignPat.right, array element defaults, rest args — while still not marking the binding names themselves as used. visit_mut_var_declarator now calls it on var_decl.name.

Function-parameter destructuring defaults were already covered: visit_mut_fn_decl visits params in full (confirmed by the existing default-parameter-usage fixture), so no change was needed there.

Tests

  • New fixture destructuring-default-references-module-const/ asserts that two module-scope consts referenced only via destructuring defaults — one inside a class static method, one inside a plain exported function — survive in both step and workflow mode. A third, genuinely-unused const is still stripped, confirming DCE is not over-broadened.
  • Verified the fixture fails on main (consts stripped) and passes with the fix.
  • Full cargo test -p swc_workflow suite green (128 fixture tests + error tests).

Spec

Updated packages/swc-plugin-workflow/spec.md (both the step-mode and workflow-mode DCE sections) to note that a reference counts even when it appears only inside a destructuring-default initializer.

Relation to prior fixes

Same symptom class as #1944 (DCE removing a declaration that surviving code references) but a distinct root cause: #1944 was about DCE ordering relative to step hoisting; this is about the usage collector never traversing destructuring-default initializers at all, and reproduces with no nested/hoisted steps involved.

🤖 Generated with Claude Code

…analysis
The DCE usage collector skipped the entire variable name pattern when
visiting a `VarDeclarator` (to avoid marking the binding name as "used").
But default-value initializers inside destructuring patterns live in that
pattern — e.g. the `TTL` in `const { ttl = TTL } = options;` — so those
references were invisible to the collector. A module-scope `const`
referenced only through such a default was treated as unused and stripped,
while the surviving code kept reading it, producing a runtime
`ReferenceError` when the default fired.
Traverse the default-value initializer expressions (and computed keys)
within destructuring patterns while still not marking the binding names
themselves, so the referenced declaration is preserved. Function-parameter
defaults were already covered (params are visited in full).
Fixes#2396.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CopilotAI review requested due to automatic review settings June 13, 2026 06:29
@pranaygp
pranaygp requested a review from a team as a code ownerJune 13, 2026 06:29
@changeset-bot

changeset-botBot commented Jun 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9006ec3

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

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

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

@vercel

vercelBot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented Jun 13, 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🥇 Express0.034s (-30.4% 🟢)1.005s (~)0.971s101.00x
💻 LocalNitro0.051s (+22.4% 🔺)1.008s (~)0.957s101.52x
🐘 PostgresNitro0.060s (-7.3% 🟢)1.012s (~)0.952s101.78x
🐘 PostgresExpress0.062s (+2.0%)1.013s (~)0.951s101.85x
🐘 PostgresNext.js (Turbopack)0.067s (-8.4% 🟢)1.012s (~)0.945s101.98x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro0.339s (-43.5% 🟢)2.573s (-3.3%)2.234s101.00x
▲ VercelExpress0.455s (-59.0% 🟢)2.707s (-24.4% 🟢)2.252s101.34x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.079s (-1.9%)2.006s (~)0.927s101.00x
💻 LocalNitro1.110s (+1.2%)2.007s (~)0.897s101.03x
🐘 PostgresExpress1.114s (~)2.011s (~)0.897s101.03x
🐘 PostgresNitro1.119s (~)2.010s (~)0.891s101.04x
🐘 PostgresNext.js (Turbopack)1.140s (-1.6%)2.010s (~)0.871s101.06x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.805s (-32.7% 🟢)3.807s (-23.9% 🟢)2.003s101.00x
▲ VercelNitro1.900s (-32.7% 🟢)3.850s (-15.3% 🟢)1.951s101.05x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express10.432s (-1.1%)11.020s (~)0.588s31.00x
🐘 PostgresNitro10.583s (~)11.017s (~)0.434s31.01x
💻 LocalNitro10.585s (+0.6%)11.024s (~)0.439s31.01x
🐘 PostgresExpress10.598s (~)11.020s (~)0.422s31.02x
🐘 PostgresNext.js (Turbopack)10.819s (~)11.018s (~)0.199s31.04x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express14.863s (-8.6% 🟢)16.580s (-11.0% 🟢)1.718s21.00x
▲ VercelNitro14.872s (-18.1% 🟢)17.769s (-10.6% 🟢)2.897s21.00x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express13.524s (-2.3%)14.027s (~)0.503s51.00x
🐘 PostgresNitro13.752s (-0.8%)14.018s (~)0.266s51.02x
🐘 PostgresExpress13.773s (~)14.023s (~)0.250s51.02x
💻 LocalNitro13.861s (~)14.028s (~)0.167s51.02x
🐘 PostgresNext.js (Turbopack)14.473s (~)15.017s (~)0.544s41.07x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express22.452s (-35.7% 🟢)24.544s (-33.5% 🟢)2.092s31.00x
▲ VercelNitro22.733s (-31.0% 🟢)24.477s (-29.8% 🟢)1.744s31.01x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express12.003s (-4.8%)12.396s (-4.8%)0.394s81.00x
🐘 PostgresExpress12.613s (~)13.019s (~)0.405s71.05x
🐘 PostgresNitro12.668s (~)13.022s (~)0.353s71.06x
💻 LocalNitro12.685s (+0.9%)13.027s (~)0.342s71.06x
🐘 PostgresNext.js (Turbopack)13.947s (~)14.306s (+1.0%)0.358s71.16x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express31.375s (-9.7% 🟢)33.491s (-9.7% 🟢)2.116s31.00x
▲ VercelNitro31.721s (-16.3% 🟢)34.300s (-13.2% 🟢)2.578s31.01x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.202s (~)2.008s (~)0.805s151.00x
💻 LocalExpress1.202s (+0.9%)2.007s (~)0.805s151.00x
🐘 PostgresNitro1.216s (~)2.008s (~)0.792s151.01x
🐘 PostgresNext.js (Turbopack)1.280s (-5.6% 🟢)2.008s (-3.3%)0.727s151.06x
💻 LocalNitro1.317s (+13.2% 🔺)2.007s (~)0.690s151.10x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.599s (+1.9%)4.260s (+4.6%)1.662s81.00x
▲ VercelNitro2.787s (-9.8% 🟢)4.617s (+0.7%)1.830s71.07x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.332s (-1.9%)2.316s (-7.6% 🟢)0.984s131.00x
🐘 PostgresNitro1.368s (+2.0%)2.509s (~)1.141s121.03x
🐘 PostgresNext.js (Turbopack)1.571s (-4.1%)2.394s (-4.6%)0.824s131.18x
💻 LocalExpress1.614s (-14.8% 🟢)2.005s (-6.8% 🟢)0.391s151.21x
💻 LocalNitro1.723s (+5.8% 🔺)2.007s (~)0.284s151.29x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.005s (-20.3% 🟢)5.201s (-3.7%)2.196s61.00x
▲ VercelExpress3.112s (-24.4% 🟢)5.163s (-14.7% 🟢)2.051s61.04x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.605s (-1.1%)4.011s (+3.2%)2.406s81.00x
🐘 PostgresNitro1.717s (+5.7% 🔺)3.760s (-9.2% 🟢)2.043s81.07x
🐘 PostgresNext.js (Turbopack)3.515s (-8.3% 🟢)4.308s (-3.1%)0.793s72.19x
💻 LocalNitro4.578s (+13.9% 🔺)5.155s (+12.5% 🔺)0.577s72.85x
💻 LocalExpress4.611s (-11.9% 🟢)5.154s (-11.9% 🟢)0.543s72.87x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.454s (-18.7% 🟢)5.446s (-4.9%)1.992s61.00x
▲ VercelExpress3.755s (-17.9% 🟢)5.803s (-11.6% 🟢)2.048s61.09x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.206s (-1.3%)2.007s (~)0.802s151.00x
🐘 PostgresExpress1.212s (~)2.008s (~)0.795s151.01x
🐘 PostgresNext.js (Turbopack)1.301s (+1.2%)2.007s (~)0.706s151.08x
💻 LocalExpress1.467s (-9.1% 🟢)2.006s (~)0.539s151.22x
💻 LocalNitro1.614s (+6.8% 🔺)2.007s (~)0.393s151.34x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.317s (-64.3% 🟢)3.984s (-50.4% 🟢)1.667s81.00x
▲ VercelExpress2.358s (+3.7%)3.953s (+2.6%)1.594s81.02x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.355s (~)2.393s (-7.7% 🟢)1.037s131.00x
🐘 PostgresExpress1.385s (-0.7%)2.592s (+4.9%)1.206s121.02x
🐘 PostgresNext.js (Turbopack)1.550s (+0.9%)2.075s (-6.7% 🟢)0.525s151.14x
💻 LocalExpress1.760s (-31.7% 🟢)2.072s (-33.3% 🟢)0.313s151.30x
💻 LocalNitro2.052s (+14.6% 🔺)2.508s (+21.0% 🔺)0.456s121.51x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.782s (-16.5% 🟢)4.629s (-13.4% 🟢)1.847s71.00x
▲ VercelNitro3.219s (-14.0% 🟢)4.836s (-11.4% 🟢)1.617s71.16x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.589s (-9.4% 🟢)4.012s (-3.0%)2.423s81.00x
🐘 PostgresNitro1.691s (-2.2%)4.148s (+6.7% 🔺)2.457s81.06x
🐘 PostgresNext.js (Turbopack)3.574s (-6.9% 🟢)4.298s (-3.3%)0.723s72.25x
💻 LocalExpress4.386s (-30.8% 🟢)5.015s (-28.6% 🟢)0.629s62.76x
💻 LocalNitro5.079s (+6.6% 🔺)5.513s (+3.1%)0.434s63.20x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.500s (-46.9% 🟢)5.977s (-28.4% 🟢)2.477s61.00x
▲ VercelExpress3.732s (-28.7% 🟢)6.025s (-13.5% 🟢)2.293s51.07x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express0.574s (-12.9% 🟢)1.004s (-3.4%)0.430s601.00x
🐘 PostgresNitro0.576s (-10.6% 🟢)1.006s (-3.4%)0.430s601.00x
🐘 PostgresExpress0.582s (-1.7%)1.007s (~)0.425s601.01x
💻 LocalNitro0.675s (+15.7% 🔺)1.040s (+3.5%)0.365s581.17x
🐘 PostgresNext.js (Turbopack)0.826s (-2.9%)1.023s (~)0.198s591.44x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.107s (-27.8% 🟢)7.860s (-22.2% 🟢)1.753s81.00x
▲ VercelExpress6.449s (-2.0%)8.127s (-5.8% 🟢)1.678s81.06x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.328s (-16.2% 🟢)2.027s (+1.0%)0.699s451.00x
🐘 PostgresExpress1.439s (~)2.030s (~)0.591s451.08x
🐘 PostgresNitro1.476s (+2.1%)2.101s (+4.6%)0.625s431.11x
💻 LocalNitro1.548s (+4.8%)2.007s (~)0.459s451.17x
🐘 PostgresNext.js (Turbopack)1.998s (-1.9%)2.315s (-9.7% 🟢)0.318s391.50x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express14.175s (-29.6% 🟢)16.338s (-25.3% 🟢)2.163s61.00x
▲ VercelNitro14.565s (-22.4% 🟢)16.655s (-19.8% 🟢)2.090s61.03x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.704s (-4.1%)3.085s (-1.7%)0.381s391.00x
🐘 PostgresExpress2.768s (~)3.112s (-0.8%)0.344s391.02x
💻 LocalExpress2.895s (-13.4% 🟢)3.245s (-19.1% 🟢)0.350s381.07x
💻 LocalNitro3.354s (+5.9% 🔺)4.011s (+2.5%)0.657s301.24x
🐘 PostgresNext.js (Turbopack)3.954s (-1.8%)4.332s (-1.6%)0.379s281.46x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express27.701s (-28.4% 🟢)30.747s (-23.9% 🟢)3.046s41.00x
▲ VercelNitro28.386s (-21.7% 🟢)31.304s (-18.0% 🟢)2.917s41.02x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.247s (-5.5% 🟢)1.006s (~)0.760s601.00x
🐘 PostgresExpress0.247s (-5.2% 🟢)1.006s (~)0.759s601.00x
🐘 PostgresNext.js (Turbopack)0.313s (+1.6%)1.006s (~)0.693s601.27x
💻 LocalExpress0.418s (-2.4%)1.004s (~)0.586s601.69x
💻 LocalNitro0.457s (+5.8% 🔺)1.005s (~)0.549s601.85x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.829s (-36.6% 🟢)3.881s (-22.8% 🟢)2.052s161.00x
▲ VercelExpress1.907s (-46.6% 🟢)3.882s (-30.6% 🟢)1.975s161.04x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.413s (~)1.029s (+2.3%)0.616s881.00x
🐘 PostgresNitro0.413s (-9.6% 🟢)1.065s (-1.3%)0.652s851.00x
🐘 PostgresNext.js (Turbopack)0.611s (-9.9% 🟢)1.105s (-13.4% 🟢)0.494s821.48x
💻 LocalExpress2.017s (-8.0% 🟢)2.537s (-11.1% 🟢)0.521s364.89x
💻 LocalNitro2.119s (+5.4% 🔺)2.715s (+11.2% 🔺)0.596s345.13x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.194s (-8.4% 🟢)5.218s (+0.9%)2.024s181.00x
▲ VercelExpress3.290s (-36.2% 🟢)5.431s (-24.8% 🟢)2.141s171.03x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.777s (-7.9% 🟢)1.367s (-7.1% 🟢)0.590s891.00x
🐘 PostgresExpress0.799s (-5.8% 🟢)1.419s (-5.9% 🟢)0.621s851.03x
🐘 PostgresNext.js (Turbopack)3.176s (+2.8%)3.889s (+1.6%)0.713s314.09x
💻 LocalExpress8.590s (-12.9% 🟢)9.254s (-10.0% 🟢)0.664s1311.06x
💻 LocalNitro8.879s (+1.8%)9.566s (+3.4%)0.687s1311.43x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.698s (-16.6% 🟢)7.627s (-11.6% 🟢)1.929s161.00x
▲ VercelNitro5.845s (-13.4% 🟢)7.928s (-8.2% 🟢)2.083s161.03x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.145s (-2.2%)2.004s (~)0.009s (-30.3% 🟢)2.014s (~)0.869s101.00x
💻 LocalNitro1.165s (+0.7%)2.005s (~)0.011s (+5.0%)2.019s (~)0.854s101.02x
🐘 PostgresNitro1.170s (-2.3%)1.995s (~)0.001s (-8.3% 🟢)2.010s (~)0.840s101.02x
🐘 PostgresExpress1.176s (~)1.998s (~)0.001s (~)2.010s (~)0.835s101.03x
🐘 PostgresNext.js (Turbopack)1.238s (~)2.000s (~)0.001s (~)2.011s (~)0.773s101.08x
💻 LocalNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.330s (-1.4%)3.506s (-0.7%)0.786s (+11.8% 🔺)4.765s (-0.5%)2.435s101.00x
▲ VercelNitro2.442s (-4.2%)3.523s (-5.1% 🟢)1.169s (+57.9% 🔺)5.176s (+4.3%)2.734s101.05x
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Express | Nitro

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.518s (-5.7% 🟢)2.009s (~)0.010s (-19.8% 🟢)2.021s (~)0.503s301.00x
🐘 PostgresNitro1.573s (-4.0%)2.001s (~)0.005s (+3.8%)2.026s (~)0.453s301.04x
💻 LocalNitro1.586s (+1.3%)2.012s (~)0.013s (+12.0% 🔺)2.028s (~)0.441s301.04x
🐘 PostgresExpress1.627s (+1.3%)2.007s (~)0.005s (+10.1% 🔺)2.027s (~)0.400s301.07x
🐘 PostgresNext.js (Turbopack)1.838s (+2.6%)2.010s (~)0.006s (+1.2%)2.026s (~)0.188s301.21x
💻 LocalNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.321s (-6.1% 🟢)7.549s (-6.2% 🟢)0.291s (+32.8% 🔺)8.340s (-5.3% 🟢)2.019s81.00x
▲ VercelExpress6.677s (-3.2%)8.183s (-2.0%)0.202s (-41.3% 🟢)8.874s (-3.8%)2.197s71.06x
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro | Express

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.779s (-4.1%)1.050s (-1.4%)0.000s (+194.7% 🔺)1.069s (-1.0%)0.290s571.00x
🐘 PostgresExpress0.811s (+1.7%)1.028s (-1.8%)0.000s (+Infinity% 🔺)1.073s (+1.2%)0.263s561.04x
🐘 PostgresNext.js (Turbopack)1.044s (+1.6%)1.581s (-2.5%)0.000s (-2.6%)1.589s (-2.6%)0.544s381.34x
💻 LocalExpress1.238s (-12.8% 🟢)1.980s (-1.7%)0.000s (-30.9% 🟢)1.982s (-1.7%)0.744s311.59x
💻 LocalNitro1.428s (+5.7% 🔺)2.015s (~)0.001s (+171.4% 🔺)2.017s (~)0.590s301.83x
💻 LocalNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.002s (-27.0% 🟢)4.590s (-20.1% 🟢)0.001s (+Infinity% 🔺)5.105s (-18.2% 🟢)2.104s121.00x
▲ VercelExpress3.054s (-17.3% 🟢)4.589s (-6.4% 🟢)0.002s (+Infinity% 🔺)5.037s (-9.3% 🟢)1.983s121.02x
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro | Express

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

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.596s (-5.8% 🟢)2.176s (~)0.000s (-50.0% 🟢)2.189s (~)0.593s281.00x
🐘 PostgresExpress1.606s (-2.8%)2.137s (-1.9%)0.000s (NaN%)2.152s (-1.6%)0.546s281.01x
🐘 PostgresNext.js (Turbopack)2.147s (-4.8%)2.652s (-2.8%)0.000s (+Infinity% 🔺)2.671s (-2.4%)0.524s231.35x
💻 LocalExpress2.630s (-20.7% 🟢)3.121s (-22.5% 🟢)0.000s (-70.0% 🟢)3.126s (-22.5% 🟢)0.496s201.65x
💻 LocalNitro3.268s (+6.1% 🔺)3.965s (+1.7%)0.001s (+85.7% 🔺)3.968s (+1.7%)0.700s162.05x
💻 LocalNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.141s (-15.8% 🟢)5.935s (-3.7%)0.003s (+Infinity% 🔺)6.439s (-5.3% 🟢)2.298s101.00x
▲ VercelExpress4.539s (-25.2% 🟢)6.277s (-17.3% 🟢)0.001s (-11.1% 🟢)6.771s (-17.3% 🟢)2.232s91.10x
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro | Express

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress20/21
🐘 PostgresNitro13/21
▲ VercelNitro11/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres12/21
Next.js (Turbopack)🐘 Postgres21/21
Nitro🐘 Postgres18/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)

📋 View full workflow run


Some benchmark jobs failed:

  • Local: failure
  • Postgres: success
  • Vercel: failure

Check the workflow run for details.

@github-actions

github-actionsBot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production129302271520
✅ 💻 Local Development190902192128
✅ 📦 Local Production190902192128
✅ 🐘 Local Postgres189502332128
✅ 🪟 Windows15200152
✅ 📋 Other88501791064
Total8043010779120

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro125027
✅ example125027
✅ express125027
✅ fastify125027
✅ hono125027
✅ nextjs-webpack14903
✅ nitro125027
✅ nuxt125027
✅ sveltekit14408
✅ vite125027
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable-lazy-discovery-disabled15200
✅ nextjs-turbopack-stable-lazy-discovery-enabled15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable-lazy-discovery-disabled15200
✅ nextjs-webpack-stable-lazy-discovery-enabled15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable-lazy-discovery-disabled15200
✅ nextjs-turbopack-stable-lazy-discovery-enabled15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable-lazy-discovery-disabled15200
✅ nextjs-webpack-stable-lazy-discovery-enabled15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable126026
✅ express-stable126026
✅ fastify-stable126026
✅ hono-stable126026
✅ nextjs-turbopack-canary132020
✅ nextjs-turbopack-stable-lazy-discovery-disabled15101
✅ nextjs-turbopack-stable-lazy-discovery-enabled15101
✅ nextjs-webpack-canary132020
✅ nextjs-webpack-stable-lazy-discovery-disabled15101
✅ nextjs-webpack-stable-lazy-discovery-enabled15101
✅ 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


Some E2E test jobs failed:

  • Vercel Prod: failure
  • Local Dev: success
  • Local Prod: success
  • Local Postgres: success
  • Windows: success

Check the workflow run for details.

CopilotAI 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.

Pull request overview

Fixes the SWC plugin’s DCE usage analysis so module-scope declarations referenced only via destructuring default initializers (e.g. const { ttl = TTL } = options) are correctly counted as “used”, preventing runtime ReferenceErrors when defaults fire.

Changes:

  • Extend ComprehensiveUsageCollector to traverse destructuring patterns and visit only default-value initializers (and computed keys), without marking binding identifiers as used.
  • Add a regression fixture covering module-scope const preservation in both step and workflow modes (and confirming truly-unused consts are still removed).
  • Update the SWC plugin spec and add a changeset for the patch release.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
packages/swc-plugin-workflow/transform/src/lib.rsAdds visit_pat_default_initializers and wires it into visit_mut_var_declarator so destructuring-default references are counted for DCE.
packages/swc-plugin-workflow/transform/tests/fixture/destructuring-default-references-module-const/input.jsNew fixture input reproducing the destructuring-default-only reference pattern and an unused const.
packages/swc-plugin-workflow/transform/tests/fixture/destructuring-default-references-module-const/output-step.jsStep-mode snapshot asserting referenced consts survive and unused const is removed.
packages/swc-plugin-workflow/transform/tests/fixture/destructuring-default-references-module-const/output-workflow.jsWorkflow-mode snapshot asserting referenced consts survive and unused const is removed.
packages/swc-plugin-workflow/spec.mdDocuments that destructuring-default initializers count as references for DCE in both modes.
.changeset/swc-destructuring-default-dce.mdPatch changeset describing the DCE fix and the prevented runtime error.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

* origin/main:
docs: document run idempotency (#2011)
Render attr_set events and run attributes in observability UI (#2393)
[ci] Fix backport job model slug (#2403)
[ci] Comment on PR when backport fails, revert to use opus 4.8 (#2400)
Update queue client to 0.3.1 (#2399)
fix(deps): upgrade esbuild to 0.28.1 (GHSA-gv7w-rqvm-qjhr) (#2395)

@TooTallNateTooTallNate left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Approve — precise root-cause fix, exhaustive pattern coverage, and I reproduced the before/after

Targeted DCE fix: the usage collector deliberately skipped var_decl.name to avoid marking binding names as used, but destructuring-default initializers (const { ttl = TTL } = options;) live inside that pattern, so references like TTL were never counted and DCE stripped the still-referenced declaration → runtime ReferenceError. The diagnosis in the PR matches the code exactly.

What I verified

  • The new visit_pat_default_initializers is exhaustive over the binding-pattern AST and visits only value positions: object KeyValue values + computed keys, Assign.value defaults, array element defaults, AssignPat.right, rest args, and expression patterns — while never descending into binding names. I traced each arm against the Pat/ObjectPatProp variants; nothing is missed and the { ttl = TTL } case correctly skips the ttl key and visits only the TTL default.
  • It cannot over-broaden DCE. The collector's visit_mut_ident blindly adds to used_identifiers, and this helper only ever adds references — the safe direction for DCE (over-counting preserves declarations, never wrongly strips). The Pat::Expr arm visiting a member expression's property ident is the same conservative behavior the collector already has when walking initializers, so it introduces no new risk.
  • Built and ran the full Rust suite: 128 fixture tests + error tests, all green.
  • Reproduced the regression: reverted just the visit_pat_default_initializers call site to simulate main, and both the step-mode and workflow-mode fixtures FAIL — the emitted output strips const TTL/const RETRIES while keeping the code that references them. Restored the fix → both pass. This is the before/after the PR claims, confirmed locally.
  • The fixture's negative case earns its keep: const UNUSED = 'dead' is still stripped in both outputs, proving the fix preserves referenced declarations only, not everything in a pattern's vicinity.
  • The fix covers the real latent bug, not just the synthetic repro: workbench/vitest/workflows/cookbook/distributed-abort-controller.ts:126 has const { ttlMs = DEFAULT_TTL_MS, graceMs = DEFAULT_GRACE_MS } = options; referencing module consts at lines 16/18 — exactly this pattern. The fixture is a faithful minimal reduction of it.
  • spec.md updated per AGENTS.md — both the step-mode and workflow-mode DCE sections gain the destructuring-default note (correct; spec.md has no version frontmatter, the bump rule applies to skills/). Changeset is a correctly-scoped @workflow/swc-plugin patch.

Notes

  • The fixture covers a class static method and a plain exported function, deliberately demonstrating the bug is class- and mode-independent (matching the issue). Good instinct to prove both, since the worked-around precedent (#1858 kill-switch) might have suggested it was class-specific.
  • Function-parameter destructuring defaults were already handled (visit_mut_fn_decl visits params in full, per the existing default-parameter-usage fixture) — the PR correctly scopes the fix to var declarators only and says so. I'd have flagged it if param defaults regressed, but they're untouched and the existing fixture still passes.
  • Distinct from #1944 as the PR notes: that was DCE ordering vs step hoisting; this is the collector never traversing these initializers at all. No nested/hoisted steps involved, which the fixture confirms.

CI lanes that are red are the session's known baseline flakes (Windows dev-server / express benchmark), unrelated to a Rust-only change. LGTM.

@pranaygp
pranaygp merged commit 53ede30 into mainJun 16, 2026
112 of 116 checks passed
@pranaygp
pranaygp deleted the pgp/swc-plugin-fix branch June 16, 2026 23:16
@github-actionsgithub-actionsBot mentioned this pull request Jun 16, 2026
github-actionsBot added a commit that referenced this pull request Jun 16, 2026
…analysis (#2398)
The DCE usage collector skipped the entire variable name pattern when
visiting a `VarDeclarator` (to avoid marking the binding name as "used").
But default-value initializers inside destructuring patterns live in that
pattern — e.g. the `TTL` in `const { ttl = TTL } = options;` — so those
references were invisible to the collector. A module-scope `const`
referenced only through such a default was treated as unused and stripped,
while the surviving code kept reading it, producing a runtime
`ReferenceError` when the default fired.
Traverse the default-value initializer expressions (and computed keys)
within destructuring patterns while still not marking the binding names
themselves, so the referenced declaration is preserved. Function-parameter
defaults were already covered (params are visited in full).
Fixes#2396.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

Backport PR opened against stable: #2465. Merge conflicts were resolved by AI — please review carefully. (backport job run)

pranaygp pushed a commit that referenced this pull request Jun 17, 2026
…analysis (#2398) (#2465)
The DCE usage collector skipped the entire variable name pattern when
visiting a `VarDeclarator` (to avoid marking the binding name as "used").
But default-value initializers inside destructuring patterns live in that
pattern — e.g. the `TTL` in `const { ttl = TTL } = options;` — so those
references were invisible to the collector. A module-scope `const`
referenced only through such a default was treated as unused and stripped,
while the surviving code kept reading it, producing a runtime
`ReferenceError` when the default fired.
Traverse the default-value initializer expressions (and computed keys)
within destructuring patterns while still not marking the binding names
themselves, so the referenced declaration is preserved. Function-parameter
defaults were already covered (params are visited in full).
Fixes#2396.
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
TooTallNate added a commit that referenced this pull request Jun 17, 2026
…default DCE test
The #2398 backport (#2465) added the destructuring-default-references-module-const
fixture but committed output-step.js verbatim from main and omitted output-client.js
entirely. Stable's SWC plugin emits a different step-registration shape
(registerStepFunction import vs. inline IIFE) and has less aggressive DCE (the
truly-unused UNUSED const is not stripped), so the copied output-step.js did not
match stable's actual output and the required output-client.js was missing.
Regenerate both fixture outputs against stable's plugin so the client_mode and
step_mode fixture tests pass. The regression being tested still holds: TTL and
RETRIES (referenced only via destructuring defaults) are preserved in all modes.
Signed-off-by: Nathan Rajlich <n@n8.io>
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.

[swc-plugin] DCE strips module-scope declarations referenced only by destructuring defaults → ReferenceError

3 participants

@pranaygp@TooTallNate
, '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" + ' fix(swc-plugin): count destructuring-default references in DCE usage analysis by pranaygp · Pull Request #2398 · vercel/workflow · GitHub
Skip to content

fix(swc-plugin): count destructuring-default references in DCE usage analysis - #2398

Merged
pranaygp merged 3 commits into
mainfrom
pgp/swc-plugin-fix
Jun 16, 2026
Merged

fix(swc-plugin): count destructuring-default references in DCE usage analysis#2398
pranaygp merged 3 commits into
mainfrom
pgp/swc-plugin-fix

Conversation

@pranaygp

Copy link
Copy Markdown
Contributor

Fixes#2396.

Problem

The SWC plugin's dead-code-elimination (DCE) usage analysis did not count identifier references that appear as default values inside a destructuring pattern (e.g. const { ttl = TTL } = options;). When a module-scope const was referenced only through such a destructuring default, the DCE pass treated it as unused and stripped it from the emitted bundle — while keeping the code that referenced it. The result was a runtime ReferenceError: <const> is not defined when the default fired.

This is mode-independent (reproduces in both step and workflow mode) and class-independent (a plain top-level function exhibits it too). It shipped as a worked-around bug in the kill-switch pattern (#1858) and remains latent in workbench/vitest/workflows/cookbook/distributed-abort-controller.ts.

Root cause

In ComprehensiveUsageCollector::visit_mut_var_declarator, the collector visited only the initializer and deliberately skipped the entire var_decl.name pattern (to avoid marking the binding name as "used"). For destructuring patterns, the default-value initializer expressions live insidevar_decl.name — so references like the TTL in { ttl = TTL } were never added to used_identifiers, and remove_dead_code then pruned the still-referenced declaration.

Fix

Added visit_pat_default_initializers, which walks a binding pattern and visits only the default-value initializer expressions (and computed keys) — ObjectPatProp::KeyValue values, ObjectPatProp::Assign.value, AssignPat.right, array element defaults, rest args — while still not marking the binding names themselves as used. visit_mut_var_declarator now calls it on var_decl.name.

Function-parameter destructuring defaults were already covered: visit_mut_fn_decl visits params in full (confirmed by the existing default-parameter-usage fixture), so no change was needed there.

Tests

  • New fixture destructuring-default-references-module-const/ asserts that two module-scope consts referenced only via destructuring defaults — one inside a class static method, one inside a plain exported function — survive in both step and workflow mode. A third, genuinely-unused const is still stripped, confirming DCE is not over-broadened.
  • Verified the fixture fails on main (consts stripped) and passes with the fix.
  • Full cargo test -p swc_workflow suite green (128 fixture tests + error tests).

Spec

Updated packages/swc-plugin-workflow/spec.md (both the step-mode and workflow-mode DCE sections) to note that a reference counts even when it appears only inside a destructuring-default initializer.

Relation to prior fixes

Same symptom class as #1944 (DCE removing a declaration that surviving code references) but a distinct root cause: #1944 was about DCE ordering relative to step hoisting; this is about the usage collector never traversing destructuring-default initializers at all, and reproduces with no nested/hoisted steps involved.

🤖 Generated with Claude Code

…analysis
The DCE usage collector skipped the entire variable name pattern when
visiting a `VarDeclarator` (to avoid marking the binding name as "used").
But default-value initializers inside destructuring patterns live in that
pattern — e.g. the `TTL` in `const { ttl = TTL } = options;` — so those
references were invisible to the collector. A module-scope `const`
referenced only through such a default was treated as unused and stripped,
while the surviving code kept reading it, producing a runtime
`ReferenceError` when the default fired.
Traverse the default-value initializer expressions (and computed keys)
within destructuring patterns while still not marking the binding names
themselves, so the referenced declaration is preserved. Function-parameter
defaults were already covered (params are visited in full).
Fixes#2396.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CopilotAI review requested due to automatic review settings June 13, 2026 06:29
@pranaygp
pranaygp requested a review from a team as a code ownerJune 13, 2026 06:29
@changeset-bot

changeset-botBot commented Jun 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9006ec3

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

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

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

@vercel

vercelBot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented Jun 13, 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🥇 Express0.034s (-30.4% 🟢)1.005s (~)0.971s101.00x
💻 LocalNitro0.051s (+22.4% 🔺)1.008s (~)0.957s101.52x
🐘 PostgresNitro0.060s (-7.3% 🟢)1.012s (~)0.952s101.78x
🐘 PostgresExpress0.062s (+2.0%)1.013s (~)0.951s101.85x
🐘 PostgresNext.js (Turbopack)0.067s (-8.4% 🟢)1.012s (~)0.945s101.98x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro0.339s (-43.5% 🟢)2.573s (-3.3%)2.234s101.00x
▲ VercelExpress0.455s (-59.0% 🟢)2.707s (-24.4% 🟢)2.252s101.34x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.079s (-1.9%)2.006s (~)0.927s101.00x
💻 LocalNitro1.110s (+1.2%)2.007s (~)0.897s101.03x
🐘 PostgresExpress1.114s (~)2.011s (~)0.897s101.03x
🐘 PostgresNitro1.119s (~)2.010s (~)0.891s101.04x
🐘 PostgresNext.js (Turbopack)1.140s (-1.6%)2.010s (~)0.871s101.06x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.805s (-32.7% 🟢)3.807s (-23.9% 🟢)2.003s101.00x
▲ VercelNitro1.900s (-32.7% 🟢)3.850s (-15.3% 🟢)1.951s101.05x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express10.432s (-1.1%)11.020s (~)0.588s31.00x
🐘 PostgresNitro10.583s (~)11.017s (~)0.434s31.01x
💻 LocalNitro10.585s (+0.6%)11.024s (~)0.439s31.01x
🐘 PostgresExpress10.598s (~)11.020s (~)0.422s31.02x
🐘 PostgresNext.js (Turbopack)10.819s (~)11.018s (~)0.199s31.04x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express14.863s (-8.6% 🟢)16.580s (-11.0% 🟢)1.718s21.00x
▲ VercelNitro14.872s (-18.1% 🟢)17.769s (-10.6% 🟢)2.897s21.00x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express13.524s (-2.3%)14.027s (~)0.503s51.00x
🐘 PostgresNitro13.752s (-0.8%)14.018s (~)0.266s51.02x
🐘 PostgresExpress13.773s (~)14.023s (~)0.250s51.02x
💻 LocalNitro13.861s (~)14.028s (~)0.167s51.02x
🐘 PostgresNext.js (Turbopack)14.473s (~)15.017s (~)0.544s41.07x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express22.452s (-35.7% 🟢)24.544s (-33.5% 🟢)2.092s31.00x
▲ VercelNitro22.733s (-31.0% 🟢)24.477s (-29.8% 🟢)1.744s31.01x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express12.003s (-4.8%)12.396s (-4.8%)0.394s81.00x
🐘 PostgresExpress12.613s (~)13.019s (~)0.405s71.05x
🐘 PostgresNitro12.668s (~)13.022s (~)0.353s71.06x
💻 LocalNitro12.685s (+0.9%)13.027s (~)0.342s71.06x
🐘 PostgresNext.js (Turbopack)13.947s (~)14.306s (+1.0%)0.358s71.16x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express31.375s (-9.7% 🟢)33.491s (-9.7% 🟢)2.116s31.00x
▲ VercelNitro31.721s (-16.3% 🟢)34.300s (-13.2% 🟢)2.578s31.01x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.202s (~)2.008s (~)0.805s151.00x
💻 LocalExpress1.202s (+0.9%)2.007s (~)0.805s151.00x
🐘 PostgresNitro1.216s (~)2.008s (~)0.792s151.01x
🐘 PostgresNext.js (Turbopack)1.280s (-5.6% 🟢)2.008s (-3.3%)0.727s151.06x
💻 LocalNitro1.317s (+13.2% 🔺)2.007s (~)0.690s151.10x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.599s (+1.9%)4.260s (+4.6%)1.662s81.00x
▲ VercelNitro2.787s (-9.8% 🟢)4.617s (+0.7%)1.830s71.07x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.332s (-1.9%)2.316s (-7.6% 🟢)0.984s131.00x
🐘 PostgresNitro1.368s (+2.0%)2.509s (~)1.141s121.03x
🐘 PostgresNext.js (Turbopack)1.571s (-4.1%)2.394s (-4.6%)0.824s131.18x
💻 LocalExpress1.614s (-14.8% 🟢)2.005s (-6.8% 🟢)0.391s151.21x
💻 LocalNitro1.723s (+5.8% 🔺)2.007s (~)0.284s151.29x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.005s (-20.3% 🟢)5.201s (-3.7%)2.196s61.00x
▲ VercelExpress3.112s (-24.4% 🟢)5.163s (-14.7% 🟢)2.051s61.04x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.605s (-1.1%)4.011s (+3.2%)2.406s81.00x
🐘 PostgresNitro1.717s (+5.7% 🔺)3.760s (-9.2% 🟢)2.043s81.07x
🐘 PostgresNext.js (Turbopack)3.515s (-8.3% 🟢)4.308s (-3.1%)0.793s72.19x
💻 LocalNitro4.578s (+13.9% 🔺)5.155s (+12.5% 🔺)0.577s72.85x
💻 LocalExpress4.611s (-11.9% 🟢)5.154s (-11.9% 🟢)0.543s72.87x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.454s (-18.7% 🟢)5.446s (-4.9%)1.992s61.00x
▲ VercelExpress3.755s (-17.9% 🟢)5.803s (-11.6% 🟢)2.048s61.09x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.206s (-1.3%)2.007s (~)0.802s151.00x
🐘 PostgresExpress1.212s (~)2.008s (~)0.795s151.01x
🐘 PostgresNext.js (Turbopack)1.301s (+1.2%)2.007s (~)0.706s151.08x
💻 LocalExpress1.467s (-9.1% 🟢)2.006s (~)0.539s151.22x
💻 LocalNitro1.614s (+6.8% 🔺)2.007s (~)0.393s151.34x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.317s (-64.3% 🟢)3.984s (-50.4% 🟢)1.667s81.00x
▲ VercelExpress2.358s (+3.7%)3.953s (+2.6%)1.594s81.02x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.355s (~)2.393s (-7.7% 🟢)1.037s131.00x
🐘 PostgresExpress1.385s (-0.7%)2.592s (+4.9%)1.206s121.02x
🐘 PostgresNext.js (Turbopack)1.550s (+0.9%)2.075s (-6.7% 🟢)0.525s151.14x
💻 LocalExpress1.760s (-31.7% 🟢)2.072s (-33.3% 🟢)0.313s151.30x
💻 LocalNitro2.052s (+14.6% 🔺)2.508s (+21.0% 🔺)0.456s121.51x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.782s (-16.5% 🟢)4.629s (-13.4% 🟢)1.847s71.00x
▲ VercelNitro3.219s (-14.0% 🟢)4.836s (-11.4% 🟢)1.617s71.16x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.589s (-9.4% 🟢)4.012s (-3.0%)2.423s81.00x
🐘 PostgresNitro1.691s (-2.2%)4.148s (+6.7% 🔺)2.457s81.06x
🐘 PostgresNext.js (Turbopack)3.574s (-6.9% 🟢)4.298s (-3.3%)0.723s72.25x
💻 LocalExpress4.386s (-30.8% 🟢)5.015s (-28.6% 🟢)0.629s62.76x
💻 LocalNitro5.079s (+6.6% 🔺)5.513s (+3.1%)0.434s63.20x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.500s (-46.9% 🟢)5.977s (-28.4% 🟢)2.477s61.00x
▲ VercelExpress3.732s (-28.7% 🟢)6.025s (-13.5% 🟢)2.293s51.07x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express0.574s (-12.9% 🟢)1.004s (-3.4%)0.430s601.00x
🐘 PostgresNitro0.576s (-10.6% 🟢)1.006s (-3.4%)0.430s601.00x
🐘 PostgresExpress0.582s (-1.7%)1.007s (~)0.425s601.01x
💻 LocalNitro0.675s (+15.7% 🔺)1.040s (+3.5%)0.365s581.17x
🐘 PostgresNext.js (Turbopack)0.826s (-2.9%)1.023s (~)0.198s591.44x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.107s (-27.8% 🟢)7.860s (-22.2% 🟢)1.753s81.00x
▲ VercelExpress6.449s (-2.0%)8.127s (-5.8% 🟢)1.678s81.06x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.328s (-16.2% 🟢)2.027s (+1.0%)0.699s451.00x
🐘 PostgresExpress1.439s (~)2.030s (~)0.591s451.08x
🐘 PostgresNitro1.476s (+2.1%)2.101s (+4.6%)0.625s431.11x
💻 LocalNitro1.548s (+4.8%)2.007s (~)0.459s451.17x
🐘 PostgresNext.js (Turbopack)1.998s (-1.9%)2.315s (-9.7% 🟢)0.318s391.50x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express14.175s (-29.6% 🟢)16.338s (-25.3% 🟢)2.163s61.00x
▲ VercelNitro14.565s (-22.4% 🟢)16.655s (-19.8% 🟢)2.090s61.03x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.704s (-4.1%)3.085s (-1.7%)0.381s391.00x
🐘 PostgresExpress2.768s (~)3.112s (-0.8%)0.344s391.02x
💻 LocalExpress2.895s (-13.4% 🟢)3.245s (-19.1% 🟢)0.350s381.07x
💻 LocalNitro3.354s (+5.9% 🔺)4.011s (+2.5%)0.657s301.24x
🐘 PostgresNext.js (Turbopack)3.954s (-1.8%)4.332s (-1.6%)0.379s281.46x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express27.701s (-28.4% 🟢)30.747s (-23.9% 🟢)3.046s41.00x
▲ VercelNitro28.386s (-21.7% 🟢)31.304s (-18.0% 🟢)2.917s41.02x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.247s (-5.5% 🟢)1.006s (~)0.760s601.00x
🐘 PostgresExpress0.247s (-5.2% 🟢)1.006s (~)0.759s601.00x
🐘 PostgresNext.js (Turbopack)0.313s (+1.6%)1.006s (~)0.693s601.27x
💻 LocalExpress0.418s (-2.4%)1.004s (~)0.586s601.69x
💻 LocalNitro0.457s (+5.8% 🔺)1.005s (~)0.549s601.85x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.829s (-36.6% 🟢)3.881s (-22.8% 🟢)2.052s161.00x
▲ VercelExpress1.907s (-46.6% 🟢)3.882s (-30.6% 🟢)1.975s161.04x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.413s (~)1.029s (+2.3%)0.616s881.00x
🐘 PostgresNitro0.413s (-9.6% 🟢)1.065s (-1.3%)0.652s851.00x
🐘 PostgresNext.js (Turbopack)0.611s (-9.9% 🟢)1.105s (-13.4% 🟢)0.494s821.48x
💻 LocalExpress2.017s (-8.0% 🟢)2.537s (-11.1% 🟢)0.521s364.89x
💻 LocalNitro2.119s (+5.4% 🔺)2.715s (+11.2% 🔺)0.596s345.13x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.194s (-8.4% 🟢)5.218s (+0.9%)2.024s181.00x
▲ VercelExpress3.290s (-36.2% 🟢)5.431s (-24.8% 🟢)2.141s171.03x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.777s (-7.9% 🟢)1.367s (-7.1% 🟢)0.590s891.00x
🐘 PostgresExpress0.799s (-5.8% 🟢)1.419s (-5.9% 🟢)0.621s851.03x
🐘 PostgresNext.js (Turbopack)3.176s (+2.8%)3.889s (+1.6%)0.713s314.09x
💻 LocalExpress8.590s (-12.9% 🟢)9.254s (-10.0% 🟢)0.664s1311.06x
💻 LocalNitro8.879s (+1.8%)9.566s (+3.4%)0.687s1311.43x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.698s (-16.6% 🟢)7.627s (-11.6% 🟢)1.929s161.00x
▲ VercelNitro5.845s (-13.4% 🟢)7.928s (-8.2% 🟢)2.083s161.03x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.145s (-2.2%)2.004s (~)0.009s (-30.3% 🟢)2.014s (~)0.869s101.00x
💻 LocalNitro1.165s (+0.7%)2.005s (~)0.011s (+5.0%)2.019s (~)0.854s101.02x
🐘 PostgresNitro1.170s (-2.3%)1.995s (~)0.001s (-8.3% 🟢)2.010s (~)0.840s101.02x
🐘 PostgresExpress1.176s (~)1.998s (~)0.001s (~)2.010s (~)0.835s101.03x
🐘 PostgresNext.js (Turbopack)1.238s (~)2.000s (~)0.001s (~)2.011s (~)0.773s101.08x
💻 LocalNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.330s (-1.4%)3.506s (-0.7%)0.786s (+11.8% 🔺)4.765s (-0.5%)2.435s101.00x
▲ VercelNitro2.442s (-4.2%)3.523s (-5.1% 🟢)1.169s (+57.9% 🔺)5.176s (+4.3%)2.734s101.05x
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Express | Nitro

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.518s (-5.7% 🟢)2.009s (~)0.010s (-19.8% 🟢)2.021s (~)0.503s301.00x
🐘 PostgresNitro1.573s (-4.0%)2.001s (~)0.005s (+3.8%)2.026s (~)0.453s301.04x
💻 LocalNitro1.586s (+1.3%)2.012s (~)0.013s (+12.0% 🔺)2.028s (~)0.441s301.04x
🐘 PostgresExpress1.627s (+1.3%)2.007s (~)0.005s (+10.1% 🔺)2.027s (~)0.400s301.07x
🐘 PostgresNext.js (Turbopack)1.838s (+2.6%)2.010s (~)0.006s (+1.2%)2.026s (~)0.188s301.21x
💻 LocalNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.321s (-6.1% 🟢)7.549s (-6.2% 🟢)0.291s (+32.8% 🔺)8.340s (-5.3% 🟢)2.019s81.00x
▲ VercelExpress6.677s (-3.2%)8.183s (-2.0%)0.202s (-41.3% 🟢)8.874s (-3.8%)2.197s71.06x
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro | Express

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.779s (-4.1%)1.050s (-1.4%)0.000s (+194.7% 🔺)1.069s (-1.0%)0.290s571.00x
🐘 PostgresExpress0.811s (+1.7%)1.028s (-1.8%)0.000s (+Infinity% 🔺)1.073s (+1.2%)0.263s561.04x
🐘 PostgresNext.js (Turbopack)1.044s (+1.6%)1.581s (-2.5%)0.000s (-2.6%)1.589s (-2.6%)0.544s381.34x
💻 LocalExpress1.238s (-12.8% 🟢)1.980s (-1.7%)0.000s (-30.9% 🟢)1.982s (-1.7%)0.744s311.59x
💻 LocalNitro1.428s (+5.7% 🔺)2.015s (~)0.001s (+171.4% 🔺)2.017s (~)0.590s301.83x
💻 LocalNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.002s (-27.0% 🟢)4.590s (-20.1% 🟢)0.001s (+Infinity% 🔺)5.105s (-18.2% 🟢)2.104s121.00x
▲ VercelExpress3.054s (-17.3% 🟢)4.589s (-6.4% 🟢)0.002s (+Infinity% 🔺)5.037s (-9.3% 🟢)1.983s121.02x
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro | Express

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

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.596s (-5.8% 🟢)2.176s (~)0.000s (-50.0% 🟢)2.189s (~)0.593s281.00x
🐘 PostgresExpress1.606s (-2.8%)2.137s (-1.9%)0.000s (NaN%)2.152s (-1.6%)0.546s281.01x
🐘 PostgresNext.js (Turbopack)2.147s (-4.8%)2.652s (-2.8%)0.000s (+Infinity% 🔺)2.671s (-2.4%)0.524s231.35x
💻 LocalExpress2.630s (-20.7% 🟢)3.121s (-22.5% 🟢)0.000s (-70.0% 🟢)3.126s (-22.5% 🟢)0.496s201.65x
💻 LocalNitro3.268s (+6.1% 🔺)3.965s (+1.7%)0.001s (+85.7% 🔺)3.968s (+1.7%)0.700s162.05x
💻 LocalNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.141s (-15.8% 🟢)5.935s (-3.7%)0.003s (+Infinity% 🔺)6.439s (-5.3% 🟢)2.298s101.00x
▲ VercelExpress4.539s (-25.2% 🟢)6.277s (-17.3% 🟢)0.001s (-11.1% 🟢)6.771s (-17.3% 🟢)2.232s91.10x
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro | Express

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress20/21
🐘 PostgresNitro13/21
▲ VercelNitro11/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres12/21
Next.js (Turbopack)🐘 Postgres21/21
Nitro🐘 Postgres18/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)

📋 View full workflow run


Some benchmark jobs failed:

  • Local: failure
  • Postgres: success
  • Vercel: failure

Check the workflow run for details.

@github-actions

github-actionsBot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production129302271520
✅ 💻 Local Development190902192128
✅ 📦 Local Production190902192128
✅ 🐘 Local Postgres189502332128
✅ 🪟 Windows15200152
✅ 📋 Other88501791064
Total8043010779120

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro125027
✅ example125027
✅ express125027
✅ fastify125027
✅ hono125027
✅ nextjs-webpack14903
✅ nitro125027
✅ nuxt125027
✅ sveltekit14408
✅ vite125027
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable-lazy-discovery-disabled15200
✅ nextjs-turbopack-stable-lazy-discovery-enabled15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable-lazy-discovery-disabled15200
✅ nextjs-webpack-stable-lazy-discovery-enabled15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable-lazy-discovery-disabled15200
✅ nextjs-turbopack-stable-lazy-discovery-enabled15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable-lazy-discovery-disabled15200
✅ nextjs-webpack-stable-lazy-discovery-enabled15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable126026
✅ express-stable126026
✅ fastify-stable126026
✅ hono-stable126026
✅ nextjs-turbopack-canary132020
✅ nextjs-turbopack-stable-lazy-discovery-disabled15101
✅ nextjs-turbopack-stable-lazy-discovery-enabled15101
✅ nextjs-webpack-canary132020
✅ nextjs-webpack-stable-lazy-discovery-disabled15101
✅ nextjs-webpack-stable-lazy-discovery-enabled15101
✅ 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


Some E2E test jobs failed:

  • Vercel Prod: failure
  • Local Dev: success
  • Local Prod: success
  • Local Postgres: success
  • Windows: success

Check the workflow run for details.

CopilotAI 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.

Pull request overview

Fixes the SWC plugin’s DCE usage analysis so module-scope declarations referenced only via destructuring default initializers (e.g. const { ttl = TTL } = options) are correctly counted as “used”, preventing runtime ReferenceErrors when defaults fire.

Changes:

  • Extend ComprehensiveUsageCollector to traverse destructuring patterns and visit only default-value initializers (and computed keys), without marking binding identifiers as used.
  • Add a regression fixture covering module-scope const preservation in both step and workflow modes (and confirming truly-unused consts are still removed).
  • Update the SWC plugin spec and add a changeset for the patch release.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
packages/swc-plugin-workflow/transform/src/lib.rsAdds visit_pat_default_initializers and wires it into visit_mut_var_declarator so destructuring-default references are counted for DCE.
packages/swc-plugin-workflow/transform/tests/fixture/destructuring-default-references-module-const/input.jsNew fixture input reproducing the destructuring-default-only reference pattern and an unused const.
packages/swc-plugin-workflow/transform/tests/fixture/destructuring-default-references-module-const/output-step.jsStep-mode snapshot asserting referenced consts survive and unused const is removed.
packages/swc-plugin-workflow/transform/tests/fixture/destructuring-default-references-module-const/output-workflow.jsWorkflow-mode snapshot asserting referenced consts survive and unused const is removed.
packages/swc-plugin-workflow/spec.mdDocuments that destructuring-default initializers count as references for DCE in both modes.
.changeset/swc-destructuring-default-dce.mdPatch changeset describing the DCE fix and the prevented runtime error.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

* origin/main:
docs: document run idempotency (#2011)
Render attr_set events and run attributes in observability UI (#2393)
[ci] Fix backport job model slug (#2403)
[ci] Comment on PR when backport fails, revert to use opus 4.8 (#2400)
Update queue client to 0.3.1 (#2399)
fix(deps): upgrade esbuild to 0.28.1 (GHSA-gv7w-rqvm-qjhr) (#2395)

@TooTallNateTooTallNate left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Approve — precise root-cause fix, exhaustive pattern coverage, and I reproduced the before/after

Targeted DCE fix: the usage collector deliberately skipped var_decl.name to avoid marking binding names as used, but destructuring-default initializers (const { ttl = TTL } = options;) live inside that pattern, so references like TTL were never counted and DCE stripped the still-referenced declaration → runtime ReferenceError. The diagnosis in the PR matches the code exactly.

What I verified

  • The new visit_pat_default_initializers is exhaustive over the binding-pattern AST and visits only value positions: object KeyValue values + computed keys, Assign.value defaults, array element defaults, AssignPat.right, rest args, and expression patterns — while never descending into binding names. I traced each arm against the Pat/ObjectPatProp variants; nothing is missed and the { ttl = TTL } case correctly skips the ttl key and visits only the TTL default.
  • It cannot over-broaden DCE. The collector's visit_mut_ident blindly adds to used_identifiers, and this helper only ever adds references — the safe direction for DCE (over-counting preserves declarations, never wrongly strips). The Pat::Expr arm visiting a member expression's property ident is the same conservative behavior the collector already has when walking initializers, so it introduces no new risk.
  • Built and ran the full Rust suite: 128 fixture tests + error tests, all green.
  • Reproduced the regression: reverted just the visit_pat_default_initializers call site to simulate main, and both the step-mode and workflow-mode fixtures FAIL — the emitted output strips const TTL/const RETRIES while keeping the code that references them. Restored the fix → both pass. This is the before/after the PR claims, confirmed locally.
  • The fixture's negative case earns its keep: const UNUSED = 'dead' is still stripped in both outputs, proving the fix preserves referenced declarations only, not everything in a pattern's vicinity.
  • The fix covers the real latent bug, not just the synthetic repro: workbench/vitest/workflows/cookbook/distributed-abort-controller.ts:126 has const { ttlMs = DEFAULT_TTL_MS, graceMs = DEFAULT_GRACE_MS } = options; referencing module consts at lines 16/18 — exactly this pattern. The fixture is a faithful minimal reduction of it.
  • spec.md updated per AGENTS.md — both the step-mode and workflow-mode DCE sections gain the destructuring-default note (correct; spec.md has no version frontmatter, the bump rule applies to skills/). Changeset is a correctly-scoped @workflow/swc-plugin patch.

Notes

  • The fixture covers a class static method and a plain exported function, deliberately demonstrating the bug is class- and mode-independent (matching the issue). Good instinct to prove both, since the worked-around precedent (#1858 kill-switch) might have suggested it was class-specific.
  • Function-parameter destructuring defaults were already handled (visit_mut_fn_decl visits params in full, per the existing default-parameter-usage fixture) — the PR correctly scopes the fix to var declarators only and says so. I'd have flagged it if param defaults regressed, but they're untouched and the existing fixture still passes.
  • Distinct from #1944 as the PR notes: that was DCE ordering vs step hoisting; this is the collector never traversing these initializers at all. No nested/hoisted steps involved, which the fixture confirms.

CI lanes that are red are the session's known baseline flakes (Windows dev-server / express benchmark), unrelated to a Rust-only change. LGTM.

@pranaygp
pranaygp merged commit 53ede30 into mainJun 16, 2026
112 of 116 checks passed
@pranaygp
pranaygp deleted the pgp/swc-plugin-fix branch June 16, 2026 23:16
@github-actionsgithub-actionsBot mentioned this pull request Jun 16, 2026
github-actionsBot added a commit that referenced this pull request Jun 16, 2026
…analysis (#2398)
The DCE usage collector skipped the entire variable name pattern when
visiting a `VarDeclarator` (to avoid marking the binding name as "used").
But default-value initializers inside destructuring patterns live in that
pattern — e.g. the `TTL` in `const { ttl = TTL } = options;` — so those
references were invisible to the collector. A module-scope `const`
referenced only through such a default was treated as unused and stripped,
while the surviving code kept reading it, producing a runtime
`ReferenceError` when the default fired.
Traverse the default-value initializer expressions (and computed keys)
within destructuring patterns while still not marking the binding names
themselves, so the referenced declaration is preserved. Function-parameter
defaults were already covered (params are visited in full).
Fixes#2396.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

Backport PR opened against stable: #2465. Merge conflicts were resolved by AI — please review carefully. (backport job run)

pranaygp pushed a commit that referenced this pull request Jun 17, 2026
…analysis (#2398) (#2465)
The DCE usage collector skipped the entire variable name pattern when
visiting a `VarDeclarator` (to avoid marking the binding name as "used").
But default-value initializers inside destructuring patterns live in that
pattern — e.g. the `TTL` in `const { ttl = TTL } = options;` — so those
references were invisible to the collector. A module-scope `const`
referenced only through such a default was treated as unused and stripped,
while the surviving code kept reading it, producing a runtime
`ReferenceError` when the default fired.
Traverse the default-value initializer expressions (and computed keys)
within destructuring patterns while still not marking the binding names
themselves, so the referenced declaration is preserved. Function-parameter
defaults were already covered (params are visited in full).
Fixes#2396.
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
TooTallNate added a commit that referenced this pull request Jun 17, 2026
…default DCE test
The #2398 backport (#2465) added the destructuring-default-references-module-const
fixture but committed output-step.js verbatim from main and omitted output-client.js
entirely. Stable's SWC plugin emits a different step-registration shape
(registerStepFunction import vs. inline IIFE) and has less aggressive DCE (the
truly-unused UNUSED const is not stripped), so the copied output-step.js did not
match stable's actual output and the required output-client.js was missing.
Regenerate both fixture outputs against stable's plugin so the client_mode and
step_mode fixture tests pass. The regression being tested still holds: TTL and
RETRIES (referenced only via destructuring defaults) are preserved in all modes.
Signed-off-by: Nathan Rajlich <n@n8.io>
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.

[swc-plugin] DCE strips module-scope declarations referenced only by destructuring defaults → ReferenceError

3 participants

@pranaygp@TooTallNate
, '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('^' + ".*" + ' fix(swc-plugin): count destructuring-default references in DCE usage analysis by pranaygp · Pull Request #2398 · vercel/workflow · GitHub
Skip to content

fix(swc-plugin): count destructuring-default references in DCE usage analysis - #2398

Merged
pranaygp merged 3 commits into
mainfrom
pgp/swc-plugin-fix
Jun 16, 2026
Merged

fix(swc-plugin): count destructuring-default references in DCE usage analysis#2398
pranaygp merged 3 commits into
mainfrom
pgp/swc-plugin-fix

Conversation

@pranaygp

Copy link
Copy Markdown
Contributor

Fixes#2396.

Problem

The SWC plugin's dead-code-elimination (DCE) usage analysis did not count identifier references that appear as default values inside a destructuring pattern (e.g. const { ttl = TTL } = options;). When a module-scope const was referenced only through such a destructuring default, the DCE pass treated it as unused and stripped it from the emitted bundle — while keeping the code that referenced it. The result was a runtime ReferenceError: <const> is not defined when the default fired.

This is mode-independent (reproduces in both step and workflow mode) and class-independent (a plain top-level function exhibits it too). It shipped as a worked-around bug in the kill-switch pattern (#1858) and remains latent in workbench/vitest/workflows/cookbook/distributed-abort-controller.ts.

Root cause

In ComprehensiveUsageCollector::visit_mut_var_declarator, the collector visited only the initializer and deliberately skipped the entire var_decl.name pattern (to avoid marking the binding name as "used"). For destructuring patterns, the default-value initializer expressions live insidevar_decl.name — so references like the TTL in { ttl = TTL } were never added to used_identifiers, and remove_dead_code then pruned the still-referenced declaration.

Fix

Added visit_pat_default_initializers, which walks a binding pattern and visits only the default-value initializer expressions (and computed keys) — ObjectPatProp::KeyValue values, ObjectPatProp::Assign.value, AssignPat.right, array element defaults, rest args — while still not marking the binding names themselves as used. visit_mut_var_declarator now calls it on var_decl.name.

Function-parameter destructuring defaults were already covered: visit_mut_fn_decl visits params in full (confirmed by the existing default-parameter-usage fixture), so no change was needed there.

Tests

  • New fixture destructuring-default-references-module-const/ asserts that two module-scope consts referenced only via destructuring defaults — one inside a class static method, one inside a plain exported function — survive in both step and workflow mode. A third, genuinely-unused const is still stripped, confirming DCE is not over-broadened.
  • Verified the fixture fails on main (consts stripped) and passes with the fix.
  • Full cargo test -p swc_workflow suite green (128 fixture tests + error tests).

Spec

Updated packages/swc-plugin-workflow/spec.md (both the step-mode and workflow-mode DCE sections) to note that a reference counts even when it appears only inside a destructuring-default initializer.

Relation to prior fixes

Same symptom class as #1944 (DCE removing a declaration that surviving code references) but a distinct root cause: #1944 was about DCE ordering relative to step hoisting; this is about the usage collector never traversing destructuring-default initializers at all, and reproduces with no nested/hoisted steps involved.

🤖 Generated with Claude Code

…analysis
The DCE usage collector skipped the entire variable name pattern when
visiting a `VarDeclarator` (to avoid marking the binding name as "used").
But default-value initializers inside destructuring patterns live in that
pattern — e.g. the `TTL` in `const { ttl = TTL } = options;` — so those
references were invisible to the collector. A module-scope `const`
referenced only through such a default was treated as unused and stripped,
while the surviving code kept reading it, producing a runtime
`ReferenceError` when the default fired.
Traverse the default-value initializer expressions (and computed keys)
within destructuring patterns while still not marking the binding names
themselves, so the referenced declaration is preserved. Function-parameter
defaults were already covered (params are visited in full).
Fixes#2396.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CopilotAI review requested due to automatic review settings June 13, 2026 06:29
@pranaygp
pranaygp requested a review from a team as a code ownerJune 13, 2026 06:29
@changeset-bot

changeset-botBot commented Jun 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9006ec3

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

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

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

@vercel

vercelBot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented Jun 13, 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🥇 Express0.034s (-30.4% 🟢)1.005s (~)0.971s101.00x
💻 LocalNitro0.051s (+22.4% 🔺)1.008s (~)0.957s101.52x
🐘 PostgresNitro0.060s (-7.3% 🟢)1.012s (~)0.952s101.78x
🐘 PostgresExpress0.062s (+2.0%)1.013s (~)0.951s101.85x
🐘 PostgresNext.js (Turbopack)0.067s (-8.4% 🟢)1.012s (~)0.945s101.98x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro0.339s (-43.5% 🟢)2.573s (-3.3%)2.234s101.00x
▲ VercelExpress0.455s (-59.0% 🟢)2.707s (-24.4% 🟢)2.252s101.34x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.079s (-1.9%)2.006s (~)0.927s101.00x
💻 LocalNitro1.110s (+1.2%)2.007s (~)0.897s101.03x
🐘 PostgresExpress1.114s (~)2.011s (~)0.897s101.03x
🐘 PostgresNitro1.119s (~)2.010s (~)0.891s101.04x
🐘 PostgresNext.js (Turbopack)1.140s (-1.6%)2.010s (~)0.871s101.06x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.805s (-32.7% 🟢)3.807s (-23.9% 🟢)2.003s101.00x
▲ VercelNitro1.900s (-32.7% 🟢)3.850s (-15.3% 🟢)1.951s101.05x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express10.432s (-1.1%)11.020s (~)0.588s31.00x
🐘 PostgresNitro10.583s (~)11.017s (~)0.434s31.01x
💻 LocalNitro10.585s (+0.6%)11.024s (~)0.439s31.01x
🐘 PostgresExpress10.598s (~)11.020s (~)0.422s31.02x
🐘 PostgresNext.js (Turbopack)10.819s (~)11.018s (~)0.199s31.04x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express14.863s (-8.6% 🟢)16.580s (-11.0% 🟢)1.718s21.00x
▲ VercelNitro14.872s (-18.1% 🟢)17.769s (-10.6% 🟢)2.897s21.00x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express13.524s (-2.3%)14.027s (~)0.503s51.00x
🐘 PostgresNitro13.752s (-0.8%)14.018s (~)0.266s51.02x
🐘 PostgresExpress13.773s (~)14.023s (~)0.250s51.02x
💻 LocalNitro13.861s (~)14.028s (~)0.167s51.02x
🐘 PostgresNext.js (Turbopack)14.473s (~)15.017s (~)0.544s41.07x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express22.452s (-35.7% 🟢)24.544s (-33.5% 🟢)2.092s31.00x
▲ VercelNitro22.733s (-31.0% 🟢)24.477s (-29.8% 🟢)1.744s31.01x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express12.003s (-4.8%)12.396s (-4.8%)0.394s81.00x
🐘 PostgresExpress12.613s (~)13.019s (~)0.405s71.05x
🐘 PostgresNitro12.668s (~)13.022s (~)0.353s71.06x
💻 LocalNitro12.685s (+0.9%)13.027s (~)0.342s71.06x
🐘 PostgresNext.js (Turbopack)13.947s (~)14.306s (+1.0%)0.358s71.16x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express31.375s (-9.7% 🟢)33.491s (-9.7% 🟢)2.116s31.00x
▲ VercelNitro31.721s (-16.3% 🟢)34.300s (-13.2% 🟢)2.578s31.01x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.202s (~)2.008s (~)0.805s151.00x
💻 LocalExpress1.202s (+0.9%)2.007s (~)0.805s151.00x
🐘 PostgresNitro1.216s (~)2.008s (~)0.792s151.01x
🐘 PostgresNext.js (Turbopack)1.280s (-5.6% 🟢)2.008s (-3.3%)0.727s151.06x
💻 LocalNitro1.317s (+13.2% 🔺)2.007s (~)0.690s151.10x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.599s (+1.9%)4.260s (+4.6%)1.662s81.00x
▲ VercelNitro2.787s (-9.8% 🟢)4.617s (+0.7%)1.830s71.07x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.332s (-1.9%)2.316s (-7.6% 🟢)0.984s131.00x
🐘 PostgresNitro1.368s (+2.0%)2.509s (~)1.141s121.03x
🐘 PostgresNext.js (Turbopack)1.571s (-4.1%)2.394s (-4.6%)0.824s131.18x
💻 LocalExpress1.614s (-14.8% 🟢)2.005s (-6.8% 🟢)0.391s151.21x
💻 LocalNitro1.723s (+5.8% 🔺)2.007s (~)0.284s151.29x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.005s (-20.3% 🟢)5.201s (-3.7%)2.196s61.00x
▲ VercelExpress3.112s (-24.4% 🟢)5.163s (-14.7% 🟢)2.051s61.04x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.605s (-1.1%)4.011s (+3.2%)2.406s81.00x
🐘 PostgresNitro1.717s (+5.7% 🔺)3.760s (-9.2% 🟢)2.043s81.07x
🐘 PostgresNext.js (Turbopack)3.515s (-8.3% 🟢)4.308s (-3.1%)0.793s72.19x
💻 LocalNitro4.578s (+13.9% 🔺)5.155s (+12.5% 🔺)0.577s72.85x
💻 LocalExpress4.611s (-11.9% 🟢)5.154s (-11.9% 🟢)0.543s72.87x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.454s (-18.7% 🟢)5.446s (-4.9%)1.992s61.00x
▲ VercelExpress3.755s (-17.9% 🟢)5.803s (-11.6% 🟢)2.048s61.09x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.206s (-1.3%)2.007s (~)0.802s151.00x
🐘 PostgresExpress1.212s (~)2.008s (~)0.795s151.01x
🐘 PostgresNext.js (Turbopack)1.301s (+1.2%)2.007s (~)0.706s151.08x
💻 LocalExpress1.467s (-9.1% 🟢)2.006s (~)0.539s151.22x
💻 LocalNitro1.614s (+6.8% 🔺)2.007s (~)0.393s151.34x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.317s (-64.3% 🟢)3.984s (-50.4% 🟢)1.667s81.00x
▲ VercelExpress2.358s (+3.7%)3.953s (+2.6%)1.594s81.02x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.355s (~)2.393s (-7.7% 🟢)1.037s131.00x
🐘 PostgresExpress1.385s (-0.7%)2.592s (+4.9%)1.206s121.02x
🐘 PostgresNext.js (Turbopack)1.550s (+0.9%)2.075s (-6.7% 🟢)0.525s151.14x
💻 LocalExpress1.760s (-31.7% 🟢)2.072s (-33.3% 🟢)0.313s151.30x
💻 LocalNitro2.052s (+14.6% 🔺)2.508s (+21.0% 🔺)0.456s121.51x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.782s (-16.5% 🟢)4.629s (-13.4% 🟢)1.847s71.00x
▲ VercelNitro3.219s (-14.0% 🟢)4.836s (-11.4% 🟢)1.617s71.16x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.589s (-9.4% 🟢)4.012s (-3.0%)2.423s81.00x
🐘 PostgresNitro1.691s (-2.2%)4.148s (+6.7% 🔺)2.457s81.06x
🐘 PostgresNext.js (Turbopack)3.574s (-6.9% 🟢)4.298s (-3.3%)0.723s72.25x
💻 LocalExpress4.386s (-30.8% 🟢)5.015s (-28.6% 🟢)0.629s62.76x
💻 LocalNitro5.079s (+6.6% 🔺)5.513s (+3.1%)0.434s63.20x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.500s (-46.9% 🟢)5.977s (-28.4% 🟢)2.477s61.00x
▲ VercelExpress3.732s (-28.7% 🟢)6.025s (-13.5% 🟢)2.293s51.07x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express0.574s (-12.9% 🟢)1.004s (-3.4%)0.430s601.00x
🐘 PostgresNitro0.576s (-10.6% 🟢)1.006s (-3.4%)0.430s601.00x
🐘 PostgresExpress0.582s (-1.7%)1.007s (~)0.425s601.01x
💻 LocalNitro0.675s (+15.7% 🔺)1.040s (+3.5%)0.365s581.17x
🐘 PostgresNext.js (Turbopack)0.826s (-2.9%)1.023s (~)0.198s591.44x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.107s (-27.8% 🟢)7.860s (-22.2% 🟢)1.753s81.00x
▲ VercelExpress6.449s (-2.0%)8.127s (-5.8% 🟢)1.678s81.06x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.328s (-16.2% 🟢)2.027s (+1.0%)0.699s451.00x
🐘 PostgresExpress1.439s (~)2.030s (~)0.591s451.08x
🐘 PostgresNitro1.476s (+2.1%)2.101s (+4.6%)0.625s431.11x
💻 LocalNitro1.548s (+4.8%)2.007s (~)0.459s451.17x
🐘 PostgresNext.js (Turbopack)1.998s (-1.9%)2.315s (-9.7% 🟢)0.318s391.50x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express14.175s (-29.6% 🟢)16.338s (-25.3% 🟢)2.163s61.00x
▲ VercelNitro14.565s (-22.4% 🟢)16.655s (-19.8% 🟢)2.090s61.03x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.704s (-4.1%)3.085s (-1.7%)0.381s391.00x
🐘 PostgresExpress2.768s (~)3.112s (-0.8%)0.344s391.02x
💻 LocalExpress2.895s (-13.4% 🟢)3.245s (-19.1% 🟢)0.350s381.07x
💻 LocalNitro3.354s (+5.9% 🔺)4.011s (+2.5%)0.657s301.24x
🐘 PostgresNext.js (Turbopack)3.954s (-1.8%)4.332s (-1.6%)0.379s281.46x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express27.701s (-28.4% 🟢)30.747s (-23.9% 🟢)3.046s41.00x
▲ VercelNitro28.386s (-21.7% 🟢)31.304s (-18.0% 🟢)2.917s41.02x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.247s (-5.5% 🟢)1.006s (~)0.760s601.00x
🐘 PostgresExpress0.247s (-5.2% 🟢)1.006s (~)0.759s601.00x
🐘 PostgresNext.js (Turbopack)0.313s (+1.6%)1.006s (~)0.693s601.27x
💻 LocalExpress0.418s (-2.4%)1.004s (~)0.586s601.69x
💻 LocalNitro0.457s (+5.8% 🔺)1.005s (~)0.549s601.85x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.829s (-36.6% 🟢)3.881s (-22.8% 🟢)2.052s161.00x
▲ VercelExpress1.907s (-46.6% 🟢)3.882s (-30.6% 🟢)1.975s161.04x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.413s (~)1.029s (+2.3%)0.616s881.00x
🐘 PostgresNitro0.413s (-9.6% 🟢)1.065s (-1.3%)0.652s851.00x
🐘 PostgresNext.js (Turbopack)0.611s (-9.9% 🟢)1.105s (-13.4% 🟢)0.494s821.48x
💻 LocalExpress2.017s (-8.0% 🟢)2.537s (-11.1% 🟢)0.521s364.89x
💻 LocalNitro2.119s (+5.4% 🔺)2.715s (+11.2% 🔺)0.596s345.13x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.194s (-8.4% 🟢)5.218s (+0.9%)2.024s181.00x
▲ VercelExpress3.290s (-36.2% 🟢)5.431s (-24.8% 🟢)2.141s171.03x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.777s (-7.9% 🟢)1.367s (-7.1% 🟢)0.590s891.00x
🐘 PostgresExpress0.799s (-5.8% 🟢)1.419s (-5.9% 🟢)0.621s851.03x
🐘 PostgresNext.js (Turbopack)3.176s (+2.8%)3.889s (+1.6%)0.713s314.09x
💻 LocalExpress8.590s (-12.9% 🟢)9.254s (-10.0% 🟢)0.664s1311.06x
💻 LocalNitro8.879s (+1.8%)9.566s (+3.4%)0.687s1311.43x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.698s (-16.6% 🟢)7.627s (-11.6% 🟢)1.929s161.00x
▲ VercelNitro5.845s (-13.4% 🟢)7.928s (-8.2% 🟢)2.083s161.03x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.145s (-2.2%)2.004s (~)0.009s (-30.3% 🟢)2.014s (~)0.869s101.00x
💻 LocalNitro1.165s (+0.7%)2.005s (~)0.011s (+5.0%)2.019s (~)0.854s101.02x
🐘 PostgresNitro1.170s (-2.3%)1.995s (~)0.001s (-8.3% 🟢)2.010s (~)0.840s101.02x
🐘 PostgresExpress1.176s (~)1.998s (~)0.001s (~)2.010s (~)0.835s101.03x
🐘 PostgresNext.js (Turbopack)1.238s (~)2.000s (~)0.001s (~)2.011s (~)0.773s101.08x
💻 LocalNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.330s (-1.4%)3.506s (-0.7%)0.786s (+11.8% 🔺)4.765s (-0.5%)2.435s101.00x
▲ VercelNitro2.442s (-4.2%)3.523s (-5.1% 🟢)1.169s (+57.9% 🔺)5.176s (+4.3%)2.734s101.05x
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Express | Nitro

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.518s (-5.7% 🟢)2.009s (~)0.010s (-19.8% 🟢)2.021s (~)0.503s301.00x
🐘 PostgresNitro1.573s (-4.0%)2.001s (~)0.005s (+3.8%)2.026s (~)0.453s301.04x
💻 LocalNitro1.586s (+1.3%)2.012s (~)0.013s (+12.0% 🔺)2.028s (~)0.441s301.04x
🐘 PostgresExpress1.627s (+1.3%)2.007s (~)0.005s (+10.1% 🔺)2.027s (~)0.400s301.07x
🐘 PostgresNext.js (Turbopack)1.838s (+2.6%)2.010s (~)0.006s (+1.2%)2.026s (~)0.188s301.21x
💻 LocalNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.321s (-6.1% 🟢)7.549s (-6.2% 🟢)0.291s (+32.8% 🔺)8.340s (-5.3% 🟢)2.019s81.00x
▲ VercelExpress6.677s (-3.2%)8.183s (-2.0%)0.202s (-41.3% 🟢)8.874s (-3.8%)2.197s71.06x
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro | Express

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.779s (-4.1%)1.050s (-1.4%)0.000s (+194.7% 🔺)1.069s (-1.0%)0.290s571.00x
🐘 PostgresExpress0.811s (+1.7%)1.028s (-1.8%)0.000s (+Infinity% 🔺)1.073s (+1.2%)0.263s561.04x
🐘 PostgresNext.js (Turbopack)1.044s (+1.6%)1.581s (-2.5%)0.000s (-2.6%)1.589s (-2.6%)0.544s381.34x
💻 LocalExpress1.238s (-12.8% 🟢)1.980s (-1.7%)0.000s (-30.9% 🟢)1.982s (-1.7%)0.744s311.59x
💻 LocalNitro1.428s (+5.7% 🔺)2.015s (~)0.001s (+171.4% 🔺)2.017s (~)0.590s301.83x
💻 LocalNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.002s (-27.0% 🟢)4.590s (-20.1% 🟢)0.001s (+Infinity% 🔺)5.105s (-18.2% 🟢)2.104s121.00x
▲ VercelExpress3.054s (-17.3% 🟢)4.589s (-6.4% 🟢)0.002s (+Infinity% 🔺)5.037s (-9.3% 🟢)1.983s121.02x
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro | Express

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

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.596s (-5.8% 🟢)2.176s (~)0.000s (-50.0% 🟢)2.189s (~)0.593s281.00x
🐘 PostgresExpress1.606s (-2.8%)2.137s (-1.9%)0.000s (NaN%)2.152s (-1.6%)0.546s281.01x
🐘 PostgresNext.js (Turbopack)2.147s (-4.8%)2.652s (-2.8%)0.000s (+Infinity% 🔺)2.671s (-2.4%)0.524s231.35x
💻 LocalExpress2.630s (-20.7% 🟢)3.121s (-22.5% 🟢)0.000s (-70.0% 🟢)3.126s (-22.5% 🟢)0.496s201.65x
💻 LocalNitro3.268s (+6.1% 🔺)3.965s (+1.7%)0.001s (+85.7% 🔺)3.968s (+1.7%)0.700s162.05x
💻 LocalNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.141s (-15.8% 🟢)5.935s (-3.7%)0.003s (+Infinity% 🔺)6.439s (-5.3% 🟢)2.298s101.00x
▲ VercelExpress4.539s (-25.2% 🟢)6.277s (-17.3% 🟢)0.001s (-11.1% 🟢)6.771s (-17.3% 🟢)2.232s91.10x
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro | Express

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress20/21
🐘 PostgresNitro13/21
▲ VercelNitro11/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres12/21
Next.js (Turbopack)🐘 Postgres21/21
Nitro🐘 Postgres18/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)

📋 View full workflow run


Some benchmark jobs failed:

  • Local: failure
  • Postgres: success
  • Vercel: failure

Check the workflow run for details.

@github-actions

github-actionsBot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production129302271520
✅ 💻 Local Development190902192128
✅ 📦 Local Production190902192128
✅ 🐘 Local Postgres189502332128
✅ 🪟 Windows15200152
✅ 📋 Other88501791064
Total8043010779120

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro125027
✅ example125027
✅ express125027
✅ fastify125027
✅ hono125027
✅ nextjs-webpack14903
✅ nitro125027
✅ nuxt125027
✅ sveltekit14408
✅ vite125027
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable-lazy-discovery-disabled15200
✅ nextjs-turbopack-stable-lazy-discovery-enabled15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable-lazy-discovery-disabled15200
✅ nextjs-webpack-stable-lazy-discovery-enabled15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable-lazy-discovery-disabled15200
✅ nextjs-turbopack-stable-lazy-discovery-enabled15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable-lazy-discovery-disabled15200
✅ nextjs-webpack-stable-lazy-discovery-enabled15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable126026
✅ express-stable126026
✅ fastify-stable126026
✅ hono-stable126026
✅ nextjs-turbopack-canary132020
✅ nextjs-turbopack-stable-lazy-discovery-disabled15101
✅ nextjs-turbopack-stable-lazy-discovery-enabled15101
✅ nextjs-webpack-canary132020
✅ nextjs-webpack-stable-lazy-discovery-disabled15101
✅ nextjs-webpack-stable-lazy-discovery-enabled15101
✅ 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


Some E2E test jobs failed:

  • Vercel Prod: failure
  • Local Dev: success
  • Local Prod: success
  • Local Postgres: success
  • Windows: success

Check the workflow run for details.

CopilotAI 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.

Pull request overview

Fixes the SWC plugin’s DCE usage analysis so module-scope declarations referenced only via destructuring default initializers (e.g. const { ttl = TTL } = options) are correctly counted as “used”, preventing runtime ReferenceErrors when defaults fire.

Changes:

  • Extend ComprehensiveUsageCollector to traverse destructuring patterns and visit only default-value initializers (and computed keys), without marking binding identifiers as used.
  • Add a regression fixture covering module-scope const preservation in both step and workflow modes (and confirming truly-unused consts are still removed).
  • Update the SWC plugin spec and add a changeset for the patch release.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
packages/swc-plugin-workflow/transform/src/lib.rsAdds visit_pat_default_initializers and wires it into visit_mut_var_declarator so destructuring-default references are counted for DCE.
packages/swc-plugin-workflow/transform/tests/fixture/destructuring-default-references-module-const/input.jsNew fixture input reproducing the destructuring-default-only reference pattern and an unused const.
packages/swc-plugin-workflow/transform/tests/fixture/destructuring-default-references-module-const/output-step.jsStep-mode snapshot asserting referenced consts survive and unused const is removed.
packages/swc-plugin-workflow/transform/tests/fixture/destructuring-default-references-module-const/output-workflow.jsWorkflow-mode snapshot asserting referenced consts survive and unused const is removed.
packages/swc-plugin-workflow/spec.mdDocuments that destructuring-default initializers count as references for DCE in both modes.
.changeset/swc-destructuring-default-dce.mdPatch changeset describing the DCE fix and the prevented runtime error.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

* origin/main:
docs: document run idempotency (#2011)
Render attr_set events and run attributes in observability UI (#2393)
[ci] Fix backport job model slug (#2403)
[ci] Comment on PR when backport fails, revert to use opus 4.8 (#2400)
Update queue client to 0.3.1 (#2399)
fix(deps): upgrade esbuild to 0.28.1 (GHSA-gv7w-rqvm-qjhr) (#2395)

@TooTallNateTooTallNate left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Approve — precise root-cause fix, exhaustive pattern coverage, and I reproduced the before/after

Targeted DCE fix: the usage collector deliberately skipped var_decl.name to avoid marking binding names as used, but destructuring-default initializers (const { ttl = TTL } = options;) live inside that pattern, so references like TTL were never counted and DCE stripped the still-referenced declaration → runtime ReferenceError. The diagnosis in the PR matches the code exactly.

What I verified

  • The new visit_pat_default_initializers is exhaustive over the binding-pattern AST and visits only value positions: object KeyValue values + computed keys, Assign.value defaults, array element defaults, AssignPat.right, rest args, and expression patterns — while never descending into binding names. I traced each arm against the Pat/ObjectPatProp variants; nothing is missed and the { ttl = TTL } case correctly skips the ttl key and visits only the TTL default.
  • It cannot over-broaden DCE. The collector's visit_mut_ident blindly adds to used_identifiers, and this helper only ever adds references — the safe direction for DCE (over-counting preserves declarations, never wrongly strips). The Pat::Expr arm visiting a member expression's property ident is the same conservative behavior the collector already has when walking initializers, so it introduces no new risk.
  • Built and ran the full Rust suite: 128 fixture tests + error tests, all green.
  • Reproduced the regression: reverted just the visit_pat_default_initializers call site to simulate main, and both the step-mode and workflow-mode fixtures FAIL — the emitted output strips const TTL/const RETRIES while keeping the code that references them. Restored the fix → both pass. This is the before/after the PR claims, confirmed locally.
  • The fixture's negative case earns its keep: const UNUSED = 'dead' is still stripped in both outputs, proving the fix preserves referenced declarations only, not everything in a pattern's vicinity.
  • The fix covers the real latent bug, not just the synthetic repro: workbench/vitest/workflows/cookbook/distributed-abort-controller.ts:126 has const { ttlMs = DEFAULT_TTL_MS, graceMs = DEFAULT_GRACE_MS } = options; referencing module consts at lines 16/18 — exactly this pattern. The fixture is a faithful minimal reduction of it.
  • spec.md updated per AGENTS.md — both the step-mode and workflow-mode DCE sections gain the destructuring-default note (correct; spec.md has no version frontmatter, the bump rule applies to skills/). Changeset is a correctly-scoped @workflow/swc-plugin patch.

Notes

  • The fixture covers a class static method and a plain exported function, deliberately demonstrating the bug is class- and mode-independent (matching the issue). Good instinct to prove both, since the worked-around precedent (#1858 kill-switch) might have suggested it was class-specific.
  • Function-parameter destructuring defaults were already handled (visit_mut_fn_decl visits params in full, per the existing default-parameter-usage fixture) — the PR correctly scopes the fix to var declarators only and says so. I'd have flagged it if param defaults regressed, but they're untouched and the existing fixture still passes.
  • Distinct from #1944 as the PR notes: that was DCE ordering vs step hoisting; this is the collector never traversing these initializers at all. No nested/hoisted steps involved, which the fixture confirms.

CI lanes that are red are the session's known baseline flakes (Windows dev-server / express benchmark), unrelated to a Rust-only change. LGTM.

@pranaygp
pranaygp merged commit 53ede30 into mainJun 16, 2026
112 of 116 checks passed
@pranaygp
pranaygp deleted the pgp/swc-plugin-fix branch June 16, 2026 23:16
@github-actionsgithub-actionsBot mentioned this pull request Jun 16, 2026
github-actionsBot added a commit that referenced this pull request Jun 16, 2026
…analysis (#2398)
The DCE usage collector skipped the entire variable name pattern when
visiting a `VarDeclarator` (to avoid marking the binding name as "used").
But default-value initializers inside destructuring patterns live in that
pattern — e.g. the `TTL` in `const { ttl = TTL } = options;` — so those
references were invisible to the collector. A module-scope `const`
referenced only through such a default was treated as unused and stripped,
while the surviving code kept reading it, producing a runtime
`ReferenceError` when the default fired.
Traverse the default-value initializer expressions (and computed keys)
within destructuring patterns while still not marking the binding names
themselves, so the referenced declaration is preserved. Function-parameter
defaults were already covered (params are visited in full).
Fixes#2396.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

Backport PR opened against stable: #2465. Merge conflicts were resolved by AI — please review carefully. (backport job run)

pranaygp pushed a commit that referenced this pull request Jun 17, 2026
…analysis (#2398) (#2465)
The DCE usage collector skipped the entire variable name pattern when
visiting a `VarDeclarator` (to avoid marking the binding name as "used").
But default-value initializers inside destructuring patterns live in that
pattern — e.g. the `TTL` in `const { ttl = TTL } = options;` — so those
references were invisible to the collector. A module-scope `const`
referenced only through such a default was treated as unused and stripped,
while the surviving code kept reading it, producing a runtime
`ReferenceError` when the default fired.
Traverse the default-value initializer expressions (and computed keys)
within destructuring patterns while still not marking the binding names
themselves, so the referenced declaration is preserved. Function-parameter
defaults were already covered (params are visited in full).
Fixes#2396.
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
TooTallNate added a commit that referenced this pull request Jun 17, 2026
…default DCE test
The #2398 backport (#2465) added the destructuring-default-references-module-const
fixture but committed output-step.js verbatim from main and omitted output-client.js
entirely. Stable's SWC plugin emits a different step-registration shape
(registerStepFunction import vs. inline IIFE) and has less aggressive DCE (the
truly-unused UNUSED const is not stripped), so the copied output-step.js did not
match stable's actual output and the required output-client.js was missing.
Regenerate both fixture outputs against stable's plugin so the client_mode and
step_mode fixture tests pass. The regression being tested still holds: TTL and
RETRIES (referenced only via destructuring defaults) are preserved in all modes.
Signed-off-by: Nathan Rajlich <n@n8.io>
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.

[swc-plugin] DCE strips module-scope declarations referenced only by destructuring defaults → ReferenceError

3 participants

@pranaygp@TooTallNate
, '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('^' + ".*" + ' fix(swc-plugin): count destructuring-default references in DCE usage analysis by pranaygp · Pull Request #2398 · vercel/workflow · GitHub
Skip to content

fix(swc-plugin): count destructuring-default references in DCE usage analysis - #2398

Merged
pranaygp merged 3 commits into
mainfrom
pgp/swc-plugin-fix
Jun 16, 2026
Merged

fix(swc-plugin): count destructuring-default references in DCE usage analysis#2398
pranaygp merged 3 commits into
mainfrom
pgp/swc-plugin-fix

Conversation

@pranaygp

Copy link
Copy Markdown
Contributor

Fixes#2396.

Problem

The SWC plugin's dead-code-elimination (DCE) usage analysis did not count identifier references that appear as default values inside a destructuring pattern (e.g. const { ttl = TTL } = options;). When a module-scope const was referenced only through such a destructuring default, the DCE pass treated it as unused and stripped it from the emitted bundle — while keeping the code that referenced it. The result was a runtime ReferenceError: <const> is not defined when the default fired.

This is mode-independent (reproduces in both step and workflow mode) and class-independent (a plain top-level function exhibits it too). It shipped as a worked-around bug in the kill-switch pattern (#1858) and remains latent in workbench/vitest/workflows/cookbook/distributed-abort-controller.ts.

Root cause

In ComprehensiveUsageCollector::visit_mut_var_declarator, the collector visited only the initializer and deliberately skipped the entire var_decl.name pattern (to avoid marking the binding name as "used"). For destructuring patterns, the default-value initializer expressions live insidevar_decl.name — so references like the TTL in { ttl = TTL } were never added to used_identifiers, and remove_dead_code then pruned the still-referenced declaration.

Fix

Added visit_pat_default_initializers, which walks a binding pattern and visits only the default-value initializer expressions (and computed keys) — ObjectPatProp::KeyValue values, ObjectPatProp::Assign.value, AssignPat.right, array element defaults, rest args — while still not marking the binding names themselves as used. visit_mut_var_declarator now calls it on var_decl.name.

Function-parameter destructuring defaults were already covered: visit_mut_fn_decl visits params in full (confirmed by the existing default-parameter-usage fixture), so no change was needed there.

Tests

  • New fixture destructuring-default-references-module-const/ asserts that two module-scope consts referenced only via destructuring defaults — one inside a class static method, one inside a plain exported function — survive in both step and workflow mode. A third, genuinely-unused const is still stripped, confirming DCE is not over-broadened.
  • Verified the fixture fails on main (consts stripped) and passes with the fix.
  • Full cargo test -p swc_workflow suite green (128 fixture tests + error tests).

Spec

Updated packages/swc-plugin-workflow/spec.md (both the step-mode and workflow-mode DCE sections) to note that a reference counts even when it appears only inside a destructuring-default initializer.

Relation to prior fixes

Same symptom class as #1944 (DCE removing a declaration that surviving code references) but a distinct root cause: #1944 was about DCE ordering relative to step hoisting; this is about the usage collector never traversing destructuring-default initializers at all, and reproduces with no nested/hoisted steps involved.

🤖 Generated with Claude Code

…analysis
The DCE usage collector skipped the entire variable name pattern when
visiting a `VarDeclarator` (to avoid marking the binding name as "used").
But default-value initializers inside destructuring patterns live in that
pattern — e.g. the `TTL` in `const { ttl = TTL } = options;` — so those
references were invisible to the collector. A module-scope `const`
referenced only through such a default was treated as unused and stripped,
while the surviving code kept reading it, producing a runtime
`ReferenceError` when the default fired.
Traverse the default-value initializer expressions (and computed keys)
within destructuring patterns while still not marking the binding names
themselves, so the referenced declaration is preserved. Function-parameter
defaults were already covered (params are visited in full).
Fixes#2396.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CopilotAI review requested due to automatic review settings June 13, 2026 06:29
@pranaygp
pranaygp requested a review from a team as a code ownerJune 13, 2026 06:29
@changeset-bot

changeset-botBot commented Jun 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9006ec3

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

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

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

@vercel

vercelBot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented Jun 13, 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🥇 Express0.034s (-30.4% 🟢)1.005s (~)0.971s101.00x
💻 LocalNitro0.051s (+22.4% 🔺)1.008s (~)0.957s101.52x
🐘 PostgresNitro0.060s (-7.3% 🟢)1.012s (~)0.952s101.78x
🐘 PostgresExpress0.062s (+2.0%)1.013s (~)0.951s101.85x
🐘 PostgresNext.js (Turbopack)0.067s (-8.4% 🟢)1.012s (~)0.945s101.98x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro0.339s (-43.5% 🟢)2.573s (-3.3%)2.234s101.00x
▲ VercelExpress0.455s (-59.0% 🟢)2.707s (-24.4% 🟢)2.252s101.34x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.079s (-1.9%)2.006s (~)0.927s101.00x
💻 LocalNitro1.110s (+1.2%)2.007s (~)0.897s101.03x
🐘 PostgresExpress1.114s (~)2.011s (~)0.897s101.03x
🐘 PostgresNitro1.119s (~)2.010s (~)0.891s101.04x
🐘 PostgresNext.js (Turbopack)1.140s (-1.6%)2.010s (~)0.871s101.06x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.805s (-32.7% 🟢)3.807s (-23.9% 🟢)2.003s101.00x
▲ VercelNitro1.900s (-32.7% 🟢)3.850s (-15.3% 🟢)1.951s101.05x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express10.432s (-1.1%)11.020s (~)0.588s31.00x
🐘 PostgresNitro10.583s (~)11.017s (~)0.434s31.01x
💻 LocalNitro10.585s (+0.6%)11.024s (~)0.439s31.01x
🐘 PostgresExpress10.598s (~)11.020s (~)0.422s31.02x
🐘 PostgresNext.js (Turbopack)10.819s (~)11.018s (~)0.199s31.04x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express14.863s (-8.6% 🟢)16.580s (-11.0% 🟢)1.718s21.00x
▲ VercelNitro14.872s (-18.1% 🟢)17.769s (-10.6% 🟢)2.897s21.00x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express13.524s (-2.3%)14.027s (~)0.503s51.00x
🐘 PostgresNitro13.752s (-0.8%)14.018s (~)0.266s51.02x
🐘 PostgresExpress13.773s (~)14.023s (~)0.250s51.02x
💻 LocalNitro13.861s (~)14.028s (~)0.167s51.02x
🐘 PostgresNext.js (Turbopack)14.473s (~)15.017s (~)0.544s41.07x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express22.452s (-35.7% 🟢)24.544s (-33.5% 🟢)2.092s31.00x
▲ VercelNitro22.733s (-31.0% 🟢)24.477s (-29.8% 🟢)1.744s31.01x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express12.003s (-4.8%)12.396s (-4.8%)0.394s81.00x
🐘 PostgresExpress12.613s (~)13.019s (~)0.405s71.05x
🐘 PostgresNitro12.668s (~)13.022s (~)0.353s71.06x
💻 LocalNitro12.685s (+0.9%)13.027s (~)0.342s71.06x
🐘 PostgresNext.js (Turbopack)13.947s (~)14.306s (+1.0%)0.358s71.16x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express31.375s (-9.7% 🟢)33.491s (-9.7% 🟢)2.116s31.00x
▲ VercelNitro31.721s (-16.3% 🟢)34.300s (-13.2% 🟢)2.578s31.01x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.202s (~)2.008s (~)0.805s151.00x
💻 LocalExpress1.202s (+0.9%)2.007s (~)0.805s151.00x
🐘 PostgresNitro1.216s (~)2.008s (~)0.792s151.01x
🐘 PostgresNext.js (Turbopack)1.280s (-5.6% 🟢)2.008s (-3.3%)0.727s151.06x
💻 LocalNitro1.317s (+13.2% 🔺)2.007s (~)0.690s151.10x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.599s (+1.9%)4.260s (+4.6%)1.662s81.00x
▲ VercelNitro2.787s (-9.8% 🟢)4.617s (+0.7%)1.830s71.07x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.332s (-1.9%)2.316s (-7.6% 🟢)0.984s131.00x
🐘 PostgresNitro1.368s (+2.0%)2.509s (~)1.141s121.03x
🐘 PostgresNext.js (Turbopack)1.571s (-4.1%)2.394s (-4.6%)0.824s131.18x
💻 LocalExpress1.614s (-14.8% 🟢)2.005s (-6.8% 🟢)0.391s151.21x
💻 LocalNitro1.723s (+5.8% 🔺)2.007s (~)0.284s151.29x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.005s (-20.3% 🟢)5.201s (-3.7%)2.196s61.00x
▲ VercelExpress3.112s (-24.4% 🟢)5.163s (-14.7% 🟢)2.051s61.04x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.605s (-1.1%)4.011s (+3.2%)2.406s81.00x
🐘 PostgresNitro1.717s (+5.7% 🔺)3.760s (-9.2% 🟢)2.043s81.07x
🐘 PostgresNext.js (Turbopack)3.515s (-8.3% 🟢)4.308s (-3.1%)0.793s72.19x
💻 LocalNitro4.578s (+13.9% 🔺)5.155s (+12.5% 🔺)0.577s72.85x
💻 LocalExpress4.611s (-11.9% 🟢)5.154s (-11.9% 🟢)0.543s72.87x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.454s (-18.7% 🟢)5.446s (-4.9%)1.992s61.00x
▲ VercelExpress3.755s (-17.9% 🟢)5.803s (-11.6% 🟢)2.048s61.09x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.206s (-1.3%)2.007s (~)0.802s151.00x
🐘 PostgresExpress1.212s (~)2.008s (~)0.795s151.01x
🐘 PostgresNext.js (Turbopack)1.301s (+1.2%)2.007s (~)0.706s151.08x
💻 LocalExpress1.467s (-9.1% 🟢)2.006s (~)0.539s151.22x
💻 LocalNitro1.614s (+6.8% 🔺)2.007s (~)0.393s151.34x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.317s (-64.3% 🟢)3.984s (-50.4% 🟢)1.667s81.00x
▲ VercelExpress2.358s (+3.7%)3.953s (+2.6%)1.594s81.02x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.355s (~)2.393s (-7.7% 🟢)1.037s131.00x
🐘 PostgresExpress1.385s (-0.7%)2.592s (+4.9%)1.206s121.02x
🐘 PostgresNext.js (Turbopack)1.550s (+0.9%)2.075s (-6.7% 🟢)0.525s151.14x
💻 LocalExpress1.760s (-31.7% 🟢)2.072s (-33.3% 🟢)0.313s151.30x
💻 LocalNitro2.052s (+14.6% 🔺)2.508s (+21.0% 🔺)0.456s121.51x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.782s (-16.5% 🟢)4.629s (-13.4% 🟢)1.847s71.00x
▲ VercelNitro3.219s (-14.0% 🟢)4.836s (-11.4% 🟢)1.617s71.16x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.589s (-9.4% 🟢)4.012s (-3.0%)2.423s81.00x
🐘 PostgresNitro1.691s (-2.2%)4.148s (+6.7% 🔺)2.457s81.06x
🐘 PostgresNext.js (Turbopack)3.574s (-6.9% 🟢)4.298s (-3.3%)0.723s72.25x
💻 LocalExpress4.386s (-30.8% 🟢)5.015s (-28.6% 🟢)0.629s62.76x
💻 LocalNitro5.079s (+6.6% 🔺)5.513s (+3.1%)0.434s63.20x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.500s (-46.9% 🟢)5.977s (-28.4% 🟢)2.477s61.00x
▲ VercelExpress3.732s (-28.7% 🟢)6.025s (-13.5% 🟢)2.293s51.07x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express0.574s (-12.9% 🟢)1.004s (-3.4%)0.430s601.00x
🐘 PostgresNitro0.576s (-10.6% 🟢)1.006s (-3.4%)0.430s601.00x
🐘 PostgresExpress0.582s (-1.7%)1.007s (~)0.425s601.01x
💻 LocalNitro0.675s (+15.7% 🔺)1.040s (+3.5%)0.365s581.17x
🐘 PostgresNext.js (Turbopack)0.826s (-2.9%)1.023s (~)0.198s591.44x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.107s (-27.8% 🟢)7.860s (-22.2% 🟢)1.753s81.00x
▲ VercelExpress6.449s (-2.0%)8.127s (-5.8% 🟢)1.678s81.06x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.328s (-16.2% 🟢)2.027s (+1.0%)0.699s451.00x
🐘 PostgresExpress1.439s (~)2.030s (~)0.591s451.08x
🐘 PostgresNitro1.476s (+2.1%)2.101s (+4.6%)0.625s431.11x
💻 LocalNitro1.548s (+4.8%)2.007s (~)0.459s451.17x
🐘 PostgresNext.js (Turbopack)1.998s (-1.9%)2.315s (-9.7% 🟢)0.318s391.50x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express14.175s (-29.6% 🟢)16.338s (-25.3% 🟢)2.163s61.00x
▲ VercelNitro14.565s (-22.4% 🟢)16.655s (-19.8% 🟢)2.090s61.03x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.704s (-4.1%)3.085s (-1.7%)0.381s391.00x
🐘 PostgresExpress2.768s (~)3.112s (-0.8%)0.344s391.02x
💻 LocalExpress2.895s (-13.4% 🟢)3.245s (-19.1% 🟢)0.350s381.07x
💻 LocalNitro3.354s (+5.9% 🔺)4.011s (+2.5%)0.657s301.24x
🐘 PostgresNext.js (Turbopack)3.954s (-1.8%)4.332s (-1.6%)0.379s281.46x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express27.701s (-28.4% 🟢)30.747s (-23.9% 🟢)3.046s41.00x
▲ VercelNitro28.386s (-21.7% 🟢)31.304s (-18.0% 🟢)2.917s41.02x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.247s (-5.5% 🟢)1.006s (~)0.760s601.00x
🐘 PostgresExpress0.247s (-5.2% 🟢)1.006s (~)0.759s601.00x
🐘 PostgresNext.js (Turbopack)0.313s (+1.6%)1.006s (~)0.693s601.27x
💻 LocalExpress0.418s (-2.4%)1.004s (~)0.586s601.69x
💻 LocalNitro0.457s (+5.8% 🔺)1.005s (~)0.549s601.85x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.829s (-36.6% 🟢)3.881s (-22.8% 🟢)2.052s161.00x
▲ VercelExpress1.907s (-46.6% 🟢)3.882s (-30.6% 🟢)1.975s161.04x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.413s (~)1.029s (+2.3%)0.616s881.00x
🐘 PostgresNitro0.413s (-9.6% 🟢)1.065s (-1.3%)0.652s851.00x
🐘 PostgresNext.js (Turbopack)0.611s (-9.9% 🟢)1.105s (-13.4% 🟢)0.494s821.48x
💻 LocalExpress2.017s (-8.0% 🟢)2.537s (-11.1% 🟢)0.521s364.89x
💻 LocalNitro2.119s (+5.4% 🔺)2.715s (+11.2% 🔺)0.596s345.13x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.194s (-8.4% 🟢)5.218s (+0.9%)2.024s181.00x
▲ VercelExpress3.290s (-36.2% 🟢)5.431s (-24.8% 🟢)2.141s171.03x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.777s (-7.9% 🟢)1.367s (-7.1% 🟢)0.590s891.00x
🐘 PostgresExpress0.799s (-5.8% 🟢)1.419s (-5.9% 🟢)0.621s851.03x
🐘 PostgresNext.js (Turbopack)3.176s (+2.8%)3.889s (+1.6%)0.713s314.09x
💻 LocalExpress8.590s (-12.9% 🟢)9.254s (-10.0% 🟢)0.664s1311.06x
💻 LocalNitro8.879s (+1.8%)9.566s (+3.4%)0.687s1311.43x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.698s (-16.6% 🟢)7.627s (-11.6% 🟢)1.929s161.00x
▲ VercelNitro5.845s (-13.4% 🟢)7.928s (-8.2% 🟢)2.083s161.03x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.145s (-2.2%)2.004s (~)0.009s (-30.3% 🟢)2.014s (~)0.869s101.00x
💻 LocalNitro1.165s (+0.7%)2.005s (~)0.011s (+5.0%)2.019s (~)0.854s101.02x
🐘 PostgresNitro1.170s (-2.3%)1.995s (~)0.001s (-8.3% 🟢)2.010s (~)0.840s101.02x
🐘 PostgresExpress1.176s (~)1.998s (~)0.001s (~)2.010s (~)0.835s101.03x
🐘 PostgresNext.js (Turbopack)1.238s (~)2.000s (~)0.001s (~)2.011s (~)0.773s101.08x
💻 LocalNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.330s (-1.4%)3.506s (-0.7%)0.786s (+11.8% 🔺)4.765s (-0.5%)2.435s101.00x
▲ VercelNitro2.442s (-4.2%)3.523s (-5.1% 🟢)1.169s (+57.9% 🔺)5.176s (+4.3%)2.734s101.05x
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Express | Nitro

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.518s (-5.7% 🟢)2.009s (~)0.010s (-19.8% 🟢)2.021s (~)0.503s301.00x
🐘 PostgresNitro1.573s (-4.0%)2.001s (~)0.005s (+3.8%)2.026s (~)0.453s301.04x
💻 LocalNitro1.586s (+1.3%)2.012s (~)0.013s (+12.0% 🔺)2.028s (~)0.441s301.04x
🐘 PostgresExpress1.627s (+1.3%)2.007s (~)0.005s (+10.1% 🔺)2.027s (~)0.400s301.07x
🐘 PostgresNext.js (Turbopack)1.838s (+2.6%)2.010s (~)0.006s (+1.2%)2.026s (~)0.188s301.21x
💻 LocalNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.321s (-6.1% 🟢)7.549s (-6.2% 🟢)0.291s (+32.8% 🔺)8.340s (-5.3% 🟢)2.019s81.00x
▲ VercelExpress6.677s (-3.2%)8.183s (-2.0%)0.202s (-41.3% 🟢)8.874s (-3.8%)2.197s71.06x
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro | Express

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.779s (-4.1%)1.050s (-1.4%)0.000s (+194.7% 🔺)1.069s (-1.0%)0.290s571.00x
🐘 PostgresExpress0.811s (+1.7%)1.028s (-1.8%)0.000s (+Infinity% 🔺)1.073s (+1.2%)0.263s561.04x
🐘 PostgresNext.js (Turbopack)1.044s (+1.6%)1.581s (-2.5%)0.000s (-2.6%)1.589s (-2.6%)0.544s381.34x
💻 LocalExpress1.238s (-12.8% 🟢)1.980s (-1.7%)0.000s (-30.9% 🟢)1.982s (-1.7%)0.744s311.59x
💻 LocalNitro1.428s (+5.7% 🔺)2.015s (~)0.001s (+171.4% 🔺)2.017s (~)0.590s301.83x
💻 LocalNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.002s (-27.0% 🟢)4.590s (-20.1% 🟢)0.001s (+Infinity% 🔺)5.105s (-18.2% 🟢)2.104s121.00x
▲ VercelExpress3.054s (-17.3% 🟢)4.589s (-6.4% 🟢)0.002s (+Infinity% 🔺)5.037s (-9.3% 🟢)1.983s121.02x
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro | Express

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

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.596s (-5.8% 🟢)2.176s (~)0.000s (-50.0% 🟢)2.189s (~)0.593s281.00x
🐘 PostgresExpress1.606s (-2.8%)2.137s (-1.9%)0.000s (NaN%)2.152s (-1.6%)0.546s281.01x
🐘 PostgresNext.js (Turbopack)2.147s (-4.8%)2.652s (-2.8%)0.000s (+Infinity% 🔺)2.671s (-2.4%)0.524s231.35x
💻 LocalExpress2.630s (-20.7% 🟢)3.121s (-22.5% 🟢)0.000s (-70.0% 🟢)3.126s (-22.5% 🟢)0.496s201.65x
💻 LocalNitro3.268s (+6.1% 🔺)3.965s (+1.7%)0.001s (+85.7% 🔺)3.968s (+1.7%)0.700s162.05x
💻 LocalNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.141s (-15.8% 🟢)5.935s (-3.7%)0.003s (+Infinity% 🔺)6.439s (-5.3% 🟢)2.298s101.00x
▲ VercelExpress4.539s (-25.2% 🟢)6.277s (-17.3% 🟢)0.001s (-11.1% 🟢)6.771s (-17.3% 🟢)2.232s91.10x
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro | Express

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress20/21
🐘 PostgresNitro13/21
▲ VercelNitro11/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres12/21
Next.js (Turbopack)🐘 Postgres21/21
Nitro🐘 Postgres18/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)

📋 View full workflow run


Some benchmark jobs failed:

  • Local: failure
  • Postgres: success
  • Vercel: failure

Check the workflow run for details.

@github-actions

github-actionsBot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production129302271520
✅ 💻 Local Development190902192128
✅ 📦 Local Production190902192128
✅ 🐘 Local Postgres189502332128
✅ 🪟 Windows15200152
✅ 📋 Other88501791064
Total8043010779120

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro125027
✅ example125027
✅ express125027
✅ fastify125027
✅ hono125027
✅ nextjs-webpack14903
✅ nitro125027
✅ nuxt125027
✅ sveltekit14408
✅ vite125027
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable-lazy-discovery-disabled15200
✅ nextjs-turbopack-stable-lazy-discovery-enabled15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable-lazy-discovery-disabled15200
✅ nextjs-webpack-stable-lazy-discovery-enabled15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable-lazy-discovery-disabled15200
✅ nextjs-turbopack-stable-lazy-discovery-enabled15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable-lazy-discovery-disabled15200
✅ nextjs-webpack-stable-lazy-discovery-enabled15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable126026
✅ express-stable126026
✅ fastify-stable126026
✅ hono-stable126026
✅ nextjs-turbopack-canary132020
✅ nextjs-turbopack-stable-lazy-discovery-disabled15101
✅ nextjs-turbopack-stable-lazy-discovery-enabled15101
✅ nextjs-webpack-canary132020
✅ nextjs-webpack-stable-lazy-discovery-disabled15101
✅ nextjs-webpack-stable-lazy-discovery-enabled15101
✅ 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


Some E2E test jobs failed:

  • Vercel Prod: failure
  • Local Dev: success
  • Local Prod: success
  • Local Postgres: success
  • Windows: success

Check the workflow run for details.

CopilotAI 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.

Pull request overview

Fixes the SWC plugin’s DCE usage analysis so module-scope declarations referenced only via destructuring default initializers (e.g. const { ttl = TTL } = options) are correctly counted as “used”, preventing runtime ReferenceErrors when defaults fire.

Changes:

  • Extend ComprehensiveUsageCollector to traverse destructuring patterns and visit only default-value initializers (and computed keys), without marking binding identifiers as used.
  • Add a regression fixture covering module-scope const preservation in both step and workflow modes (and confirming truly-unused consts are still removed).
  • Update the SWC plugin spec and add a changeset for the patch release.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
packages/swc-plugin-workflow/transform/src/lib.rsAdds visit_pat_default_initializers and wires it into visit_mut_var_declarator so destructuring-default references are counted for DCE.
packages/swc-plugin-workflow/transform/tests/fixture/destructuring-default-references-module-const/input.jsNew fixture input reproducing the destructuring-default-only reference pattern and an unused const.
packages/swc-plugin-workflow/transform/tests/fixture/destructuring-default-references-module-const/output-step.jsStep-mode snapshot asserting referenced consts survive and unused const is removed.
packages/swc-plugin-workflow/transform/tests/fixture/destructuring-default-references-module-const/output-workflow.jsWorkflow-mode snapshot asserting referenced consts survive and unused const is removed.
packages/swc-plugin-workflow/spec.mdDocuments that destructuring-default initializers count as references for DCE in both modes.
.changeset/swc-destructuring-default-dce.mdPatch changeset describing the DCE fix and the prevented runtime error.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

* origin/main:
docs: document run idempotency (#2011)
Render attr_set events and run attributes in observability UI (#2393)
[ci] Fix backport job model slug (#2403)
[ci] Comment on PR when backport fails, revert to use opus 4.8 (#2400)
Update queue client to 0.3.1 (#2399)
fix(deps): upgrade esbuild to 0.28.1 (GHSA-gv7w-rqvm-qjhr) (#2395)

@TooTallNateTooTallNate left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Approve — precise root-cause fix, exhaustive pattern coverage, and I reproduced the before/after

Targeted DCE fix: the usage collector deliberately skipped var_decl.name to avoid marking binding names as used, but destructuring-default initializers (const { ttl = TTL } = options;) live inside that pattern, so references like TTL were never counted and DCE stripped the still-referenced declaration → runtime ReferenceError. The diagnosis in the PR matches the code exactly.

What I verified

  • The new visit_pat_default_initializers is exhaustive over the binding-pattern AST and visits only value positions: object KeyValue values + computed keys, Assign.value defaults, array element defaults, AssignPat.right, rest args, and expression patterns — while never descending into binding names. I traced each arm against the Pat/ObjectPatProp variants; nothing is missed and the { ttl = TTL } case correctly skips the ttl key and visits only the TTL default.
  • It cannot over-broaden DCE. The collector's visit_mut_ident blindly adds to used_identifiers, and this helper only ever adds references — the safe direction for DCE (over-counting preserves declarations, never wrongly strips). The Pat::Expr arm visiting a member expression's property ident is the same conservative behavior the collector already has when walking initializers, so it introduces no new risk.
  • Built and ran the full Rust suite: 128 fixture tests + error tests, all green.
  • Reproduced the regression: reverted just the visit_pat_default_initializers call site to simulate main, and both the step-mode and workflow-mode fixtures FAIL — the emitted output strips const TTL/const RETRIES while keeping the code that references them. Restored the fix → both pass. This is the before/after the PR claims, confirmed locally.
  • The fixture's negative case earns its keep: const UNUSED = 'dead' is still stripped in both outputs, proving the fix preserves referenced declarations only, not everything in a pattern's vicinity.
  • The fix covers the real latent bug, not just the synthetic repro: workbench/vitest/workflows/cookbook/distributed-abort-controller.ts:126 has const { ttlMs = DEFAULT_TTL_MS, graceMs = DEFAULT_GRACE_MS } = options; referencing module consts at lines 16/18 — exactly this pattern. The fixture is a faithful minimal reduction of it.
  • spec.md updated per AGENTS.md — both the step-mode and workflow-mode DCE sections gain the destructuring-default note (correct; spec.md has no version frontmatter, the bump rule applies to skills/). Changeset is a correctly-scoped @workflow/swc-plugin patch.

Notes

  • The fixture covers a class static method and a plain exported function, deliberately demonstrating the bug is class- and mode-independent (matching the issue). Good instinct to prove both, since the worked-around precedent (#1858 kill-switch) might have suggested it was class-specific.
  • Function-parameter destructuring defaults were already handled (visit_mut_fn_decl visits params in full, per the existing default-parameter-usage fixture) — the PR correctly scopes the fix to var declarators only and says so. I'd have flagged it if param defaults regressed, but they're untouched and the existing fixture still passes.
  • Distinct from #1944 as the PR notes: that was DCE ordering vs step hoisting; this is the collector never traversing these initializers at all. No nested/hoisted steps involved, which the fixture confirms.

CI lanes that are red are the session's known baseline flakes (Windows dev-server / express benchmark), unrelated to a Rust-only change. LGTM.

@pranaygp
pranaygp merged commit 53ede30 into mainJun 16, 2026
112 of 116 checks passed
@pranaygp
pranaygp deleted the pgp/swc-plugin-fix branch June 16, 2026 23:16
@github-actionsgithub-actionsBot mentioned this pull request Jun 16, 2026
github-actionsBot added a commit that referenced this pull request Jun 16, 2026
…analysis (#2398)
The DCE usage collector skipped the entire variable name pattern when
visiting a `VarDeclarator` (to avoid marking the binding name as "used").
But default-value initializers inside destructuring patterns live in that
pattern — e.g. the `TTL` in `const { ttl = TTL } = options;` — so those
references were invisible to the collector. A module-scope `const`
referenced only through such a default was treated as unused and stripped,
while the surviving code kept reading it, producing a runtime
`ReferenceError` when the default fired.
Traverse the default-value initializer expressions (and computed keys)
within destructuring patterns while still not marking the binding names
themselves, so the referenced declaration is preserved. Function-parameter
defaults were already covered (params are visited in full).
Fixes#2396.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

Backport PR opened against stable: #2465. Merge conflicts were resolved by AI — please review carefully. (backport job run)

pranaygp pushed a commit that referenced this pull request Jun 17, 2026
…analysis (#2398) (#2465)
The DCE usage collector skipped the entire variable name pattern when
visiting a `VarDeclarator` (to avoid marking the binding name as "used").
But default-value initializers inside destructuring patterns live in that
pattern — e.g. the `TTL` in `const { ttl = TTL } = options;` — so those
references were invisible to the collector. A module-scope `const`
referenced only through such a default was treated as unused and stripped,
while the surviving code kept reading it, producing a runtime
`ReferenceError` when the default fired.
Traverse the default-value initializer expressions (and computed keys)
within destructuring patterns while still not marking the binding names
themselves, so the referenced declaration is preserved. Function-parameter
defaults were already covered (params are visited in full).
Fixes#2396.
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
TooTallNate added a commit that referenced this pull request Jun 17, 2026
…default DCE test
The #2398 backport (#2465) added the destructuring-default-references-module-const
fixture but committed output-step.js verbatim from main and omitted output-client.js
entirely. Stable's SWC plugin emits a different step-registration shape
(registerStepFunction import vs. inline IIFE) and has less aggressive DCE (the
truly-unused UNUSED const is not stripped), so the copied output-step.js did not
match stable's actual output and the required output-client.js was missing.
Regenerate both fixture outputs against stable's plugin so the client_mode and
step_mode fixture tests pass. The regression being tested still holds: TTL and
RETRIES (referenced only via destructuring defaults) are preserved in all modes.
Signed-off-by: Nathan Rajlich <n@n8.io>
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.

[swc-plugin] DCE strips module-scope declarations referenced only by destructuring defaults → ReferenceError

3 participants

@pranaygp@TooTallNate
, '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); } })(); })(); fix(swc-plugin): count destructuring-default references in DCE usage analysis by pranaygp · Pull Request #2398 · vercel/workflow · GitHub
Skip to content

fix(swc-plugin): count destructuring-default references in DCE usage analysis - #2398

Merged
pranaygp merged 3 commits into
mainfrom
pgp/swc-plugin-fix
Jun 16, 2026
Merged

fix(swc-plugin): count destructuring-default references in DCE usage analysis#2398
pranaygp merged 3 commits into
mainfrom
pgp/swc-plugin-fix

Conversation

@pranaygp

Copy link
Copy Markdown
Contributor

Fixes#2396.

Problem

The SWC plugin's dead-code-elimination (DCE) usage analysis did not count identifier references that appear as default values inside a destructuring pattern (e.g. const { ttl = TTL } = options;). When a module-scope const was referenced only through such a destructuring default, the DCE pass treated it as unused and stripped it from the emitted bundle — while keeping the code that referenced it. The result was a runtime ReferenceError: <const> is not defined when the default fired.

This is mode-independent (reproduces in both step and workflow mode) and class-independent (a plain top-level function exhibits it too). It shipped as a worked-around bug in the kill-switch pattern (#1858) and remains latent in workbench/vitest/workflows/cookbook/distributed-abort-controller.ts.

Root cause

In ComprehensiveUsageCollector::visit_mut_var_declarator, the collector visited only the initializer and deliberately skipped the entire var_decl.name pattern (to avoid marking the binding name as "used"). For destructuring patterns, the default-value initializer expressions live insidevar_decl.name — so references like the TTL in { ttl = TTL } were never added to used_identifiers, and remove_dead_code then pruned the still-referenced declaration.

Fix

Added visit_pat_default_initializers, which walks a binding pattern and visits only the default-value initializer expressions (and computed keys) — ObjectPatProp::KeyValue values, ObjectPatProp::Assign.value, AssignPat.right, array element defaults, rest args — while still not marking the binding names themselves as used. visit_mut_var_declarator now calls it on var_decl.name.

Function-parameter destructuring defaults were already covered: visit_mut_fn_decl visits params in full (confirmed by the existing default-parameter-usage fixture), so no change was needed there.

Tests

  • New fixture destructuring-default-references-module-const/ asserts that two module-scope consts referenced only via destructuring defaults — one inside a class static method, one inside a plain exported function — survive in both step and workflow mode. A third, genuinely-unused const is still stripped, confirming DCE is not over-broadened.
  • Verified the fixture fails on main (consts stripped) and passes with the fix.
  • Full cargo test -p swc_workflow suite green (128 fixture tests + error tests).

Spec

Updated packages/swc-plugin-workflow/spec.md (both the step-mode and workflow-mode DCE sections) to note that a reference counts even when it appears only inside a destructuring-default initializer.

Relation to prior fixes

Same symptom class as #1944 (DCE removing a declaration that surviving code references) but a distinct root cause: #1944 was about DCE ordering relative to step hoisting; this is about the usage collector never traversing destructuring-default initializers at all, and reproduces with no nested/hoisted steps involved.

🤖 Generated with Claude Code

…analysis
The DCE usage collector skipped the entire variable name pattern when
visiting a `VarDeclarator` (to avoid marking the binding name as "used").
But default-value initializers inside destructuring patterns live in that
pattern — e.g. the `TTL` in `const { ttl = TTL } = options;` — so those
references were invisible to the collector. A module-scope `const`
referenced only through such a default was treated as unused and stripped,
while the surviving code kept reading it, producing a runtime
`ReferenceError` when the default fired.
Traverse the default-value initializer expressions (and computed keys)
within destructuring patterns while still not marking the binding names
themselves, so the referenced declaration is preserved. Function-parameter
defaults were already covered (params are visited in full).
Fixes#2396.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CopilotAI review requested due to automatic review settings June 13, 2026 06:29
@pranaygp
pranaygp requested a review from a team as a code ownerJune 13, 2026 06:29
@changeset-bot

changeset-botBot commented Jun 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9006ec3

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

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

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

@vercel

vercelBot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented Jun 13, 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🥇 Express0.034s (-30.4% 🟢)1.005s (~)0.971s101.00x
💻 LocalNitro0.051s (+22.4% 🔺)1.008s (~)0.957s101.52x
🐘 PostgresNitro0.060s (-7.3% 🟢)1.012s (~)0.952s101.78x
🐘 PostgresExpress0.062s (+2.0%)1.013s (~)0.951s101.85x
🐘 PostgresNext.js (Turbopack)0.067s (-8.4% 🟢)1.012s (~)0.945s101.98x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro0.339s (-43.5% 🟢)2.573s (-3.3%)2.234s101.00x
▲ VercelExpress0.455s (-59.0% 🟢)2.707s (-24.4% 🟢)2.252s101.34x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.079s (-1.9%)2.006s (~)0.927s101.00x
💻 LocalNitro1.110s (+1.2%)2.007s (~)0.897s101.03x
🐘 PostgresExpress1.114s (~)2.011s (~)0.897s101.03x
🐘 PostgresNitro1.119s (~)2.010s (~)0.891s101.04x
🐘 PostgresNext.js (Turbopack)1.140s (-1.6%)2.010s (~)0.871s101.06x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.805s (-32.7% 🟢)3.807s (-23.9% 🟢)2.003s101.00x
▲ VercelNitro1.900s (-32.7% 🟢)3.850s (-15.3% 🟢)1.951s101.05x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express10.432s (-1.1%)11.020s (~)0.588s31.00x
🐘 PostgresNitro10.583s (~)11.017s (~)0.434s31.01x
💻 LocalNitro10.585s (+0.6%)11.024s (~)0.439s31.01x
🐘 PostgresExpress10.598s (~)11.020s (~)0.422s31.02x
🐘 PostgresNext.js (Turbopack)10.819s (~)11.018s (~)0.199s31.04x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express14.863s (-8.6% 🟢)16.580s (-11.0% 🟢)1.718s21.00x
▲ VercelNitro14.872s (-18.1% 🟢)17.769s (-10.6% 🟢)2.897s21.00x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express13.524s (-2.3%)14.027s (~)0.503s51.00x
🐘 PostgresNitro13.752s (-0.8%)14.018s (~)0.266s51.02x
🐘 PostgresExpress13.773s (~)14.023s (~)0.250s51.02x
💻 LocalNitro13.861s (~)14.028s (~)0.167s51.02x
🐘 PostgresNext.js (Turbopack)14.473s (~)15.017s (~)0.544s41.07x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express22.452s (-35.7% 🟢)24.544s (-33.5% 🟢)2.092s31.00x
▲ VercelNitro22.733s (-31.0% 🟢)24.477s (-29.8% 🟢)1.744s31.01x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express12.003s (-4.8%)12.396s (-4.8%)0.394s81.00x
🐘 PostgresExpress12.613s (~)13.019s (~)0.405s71.05x
🐘 PostgresNitro12.668s (~)13.022s (~)0.353s71.06x
💻 LocalNitro12.685s (+0.9%)13.027s (~)0.342s71.06x
🐘 PostgresNext.js (Turbopack)13.947s (~)14.306s (+1.0%)0.358s71.16x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express31.375s (-9.7% 🟢)33.491s (-9.7% 🟢)2.116s31.00x
▲ VercelNitro31.721s (-16.3% 🟢)34.300s (-13.2% 🟢)2.578s31.01x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.202s (~)2.008s (~)0.805s151.00x
💻 LocalExpress1.202s (+0.9%)2.007s (~)0.805s151.00x
🐘 PostgresNitro1.216s (~)2.008s (~)0.792s151.01x
🐘 PostgresNext.js (Turbopack)1.280s (-5.6% 🟢)2.008s (-3.3%)0.727s151.06x
💻 LocalNitro1.317s (+13.2% 🔺)2.007s (~)0.690s151.10x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.599s (+1.9%)4.260s (+4.6%)1.662s81.00x
▲ VercelNitro2.787s (-9.8% 🟢)4.617s (+0.7%)1.830s71.07x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.332s (-1.9%)2.316s (-7.6% 🟢)0.984s131.00x
🐘 PostgresNitro1.368s (+2.0%)2.509s (~)1.141s121.03x
🐘 PostgresNext.js (Turbopack)1.571s (-4.1%)2.394s (-4.6%)0.824s131.18x
💻 LocalExpress1.614s (-14.8% 🟢)2.005s (-6.8% 🟢)0.391s151.21x
💻 LocalNitro1.723s (+5.8% 🔺)2.007s (~)0.284s151.29x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.005s (-20.3% 🟢)5.201s (-3.7%)2.196s61.00x
▲ VercelExpress3.112s (-24.4% 🟢)5.163s (-14.7% 🟢)2.051s61.04x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.605s (-1.1%)4.011s (+3.2%)2.406s81.00x
🐘 PostgresNitro1.717s (+5.7% 🔺)3.760s (-9.2% 🟢)2.043s81.07x
🐘 PostgresNext.js (Turbopack)3.515s (-8.3% 🟢)4.308s (-3.1%)0.793s72.19x
💻 LocalNitro4.578s (+13.9% 🔺)5.155s (+12.5% 🔺)0.577s72.85x
💻 LocalExpress4.611s (-11.9% 🟢)5.154s (-11.9% 🟢)0.543s72.87x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.454s (-18.7% 🟢)5.446s (-4.9%)1.992s61.00x
▲ VercelExpress3.755s (-17.9% 🟢)5.803s (-11.6% 🟢)2.048s61.09x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.206s (-1.3%)2.007s (~)0.802s151.00x
🐘 PostgresExpress1.212s (~)2.008s (~)0.795s151.01x
🐘 PostgresNext.js (Turbopack)1.301s (+1.2%)2.007s (~)0.706s151.08x
💻 LocalExpress1.467s (-9.1% 🟢)2.006s (~)0.539s151.22x
💻 LocalNitro1.614s (+6.8% 🔺)2.007s (~)0.393s151.34x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.317s (-64.3% 🟢)3.984s (-50.4% 🟢)1.667s81.00x
▲ VercelExpress2.358s (+3.7%)3.953s (+2.6%)1.594s81.02x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.355s (~)2.393s (-7.7% 🟢)1.037s131.00x
🐘 PostgresExpress1.385s (-0.7%)2.592s (+4.9%)1.206s121.02x
🐘 PostgresNext.js (Turbopack)1.550s (+0.9%)2.075s (-6.7% 🟢)0.525s151.14x
💻 LocalExpress1.760s (-31.7% 🟢)2.072s (-33.3% 🟢)0.313s151.30x
💻 LocalNitro2.052s (+14.6% 🔺)2.508s (+21.0% 🔺)0.456s121.51x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.782s (-16.5% 🟢)4.629s (-13.4% 🟢)1.847s71.00x
▲ VercelNitro3.219s (-14.0% 🟢)4.836s (-11.4% 🟢)1.617s71.16x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.589s (-9.4% 🟢)4.012s (-3.0%)2.423s81.00x
🐘 PostgresNitro1.691s (-2.2%)4.148s (+6.7% 🔺)2.457s81.06x
🐘 PostgresNext.js (Turbopack)3.574s (-6.9% 🟢)4.298s (-3.3%)0.723s72.25x
💻 LocalExpress4.386s (-30.8% 🟢)5.015s (-28.6% 🟢)0.629s62.76x
💻 LocalNitro5.079s (+6.6% 🔺)5.513s (+3.1%)0.434s63.20x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.500s (-46.9% 🟢)5.977s (-28.4% 🟢)2.477s61.00x
▲ VercelExpress3.732s (-28.7% 🟢)6.025s (-13.5% 🟢)2.293s51.07x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express0.574s (-12.9% 🟢)1.004s (-3.4%)0.430s601.00x
🐘 PostgresNitro0.576s (-10.6% 🟢)1.006s (-3.4%)0.430s601.00x
🐘 PostgresExpress0.582s (-1.7%)1.007s (~)0.425s601.01x
💻 LocalNitro0.675s (+15.7% 🔺)1.040s (+3.5%)0.365s581.17x
🐘 PostgresNext.js (Turbopack)0.826s (-2.9%)1.023s (~)0.198s591.44x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.107s (-27.8% 🟢)7.860s (-22.2% 🟢)1.753s81.00x
▲ VercelExpress6.449s (-2.0%)8.127s (-5.8% 🟢)1.678s81.06x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.328s (-16.2% 🟢)2.027s (+1.0%)0.699s451.00x
🐘 PostgresExpress1.439s (~)2.030s (~)0.591s451.08x
🐘 PostgresNitro1.476s (+2.1%)2.101s (+4.6%)0.625s431.11x
💻 LocalNitro1.548s (+4.8%)2.007s (~)0.459s451.17x
🐘 PostgresNext.js (Turbopack)1.998s (-1.9%)2.315s (-9.7% 🟢)0.318s391.50x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express14.175s (-29.6% 🟢)16.338s (-25.3% 🟢)2.163s61.00x
▲ VercelNitro14.565s (-22.4% 🟢)16.655s (-19.8% 🟢)2.090s61.03x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.704s (-4.1%)3.085s (-1.7%)0.381s391.00x
🐘 PostgresExpress2.768s (~)3.112s (-0.8%)0.344s391.02x
💻 LocalExpress2.895s (-13.4% 🟢)3.245s (-19.1% 🟢)0.350s381.07x
💻 LocalNitro3.354s (+5.9% 🔺)4.011s (+2.5%)0.657s301.24x
🐘 PostgresNext.js (Turbopack)3.954s (-1.8%)4.332s (-1.6%)0.379s281.46x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express27.701s (-28.4% 🟢)30.747s (-23.9% 🟢)3.046s41.00x
▲ VercelNitro28.386s (-21.7% 🟢)31.304s (-18.0% 🟢)2.917s41.02x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.247s (-5.5% 🟢)1.006s (~)0.760s601.00x
🐘 PostgresExpress0.247s (-5.2% 🟢)1.006s (~)0.759s601.00x
🐘 PostgresNext.js (Turbopack)0.313s (+1.6%)1.006s (~)0.693s601.27x
💻 LocalExpress0.418s (-2.4%)1.004s (~)0.586s601.69x
💻 LocalNitro0.457s (+5.8% 🔺)1.005s (~)0.549s601.85x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.829s (-36.6% 🟢)3.881s (-22.8% 🟢)2.052s161.00x
▲ VercelExpress1.907s (-46.6% 🟢)3.882s (-30.6% 🟢)1.975s161.04x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.413s (~)1.029s (+2.3%)0.616s881.00x
🐘 PostgresNitro0.413s (-9.6% 🟢)1.065s (-1.3%)0.652s851.00x
🐘 PostgresNext.js (Turbopack)0.611s (-9.9% 🟢)1.105s (-13.4% 🟢)0.494s821.48x
💻 LocalExpress2.017s (-8.0% 🟢)2.537s (-11.1% 🟢)0.521s364.89x
💻 LocalNitro2.119s (+5.4% 🔺)2.715s (+11.2% 🔺)0.596s345.13x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.194s (-8.4% 🟢)5.218s (+0.9%)2.024s181.00x
▲ VercelExpress3.290s (-36.2% 🟢)5.431s (-24.8% 🟢)2.141s171.03x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro | Express

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.777s (-7.9% 🟢)1.367s (-7.1% 🟢)0.590s891.00x
🐘 PostgresExpress0.799s (-5.8% 🟢)1.419s (-5.9% 🟢)0.621s851.03x
🐘 PostgresNext.js (Turbopack)3.176s (+2.8%)3.889s (+1.6%)0.713s314.09x
💻 LocalExpress8.590s (-12.9% 🟢)9.254s (-10.0% 🟢)0.664s1311.06x
💻 LocalNitro8.879s (+1.8%)9.566s (+3.4%)0.687s1311.43x
💻 LocalNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.698s (-16.6% 🟢)7.627s (-11.6% 🟢)1.929s161.00x
▲ VercelNitro5.845s (-13.4% 🟢)7.928s (-8.2% 🟢)2.083s161.03x
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Express | Nitro

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.145s (-2.2%)2.004s (~)0.009s (-30.3% 🟢)2.014s (~)0.869s101.00x
💻 LocalNitro1.165s (+0.7%)2.005s (~)0.011s (+5.0%)2.019s (~)0.854s101.02x
🐘 PostgresNitro1.170s (-2.3%)1.995s (~)0.001s (-8.3% 🟢)2.010s (~)0.840s101.02x
🐘 PostgresExpress1.176s (~)1.998s (~)0.001s (~)2.010s (~)0.835s101.03x
🐘 PostgresNext.js (Turbopack)1.238s (~)2.000s (~)0.001s (~)2.011s (~)0.773s101.08x
💻 LocalNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.330s (-1.4%)3.506s (-0.7%)0.786s (+11.8% 🔺)4.765s (-0.5%)2.435s101.00x
▲ VercelNitro2.442s (-4.2%)3.523s (-5.1% 🟢)1.169s (+57.9% 🔺)5.176s (+4.3%)2.734s101.05x
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Express | Nitro

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.518s (-5.7% 🟢)2.009s (~)0.010s (-19.8% 🟢)2.021s (~)0.503s301.00x
🐘 PostgresNitro1.573s (-4.0%)2.001s (~)0.005s (+3.8%)2.026s (~)0.453s301.04x
💻 LocalNitro1.586s (+1.3%)2.012s (~)0.013s (+12.0% 🔺)2.028s (~)0.441s301.04x
🐘 PostgresExpress1.627s (+1.3%)2.007s (~)0.005s (+10.1% 🔺)2.027s (~)0.400s301.07x
🐘 PostgresNext.js (Turbopack)1.838s (+2.6%)2.010s (~)0.006s (+1.2%)2.026s (~)0.188s301.21x
💻 LocalNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.321s (-6.1% 🟢)7.549s (-6.2% 🟢)0.291s (+32.8% 🔺)8.340s (-5.3% 🟢)2.019s81.00x
▲ VercelExpress6.677s (-3.2%)8.183s (-2.0%)0.202s (-41.3% 🟢)8.874s (-3.8%)2.197s71.06x
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro | Express

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.779s (-4.1%)1.050s (-1.4%)0.000s (+194.7% 🔺)1.069s (-1.0%)0.290s571.00x
🐘 PostgresExpress0.811s (+1.7%)1.028s (-1.8%)0.000s (+Infinity% 🔺)1.073s (+1.2%)0.263s561.04x
🐘 PostgresNext.js (Turbopack)1.044s (+1.6%)1.581s (-2.5%)0.000s (-2.6%)1.589s (-2.6%)0.544s381.34x
💻 LocalExpress1.238s (-12.8% 🟢)1.980s (-1.7%)0.000s (-30.9% 🟢)1.982s (-1.7%)0.744s311.59x
💻 LocalNitro1.428s (+5.7% 🔺)2.015s (~)0.001s (+171.4% 🔺)2.017s (~)0.590s301.83x
💻 LocalNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.002s (-27.0% 🟢)4.590s (-20.1% 🟢)0.001s (+Infinity% 🔺)5.105s (-18.2% 🟢)2.104s121.00x
▲ VercelExpress3.054s (-17.3% 🟢)4.589s (-6.4% 🟢)0.002s (+Infinity% 🔺)5.037s (-9.3% 🟢)1.983s121.02x
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro | Express

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

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.596s (-5.8% 🟢)2.176s (~)0.000s (-50.0% 🟢)2.189s (~)0.593s281.00x
🐘 PostgresExpress1.606s (-2.8%)2.137s (-1.9%)0.000s (NaN%)2.152s (-1.6%)0.546s281.01x
🐘 PostgresNext.js (Turbopack)2.147s (-4.8%)2.652s (-2.8%)0.000s (+Infinity% 🔺)2.671s (-2.4%)0.524s231.35x
💻 LocalExpress2.630s (-20.7% 🟢)3.121s (-22.5% 🟢)0.000s (-70.0% 🟢)3.126s (-22.5% 🟢)0.496s201.65x
💻 LocalNitro3.268s (+6.1% 🔺)3.965s (+1.7%)0.001s (+85.7% 🔺)3.968s (+1.7%)0.700s162.05x
💻 LocalNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.141s (-15.8% 🟢)5.935s (-3.7%)0.003s (+Infinity% 🔺)6.439s (-5.3% 🟢)2.298s101.00x
▲ VercelExpress4.539s (-25.2% 🟢)6.277s (-17.3% 🟢)0.001s (-11.1% 🟢)6.771s (-17.3% 🟢)2.232s91.10x
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro | Express

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress20/21
🐘 PostgresNitro13/21
▲ VercelNitro11/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres12/21
Next.js (Turbopack)🐘 Postgres21/21
Nitro🐘 Postgres18/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)

📋 View full workflow run


Some benchmark jobs failed:

  • Local: failure
  • Postgres: success
  • Vercel: failure

Check the workflow run for details.

@github-actions

github-actionsBot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production129302271520
✅ 💻 Local Development190902192128
✅ 📦 Local Production190902192128
✅ 🐘 Local Postgres189502332128
✅ 🪟 Windows15200152
✅ 📋 Other88501791064
Total8043010779120

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro125027
✅ example125027
✅ express125027
✅ fastify125027
✅ hono125027
✅ nextjs-webpack14903
✅ nitro125027
✅ nuxt125027
✅ sveltekit14408
✅ vite125027
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable-lazy-discovery-disabled15200
✅ nextjs-turbopack-stable-lazy-discovery-enabled15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable-lazy-discovery-disabled15200
✅ nextjs-webpack-stable-lazy-discovery-enabled15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable-lazy-discovery-disabled15200
✅ nextjs-turbopack-stable-lazy-discovery-enabled15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable-lazy-discovery-disabled15200
✅ nextjs-webpack-stable-lazy-discovery-enabled15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable126026
✅ express-stable126026
✅ fastify-stable126026
✅ hono-stable126026
✅ nextjs-turbopack-canary132020
✅ nextjs-turbopack-stable-lazy-discovery-disabled15101
✅ nextjs-turbopack-stable-lazy-discovery-enabled15101
✅ nextjs-webpack-canary132020
✅ nextjs-webpack-stable-lazy-discovery-disabled15101
✅ nextjs-webpack-stable-lazy-discovery-enabled15101
✅ 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


Some E2E test jobs failed:

  • Vercel Prod: failure
  • Local Dev: success
  • Local Prod: success
  • Local Postgres: success
  • Windows: success

Check the workflow run for details.

CopilotAI 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.

Pull request overview

Fixes the SWC plugin’s DCE usage analysis so module-scope declarations referenced only via destructuring default initializers (e.g. const { ttl = TTL } = options) are correctly counted as “used”, preventing runtime ReferenceErrors when defaults fire.

Changes:

  • Extend ComprehensiveUsageCollector to traverse destructuring patterns and visit only default-value initializers (and computed keys), without marking binding identifiers as used.
  • Add a regression fixture covering module-scope const preservation in both step and workflow modes (and confirming truly-unused consts are still removed).
  • Update the SWC plugin spec and add a changeset for the patch release.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
packages/swc-plugin-workflow/transform/src/lib.rsAdds visit_pat_default_initializers and wires it into visit_mut_var_declarator so destructuring-default references are counted for DCE.
packages/swc-plugin-workflow/transform/tests/fixture/destructuring-default-references-module-const/input.jsNew fixture input reproducing the destructuring-default-only reference pattern and an unused const.
packages/swc-plugin-workflow/transform/tests/fixture/destructuring-default-references-module-const/output-step.jsStep-mode snapshot asserting referenced consts survive and unused const is removed.
packages/swc-plugin-workflow/transform/tests/fixture/destructuring-default-references-module-const/output-workflow.jsWorkflow-mode snapshot asserting referenced consts survive and unused const is removed.
packages/swc-plugin-workflow/spec.mdDocuments that destructuring-default initializers count as references for DCE in both modes.
.changeset/swc-destructuring-default-dce.mdPatch changeset describing the DCE fix and the prevented runtime error.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

* origin/main:
docs: document run idempotency (#2011)
Render attr_set events and run attributes in observability UI (#2393)
[ci] Fix backport job model slug (#2403)
[ci] Comment on PR when backport fails, revert to use opus 4.8 (#2400)
Update queue client to 0.3.1 (#2399)
fix(deps): upgrade esbuild to 0.28.1 (GHSA-gv7w-rqvm-qjhr) (#2395)

@TooTallNateTooTallNate left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Approve — precise root-cause fix, exhaustive pattern coverage, and I reproduced the before/after

Targeted DCE fix: the usage collector deliberately skipped var_decl.name to avoid marking binding names as used, but destructuring-default initializers (const { ttl = TTL } = options;) live inside that pattern, so references like TTL were never counted and DCE stripped the still-referenced declaration → runtime ReferenceError. The diagnosis in the PR matches the code exactly.

What I verified

  • The new visit_pat_default_initializers is exhaustive over the binding-pattern AST and visits only value positions: object KeyValue values + computed keys, Assign.value defaults, array element defaults, AssignPat.right, rest args, and expression patterns — while never descending into binding names. I traced each arm against the Pat/ObjectPatProp variants; nothing is missed and the { ttl = TTL } case correctly skips the ttl key and visits only the TTL default.
  • It cannot over-broaden DCE. The collector's visit_mut_ident blindly adds to used_identifiers, and this helper only ever adds references — the safe direction for DCE (over-counting preserves declarations, never wrongly strips). The Pat::Expr arm visiting a member expression's property ident is the same conservative behavior the collector already has when walking initializers, so it introduces no new risk.
  • Built and ran the full Rust suite: 128 fixture tests + error tests, all green.
  • Reproduced the regression: reverted just the visit_pat_default_initializers call site to simulate main, and both the step-mode and workflow-mode fixtures FAIL — the emitted output strips const TTL/const RETRIES while keeping the code that references them. Restored the fix → both pass. This is the before/after the PR claims, confirmed locally.
  • The fixture's negative case earns its keep: const UNUSED = 'dead' is still stripped in both outputs, proving the fix preserves referenced declarations only, not everything in a pattern's vicinity.
  • The fix covers the real latent bug, not just the synthetic repro: workbench/vitest/workflows/cookbook/distributed-abort-controller.ts:126 has const { ttlMs = DEFAULT_TTL_MS, graceMs = DEFAULT_GRACE_MS } = options; referencing module consts at lines 16/18 — exactly this pattern. The fixture is a faithful minimal reduction of it.
  • spec.md updated per AGENTS.md — both the step-mode and workflow-mode DCE sections gain the destructuring-default note (correct; spec.md has no version frontmatter, the bump rule applies to skills/). Changeset is a correctly-scoped @workflow/swc-plugin patch.

Notes

  • The fixture covers a class static method and a plain exported function, deliberately demonstrating the bug is class- and mode-independent (matching the issue). Good instinct to prove both, since the worked-around precedent (#1858 kill-switch) might have suggested it was class-specific.
  • Function-parameter destructuring defaults were already handled (visit_mut_fn_decl visits params in full, per the existing default-parameter-usage fixture) — the PR correctly scopes the fix to var declarators only and says so. I'd have flagged it if param defaults regressed, but they're untouched and the existing fixture still passes.
  • Distinct from #1944 as the PR notes: that was DCE ordering vs step hoisting; this is the collector never traversing these initializers at all. No nested/hoisted steps involved, which the fixture confirms.

CI lanes that are red are the session's known baseline flakes (Windows dev-server / express benchmark), unrelated to a Rust-only change. LGTM.

@pranaygp
pranaygp merged commit 53ede30 into mainJun 16, 2026
112 of 116 checks passed
@pranaygp
pranaygp deleted the pgp/swc-plugin-fix branch June 16, 2026 23:16
@github-actionsgithub-actionsBot mentioned this pull request Jun 16, 2026
github-actionsBot added a commit that referenced this pull request Jun 16, 2026
…analysis (#2398)
The DCE usage collector skipped the entire variable name pattern when
visiting a `VarDeclarator` (to avoid marking the binding name as "used").
But default-value initializers inside destructuring patterns live in that
pattern — e.g. the `TTL` in `const { ttl = TTL } = options;` — so those
references were invisible to the collector. A module-scope `const`
referenced only through such a default was treated as unused and stripped,
while the surviving code kept reading it, producing a runtime
`ReferenceError` when the default fired.
Traverse the default-value initializer expressions (and computed keys)
within destructuring patterns while still not marking the binding names
themselves, so the referenced declaration is preserved. Function-parameter
defaults were already covered (params are visited in full).
Fixes#2396.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

Backport PR opened against stable: #2465. Merge conflicts were resolved by AI — please review carefully. (backport job run)

pranaygp pushed a commit that referenced this pull request Jun 17, 2026
…analysis (#2398) (#2465)
The DCE usage collector skipped the entire variable name pattern when
visiting a `VarDeclarator` (to avoid marking the binding name as "used").
But default-value initializers inside destructuring patterns live in that
pattern — e.g. the `TTL` in `const { ttl = TTL } = options;` — so those
references were invisible to the collector. A module-scope `const`
referenced only through such a default was treated as unused and stripped,
while the surviving code kept reading it, producing a runtime
`ReferenceError` when the default fired.
Traverse the default-value initializer expressions (and computed keys)
within destructuring patterns while still not marking the binding names
themselves, so the referenced declaration is preserved. Function-parameter
defaults were already covered (params are visited in full).
Fixes#2396.
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
TooTallNate added a commit that referenced this pull request Jun 17, 2026
…default DCE test
The #2398 backport (#2465) added the destructuring-default-references-module-const
fixture but committed output-step.js verbatim from main and omitted output-client.js
entirely. Stable's SWC plugin emits a different step-registration shape
(registerStepFunction import vs. inline IIFE) and has less aggressive DCE (the
truly-unused UNUSED const is not stripped), so the copied output-step.js did not
match stable's actual output and the required output-client.js was missing.
Regenerate both fixture outputs against stable's plugin so the client_mode and
step_mode fixture tests pass. The regression being tested still holds: TTL and
RETRIES (referenced only via destructuring defaults) are preserved in all modes.
Signed-off-by: Nathan Rajlich <n@n8.io>
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.

[swc-plugin] DCE strips module-scope declarations referenced only by destructuring defaults → ReferenceError

3 participants

@pranaygp@TooTallNate