[swc-plugin] Capture lexical this for nested arrow step functions - #1935

Merged
TooTallNate merged 6 commits into
mainfrom
feat/swc-lexical-this-capture
May 5, 2026
Merged

[swc-plugin] Capture lexical this for nested arrow step functions#1935
TooTallNate merged 6 commits into
mainfrom
feat/swc-lexical-this-capture

Conversation

@TooTallNate

Copy link
Copy Markdown
Member

Summary

Teaches the SWC plugin to handle "use step" arrow functions whose body lexically captures this from an enclosing method/function — a pattern that comes up naturally with the AI SDK tool({...}) factory and other "method that returns an object literal containing arrow steps" shapes.

Before this PR:

classReadFileTool{// serializable...createTool(context){returntool({execute: async(input)=>{'use step';returnthis.service.readFileContent(input,context);// `this` was lost},});}}

The compiler would hoist execute to module scope as an arrow with a bare return this.service…, so at runtime this was undefined. Workflow mode produced a step proxy with no this plumbing.

After this PR:

  • Workflow mode wraps the proxy with .bind(this) so invoking it captures the caller's this as thisVal on the queue item.
  • Step mode hoists the body as a regular function (not an arrow) so the runtime's existing stepFn.apply(thisVal, args) rebinds this inside the hoisted body.

The runtime already had thisVal plumbing for instance-method steps; this PR is purely a compiler change feeding that existing pipeline.

How

  • New LexicalThisDetector (Visit) walks an arrow body looking for ThisExpr. Recurses through nested arrows but stops at Function/Constructor/MethodProp/GetterProp/SetterProp/ClassMethod/PrivateMethod/StaticBlock because those introduce their own this.
  • nested_step_functions gains a references_lexical_this: bool field, threaded through every push site.
  • Two new helpers: wrap_with_bind_this(expr) and create_step_proxy_reference_maybe_bound(...).
  • Step-mode hoister checks was_arrow && !references_lexical_this to decide arrow vs. function emission.

Caveat

Capture only works at runtime if the captured this is serializable across the workflow→step boundary — i.e. the enclosing class implements WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE. Same precondition as the existing instance-method-step support. NestJS @Injectable() instances still need a separate provider rehydration path (the third option from the original issue).

Output examples

Workflow mode:

execute: globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//./input//_anonymousStep0",()=>({ context })).bind(this)

Step mode (hoisted as function, not arrow):

asyncfunction_anonymousStep0(input){const{ context }=(function(){/* closure-var IIFE */})();returnthis.service.readFileContent(input,context);}

Tests

  • 4 new SWC fixture tests (nested-arrow-step-lexical-this, nested-arrow-step-lexical-this-var-decl, both modes). Total fixture tests: 114 → 118.
  • New step.test.ts runtime test exercising useStep(stepId).bind(instance) and asserting thisVal: instance on the queue item.
  • All 843 @workflow/core tests pass.
  • pnpm typecheck clean; workbench/example builds against the rebuilt wasm.

Refs

#1865 (specifically the linked comment)

When a nested arrow `"use step"` references the enclosing function/method's
`this`, plumb that `this` through the workflow runtime so the step body
sees the correct receiver.
- Workflow mode wraps the step proxy with `.bind(this)`, so invoking the
proxy captures the caller's `this` as `thisVal` on the queue item.
- Step mode hoists the body as a regular `function` (not an arrow) so the
runtime's `stepFn.apply(thisVal, args)` rebinds `this` inside the
hoisted body.
Detection only fires for arrows, since arrows inherit `this` lexically.
Nested non-arrow functions/methods/getters/setters introduce their own
`this`, so the detector stops at those boundaries.
The runtime already supported `thisVal` for instance-method steps; this
PR is purely a compiler change to feed the existing pipeline.
Caveat: capture works at runtime only when the captured value is
serializable across the workflow->step boundary (i.e. the enclosing
class implements `WORKFLOW_SERIALIZE`/`WORKFLOW_DESERIALIZE`).
Refs #1865
CopilotAI review requested due to automatic review settings May 5, 2026 05:51
@changeset-bot

changeset-botBot commented May 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a19805f

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

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

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 May 5, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented May 5, 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.031s (-29.6% 🟢)1.006s (~)0.974s101.00x
💻 LocalNitro0.031s (-27.6% 🟢)1.005s (~)0.974s101.00x
🐘 PostgresExpress0.046s (-21.4% 🟢)1.012s (~)0.966s101.46x
💻 LocalNext.js (Turbopack)0.050s1.006s0.956s101.60x
🌐 RedisNext.js (Turbopack)0.052s1.005s0.953s101.67x
🐘 PostgresNitro0.053s (-44.3% 🟢)1.013s (-2.9%)0.960s101.70x
🌐 MongoDBNext.js (Turbopack)0.100s1.008s0.908s103.20x
🐘 PostgresNext.js (Turbopack)0.177s1.043s0.866s105.68x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro0.242s (-40.8% 🟢)2.094s (-16.5% 🟢)1.852s101.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.069s (-5.0% 🟢)2.007s (~)0.938s101.00x
💻 LocalNitro1.073s (-5.1% 🟢)2.006s (~)0.932s101.00x
🐘 PostgresExpress1.084s (-5.5% 🟢)2.010s (~)0.926s101.01x
🐘 PostgresNitro1.084s (-4.9%)2.010s (~)0.926s101.01x
🌐 RedisNext.js (Turbopack)1.111s2.007s0.895s101.04x
💻 LocalNext.js (Turbopack)1.113s2.007s0.894s101.04x
🌐 MongoDBNext.js (Turbopack)1.169s2.009s0.840s101.09x
🐘 PostgresNext.js (Turbopack)1.229s2.048s0.819s101.15x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.643s (-57.8% 🟢)3.930s (-33.5% 🟢)2.287s101.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro10.404s (-4.9%)11.021s (~)0.617s31.00x
💻 LocalExpress10.413s (-4.7%)11.022s (~)0.610s31.00x
🐘 PostgresExpress10.426s (-4.9%)11.018s (~)0.592s31.00x
🐘 PostgresNitro10.482s (-3.6%)11.020s (~)0.538s31.01x
🌐 RedisNext.js (Turbopack)10.642s11.023s0.381s31.02x
💻 LocalNext.js (Turbopack)10.680s11.024s0.344s31.03x
🌐 MongoDBNext.js (Turbopack)10.784s11.021s0.238s31.04x
🐘 PostgresNext.js (Turbopack)11.142s11.680s0.538s31.07x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro13.533s (-43.0% 🟢)15.534s (-38.2% 🟢)2.001s21.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express13.449s (-10.2% 🟢)14.027s (-6.7% 🟢)0.578s51.00x
🐘 PostgresExpress13.453s (-7.8% 🟢)14.018s (-6.7% 🟢)0.565s51.00x
💻 LocalNitro13.485s (-10.5% 🟢)14.026s (-12.5% 🟢)0.541s51.00x
🐘 PostgresNitro13.521s (-7.4% 🟢)14.023s (-6.7% 🟢)0.502s51.01x
🌐 RedisNext.js (Turbopack)14.030s14.428s0.398s51.04x
💻 LocalNext.js (Turbopack)14.125s15.030s0.905s41.05x
🌐 MongoDBNext.js (Turbopack)14.212s15.023s0.811s41.06x
🐘 PostgresNext.js (Turbopack)15.396s16.019s0.624s41.14x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro22.032s (-65.8% 🟢)24.542s (-63.2% 🟢)2.510s31.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro11.936s (-28.9% 🟢)12.147s (-28.7% 🟢)0.211s81.00x
🐘 PostgresExpress11.971s (-14.5% 🟢)12.516s (-14.2% 🟢)0.545s81.00x
💻 LocalExpress11.979s (-27.8% 🟢)12.273s (-27.9% 🟢)0.294s81.00x
🐘 PostgresNitro12.038s (-13.8% 🟢)12.766s (-10.8% 🟢)0.728s81.01x
🌐 RedisNext.js (Turbopack)13.002s13.311s0.309s71.09x
🌐 MongoDBNext.js (Turbopack)13.250s14.020s0.770s71.11x
💻 LocalNext.js (Turbopack)13.313s14.026s0.713s71.12x
🐘 PostgresNext.js (Turbopack)16.668s17.052s0.385s61.40x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro33.281s (-92.1% 🟢)35.586s (-91.6% 🟢)2.305s31.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.148s (-8.9% 🟢)2.008s (~)0.860s151.00x
🐘 PostgresNitro1.164s (-8.7% 🟢)2.007s (~)0.843s151.01x
💻 LocalExpress1.184s (-20.5% 🟢)2.006s (~)0.822s151.03x
💻 LocalNitro1.185s (-27.4% 🟢)2.006s (-3.3%)0.821s151.03x
🌐 RedisNext.js (Turbopack)1.249s2.006s0.757s151.09x
💻 LocalNext.js (Turbopack)1.307s2.006s0.699s151.14x
🐘 PostgresNext.js (Turbopack)1.785s2.202s0.417s141.55x
🌐 MongoDBNext.js (Turbopack)2.029s2.827s0.798s111.77x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.530s (-10.2% 🟢)4.390s (+1.6%)1.860s71.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.231s (-47.8% 🟢)2.009s (-33.2% 🟢)0.778s151.00x
🐘 PostgresNitro1.247s (-47.0% 🟢)2.008s (-33.3% 🟢)0.761s151.01x
🐘 PostgresNext.js (Turbopack)1.696s2.200s0.504s141.38x
💻 LocalNitro1.796s (-42.9% 🟢)2.072s (-46.7% 🟢)0.276s151.46x
💻 LocalExpress1.798s (-39.1% 🟢)2.073s (-40.0% 🟢)0.275s151.46x
💻 LocalNext.js (Turbopack)1.864s2.221s0.357s141.51x
🌐 RedisNext.js (Turbopack)2.370s3.008s0.637s101.92x
🌐 MongoDBNext.js (Turbopack)3.591s4.010s0.419s82.92x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.386s (-16.4% 🟢)5.511s (-6.9% 🟢)2.125s61.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.373s (-60.6% 🟢)2.007s (-49.9% 🟢)0.634s151.00x
🐘 PostgresNitro1.444s (-58.5% 🟢)2.008s (-49.9% 🟢)0.565s151.05x
🐘 PostgresNext.js (Turbopack)2.200s3.032s0.832s101.60x
🌐 RedisNext.js (Turbopack)3.616s4.010s0.394s82.63x
💻 LocalExpress5.169s (-38.0% 🟢)5.849s (-35.2% 🟢)0.680s63.76x
💻 LocalNitro5.416s (-35.1% 🟢)6.016s (-33.3% 🟢)0.600s53.94x
💻 LocalNext.js (Turbopack)5.970s6.414s0.444s54.35x
🌐 MongoDBNext.js (Turbopack)6.338s7.014s0.676s54.62x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro7.725s (+119.1% 🔺)10.152s (+83.5% 🔺)2.427s31.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.152s (-8.4% 🟢)2.007s (~)0.856s151.00x
🐘 PostgresNitro1.174s (-6.6% 🟢)2.009s (~)0.835s151.02x
🌐 RedisNext.js (Turbopack)1.229s2.006s0.777s151.07x
💻 LocalNitro1.325s (-29.0% 🟢)2.006s (-14.3% 🟢)0.681s151.15x
💻 LocalNext.js (Turbopack)1.353s2.007s0.654s151.17x
💻 LocalExpress1.413s (-25.4% 🟢)2.007s (-15.1% 🟢)0.594s151.23x
🐘 PostgresNext.js (Turbopack)1.630s2.184s0.554s141.42x
🌐 MongoDBNext.js (Turbopack)2.057s3.008s0.952s101.79x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.585s (+5.1% 🔺)4.490s (+7.7% 🔺)1.905s71.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.239s (-47.1% 🟢)2.008s (-33.3% 🟢)0.768s151.00x
🐘 PostgresNitro1.273s (-45.6% 🟢)2.009s (-33.3% 🟢)0.737s151.03x
🐘 PostgresNext.js (Turbopack)1.770s2.594s0.824s121.43x
💻 LocalNitro1.982s (-35.3% 🟢)2.393s (-38.4% 🟢)0.411s131.60x
💻 LocalExpress2.047s (-34.6% 🟢)2.394s (-36.4% 🟢)0.346s131.65x
💻 LocalNext.js (Turbopack)2.054s2.508s0.455s121.66x
🌐 RedisNext.js (Turbopack)2.354s3.008s0.654s101.90x
🌐 MongoDBNext.js (Turbopack)3.576s4.009s0.433s82.89x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro54.294s (+1579.3% 🔺)56.491s (+1012.7% 🔺)2.197s61.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.379s (-60.6% 🟢)2.008s (-49.9% 🟢)0.629s151.00x
🐘 PostgresNitro1.414s (-59.4% 🟢)2.008s (-49.9% 🟢)0.594s151.03x
🐘 PostgresNext.js (Turbopack)2.116s2.606s0.490s121.53x
🌐 RedisNext.js (Turbopack)3.586s4.010s0.424s82.60x
💻 LocalNitro5.772s (-36.9% 🟢)6.214s (-38.0% 🟢)0.441s54.18x
💻 LocalExpress5.890s (-33.1% 🟢)6.214s (-33.0% 🟢)0.324s54.27x
💻 LocalNext.js (Turbopack)6.126s6.618s0.492s54.44x
🌐 MongoDBNext.js (Turbopack)6.338s7.011s0.673s54.60x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.693s (+31.4% 🔺)9.252s (+35.7% 🔺)2.560s41.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.460s (-45.2% 🟢)1.024s (~)0.564s591.00x
💻 LocalNitro0.484s (-50.7% 🟢)1.021s (-6.6% 🟢)0.538s591.05x
🐘 PostgresNitro0.491s (-40.1% 🟢)1.024s (+1.8%)0.533s591.07x
💻 LocalExpress0.500s (-49.2% 🟢)1.004s (-6.7% 🟢)0.505s601.09x
🌐 RedisNext.js (Turbopack)0.619s1.004s0.386s601.35x
🌐 MongoDBNext.js (Turbopack)0.724s1.005s0.281s601.57x
💻 LocalNext.js (Turbopack)0.748s1.005s0.256s601.63x
🐘 PostgresNext.js (Turbopack)0.860s1.301s0.441s471.87x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.067s (-77.0% 🟢)6.854s (-71.5% 🟢)1.787s91.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.017s (-48.6% 🟢)1.586s (-29.8% 🟢)0.569s571.00x
🐘 PostgresNitro1.143s (-40.7% 🟢)1.986s (-5.4% 🟢)0.843s461.12x
💻 LocalExpress1.210s (-59.9% 🟢)2.005s (-44.1% 🟢)0.795s451.19x
💻 LocalNitro1.212s (-60.1% 🟢)2.006s (-46.6% 🟢)0.793s451.19x
🌐 RedisNext.js (Turbopack)1.513s2.006s0.493s451.49x
🌐 MongoDBNext.js (Turbopack)1.821s2.007s0.186s451.79x
💻 LocalNext.js (Turbopack)1.825s2.006s0.181s451.80x
🐘 PostgresNext.js (Turbopack)2.605s3.071s0.465s302.56x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro13.232s (-66.5% 🟢)15.905s (-61.5% 🟢)2.672s61.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express2.137s (-46.4% 🟢)2.604s (-40.4% 🟢)0.466s471.00x
🐘 PostgresNitro2.235s (-45.5% 🟢)3.034s (-34.1% 🟢)0.799s401.05x
💻 LocalExpress2.693s (-70.8% 🟢)3.008s (-70.0% 🟢)0.314s401.26x
💻 LocalNitro2.712s (-70.8% 🟢)3.057s (-69.5% 🟢)0.346s401.27x
🌐 RedisNext.js (Turbopack)2.959s3.135s0.176s391.38x
💻 LocalNext.js (Turbopack)3.941s4.366s0.426s281.84x
🌐 MongoDBNext.js (Turbopack)4.155s5.012s0.857s241.94x
🐘 PostgresNext.js (Turbopack)4.540s5.107s0.567s252.12x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro41.246s (-57.4% 🟢)43.425s (-55.9% 🟢)2.180s31.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.189s (-33.0% 🟢)1.005s (~)0.816s601.00x
🐘 PostgresNitro0.198s (-30.3% 🟢)1.006s (~)0.809s601.04x
🌐 RedisNext.js (Turbopack)0.242s1.004s0.763s601.28x
💻 LocalExpress0.473s (-15.7% 🟢)1.004s (~)0.532s602.50x
💻 LocalNitro0.539s (-10.9% 🟢)1.096s (+7.3% 🔺)0.557s552.85x
💻 LocalNext.js (Turbopack)0.592s1.022s0.429s593.13x
🐘 PostgresNext.js (Turbopack)0.679s1.259s0.580s483.59x
🌐 MongoDBNext.js (Turbopack)1.029s1.749s0.720s355.44x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.252s (+35.6% 🔺)4.327s (+29.1% 🔺)2.074s141.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.320s (-37.3% 🟢)1.006s (~)0.687s901.00x
🐘 PostgresNitro0.340s (-31.6% 🟢)1.007s (~)0.667s901.06x
🌐 RedisNext.js (Turbopack)0.408s1.004s0.596s901.28x
🐘 PostgresNext.js (Turbopack)0.895s1.442s0.547s632.80x
💻 LocalNitro2.133s (-16.0% 🟢)2.883s (-4.2%)0.750s326.67x
💻 LocalExpress2.304s (-8.3% 🟢)2.943s (-2.2%)0.639s317.21x
🌐 MongoDBNext.js (Turbopack)2.605s3.006s0.401s308.15x
💻 LocalNext.js (Turbopack)2.641s3.183s0.542s298.26x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro8.322s (+158.0% 🔺)10.481s (+117.4% 🔺)2.158s91.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.647s (-21.0% 🟢)1.006s (-1.1%)0.359s1201.00x
🐘 PostgresNitro0.698s (-11.6% 🟢)1.007s (~)0.308s1201.08x
🌐 RedisNext.js (Turbopack)0.762s1.004s0.242s1201.18x
🐘 PostgresNext.js (Turbopack)1.313s1.779s0.467s682.03x
🌐 MongoDBNext.js (Turbopack)5.386s6.013s0.627s208.33x
💻 LocalNitro10.093s (-9.8% 🟢)10.612s (-9.0% 🟢)0.519s1215.61x
💻 LocalExpress10.309s (-7.9% 🟢)10.864s (-9.0% 🟢)0.555s1215.94x
💻 LocalNext.js (Turbopack)11.645s12.330s0.684s1018.01x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro21.406s (+177.2% 🔺)23.498s (+150.0% 🔺)2.092s61.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.134s (+452.9% 🔺)1.998s (+100.1% 🔺)0.002s (-6.3% 🟢)2.010s (+98.7% 🔺)0.876s101.00x
💻 LocalExpress1.134s (+469.8% 🔺)2.005s (+99.6% 🔺)0.012s (+0.8%)2.020s (+98.4% 🔺)0.885s101.00x
💻 LocalNitro1.136s (+431.6% 🔺)2.005s (+99.6% 🔺)0.012s (-1.6%)2.020s (+98.2% 🔺)0.883s101.00x
🐘 PostgresNitro1.154s (+462.9% 🔺)2.002s (+100.2% 🔺)0.002s (~)2.013s (+99.0% 🔺)0.859s101.02x
💻 LocalNext.js (Turbopack)1.198s2.003s0.013s2.020s0.823s101.06x
🐘 PostgresNext.js (Turbopack)1.533s1.999s0.410s2.441s0.908s101.35x
🌐 MongoDBNext.js (Turbopack)⚠️missing-----
🌐 RedisNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.173s (-43.3% 🟢)3.567s (-32.4% 🟢)1.733s (+133.5% 🔺)5.800s (-10.5% 🟢)3.628s101.00x
▲ VercelExpress⚠️missing-----
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.509s (+139.5% 🔺)2.003s (+99.1% 🔺)0.004s (-2.5%)2.024s (+97.9% 🔺)0.515s301.00x
💻 LocalNitro1.528s (+82.1% 🔺)2.011s (+98.7% 🔺)0.009s (-5.7% 🟢)2.022s (+81.2% 🔺)0.494s301.01x
💻 LocalExpress1.530s (+102.1% 🔺)2.011s (+95.5% 🔺)0.010s (+4.5%)2.023s (+94.6% 🔺)0.493s301.01x
🐘 PostgresNitro1.576s (+152.5% 🔺)2.005s (+99.2% 🔺)0.004s (-3.3%)2.027s (+98.3% 🔺)0.451s301.04x
💻 LocalNext.js (Turbopack)1.698s2.010s0.011s2.024s0.327s301.13x
🐘 PostgresNext.js (Turbopack)2.628s3.008s0.004s3.036s0.408s201.74x
🌐 MongoDBNext.js (Turbopack)⚠️missing-----
🌐 RedisNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.770s (-80.4% 🟢)7.572s (-75.4% 🟢)0.449s (+300.9% 🔺)8.603s (-72.9% 🟢)2.833s71.00x
▲ VercelExpress⚠️missing-----
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.668s (-30.5% 🟢)1.049s (-17.9% 🟢)0.000s (-59.6% 🟢)1.060s (-18.9% 🟢)0.392s571.00x
🐘 PostgresNitro0.716s (-26.0% 🟢)1.050s (-15.8% 🟢)0.000s (-57.9% 🟢)1.068s (-15.1% 🟢)0.351s571.07x
💻 LocalExpress1.350s (+10.2% 🔺)2.015s (~)0.001s (+70.0% 🔺)2.017s (~)0.667s302.02x
💻 LocalNitro1.358s (+11.1% 🔺)2.015s (~)0.000s (+133.3% 🔺)2.017s (~)0.659s302.03x
🐘 PostgresNext.js (Turbopack)1.370s1.659s0.000s1.724s0.354s362.05x
💻 LocalNext.js (Turbopack)1.498s2.014s0.000s2.017s0.519s302.24x
🌐 MongoDBNext.js (Turbopack)⚠️missing-----
🌐 RedisNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.519s (+15.4% 🔺)5.014s (+14.1% 🔺)0.000s (-100.0% 🟢)5.521s (+14.8% 🔺)2.002s111.00x
▲ VercelExpress⚠️missing-----
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro

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

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.332s (-24.8% 🟢)1.998s (-8.3% 🟢)0.000s (+Infinity% 🔺)2.018s (-8.2% 🟢)0.686s301.00x
🐘 PostgresNitro1.458s (-18.6% 🟢)2.100s (-1.9%)0.000s (-3.4%)2.115s (-2.7%)0.656s291.09x
💻 LocalNext.js (Turbopack)2.949s3.357s0.000s3.367s0.418s182.21x
💻 LocalNitro3.144s (-7.2% 🟢)4.029s (~)0.000s (-25.0% 🟢)4.032s (~)0.888s152.36x
💻 LocalExpress3.177s (-8.4% 🟢)3.967s (-1.7%)0.000s (-84.4% 🟢)3.969s (-1.7%)0.792s162.39x
🐘 PostgresNext.js (Turbopack)3.436s3.946s0.000s4.008s0.572s152.58x
🌐 MongoDBNext.js (Turbopack)⚠️missing-----
🌐 RedisNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.077s (+48.4% 🔺)7.737s (+44.0% 🔺)0.000s (-54.2% 🟢)8.390s (+44.8% 🔺)2.313s81.00x
▲ VercelExpress⚠️missing-----
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress10/21
🐘 PostgresExpress21/21
▲ VercelNitro21/21
Fastest World by Framework

Winner determined by most benchmark wins

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

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)

📋 View full workflow run


Some benchmark jobs failed:

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

Check the workflow run for details.

@github-actions

github-actionsBot commented May 5, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production92502191144
✅ 💻 Local Development123702191456
✅ 📦 Local Production123702191456
✅ 🐘 Local Postgres123702191456
✅ 🪟 Windows10400104
✅ 📋 Other5520176728
Total5292010526344

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro78026
✅ example78026
✅ express78026
✅ fastify78026
✅ hono78026
✅ nextjs-turbopack10202
✅ nextjs-webpack10202
✅ nitro78026
✅ nuxt78026
✅ sveltekit9707
✅ vite78026
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack10400
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable79025
✅ e2e-local-dev-tanstack-start-79025
✅ e2e-local-postgres-nest-stable79025
✅ e2e-local-postgres-tanstack-start-79025
✅ e2e-local-prod-nest-stable79025
✅ e2e-local-prod-tanstack-start-79025
✅ e2e-vercel-prod-tanstack-start78026

📋 View full workflow run

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

Updates @workflow/swc-plugin to correctly preserve lexical this for nested arrow "use step" functions (common in “method returns object literal with arrow steps” patterns), by detecting this usage, binding step proxies in workflow mode, and hoisting step bodies as regular functions in step mode so runtime apply(thisVal, args) rebinding works.

Changes:

  • Add lexical-this detection for nested arrow step bodies and thread a references_lexical_this flag through nested-step tracking.
  • Emit .bind(this) on workflow-mode step proxies when lexical this is referenced; adjust step-mode hoisting to emit function instead of arrow for those steps.
  • Add SWC fixture coverage + a core runtime test; document the new behavior and ship a changeset.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
packages/swc-plugin-workflow/transform/src/lib.rsImplements lexical-this detection, proxy binding, and step-mode hoist changes.
packages/swc-plugin-workflow/spec.mdDocuments lexical this capture behavior and updates notes about this in steps.
packages/core/src/step.test.tsAdds runtime test ensuring .bind(this) captures thisVal onto the invocation queue item.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this/input.jsNew fixture input covering nested arrow step referencing this in a method.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this/output-workflow.jsExpected workflow-mode output showing .bind(this) on the step proxy.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this/output-step.jsExpected step-mode output showing hoisting as async function using this.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this-var-decl/input.jsNew fixture input for var-declarator arrow step capturing this.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this-var-decl/output-workflow.jsExpected workflow-mode output with bound proxy for var-declarator case.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this-var-decl/output-step.jsExpected step-mode output hoisting as function for var-declarator case.
.changeset/swc-lexical-this-capture.mdPatch changeset describing the new lexical-this behavior.

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

Comment threadpackages/swc-plugin-workflow/transform/src/lib.rs
Comment threadpackages/swc-plugin-workflow/transform/src/lib.rs
Comment threadpackages/swc-plugin-workflow/transform/src/lib.rs
Comment threadpackages/swc-plugin-workflow/spec.md Outdated
Comment threadpackages/core/src/step.test.ts Outdated
…ction
- core: Override `.bind` on step proxies so the bound function retains
`stepId` and `__closureVarsFn`. Without this, a bound proxy that flows
through workflow serialization (e.g. as a step argument) would be
treated as a non-serializable plain function by `getStepFunctionReducer`.
- swc-plugin: Detector now also walks `arrow.params` so `this` references
in default values / destructuring initializers (e.g. `(x = this.foo) =>
...`) trigger the `.bind(this)` path.
- swc-plugin: Class bodies inside the arrow body are now treated as
`this`-binding boundaries — `this` inside class field initializers,
methods, etc. is bound to the class instance, not the outer arrow. The
detector still walks `extends` clauses and computed property keys
because those are evaluated in the surrounding scope.
- spec.md: Sharpen the note about `this` in step bodies — it's
syntactically allowed but only meaningful for instance-method steps and
lexical-`this` arrow steps; other shapes compile but `this` will be
whatever the caller of the step proxy passes.
- Add `lexical-this-detector-edge-cases` fixture covering both the
default-param positive case and the inner-class false-positive guard.
- Strengthen the runtime test to assert `stepId` / `__closureVarsFn`
survive `.bind(...)`.
… coverage
Without this, a `useStep(...).bind(thisArg)` proxy that flows through
workflow serialization (e.g. passed as a step argument) would lose its
receiver: the reducer captured `stepId` but not the bound `this`, and
the step-bundle reviver returned the raw registered step body which
ignores any `this` the caller passes.
Now:
- step.ts `.bind` override stashes the bound value on the result as
`__boundThis` so the reducer can see it.
- The reducer serializes `boundThis` (using property presence so
`bind(null)`/`bind(undefined)` round-trip faithfully).
- The workflow-bundle reviver re-binds the freshly created proxy.
- The step-bundle reviver wraps the registered body so it's invoked
with `apply(boundThis, args)` (and still runs inside the closure-vars
AsyncLocalStorage frame when `closureVars` is present).
E2E coverage extends `instanceMethodStepWorkflow` with two new shapes:
- `counter.makeAdder(7).add(2)` — direct invocation of a lexical-`this`
arrow step. Verifies `bind(this)` carries `thisVal` to the queue and
the step body sees `this.value` correctly.
- `invokeAdderFromStep(adder.add, 3)` — passes the bound proxy as a
step argument so the round-trip path exercises both the reducer and
the step-bundle reviver, with the inner call running inline. Without
the new `boundThis` plumbing this previously failed with `Cannot read
properties of undefined (reading 'value')`.

@karthikscale3karthikscale3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AI review: Solid implementation — the LexicalThisDetector, the .bind() override, and the serialization round-trip all hang together correctly. No regressions found. The arguments fix is a genuine bug fix and the dead this/arguments error-check removal is safe. All completed CI checks pass. LGTM

Comment threadpackages/core/src/step.ts
Comment threadpackages/core/src/serialization/serialization.test.ts
…+boundThis
- step.ts `.bind` override now also stashes `__boundArgs` when the caller
supplied prefilled args (`useStep(...).bind(thisArg, x, y)`). The
reducer serializes them as `boundArgs`, and both the workflow- and
step-bundle revivers re-apply them (the workflow reviver via
`bind(boundThis, ...boundArgs)`, the step-bundle reviver by
prepending to the runtime args). The SWC plugin only ever emits
`.bind(this)` today, but this keeps partial application faithful in
case hand-written code ever calls `.bind` with extra args.
- Add two new unit tests in `serialization.test.ts`:
- `closureVars + boundThis` combo: exercises the step-bundle reviver's
inner branch (`contextStorage.run(newContext, () =>
stepFn.apply(callThis, callArgs))`) in isolation — previously only
covered end-to-end by the `instanceMethodStepWorkflow` e2e test.
- `boundArgs` round-trip: codifies that prefilled args survive
serialization.
@github-actions

Copy link
Copy Markdown
Contributor

Backport PR opened against stable: #1945. Merge conflicts were resolved by AI — please review carefully.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-stableCherry-pick this PR to the stable branch when merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@TooTallNate@karthikscale3
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n 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;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

[swc-plugin] Capture lexical this for nested arrow step functions - #1935

Merged
TooTallNate merged 6 commits into
mainfrom
feat/swc-lexical-this-capture
May 5, 2026
Merged

[swc-plugin] Capture lexical this for nested arrow step functions#1935
TooTallNate merged 6 commits into
mainfrom
feat/swc-lexical-this-capture

Conversation

@TooTallNate

Copy link
Copy Markdown
Member

Summary

Teaches the SWC plugin to handle "use step" arrow functions whose body lexically captures this from an enclosing method/function — a pattern that comes up naturally with the AI SDK tool({...}) factory and other "method that returns an object literal containing arrow steps" shapes.

Before this PR:

classReadFileTool{// serializable...createTool(context){returntool({execute: async(input)=>{'use step';returnthis.service.readFileContent(input,context);// `this` was lost},});}}

The compiler would hoist execute to module scope as an arrow with a bare return this.service…, so at runtime this was undefined. Workflow mode produced a step proxy with no this plumbing.

After this PR:

  • Workflow mode wraps the proxy with .bind(this) so invoking it captures the caller's this as thisVal on the queue item.
  • Step mode hoists the body as a regular function (not an arrow) so the runtime's existing stepFn.apply(thisVal, args) rebinds this inside the hoisted body.

The runtime already had thisVal plumbing for instance-method steps; this PR is purely a compiler change feeding that existing pipeline.

How

  • New LexicalThisDetector (Visit) walks an arrow body looking for ThisExpr. Recurses through nested arrows but stops at Function/Constructor/MethodProp/GetterProp/SetterProp/ClassMethod/PrivateMethod/StaticBlock because those introduce their own this.
  • nested_step_functions gains a references_lexical_this: bool field, threaded through every push site.
  • Two new helpers: wrap_with_bind_this(expr) and create_step_proxy_reference_maybe_bound(...).
  • Step-mode hoister checks was_arrow && !references_lexical_this to decide arrow vs. function emission.

Caveat

Capture only works at runtime if the captured this is serializable across the workflow→step boundary — i.e. the enclosing class implements WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE. Same precondition as the existing instance-method-step support. NestJS @Injectable() instances still need a separate provider rehydration path (the third option from the original issue).

Output examples

Workflow mode:

execute: globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//./input//_anonymousStep0",()=>({ context })).bind(this)

Step mode (hoisted as function, not arrow):

asyncfunction_anonymousStep0(input){const{ context }=(function(){/* closure-var IIFE */})();returnthis.service.readFileContent(input,context);}

Tests

  • 4 new SWC fixture tests (nested-arrow-step-lexical-this, nested-arrow-step-lexical-this-var-decl, both modes). Total fixture tests: 114 → 118.
  • New step.test.ts runtime test exercising useStep(stepId).bind(instance) and asserting thisVal: instance on the queue item.
  • All 843 @workflow/core tests pass.
  • pnpm typecheck clean; workbench/example builds against the rebuilt wasm.

Refs

#1865 (specifically the linked comment)

When a nested arrow `"use step"` references the enclosing function/method's
`this`, plumb that `this` through the workflow runtime so the step body
sees the correct receiver.
- Workflow mode wraps the step proxy with `.bind(this)`, so invoking the
proxy captures the caller's `this` as `thisVal` on the queue item.
- Step mode hoists the body as a regular `function` (not an arrow) so the
runtime's `stepFn.apply(thisVal, args)` rebinds `this` inside the
hoisted body.
Detection only fires for arrows, since arrows inherit `this` lexically.
Nested non-arrow functions/methods/getters/setters introduce their own
`this`, so the detector stops at those boundaries.
The runtime already supported `thisVal` for instance-method steps; this
PR is purely a compiler change to feed the existing pipeline.
Caveat: capture works at runtime only when the captured value is
serializable across the workflow->step boundary (i.e. the enclosing
class implements `WORKFLOW_SERIALIZE`/`WORKFLOW_DESERIALIZE`).
Refs #1865
CopilotAI review requested due to automatic review settings May 5, 2026 05:51
@changeset-bot

changeset-botBot commented May 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a19805f

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

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

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 May 5, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented May 5, 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.031s (-29.6% 🟢)1.006s (~)0.974s101.00x
💻 LocalNitro0.031s (-27.6% 🟢)1.005s (~)0.974s101.00x
🐘 PostgresExpress0.046s (-21.4% 🟢)1.012s (~)0.966s101.46x
💻 LocalNext.js (Turbopack)0.050s1.006s0.956s101.60x
🌐 RedisNext.js (Turbopack)0.052s1.005s0.953s101.67x
🐘 PostgresNitro0.053s (-44.3% 🟢)1.013s (-2.9%)0.960s101.70x
🌐 MongoDBNext.js (Turbopack)0.100s1.008s0.908s103.20x
🐘 PostgresNext.js (Turbopack)0.177s1.043s0.866s105.68x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro0.242s (-40.8% 🟢)2.094s (-16.5% 🟢)1.852s101.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.069s (-5.0% 🟢)2.007s (~)0.938s101.00x
💻 LocalNitro1.073s (-5.1% 🟢)2.006s (~)0.932s101.00x
🐘 PostgresExpress1.084s (-5.5% 🟢)2.010s (~)0.926s101.01x
🐘 PostgresNitro1.084s (-4.9%)2.010s (~)0.926s101.01x
🌐 RedisNext.js (Turbopack)1.111s2.007s0.895s101.04x
💻 LocalNext.js (Turbopack)1.113s2.007s0.894s101.04x
🌐 MongoDBNext.js (Turbopack)1.169s2.009s0.840s101.09x
🐘 PostgresNext.js (Turbopack)1.229s2.048s0.819s101.15x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.643s (-57.8% 🟢)3.930s (-33.5% 🟢)2.287s101.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro10.404s (-4.9%)11.021s (~)0.617s31.00x
💻 LocalExpress10.413s (-4.7%)11.022s (~)0.610s31.00x
🐘 PostgresExpress10.426s (-4.9%)11.018s (~)0.592s31.00x
🐘 PostgresNitro10.482s (-3.6%)11.020s (~)0.538s31.01x
🌐 RedisNext.js (Turbopack)10.642s11.023s0.381s31.02x
💻 LocalNext.js (Turbopack)10.680s11.024s0.344s31.03x
🌐 MongoDBNext.js (Turbopack)10.784s11.021s0.238s31.04x
🐘 PostgresNext.js (Turbopack)11.142s11.680s0.538s31.07x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro13.533s (-43.0% 🟢)15.534s (-38.2% 🟢)2.001s21.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express13.449s (-10.2% 🟢)14.027s (-6.7% 🟢)0.578s51.00x
🐘 PostgresExpress13.453s (-7.8% 🟢)14.018s (-6.7% 🟢)0.565s51.00x
💻 LocalNitro13.485s (-10.5% 🟢)14.026s (-12.5% 🟢)0.541s51.00x
🐘 PostgresNitro13.521s (-7.4% 🟢)14.023s (-6.7% 🟢)0.502s51.01x
🌐 RedisNext.js (Turbopack)14.030s14.428s0.398s51.04x
💻 LocalNext.js (Turbopack)14.125s15.030s0.905s41.05x
🌐 MongoDBNext.js (Turbopack)14.212s15.023s0.811s41.06x
🐘 PostgresNext.js (Turbopack)15.396s16.019s0.624s41.14x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro22.032s (-65.8% 🟢)24.542s (-63.2% 🟢)2.510s31.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro11.936s (-28.9% 🟢)12.147s (-28.7% 🟢)0.211s81.00x
🐘 PostgresExpress11.971s (-14.5% 🟢)12.516s (-14.2% 🟢)0.545s81.00x
💻 LocalExpress11.979s (-27.8% 🟢)12.273s (-27.9% 🟢)0.294s81.00x
🐘 PostgresNitro12.038s (-13.8% 🟢)12.766s (-10.8% 🟢)0.728s81.01x
🌐 RedisNext.js (Turbopack)13.002s13.311s0.309s71.09x
🌐 MongoDBNext.js (Turbopack)13.250s14.020s0.770s71.11x
💻 LocalNext.js (Turbopack)13.313s14.026s0.713s71.12x
🐘 PostgresNext.js (Turbopack)16.668s17.052s0.385s61.40x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro33.281s (-92.1% 🟢)35.586s (-91.6% 🟢)2.305s31.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.148s (-8.9% 🟢)2.008s (~)0.860s151.00x
🐘 PostgresNitro1.164s (-8.7% 🟢)2.007s (~)0.843s151.01x
💻 LocalExpress1.184s (-20.5% 🟢)2.006s (~)0.822s151.03x
💻 LocalNitro1.185s (-27.4% 🟢)2.006s (-3.3%)0.821s151.03x
🌐 RedisNext.js (Turbopack)1.249s2.006s0.757s151.09x
💻 LocalNext.js (Turbopack)1.307s2.006s0.699s151.14x
🐘 PostgresNext.js (Turbopack)1.785s2.202s0.417s141.55x
🌐 MongoDBNext.js (Turbopack)2.029s2.827s0.798s111.77x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.530s (-10.2% 🟢)4.390s (+1.6%)1.860s71.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.231s (-47.8% 🟢)2.009s (-33.2% 🟢)0.778s151.00x
🐘 PostgresNitro1.247s (-47.0% 🟢)2.008s (-33.3% 🟢)0.761s151.01x
🐘 PostgresNext.js (Turbopack)1.696s2.200s0.504s141.38x
💻 LocalNitro1.796s (-42.9% 🟢)2.072s (-46.7% 🟢)0.276s151.46x
💻 LocalExpress1.798s (-39.1% 🟢)2.073s (-40.0% 🟢)0.275s151.46x
💻 LocalNext.js (Turbopack)1.864s2.221s0.357s141.51x
🌐 RedisNext.js (Turbopack)2.370s3.008s0.637s101.92x
🌐 MongoDBNext.js (Turbopack)3.591s4.010s0.419s82.92x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.386s (-16.4% 🟢)5.511s (-6.9% 🟢)2.125s61.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.373s (-60.6% 🟢)2.007s (-49.9% 🟢)0.634s151.00x
🐘 PostgresNitro1.444s (-58.5% 🟢)2.008s (-49.9% 🟢)0.565s151.05x
🐘 PostgresNext.js (Turbopack)2.200s3.032s0.832s101.60x
🌐 RedisNext.js (Turbopack)3.616s4.010s0.394s82.63x
💻 LocalExpress5.169s (-38.0% 🟢)5.849s (-35.2% 🟢)0.680s63.76x
💻 LocalNitro5.416s (-35.1% 🟢)6.016s (-33.3% 🟢)0.600s53.94x
💻 LocalNext.js (Turbopack)5.970s6.414s0.444s54.35x
🌐 MongoDBNext.js (Turbopack)6.338s7.014s0.676s54.62x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro7.725s (+119.1% 🔺)10.152s (+83.5% 🔺)2.427s31.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.152s (-8.4% 🟢)2.007s (~)0.856s151.00x
🐘 PostgresNitro1.174s (-6.6% 🟢)2.009s (~)0.835s151.02x
🌐 RedisNext.js (Turbopack)1.229s2.006s0.777s151.07x
💻 LocalNitro1.325s (-29.0% 🟢)2.006s (-14.3% 🟢)0.681s151.15x
💻 LocalNext.js (Turbopack)1.353s2.007s0.654s151.17x
💻 LocalExpress1.413s (-25.4% 🟢)2.007s (-15.1% 🟢)0.594s151.23x
🐘 PostgresNext.js (Turbopack)1.630s2.184s0.554s141.42x
🌐 MongoDBNext.js (Turbopack)2.057s3.008s0.952s101.79x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.585s (+5.1% 🔺)4.490s (+7.7% 🔺)1.905s71.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.239s (-47.1% 🟢)2.008s (-33.3% 🟢)0.768s151.00x
🐘 PostgresNitro1.273s (-45.6% 🟢)2.009s (-33.3% 🟢)0.737s151.03x
🐘 PostgresNext.js (Turbopack)1.770s2.594s0.824s121.43x
💻 LocalNitro1.982s (-35.3% 🟢)2.393s (-38.4% 🟢)0.411s131.60x
💻 LocalExpress2.047s (-34.6% 🟢)2.394s (-36.4% 🟢)0.346s131.65x
💻 LocalNext.js (Turbopack)2.054s2.508s0.455s121.66x
🌐 RedisNext.js (Turbopack)2.354s3.008s0.654s101.90x
🌐 MongoDBNext.js (Turbopack)3.576s4.009s0.433s82.89x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro54.294s (+1579.3% 🔺)56.491s (+1012.7% 🔺)2.197s61.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.379s (-60.6% 🟢)2.008s (-49.9% 🟢)0.629s151.00x
🐘 PostgresNitro1.414s (-59.4% 🟢)2.008s (-49.9% 🟢)0.594s151.03x
🐘 PostgresNext.js (Turbopack)2.116s2.606s0.490s121.53x
🌐 RedisNext.js (Turbopack)3.586s4.010s0.424s82.60x
💻 LocalNitro5.772s (-36.9% 🟢)6.214s (-38.0% 🟢)0.441s54.18x
💻 LocalExpress5.890s (-33.1% 🟢)6.214s (-33.0% 🟢)0.324s54.27x
💻 LocalNext.js (Turbopack)6.126s6.618s0.492s54.44x
🌐 MongoDBNext.js (Turbopack)6.338s7.011s0.673s54.60x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.693s (+31.4% 🔺)9.252s (+35.7% 🔺)2.560s41.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.460s (-45.2% 🟢)1.024s (~)0.564s591.00x
💻 LocalNitro0.484s (-50.7% 🟢)1.021s (-6.6% 🟢)0.538s591.05x
🐘 PostgresNitro0.491s (-40.1% 🟢)1.024s (+1.8%)0.533s591.07x
💻 LocalExpress0.500s (-49.2% 🟢)1.004s (-6.7% 🟢)0.505s601.09x
🌐 RedisNext.js (Turbopack)0.619s1.004s0.386s601.35x
🌐 MongoDBNext.js (Turbopack)0.724s1.005s0.281s601.57x
💻 LocalNext.js (Turbopack)0.748s1.005s0.256s601.63x
🐘 PostgresNext.js (Turbopack)0.860s1.301s0.441s471.87x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.067s (-77.0% 🟢)6.854s (-71.5% 🟢)1.787s91.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.017s (-48.6% 🟢)1.586s (-29.8% 🟢)0.569s571.00x
🐘 PostgresNitro1.143s (-40.7% 🟢)1.986s (-5.4% 🟢)0.843s461.12x
💻 LocalExpress1.210s (-59.9% 🟢)2.005s (-44.1% 🟢)0.795s451.19x
💻 LocalNitro1.212s (-60.1% 🟢)2.006s (-46.6% 🟢)0.793s451.19x
🌐 RedisNext.js (Turbopack)1.513s2.006s0.493s451.49x
🌐 MongoDBNext.js (Turbopack)1.821s2.007s0.186s451.79x
💻 LocalNext.js (Turbopack)1.825s2.006s0.181s451.80x
🐘 PostgresNext.js (Turbopack)2.605s3.071s0.465s302.56x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro13.232s (-66.5% 🟢)15.905s (-61.5% 🟢)2.672s61.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express2.137s (-46.4% 🟢)2.604s (-40.4% 🟢)0.466s471.00x
🐘 PostgresNitro2.235s (-45.5% 🟢)3.034s (-34.1% 🟢)0.799s401.05x
💻 LocalExpress2.693s (-70.8% 🟢)3.008s (-70.0% 🟢)0.314s401.26x
💻 LocalNitro2.712s (-70.8% 🟢)3.057s (-69.5% 🟢)0.346s401.27x
🌐 RedisNext.js (Turbopack)2.959s3.135s0.176s391.38x
💻 LocalNext.js (Turbopack)3.941s4.366s0.426s281.84x
🌐 MongoDBNext.js (Turbopack)4.155s5.012s0.857s241.94x
🐘 PostgresNext.js (Turbopack)4.540s5.107s0.567s252.12x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro41.246s (-57.4% 🟢)43.425s (-55.9% 🟢)2.180s31.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.189s (-33.0% 🟢)1.005s (~)0.816s601.00x
🐘 PostgresNitro0.198s (-30.3% 🟢)1.006s (~)0.809s601.04x
🌐 RedisNext.js (Turbopack)0.242s1.004s0.763s601.28x
💻 LocalExpress0.473s (-15.7% 🟢)1.004s (~)0.532s602.50x
💻 LocalNitro0.539s (-10.9% 🟢)1.096s (+7.3% 🔺)0.557s552.85x
💻 LocalNext.js (Turbopack)0.592s1.022s0.429s593.13x
🐘 PostgresNext.js (Turbopack)0.679s1.259s0.580s483.59x
🌐 MongoDBNext.js (Turbopack)1.029s1.749s0.720s355.44x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.252s (+35.6% 🔺)4.327s (+29.1% 🔺)2.074s141.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.320s (-37.3% 🟢)1.006s (~)0.687s901.00x
🐘 PostgresNitro0.340s (-31.6% 🟢)1.007s (~)0.667s901.06x
🌐 RedisNext.js (Turbopack)0.408s1.004s0.596s901.28x
🐘 PostgresNext.js (Turbopack)0.895s1.442s0.547s632.80x
💻 LocalNitro2.133s (-16.0% 🟢)2.883s (-4.2%)0.750s326.67x
💻 LocalExpress2.304s (-8.3% 🟢)2.943s (-2.2%)0.639s317.21x
🌐 MongoDBNext.js (Turbopack)2.605s3.006s0.401s308.15x
💻 LocalNext.js (Turbopack)2.641s3.183s0.542s298.26x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro8.322s (+158.0% 🔺)10.481s (+117.4% 🔺)2.158s91.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.647s (-21.0% 🟢)1.006s (-1.1%)0.359s1201.00x
🐘 PostgresNitro0.698s (-11.6% 🟢)1.007s (~)0.308s1201.08x
🌐 RedisNext.js (Turbopack)0.762s1.004s0.242s1201.18x
🐘 PostgresNext.js (Turbopack)1.313s1.779s0.467s682.03x
🌐 MongoDBNext.js (Turbopack)5.386s6.013s0.627s208.33x
💻 LocalNitro10.093s (-9.8% 🟢)10.612s (-9.0% 🟢)0.519s1215.61x
💻 LocalExpress10.309s (-7.9% 🟢)10.864s (-9.0% 🟢)0.555s1215.94x
💻 LocalNext.js (Turbopack)11.645s12.330s0.684s1018.01x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro21.406s (+177.2% 🔺)23.498s (+150.0% 🔺)2.092s61.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.134s (+452.9% 🔺)1.998s (+100.1% 🔺)0.002s (-6.3% 🟢)2.010s (+98.7% 🔺)0.876s101.00x
💻 LocalExpress1.134s (+469.8% 🔺)2.005s (+99.6% 🔺)0.012s (+0.8%)2.020s (+98.4% 🔺)0.885s101.00x
💻 LocalNitro1.136s (+431.6% 🔺)2.005s (+99.6% 🔺)0.012s (-1.6%)2.020s (+98.2% 🔺)0.883s101.00x
🐘 PostgresNitro1.154s (+462.9% 🔺)2.002s (+100.2% 🔺)0.002s (~)2.013s (+99.0% 🔺)0.859s101.02x
💻 LocalNext.js (Turbopack)1.198s2.003s0.013s2.020s0.823s101.06x
🐘 PostgresNext.js (Turbopack)1.533s1.999s0.410s2.441s0.908s101.35x
🌐 MongoDBNext.js (Turbopack)⚠️missing-----
🌐 RedisNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.173s (-43.3% 🟢)3.567s (-32.4% 🟢)1.733s (+133.5% 🔺)5.800s (-10.5% 🟢)3.628s101.00x
▲ VercelExpress⚠️missing-----
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.509s (+139.5% 🔺)2.003s (+99.1% 🔺)0.004s (-2.5%)2.024s (+97.9% 🔺)0.515s301.00x
💻 LocalNitro1.528s (+82.1% 🔺)2.011s (+98.7% 🔺)0.009s (-5.7% 🟢)2.022s (+81.2% 🔺)0.494s301.01x
💻 LocalExpress1.530s (+102.1% 🔺)2.011s (+95.5% 🔺)0.010s (+4.5%)2.023s (+94.6% 🔺)0.493s301.01x
🐘 PostgresNitro1.576s (+152.5% 🔺)2.005s (+99.2% 🔺)0.004s (-3.3%)2.027s (+98.3% 🔺)0.451s301.04x
💻 LocalNext.js (Turbopack)1.698s2.010s0.011s2.024s0.327s301.13x
🐘 PostgresNext.js (Turbopack)2.628s3.008s0.004s3.036s0.408s201.74x
🌐 MongoDBNext.js (Turbopack)⚠️missing-----
🌐 RedisNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.770s (-80.4% 🟢)7.572s (-75.4% 🟢)0.449s (+300.9% 🔺)8.603s (-72.9% 🟢)2.833s71.00x
▲ VercelExpress⚠️missing-----
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.668s (-30.5% 🟢)1.049s (-17.9% 🟢)0.000s (-59.6% 🟢)1.060s (-18.9% 🟢)0.392s571.00x
🐘 PostgresNitro0.716s (-26.0% 🟢)1.050s (-15.8% 🟢)0.000s (-57.9% 🟢)1.068s (-15.1% 🟢)0.351s571.07x
💻 LocalExpress1.350s (+10.2% 🔺)2.015s (~)0.001s (+70.0% 🔺)2.017s (~)0.667s302.02x
💻 LocalNitro1.358s (+11.1% 🔺)2.015s (~)0.000s (+133.3% 🔺)2.017s (~)0.659s302.03x
🐘 PostgresNext.js (Turbopack)1.370s1.659s0.000s1.724s0.354s362.05x
💻 LocalNext.js (Turbopack)1.498s2.014s0.000s2.017s0.519s302.24x
🌐 MongoDBNext.js (Turbopack)⚠️missing-----
🌐 RedisNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.519s (+15.4% 🔺)5.014s (+14.1% 🔺)0.000s (-100.0% 🟢)5.521s (+14.8% 🔺)2.002s111.00x
▲ VercelExpress⚠️missing-----
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro

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

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.332s (-24.8% 🟢)1.998s (-8.3% 🟢)0.000s (+Infinity% 🔺)2.018s (-8.2% 🟢)0.686s301.00x
🐘 PostgresNitro1.458s (-18.6% 🟢)2.100s (-1.9%)0.000s (-3.4%)2.115s (-2.7%)0.656s291.09x
💻 LocalNext.js (Turbopack)2.949s3.357s0.000s3.367s0.418s182.21x
💻 LocalNitro3.144s (-7.2% 🟢)4.029s (~)0.000s (-25.0% 🟢)4.032s (~)0.888s152.36x
💻 LocalExpress3.177s (-8.4% 🟢)3.967s (-1.7%)0.000s (-84.4% 🟢)3.969s (-1.7%)0.792s162.39x
🐘 PostgresNext.js (Turbopack)3.436s3.946s0.000s4.008s0.572s152.58x
🌐 MongoDBNext.js (Turbopack)⚠️missing-----
🌐 RedisNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.077s (+48.4% 🔺)7.737s (+44.0% 🔺)0.000s (-54.2% 🟢)8.390s (+44.8% 🔺)2.313s81.00x
▲ VercelExpress⚠️missing-----
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress10/21
🐘 PostgresExpress21/21
▲ VercelNitro21/21
Fastest World by Framework

Winner determined by most benchmark wins

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

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)

📋 View full workflow run


Some benchmark jobs failed:

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

Check the workflow run for details.

@github-actions

github-actionsBot commented May 5, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production92502191144
✅ 💻 Local Development123702191456
✅ 📦 Local Production123702191456
✅ 🐘 Local Postgres123702191456
✅ 🪟 Windows10400104
✅ 📋 Other5520176728
Total5292010526344

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro78026
✅ example78026
✅ express78026
✅ fastify78026
✅ hono78026
✅ nextjs-turbopack10202
✅ nextjs-webpack10202
✅ nitro78026
✅ nuxt78026
✅ sveltekit9707
✅ vite78026
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack10400
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable79025
✅ e2e-local-dev-tanstack-start-79025
✅ e2e-local-postgres-nest-stable79025
✅ e2e-local-postgres-tanstack-start-79025
✅ e2e-local-prod-nest-stable79025
✅ e2e-local-prod-tanstack-start-79025
✅ e2e-vercel-prod-tanstack-start78026

📋 View full workflow run

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

Updates @workflow/swc-plugin to correctly preserve lexical this for nested arrow "use step" functions (common in “method returns object literal with arrow steps” patterns), by detecting this usage, binding step proxies in workflow mode, and hoisting step bodies as regular functions in step mode so runtime apply(thisVal, args) rebinding works.

Changes:

  • Add lexical-this detection for nested arrow step bodies and thread a references_lexical_this flag through nested-step tracking.
  • Emit .bind(this) on workflow-mode step proxies when lexical this is referenced; adjust step-mode hoisting to emit function instead of arrow for those steps.
  • Add SWC fixture coverage + a core runtime test; document the new behavior and ship a changeset.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
packages/swc-plugin-workflow/transform/src/lib.rsImplements lexical-this detection, proxy binding, and step-mode hoist changes.
packages/swc-plugin-workflow/spec.mdDocuments lexical this capture behavior and updates notes about this in steps.
packages/core/src/step.test.tsAdds runtime test ensuring .bind(this) captures thisVal onto the invocation queue item.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this/input.jsNew fixture input covering nested arrow step referencing this in a method.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this/output-workflow.jsExpected workflow-mode output showing .bind(this) on the step proxy.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this/output-step.jsExpected step-mode output showing hoisting as async function using this.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this-var-decl/input.jsNew fixture input for var-declarator arrow step capturing this.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this-var-decl/output-workflow.jsExpected workflow-mode output with bound proxy for var-declarator case.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this-var-decl/output-step.jsExpected step-mode output hoisting as function for var-declarator case.
.changeset/swc-lexical-this-capture.mdPatch changeset describing the new lexical-this behavior.

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

Comment threadpackages/swc-plugin-workflow/transform/src/lib.rs
Comment threadpackages/swc-plugin-workflow/transform/src/lib.rs
Comment threadpackages/swc-plugin-workflow/transform/src/lib.rs
Comment threadpackages/swc-plugin-workflow/spec.md Outdated
Comment threadpackages/core/src/step.test.ts Outdated
…ction
- core: Override `.bind` on step proxies so the bound function retains
`stepId` and `__closureVarsFn`. Without this, a bound proxy that flows
through workflow serialization (e.g. as a step argument) would be
treated as a non-serializable plain function by `getStepFunctionReducer`.
- swc-plugin: Detector now also walks `arrow.params` so `this` references
in default values / destructuring initializers (e.g. `(x = this.foo) =>
...`) trigger the `.bind(this)` path.
- swc-plugin: Class bodies inside the arrow body are now treated as
`this`-binding boundaries — `this` inside class field initializers,
methods, etc. is bound to the class instance, not the outer arrow. The
detector still walks `extends` clauses and computed property keys
because those are evaluated in the surrounding scope.
- spec.md: Sharpen the note about `this` in step bodies — it's
syntactically allowed but only meaningful for instance-method steps and
lexical-`this` arrow steps; other shapes compile but `this` will be
whatever the caller of the step proxy passes.
- Add `lexical-this-detector-edge-cases` fixture covering both the
default-param positive case and the inner-class false-positive guard.
- Strengthen the runtime test to assert `stepId` / `__closureVarsFn`
survive `.bind(...)`.
… coverage
Without this, a `useStep(...).bind(thisArg)` proxy that flows through
workflow serialization (e.g. passed as a step argument) would lose its
receiver: the reducer captured `stepId` but not the bound `this`, and
the step-bundle reviver returned the raw registered step body which
ignores any `this` the caller passes.
Now:
- step.ts `.bind` override stashes the bound value on the result as
`__boundThis` so the reducer can see it.
- The reducer serializes `boundThis` (using property presence so
`bind(null)`/`bind(undefined)` round-trip faithfully).
- The workflow-bundle reviver re-binds the freshly created proxy.
- The step-bundle reviver wraps the registered body so it's invoked
with `apply(boundThis, args)` (and still runs inside the closure-vars
AsyncLocalStorage frame when `closureVars` is present).
E2E coverage extends `instanceMethodStepWorkflow` with two new shapes:
- `counter.makeAdder(7).add(2)` — direct invocation of a lexical-`this`
arrow step. Verifies `bind(this)` carries `thisVal` to the queue and
the step body sees `this.value` correctly.
- `invokeAdderFromStep(adder.add, 3)` — passes the bound proxy as a
step argument so the round-trip path exercises both the reducer and
the step-bundle reviver, with the inner call running inline. Without
the new `boundThis` plumbing this previously failed with `Cannot read
properties of undefined (reading 'value')`.

@karthikscale3karthikscale3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AI review: Solid implementation — the LexicalThisDetector, the .bind() override, and the serialization round-trip all hang together correctly. No regressions found. The arguments fix is a genuine bug fix and the dead this/arguments error-check removal is safe. All completed CI checks pass. LGTM

Comment threadpackages/core/src/step.ts
Comment threadpackages/core/src/serialization/serialization.test.ts
…+boundThis
- step.ts `.bind` override now also stashes `__boundArgs` when the caller
supplied prefilled args (`useStep(...).bind(thisArg, x, y)`). The
reducer serializes them as `boundArgs`, and both the workflow- and
step-bundle revivers re-apply them (the workflow reviver via
`bind(boundThis, ...boundArgs)`, the step-bundle reviver by
prepending to the runtime args). The SWC plugin only ever emits
`.bind(this)` today, but this keeps partial application faithful in
case hand-written code ever calls `.bind` with extra args.
- Add two new unit tests in `serialization.test.ts`:
- `closureVars + boundThis` combo: exercises the step-bundle reviver's
inner branch (`contextStorage.run(newContext, () =>
stepFn.apply(callThis, callArgs))`) in isolation — previously only
covered end-to-end by the `instanceMethodStepWorkflow` e2e test.
- `boundArgs` round-trip: codifies that prefilled args survive
serialization.
@github-actions

Copy link
Copy Markdown
Contributor

Backport PR opened against stable: #1945. Merge conflicts were resolved by AI — please review carefully.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-stableCherry-pick this PR to the stable branch when merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

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

[swc-plugin] Capture lexical this for nested arrow step functions - #1935

Merged
TooTallNate merged 6 commits into
mainfrom
feat/swc-lexical-this-capture
May 5, 2026
Merged

[swc-plugin] Capture lexical this for nested arrow step functions#1935
TooTallNate merged 6 commits into
mainfrom
feat/swc-lexical-this-capture

Conversation

@TooTallNate

Copy link
Copy Markdown
Member

Summary

Teaches the SWC plugin to handle "use step" arrow functions whose body lexically captures this from an enclosing method/function — a pattern that comes up naturally with the AI SDK tool({...}) factory and other "method that returns an object literal containing arrow steps" shapes.

Before this PR:

classReadFileTool{// serializable...createTool(context){returntool({execute: async(input)=>{'use step';returnthis.service.readFileContent(input,context);// `this` was lost},});}}

The compiler would hoist execute to module scope as an arrow with a bare return this.service…, so at runtime this was undefined. Workflow mode produced a step proxy with no this plumbing.

After this PR:

  • Workflow mode wraps the proxy with .bind(this) so invoking it captures the caller's this as thisVal on the queue item.
  • Step mode hoists the body as a regular function (not an arrow) so the runtime's existing stepFn.apply(thisVal, args) rebinds this inside the hoisted body.

The runtime already had thisVal plumbing for instance-method steps; this PR is purely a compiler change feeding that existing pipeline.

How

  • New LexicalThisDetector (Visit) walks an arrow body looking for ThisExpr. Recurses through nested arrows but stops at Function/Constructor/MethodProp/GetterProp/SetterProp/ClassMethod/PrivateMethod/StaticBlock because those introduce their own this.
  • nested_step_functions gains a references_lexical_this: bool field, threaded through every push site.
  • Two new helpers: wrap_with_bind_this(expr) and create_step_proxy_reference_maybe_bound(...).
  • Step-mode hoister checks was_arrow && !references_lexical_this to decide arrow vs. function emission.

Caveat

Capture only works at runtime if the captured this is serializable across the workflow→step boundary — i.e. the enclosing class implements WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE. Same precondition as the existing instance-method-step support. NestJS @Injectable() instances still need a separate provider rehydration path (the third option from the original issue).

Output examples

Workflow mode:

execute: globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//./input//_anonymousStep0",()=>({ context })).bind(this)

Step mode (hoisted as function, not arrow):

asyncfunction_anonymousStep0(input){const{ context }=(function(){/* closure-var IIFE */})();returnthis.service.readFileContent(input,context);}

Tests

  • 4 new SWC fixture tests (nested-arrow-step-lexical-this, nested-arrow-step-lexical-this-var-decl, both modes). Total fixture tests: 114 → 118.
  • New step.test.ts runtime test exercising useStep(stepId).bind(instance) and asserting thisVal: instance on the queue item.
  • All 843 @workflow/core tests pass.
  • pnpm typecheck clean; workbench/example builds against the rebuilt wasm.

Refs

#1865 (specifically the linked comment)

When a nested arrow `"use step"` references the enclosing function/method's
`this`, plumb that `this` through the workflow runtime so the step body
sees the correct receiver.
- Workflow mode wraps the step proxy with `.bind(this)`, so invoking the
proxy captures the caller's `this` as `thisVal` on the queue item.
- Step mode hoists the body as a regular `function` (not an arrow) so the
runtime's `stepFn.apply(thisVal, args)` rebinds `this` inside the
hoisted body.
Detection only fires for arrows, since arrows inherit `this` lexically.
Nested non-arrow functions/methods/getters/setters introduce their own
`this`, so the detector stops at those boundaries.
The runtime already supported `thisVal` for instance-method steps; this
PR is purely a compiler change to feed the existing pipeline.
Caveat: capture works at runtime only when the captured value is
serializable across the workflow->step boundary (i.e. the enclosing
class implements `WORKFLOW_SERIALIZE`/`WORKFLOW_DESERIALIZE`).
Refs #1865
CopilotAI review requested due to automatic review settings May 5, 2026 05:51
@changeset-bot

changeset-botBot commented May 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a19805f

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

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

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 May 5, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented May 5, 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.031s (-29.6% 🟢)1.006s (~)0.974s101.00x
💻 LocalNitro0.031s (-27.6% 🟢)1.005s (~)0.974s101.00x
🐘 PostgresExpress0.046s (-21.4% 🟢)1.012s (~)0.966s101.46x
💻 LocalNext.js (Turbopack)0.050s1.006s0.956s101.60x
🌐 RedisNext.js (Turbopack)0.052s1.005s0.953s101.67x
🐘 PostgresNitro0.053s (-44.3% 🟢)1.013s (-2.9%)0.960s101.70x
🌐 MongoDBNext.js (Turbopack)0.100s1.008s0.908s103.20x
🐘 PostgresNext.js (Turbopack)0.177s1.043s0.866s105.68x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro0.242s (-40.8% 🟢)2.094s (-16.5% 🟢)1.852s101.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.069s (-5.0% 🟢)2.007s (~)0.938s101.00x
💻 LocalNitro1.073s (-5.1% 🟢)2.006s (~)0.932s101.00x
🐘 PostgresExpress1.084s (-5.5% 🟢)2.010s (~)0.926s101.01x
🐘 PostgresNitro1.084s (-4.9%)2.010s (~)0.926s101.01x
🌐 RedisNext.js (Turbopack)1.111s2.007s0.895s101.04x
💻 LocalNext.js (Turbopack)1.113s2.007s0.894s101.04x
🌐 MongoDBNext.js (Turbopack)1.169s2.009s0.840s101.09x
🐘 PostgresNext.js (Turbopack)1.229s2.048s0.819s101.15x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.643s (-57.8% 🟢)3.930s (-33.5% 🟢)2.287s101.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro10.404s (-4.9%)11.021s (~)0.617s31.00x
💻 LocalExpress10.413s (-4.7%)11.022s (~)0.610s31.00x
🐘 PostgresExpress10.426s (-4.9%)11.018s (~)0.592s31.00x
🐘 PostgresNitro10.482s (-3.6%)11.020s (~)0.538s31.01x
🌐 RedisNext.js (Turbopack)10.642s11.023s0.381s31.02x
💻 LocalNext.js (Turbopack)10.680s11.024s0.344s31.03x
🌐 MongoDBNext.js (Turbopack)10.784s11.021s0.238s31.04x
🐘 PostgresNext.js (Turbopack)11.142s11.680s0.538s31.07x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro13.533s (-43.0% 🟢)15.534s (-38.2% 🟢)2.001s21.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express13.449s (-10.2% 🟢)14.027s (-6.7% 🟢)0.578s51.00x
🐘 PostgresExpress13.453s (-7.8% 🟢)14.018s (-6.7% 🟢)0.565s51.00x
💻 LocalNitro13.485s (-10.5% 🟢)14.026s (-12.5% 🟢)0.541s51.00x
🐘 PostgresNitro13.521s (-7.4% 🟢)14.023s (-6.7% 🟢)0.502s51.01x
🌐 RedisNext.js (Turbopack)14.030s14.428s0.398s51.04x
💻 LocalNext.js (Turbopack)14.125s15.030s0.905s41.05x
🌐 MongoDBNext.js (Turbopack)14.212s15.023s0.811s41.06x
🐘 PostgresNext.js (Turbopack)15.396s16.019s0.624s41.14x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro22.032s (-65.8% 🟢)24.542s (-63.2% 🟢)2.510s31.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro11.936s (-28.9% 🟢)12.147s (-28.7% 🟢)0.211s81.00x
🐘 PostgresExpress11.971s (-14.5% 🟢)12.516s (-14.2% 🟢)0.545s81.00x
💻 LocalExpress11.979s (-27.8% 🟢)12.273s (-27.9% 🟢)0.294s81.00x
🐘 PostgresNitro12.038s (-13.8% 🟢)12.766s (-10.8% 🟢)0.728s81.01x
🌐 RedisNext.js (Turbopack)13.002s13.311s0.309s71.09x
🌐 MongoDBNext.js (Turbopack)13.250s14.020s0.770s71.11x
💻 LocalNext.js (Turbopack)13.313s14.026s0.713s71.12x
🐘 PostgresNext.js (Turbopack)16.668s17.052s0.385s61.40x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro33.281s (-92.1% 🟢)35.586s (-91.6% 🟢)2.305s31.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.148s (-8.9% 🟢)2.008s (~)0.860s151.00x
🐘 PostgresNitro1.164s (-8.7% 🟢)2.007s (~)0.843s151.01x
💻 LocalExpress1.184s (-20.5% 🟢)2.006s (~)0.822s151.03x
💻 LocalNitro1.185s (-27.4% 🟢)2.006s (-3.3%)0.821s151.03x
🌐 RedisNext.js (Turbopack)1.249s2.006s0.757s151.09x
💻 LocalNext.js (Turbopack)1.307s2.006s0.699s151.14x
🐘 PostgresNext.js (Turbopack)1.785s2.202s0.417s141.55x
🌐 MongoDBNext.js (Turbopack)2.029s2.827s0.798s111.77x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.530s (-10.2% 🟢)4.390s (+1.6%)1.860s71.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.231s (-47.8% 🟢)2.009s (-33.2% 🟢)0.778s151.00x
🐘 PostgresNitro1.247s (-47.0% 🟢)2.008s (-33.3% 🟢)0.761s151.01x
🐘 PostgresNext.js (Turbopack)1.696s2.200s0.504s141.38x
💻 LocalNitro1.796s (-42.9% 🟢)2.072s (-46.7% 🟢)0.276s151.46x
💻 LocalExpress1.798s (-39.1% 🟢)2.073s (-40.0% 🟢)0.275s151.46x
💻 LocalNext.js (Turbopack)1.864s2.221s0.357s141.51x
🌐 RedisNext.js (Turbopack)2.370s3.008s0.637s101.92x
🌐 MongoDBNext.js (Turbopack)3.591s4.010s0.419s82.92x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.386s (-16.4% 🟢)5.511s (-6.9% 🟢)2.125s61.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.373s (-60.6% 🟢)2.007s (-49.9% 🟢)0.634s151.00x
🐘 PostgresNitro1.444s (-58.5% 🟢)2.008s (-49.9% 🟢)0.565s151.05x
🐘 PostgresNext.js (Turbopack)2.200s3.032s0.832s101.60x
🌐 RedisNext.js (Turbopack)3.616s4.010s0.394s82.63x
💻 LocalExpress5.169s (-38.0% 🟢)5.849s (-35.2% 🟢)0.680s63.76x
💻 LocalNitro5.416s (-35.1% 🟢)6.016s (-33.3% 🟢)0.600s53.94x
💻 LocalNext.js (Turbopack)5.970s6.414s0.444s54.35x
🌐 MongoDBNext.js (Turbopack)6.338s7.014s0.676s54.62x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro7.725s (+119.1% 🔺)10.152s (+83.5% 🔺)2.427s31.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.152s (-8.4% 🟢)2.007s (~)0.856s151.00x
🐘 PostgresNitro1.174s (-6.6% 🟢)2.009s (~)0.835s151.02x
🌐 RedisNext.js (Turbopack)1.229s2.006s0.777s151.07x
💻 LocalNitro1.325s (-29.0% 🟢)2.006s (-14.3% 🟢)0.681s151.15x
💻 LocalNext.js (Turbopack)1.353s2.007s0.654s151.17x
💻 LocalExpress1.413s (-25.4% 🟢)2.007s (-15.1% 🟢)0.594s151.23x
🐘 PostgresNext.js (Turbopack)1.630s2.184s0.554s141.42x
🌐 MongoDBNext.js (Turbopack)2.057s3.008s0.952s101.79x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.585s (+5.1% 🔺)4.490s (+7.7% 🔺)1.905s71.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.239s (-47.1% 🟢)2.008s (-33.3% 🟢)0.768s151.00x
🐘 PostgresNitro1.273s (-45.6% 🟢)2.009s (-33.3% 🟢)0.737s151.03x
🐘 PostgresNext.js (Turbopack)1.770s2.594s0.824s121.43x
💻 LocalNitro1.982s (-35.3% 🟢)2.393s (-38.4% 🟢)0.411s131.60x
💻 LocalExpress2.047s (-34.6% 🟢)2.394s (-36.4% 🟢)0.346s131.65x
💻 LocalNext.js (Turbopack)2.054s2.508s0.455s121.66x
🌐 RedisNext.js (Turbopack)2.354s3.008s0.654s101.90x
🌐 MongoDBNext.js (Turbopack)3.576s4.009s0.433s82.89x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro54.294s (+1579.3% 🔺)56.491s (+1012.7% 🔺)2.197s61.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.379s (-60.6% 🟢)2.008s (-49.9% 🟢)0.629s151.00x
🐘 PostgresNitro1.414s (-59.4% 🟢)2.008s (-49.9% 🟢)0.594s151.03x
🐘 PostgresNext.js (Turbopack)2.116s2.606s0.490s121.53x
🌐 RedisNext.js (Turbopack)3.586s4.010s0.424s82.60x
💻 LocalNitro5.772s (-36.9% 🟢)6.214s (-38.0% 🟢)0.441s54.18x
💻 LocalExpress5.890s (-33.1% 🟢)6.214s (-33.0% 🟢)0.324s54.27x
💻 LocalNext.js (Turbopack)6.126s6.618s0.492s54.44x
🌐 MongoDBNext.js (Turbopack)6.338s7.011s0.673s54.60x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.693s (+31.4% 🔺)9.252s (+35.7% 🔺)2.560s41.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.460s (-45.2% 🟢)1.024s (~)0.564s591.00x
💻 LocalNitro0.484s (-50.7% 🟢)1.021s (-6.6% 🟢)0.538s591.05x
🐘 PostgresNitro0.491s (-40.1% 🟢)1.024s (+1.8%)0.533s591.07x
💻 LocalExpress0.500s (-49.2% 🟢)1.004s (-6.7% 🟢)0.505s601.09x
🌐 RedisNext.js (Turbopack)0.619s1.004s0.386s601.35x
🌐 MongoDBNext.js (Turbopack)0.724s1.005s0.281s601.57x
💻 LocalNext.js (Turbopack)0.748s1.005s0.256s601.63x
🐘 PostgresNext.js (Turbopack)0.860s1.301s0.441s471.87x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.067s (-77.0% 🟢)6.854s (-71.5% 🟢)1.787s91.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.017s (-48.6% 🟢)1.586s (-29.8% 🟢)0.569s571.00x
🐘 PostgresNitro1.143s (-40.7% 🟢)1.986s (-5.4% 🟢)0.843s461.12x
💻 LocalExpress1.210s (-59.9% 🟢)2.005s (-44.1% 🟢)0.795s451.19x
💻 LocalNitro1.212s (-60.1% 🟢)2.006s (-46.6% 🟢)0.793s451.19x
🌐 RedisNext.js (Turbopack)1.513s2.006s0.493s451.49x
🌐 MongoDBNext.js (Turbopack)1.821s2.007s0.186s451.79x
💻 LocalNext.js (Turbopack)1.825s2.006s0.181s451.80x
🐘 PostgresNext.js (Turbopack)2.605s3.071s0.465s302.56x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro13.232s (-66.5% 🟢)15.905s (-61.5% 🟢)2.672s61.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express2.137s (-46.4% 🟢)2.604s (-40.4% 🟢)0.466s471.00x
🐘 PostgresNitro2.235s (-45.5% 🟢)3.034s (-34.1% 🟢)0.799s401.05x
💻 LocalExpress2.693s (-70.8% 🟢)3.008s (-70.0% 🟢)0.314s401.26x
💻 LocalNitro2.712s (-70.8% 🟢)3.057s (-69.5% 🟢)0.346s401.27x
🌐 RedisNext.js (Turbopack)2.959s3.135s0.176s391.38x
💻 LocalNext.js (Turbopack)3.941s4.366s0.426s281.84x
🌐 MongoDBNext.js (Turbopack)4.155s5.012s0.857s241.94x
🐘 PostgresNext.js (Turbopack)4.540s5.107s0.567s252.12x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro41.246s (-57.4% 🟢)43.425s (-55.9% 🟢)2.180s31.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.189s (-33.0% 🟢)1.005s (~)0.816s601.00x
🐘 PostgresNitro0.198s (-30.3% 🟢)1.006s (~)0.809s601.04x
🌐 RedisNext.js (Turbopack)0.242s1.004s0.763s601.28x
💻 LocalExpress0.473s (-15.7% 🟢)1.004s (~)0.532s602.50x
💻 LocalNitro0.539s (-10.9% 🟢)1.096s (+7.3% 🔺)0.557s552.85x
💻 LocalNext.js (Turbopack)0.592s1.022s0.429s593.13x
🐘 PostgresNext.js (Turbopack)0.679s1.259s0.580s483.59x
🌐 MongoDBNext.js (Turbopack)1.029s1.749s0.720s355.44x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.252s (+35.6% 🔺)4.327s (+29.1% 🔺)2.074s141.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.320s (-37.3% 🟢)1.006s (~)0.687s901.00x
🐘 PostgresNitro0.340s (-31.6% 🟢)1.007s (~)0.667s901.06x
🌐 RedisNext.js (Turbopack)0.408s1.004s0.596s901.28x
🐘 PostgresNext.js (Turbopack)0.895s1.442s0.547s632.80x
💻 LocalNitro2.133s (-16.0% 🟢)2.883s (-4.2%)0.750s326.67x
💻 LocalExpress2.304s (-8.3% 🟢)2.943s (-2.2%)0.639s317.21x
🌐 MongoDBNext.js (Turbopack)2.605s3.006s0.401s308.15x
💻 LocalNext.js (Turbopack)2.641s3.183s0.542s298.26x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro8.322s (+158.0% 🔺)10.481s (+117.4% 🔺)2.158s91.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.647s (-21.0% 🟢)1.006s (-1.1%)0.359s1201.00x
🐘 PostgresNitro0.698s (-11.6% 🟢)1.007s (~)0.308s1201.08x
🌐 RedisNext.js (Turbopack)0.762s1.004s0.242s1201.18x
🐘 PostgresNext.js (Turbopack)1.313s1.779s0.467s682.03x
🌐 MongoDBNext.js (Turbopack)5.386s6.013s0.627s208.33x
💻 LocalNitro10.093s (-9.8% 🟢)10.612s (-9.0% 🟢)0.519s1215.61x
💻 LocalExpress10.309s (-7.9% 🟢)10.864s (-9.0% 🟢)0.555s1215.94x
💻 LocalNext.js (Turbopack)11.645s12.330s0.684s1018.01x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro21.406s (+177.2% 🔺)23.498s (+150.0% 🔺)2.092s61.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.134s (+452.9% 🔺)1.998s (+100.1% 🔺)0.002s (-6.3% 🟢)2.010s (+98.7% 🔺)0.876s101.00x
💻 LocalExpress1.134s (+469.8% 🔺)2.005s (+99.6% 🔺)0.012s (+0.8%)2.020s (+98.4% 🔺)0.885s101.00x
💻 LocalNitro1.136s (+431.6% 🔺)2.005s (+99.6% 🔺)0.012s (-1.6%)2.020s (+98.2% 🔺)0.883s101.00x
🐘 PostgresNitro1.154s (+462.9% 🔺)2.002s (+100.2% 🔺)0.002s (~)2.013s (+99.0% 🔺)0.859s101.02x
💻 LocalNext.js (Turbopack)1.198s2.003s0.013s2.020s0.823s101.06x
🐘 PostgresNext.js (Turbopack)1.533s1.999s0.410s2.441s0.908s101.35x
🌐 MongoDBNext.js (Turbopack)⚠️missing-----
🌐 RedisNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.173s (-43.3% 🟢)3.567s (-32.4% 🟢)1.733s (+133.5% 🔺)5.800s (-10.5% 🟢)3.628s101.00x
▲ VercelExpress⚠️missing-----
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.509s (+139.5% 🔺)2.003s (+99.1% 🔺)0.004s (-2.5%)2.024s (+97.9% 🔺)0.515s301.00x
💻 LocalNitro1.528s (+82.1% 🔺)2.011s (+98.7% 🔺)0.009s (-5.7% 🟢)2.022s (+81.2% 🔺)0.494s301.01x
💻 LocalExpress1.530s (+102.1% 🔺)2.011s (+95.5% 🔺)0.010s (+4.5%)2.023s (+94.6% 🔺)0.493s301.01x
🐘 PostgresNitro1.576s (+152.5% 🔺)2.005s (+99.2% 🔺)0.004s (-3.3%)2.027s (+98.3% 🔺)0.451s301.04x
💻 LocalNext.js (Turbopack)1.698s2.010s0.011s2.024s0.327s301.13x
🐘 PostgresNext.js (Turbopack)2.628s3.008s0.004s3.036s0.408s201.74x
🌐 MongoDBNext.js (Turbopack)⚠️missing-----
🌐 RedisNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.770s (-80.4% 🟢)7.572s (-75.4% 🟢)0.449s (+300.9% 🔺)8.603s (-72.9% 🟢)2.833s71.00x
▲ VercelExpress⚠️missing-----
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.668s (-30.5% 🟢)1.049s (-17.9% 🟢)0.000s (-59.6% 🟢)1.060s (-18.9% 🟢)0.392s571.00x
🐘 PostgresNitro0.716s (-26.0% 🟢)1.050s (-15.8% 🟢)0.000s (-57.9% 🟢)1.068s (-15.1% 🟢)0.351s571.07x
💻 LocalExpress1.350s (+10.2% 🔺)2.015s (~)0.001s (+70.0% 🔺)2.017s (~)0.667s302.02x
💻 LocalNitro1.358s (+11.1% 🔺)2.015s (~)0.000s (+133.3% 🔺)2.017s (~)0.659s302.03x
🐘 PostgresNext.js (Turbopack)1.370s1.659s0.000s1.724s0.354s362.05x
💻 LocalNext.js (Turbopack)1.498s2.014s0.000s2.017s0.519s302.24x
🌐 MongoDBNext.js (Turbopack)⚠️missing-----
🌐 RedisNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.519s (+15.4% 🔺)5.014s (+14.1% 🔺)0.000s (-100.0% 🟢)5.521s (+14.8% 🔺)2.002s111.00x
▲ VercelExpress⚠️missing-----
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro

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

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.332s (-24.8% 🟢)1.998s (-8.3% 🟢)0.000s (+Infinity% 🔺)2.018s (-8.2% 🟢)0.686s301.00x
🐘 PostgresNitro1.458s (-18.6% 🟢)2.100s (-1.9%)0.000s (-3.4%)2.115s (-2.7%)0.656s291.09x
💻 LocalNext.js (Turbopack)2.949s3.357s0.000s3.367s0.418s182.21x
💻 LocalNitro3.144s (-7.2% 🟢)4.029s (~)0.000s (-25.0% 🟢)4.032s (~)0.888s152.36x
💻 LocalExpress3.177s (-8.4% 🟢)3.967s (-1.7%)0.000s (-84.4% 🟢)3.969s (-1.7%)0.792s162.39x
🐘 PostgresNext.js (Turbopack)3.436s3.946s0.000s4.008s0.572s152.58x
🌐 MongoDBNext.js (Turbopack)⚠️missing-----
🌐 RedisNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.077s (+48.4% 🔺)7.737s (+44.0% 🔺)0.000s (-54.2% 🟢)8.390s (+44.8% 🔺)2.313s81.00x
▲ VercelExpress⚠️missing-----
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress10/21
🐘 PostgresExpress21/21
▲ VercelNitro21/21
Fastest World by Framework

Winner determined by most benchmark wins

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

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)

📋 View full workflow run


Some benchmark jobs failed:

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

Check the workflow run for details.

@github-actions

github-actionsBot commented May 5, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production92502191144
✅ 💻 Local Development123702191456
✅ 📦 Local Production123702191456
✅ 🐘 Local Postgres123702191456
✅ 🪟 Windows10400104
✅ 📋 Other5520176728
Total5292010526344

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro78026
✅ example78026
✅ express78026
✅ fastify78026
✅ hono78026
✅ nextjs-turbopack10202
✅ nextjs-webpack10202
✅ nitro78026
✅ nuxt78026
✅ sveltekit9707
✅ vite78026
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack10400
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable79025
✅ e2e-local-dev-tanstack-start-79025
✅ e2e-local-postgres-nest-stable79025
✅ e2e-local-postgres-tanstack-start-79025
✅ e2e-local-prod-nest-stable79025
✅ e2e-local-prod-tanstack-start-79025
✅ e2e-vercel-prod-tanstack-start78026

📋 View full workflow run

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

Updates @workflow/swc-plugin to correctly preserve lexical this for nested arrow "use step" functions (common in “method returns object literal with arrow steps” patterns), by detecting this usage, binding step proxies in workflow mode, and hoisting step bodies as regular functions in step mode so runtime apply(thisVal, args) rebinding works.

Changes:

  • Add lexical-this detection for nested arrow step bodies and thread a references_lexical_this flag through nested-step tracking.
  • Emit .bind(this) on workflow-mode step proxies when lexical this is referenced; adjust step-mode hoisting to emit function instead of arrow for those steps.
  • Add SWC fixture coverage + a core runtime test; document the new behavior and ship a changeset.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
packages/swc-plugin-workflow/transform/src/lib.rsImplements lexical-this detection, proxy binding, and step-mode hoist changes.
packages/swc-plugin-workflow/spec.mdDocuments lexical this capture behavior and updates notes about this in steps.
packages/core/src/step.test.tsAdds runtime test ensuring .bind(this) captures thisVal onto the invocation queue item.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this/input.jsNew fixture input covering nested arrow step referencing this in a method.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this/output-workflow.jsExpected workflow-mode output showing .bind(this) on the step proxy.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this/output-step.jsExpected step-mode output showing hoisting as async function using this.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this-var-decl/input.jsNew fixture input for var-declarator arrow step capturing this.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this-var-decl/output-workflow.jsExpected workflow-mode output with bound proxy for var-declarator case.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this-var-decl/output-step.jsExpected step-mode output hoisting as function for var-declarator case.
.changeset/swc-lexical-this-capture.mdPatch changeset describing the new lexical-this behavior.

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

Comment threadpackages/swc-plugin-workflow/transform/src/lib.rs
Comment threadpackages/swc-plugin-workflow/transform/src/lib.rs
Comment threadpackages/swc-plugin-workflow/transform/src/lib.rs
Comment threadpackages/swc-plugin-workflow/spec.md Outdated
Comment threadpackages/core/src/step.test.ts Outdated
…ction
- core: Override `.bind` on step proxies so the bound function retains
`stepId` and `__closureVarsFn`. Without this, a bound proxy that flows
through workflow serialization (e.g. as a step argument) would be
treated as a non-serializable plain function by `getStepFunctionReducer`.
- swc-plugin: Detector now also walks `arrow.params` so `this` references
in default values / destructuring initializers (e.g. `(x = this.foo) =>
...`) trigger the `.bind(this)` path.
- swc-plugin: Class bodies inside the arrow body are now treated as
`this`-binding boundaries — `this` inside class field initializers,
methods, etc. is bound to the class instance, not the outer arrow. The
detector still walks `extends` clauses and computed property keys
because those are evaluated in the surrounding scope.
- spec.md: Sharpen the note about `this` in step bodies — it's
syntactically allowed but only meaningful for instance-method steps and
lexical-`this` arrow steps; other shapes compile but `this` will be
whatever the caller of the step proxy passes.
- Add `lexical-this-detector-edge-cases` fixture covering both the
default-param positive case and the inner-class false-positive guard.
- Strengthen the runtime test to assert `stepId` / `__closureVarsFn`
survive `.bind(...)`.
… coverage
Without this, a `useStep(...).bind(thisArg)` proxy that flows through
workflow serialization (e.g. passed as a step argument) would lose its
receiver: the reducer captured `stepId` but not the bound `this`, and
the step-bundle reviver returned the raw registered step body which
ignores any `this` the caller passes.
Now:
- step.ts `.bind` override stashes the bound value on the result as
`__boundThis` so the reducer can see it.
- The reducer serializes `boundThis` (using property presence so
`bind(null)`/`bind(undefined)` round-trip faithfully).
- The workflow-bundle reviver re-binds the freshly created proxy.
- The step-bundle reviver wraps the registered body so it's invoked
with `apply(boundThis, args)` (and still runs inside the closure-vars
AsyncLocalStorage frame when `closureVars` is present).
E2E coverage extends `instanceMethodStepWorkflow` with two new shapes:
- `counter.makeAdder(7).add(2)` — direct invocation of a lexical-`this`
arrow step. Verifies `bind(this)` carries `thisVal` to the queue and
the step body sees `this.value` correctly.
- `invokeAdderFromStep(adder.add, 3)` — passes the bound proxy as a
step argument so the round-trip path exercises both the reducer and
the step-bundle reviver, with the inner call running inline. Without
the new `boundThis` plumbing this previously failed with `Cannot read
properties of undefined (reading 'value')`.

@karthikscale3karthikscale3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AI review: Solid implementation — the LexicalThisDetector, the .bind() override, and the serialization round-trip all hang together correctly. No regressions found. The arguments fix is a genuine bug fix and the dead this/arguments error-check removal is safe. All completed CI checks pass. LGTM

Comment threadpackages/core/src/step.ts
Comment threadpackages/core/src/serialization/serialization.test.ts
…+boundThis
- step.ts `.bind` override now also stashes `__boundArgs` when the caller
supplied prefilled args (`useStep(...).bind(thisArg, x, y)`). The
reducer serializes them as `boundArgs`, and both the workflow- and
step-bundle revivers re-apply them (the workflow reviver via
`bind(boundThis, ...boundArgs)`, the step-bundle reviver by
prepending to the runtime args). The SWC plugin only ever emits
`.bind(this)` today, but this keeps partial application faithful in
case hand-written code ever calls `.bind` with extra args.
- Add two new unit tests in `serialization.test.ts`:
- `closureVars + boundThis` combo: exercises the step-bundle reviver's
inner branch (`contextStorage.run(newContext, () =>
stepFn.apply(callThis, callArgs))`) in isolation — previously only
covered end-to-end by the `instanceMethodStepWorkflow` e2e test.
- `boundArgs` round-trip: codifies that prefilled args survive
serialization.
@github-actions

Copy link
Copy Markdown
Contributor

Backport PR opened against stable: #1945. Merge conflicts were resolved by AI — please review carefully.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-stableCherry-pick this PR to the stable branch when merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

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

[swc-plugin] Capture lexical this for nested arrow step functions - #1935

Merged
TooTallNate merged 6 commits into
mainfrom
feat/swc-lexical-this-capture
May 5, 2026
Merged

[swc-plugin] Capture lexical this for nested arrow step functions#1935
TooTallNate merged 6 commits into
mainfrom
feat/swc-lexical-this-capture

Conversation

@TooTallNate

Copy link
Copy Markdown
Member

Summary

Teaches the SWC plugin to handle "use step" arrow functions whose body lexically captures this from an enclosing method/function — a pattern that comes up naturally with the AI SDK tool({...}) factory and other "method that returns an object literal containing arrow steps" shapes.

Before this PR:

classReadFileTool{// serializable...createTool(context){returntool({execute: async(input)=>{'use step';returnthis.service.readFileContent(input,context);// `this` was lost},});}}

The compiler would hoist execute to module scope as an arrow with a bare return this.service…, so at runtime this was undefined. Workflow mode produced a step proxy with no this plumbing.

After this PR:

  • Workflow mode wraps the proxy with .bind(this) so invoking it captures the caller's this as thisVal on the queue item.
  • Step mode hoists the body as a regular function (not an arrow) so the runtime's existing stepFn.apply(thisVal, args) rebinds this inside the hoisted body.

The runtime already had thisVal plumbing for instance-method steps; this PR is purely a compiler change feeding that existing pipeline.

How

  • New LexicalThisDetector (Visit) walks an arrow body looking for ThisExpr. Recurses through nested arrows but stops at Function/Constructor/MethodProp/GetterProp/SetterProp/ClassMethod/PrivateMethod/StaticBlock because those introduce their own this.
  • nested_step_functions gains a references_lexical_this: bool field, threaded through every push site.
  • Two new helpers: wrap_with_bind_this(expr) and create_step_proxy_reference_maybe_bound(...).
  • Step-mode hoister checks was_arrow && !references_lexical_this to decide arrow vs. function emission.

Caveat

Capture only works at runtime if the captured this is serializable across the workflow→step boundary — i.e. the enclosing class implements WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE. Same precondition as the existing instance-method-step support. NestJS @Injectable() instances still need a separate provider rehydration path (the third option from the original issue).

Output examples

Workflow mode:

execute: globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//./input//_anonymousStep0",()=>({ context })).bind(this)

Step mode (hoisted as function, not arrow):

asyncfunction_anonymousStep0(input){const{ context }=(function(){/* closure-var IIFE */})();returnthis.service.readFileContent(input,context);}

Tests

  • 4 new SWC fixture tests (nested-arrow-step-lexical-this, nested-arrow-step-lexical-this-var-decl, both modes). Total fixture tests: 114 → 118.
  • New step.test.ts runtime test exercising useStep(stepId).bind(instance) and asserting thisVal: instance on the queue item.
  • All 843 @workflow/core tests pass.
  • pnpm typecheck clean; workbench/example builds against the rebuilt wasm.

Refs

#1865 (specifically the linked comment)

When a nested arrow `"use step"` references the enclosing function/method's
`this`, plumb that `this` through the workflow runtime so the step body
sees the correct receiver.
- Workflow mode wraps the step proxy with `.bind(this)`, so invoking the
proxy captures the caller's `this` as `thisVal` on the queue item.
- Step mode hoists the body as a regular `function` (not an arrow) so the
runtime's `stepFn.apply(thisVal, args)` rebinds `this` inside the
hoisted body.
Detection only fires for arrows, since arrows inherit `this` lexically.
Nested non-arrow functions/methods/getters/setters introduce their own
`this`, so the detector stops at those boundaries.
The runtime already supported `thisVal` for instance-method steps; this
PR is purely a compiler change to feed the existing pipeline.
Caveat: capture works at runtime only when the captured value is
serializable across the workflow->step boundary (i.e. the enclosing
class implements `WORKFLOW_SERIALIZE`/`WORKFLOW_DESERIALIZE`).
Refs #1865
CopilotAI review requested due to automatic review settings May 5, 2026 05:51
@changeset-bot

changeset-botBot commented May 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a19805f

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

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

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 May 5, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented May 5, 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.031s (-29.6% 🟢)1.006s (~)0.974s101.00x
💻 LocalNitro0.031s (-27.6% 🟢)1.005s (~)0.974s101.00x
🐘 PostgresExpress0.046s (-21.4% 🟢)1.012s (~)0.966s101.46x
💻 LocalNext.js (Turbopack)0.050s1.006s0.956s101.60x
🌐 RedisNext.js (Turbopack)0.052s1.005s0.953s101.67x
🐘 PostgresNitro0.053s (-44.3% 🟢)1.013s (-2.9%)0.960s101.70x
🌐 MongoDBNext.js (Turbopack)0.100s1.008s0.908s103.20x
🐘 PostgresNext.js (Turbopack)0.177s1.043s0.866s105.68x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro0.242s (-40.8% 🟢)2.094s (-16.5% 🟢)1.852s101.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.069s (-5.0% 🟢)2.007s (~)0.938s101.00x
💻 LocalNitro1.073s (-5.1% 🟢)2.006s (~)0.932s101.00x
🐘 PostgresExpress1.084s (-5.5% 🟢)2.010s (~)0.926s101.01x
🐘 PostgresNitro1.084s (-4.9%)2.010s (~)0.926s101.01x
🌐 RedisNext.js (Turbopack)1.111s2.007s0.895s101.04x
💻 LocalNext.js (Turbopack)1.113s2.007s0.894s101.04x
🌐 MongoDBNext.js (Turbopack)1.169s2.009s0.840s101.09x
🐘 PostgresNext.js (Turbopack)1.229s2.048s0.819s101.15x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.643s (-57.8% 🟢)3.930s (-33.5% 🟢)2.287s101.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro10.404s (-4.9%)11.021s (~)0.617s31.00x
💻 LocalExpress10.413s (-4.7%)11.022s (~)0.610s31.00x
🐘 PostgresExpress10.426s (-4.9%)11.018s (~)0.592s31.00x
🐘 PostgresNitro10.482s (-3.6%)11.020s (~)0.538s31.01x
🌐 RedisNext.js (Turbopack)10.642s11.023s0.381s31.02x
💻 LocalNext.js (Turbopack)10.680s11.024s0.344s31.03x
🌐 MongoDBNext.js (Turbopack)10.784s11.021s0.238s31.04x
🐘 PostgresNext.js (Turbopack)11.142s11.680s0.538s31.07x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro13.533s (-43.0% 🟢)15.534s (-38.2% 🟢)2.001s21.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express13.449s (-10.2% 🟢)14.027s (-6.7% 🟢)0.578s51.00x
🐘 PostgresExpress13.453s (-7.8% 🟢)14.018s (-6.7% 🟢)0.565s51.00x
💻 LocalNitro13.485s (-10.5% 🟢)14.026s (-12.5% 🟢)0.541s51.00x
🐘 PostgresNitro13.521s (-7.4% 🟢)14.023s (-6.7% 🟢)0.502s51.01x
🌐 RedisNext.js (Turbopack)14.030s14.428s0.398s51.04x
💻 LocalNext.js (Turbopack)14.125s15.030s0.905s41.05x
🌐 MongoDBNext.js (Turbopack)14.212s15.023s0.811s41.06x
🐘 PostgresNext.js (Turbopack)15.396s16.019s0.624s41.14x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro22.032s (-65.8% 🟢)24.542s (-63.2% 🟢)2.510s31.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro11.936s (-28.9% 🟢)12.147s (-28.7% 🟢)0.211s81.00x
🐘 PostgresExpress11.971s (-14.5% 🟢)12.516s (-14.2% 🟢)0.545s81.00x
💻 LocalExpress11.979s (-27.8% 🟢)12.273s (-27.9% 🟢)0.294s81.00x
🐘 PostgresNitro12.038s (-13.8% 🟢)12.766s (-10.8% 🟢)0.728s81.01x
🌐 RedisNext.js (Turbopack)13.002s13.311s0.309s71.09x
🌐 MongoDBNext.js (Turbopack)13.250s14.020s0.770s71.11x
💻 LocalNext.js (Turbopack)13.313s14.026s0.713s71.12x
🐘 PostgresNext.js (Turbopack)16.668s17.052s0.385s61.40x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro33.281s (-92.1% 🟢)35.586s (-91.6% 🟢)2.305s31.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.148s (-8.9% 🟢)2.008s (~)0.860s151.00x
🐘 PostgresNitro1.164s (-8.7% 🟢)2.007s (~)0.843s151.01x
💻 LocalExpress1.184s (-20.5% 🟢)2.006s (~)0.822s151.03x
💻 LocalNitro1.185s (-27.4% 🟢)2.006s (-3.3%)0.821s151.03x
🌐 RedisNext.js (Turbopack)1.249s2.006s0.757s151.09x
💻 LocalNext.js (Turbopack)1.307s2.006s0.699s151.14x
🐘 PostgresNext.js (Turbopack)1.785s2.202s0.417s141.55x
🌐 MongoDBNext.js (Turbopack)2.029s2.827s0.798s111.77x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.530s (-10.2% 🟢)4.390s (+1.6%)1.860s71.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.231s (-47.8% 🟢)2.009s (-33.2% 🟢)0.778s151.00x
🐘 PostgresNitro1.247s (-47.0% 🟢)2.008s (-33.3% 🟢)0.761s151.01x
🐘 PostgresNext.js (Turbopack)1.696s2.200s0.504s141.38x
💻 LocalNitro1.796s (-42.9% 🟢)2.072s (-46.7% 🟢)0.276s151.46x
💻 LocalExpress1.798s (-39.1% 🟢)2.073s (-40.0% 🟢)0.275s151.46x
💻 LocalNext.js (Turbopack)1.864s2.221s0.357s141.51x
🌐 RedisNext.js (Turbopack)2.370s3.008s0.637s101.92x
🌐 MongoDBNext.js (Turbopack)3.591s4.010s0.419s82.92x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.386s (-16.4% 🟢)5.511s (-6.9% 🟢)2.125s61.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.373s (-60.6% 🟢)2.007s (-49.9% 🟢)0.634s151.00x
🐘 PostgresNitro1.444s (-58.5% 🟢)2.008s (-49.9% 🟢)0.565s151.05x
🐘 PostgresNext.js (Turbopack)2.200s3.032s0.832s101.60x
🌐 RedisNext.js (Turbopack)3.616s4.010s0.394s82.63x
💻 LocalExpress5.169s (-38.0% 🟢)5.849s (-35.2% 🟢)0.680s63.76x
💻 LocalNitro5.416s (-35.1% 🟢)6.016s (-33.3% 🟢)0.600s53.94x
💻 LocalNext.js (Turbopack)5.970s6.414s0.444s54.35x
🌐 MongoDBNext.js (Turbopack)6.338s7.014s0.676s54.62x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro7.725s (+119.1% 🔺)10.152s (+83.5% 🔺)2.427s31.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.152s (-8.4% 🟢)2.007s (~)0.856s151.00x
🐘 PostgresNitro1.174s (-6.6% 🟢)2.009s (~)0.835s151.02x
🌐 RedisNext.js (Turbopack)1.229s2.006s0.777s151.07x
💻 LocalNitro1.325s (-29.0% 🟢)2.006s (-14.3% 🟢)0.681s151.15x
💻 LocalNext.js (Turbopack)1.353s2.007s0.654s151.17x
💻 LocalExpress1.413s (-25.4% 🟢)2.007s (-15.1% 🟢)0.594s151.23x
🐘 PostgresNext.js (Turbopack)1.630s2.184s0.554s141.42x
🌐 MongoDBNext.js (Turbopack)2.057s3.008s0.952s101.79x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.585s (+5.1% 🔺)4.490s (+7.7% 🔺)1.905s71.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.239s (-47.1% 🟢)2.008s (-33.3% 🟢)0.768s151.00x
🐘 PostgresNitro1.273s (-45.6% 🟢)2.009s (-33.3% 🟢)0.737s151.03x
🐘 PostgresNext.js (Turbopack)1.770s2.594s0.824s121.43x
💻 LocalNitro1.982s (-35.3% 🟢)2.393s (-38.4% 🟢)0.411s131.60x
💻 LocalExpress2.047s (-34.6% 🟢)2.394s (-36.4% 🟢)0.346s131.65x
💻 LocalNext.js (Turbopack)2.054s2.508s0.455s121.66x
🌐 RedisNext.js (Turbopack)2.354s3.008s0.654s101.90x
🌐 MongoDBNext.js (Turbopack)3.576s4.009s0.433s82.89x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro54.294s (+1579.3% 🔺)56.491s (+1012.7% 🔺)2.197s61.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.379s (-60.6% 🟢)2.008s (-49.9% 🟢)0.629s151.00x
🐘 PostgresNitro1.414s (-59.4% 🟢)2.008s (-49.9% 🟢)0.594s151.03x
🐘 PostgresNext.js (Turbopack)2.116s2.606s0.490s121.53x
🌐 RedisNext.js (Turbopack)3.586s4.010s0.424s82.60x
💻 LocalNitro5.772s (-36.9% 🟢)6.214s (-38.0% 🟢)0.441s54.18x
💻 LocalExpress5.890s (-33.1% 🟢)6.214s (-33.0% 🟢)0.324s54.27x
💻 LocalNext.js (Turbopack)6.126s6.618s0.492s54.44x
🌐 MongoDBNext.js (Turbopack)6.338s7.011s0.673s54.60x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.693s (+31.4% 🔺)9.252s (+35.7% 🔺)2.560s41.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.460s (-45.2% 🟢)1.024s (~)0.564s591.00x
💻 LocalNitro0.484s (-50.7% 🟢)1.021s (-6.6% 🟢)0.538s591.05x
🐘 PostgresNitro0.491s (-40.1% 🟢)1.024s (+1.8%)0.533s591.07x
💻 LocalExpress0.500s (-49.2% 🟢)1.004s (-6.7% 🟢)0.505s601.09x
🌐 RedisNext.js (Turbopack)0.619s1.004s0.386s601.35x
🌐 MongoDBNext.js (Turbopack)0.724s1.005s0.281s601.57x
💻 LocalNext.js (Turbopack)0.748s1.005s0.256s601.63x
🐘 PostgresNext.js (Turbopack)0.860s1.301s0.441s471.87x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.067s (-77.0% 🟢)6.854s (-71.5% 🟢)1.787s91.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.017s (-48.6% 🟢)1.586s (-29.8% 🟢)0.569s571.00x
🐘 PostgresNitro1.143s (-40.7% 🟢)1.986s (-5.4% 🟢)0.843s461.12x
💻 LocalExpress1.210s (-59.9% 🟢)2.005s (-44.1% 🟢)0.795s451.19x
💻 LocalNitro1.212s (-60.1% 🟢)2.006s (-46.6% 🟢)0.793s451.19x
🌐 RedisNext.js (Turbopack)1.513s2.006s0.493s451.49x
🌐 MongoDBNext.js (Turbopack)1.821s2.007s0.186s451.79x
💻 LocalNext.js (Turbopack)1.825s2.006s0.181s451.80x
🐘 PostgresNext.js (Turbopack)2.605s3.071s0.465s302.56x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro13.232s (-66.5% 🟢)15.905s (-61.5% 🟢)2.672s61.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express2.137s (-46.4% 🟢)2.604s (-40.4% 🟢)0.466s471.00x
🐘 PostgresNitro2.235s (-45.5% 🟢)3.034s (-34.1% 🟢)0.799s401.05x
💻 LocalExpress2.693s (-70.8% 🟢)3.008s (-70.0% 🟢)0.314s401.26x
💻 LocalNitro2.712s (-70.8% 🟢)3.057s (-69.5% 🟢)0.346s401.27x
🌐 RedisNext.js (Turbopack)2.959s3.135s0.176s391.38x
💻 LocalNext.js (Turbopack)3.941s4.366s0.426s281.84x
🌐 MongoDBNext.js (Turbopack)4.155s5.012s0.857s241.94x
🐘 PostgresNext.js (Turbopack)4.540s5.107s0.567s252.12x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro41.246s (-57.4% 🟢)43.425s (-55.9% 🟢)2.180s31.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.189s (-33.0% 🟢)1.005s (~)0.816s601.00x
🐘 PostgresNitro0.198s (-30.3% 🟢)1.006s (~)0.809s601.04x
🌐 RedisNext.js (Turbopack)0.242s1.004s0.763s601.28x
💻 LocalExpress0.473s (-15.7% 🟢)1.004s (~)0.532s602.50x
💻 LocalNitro0.539s (-10.9% 🟢)1.096s (+7.3% 🔺)0.557s552.85x
💻 LocalNext.js (Turbopack)0.592s1.022s0.429s593.13x
🐘 PostgresNext.js (Turbopack)0.679s1.259s0.580s483.59x
🌐 MongoDBNext.js (Turbopack)1.029s1.749s0.720s355.44x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.252s (+35.6% 🔺)4.327s (+29.1% 🔺)2.074s141.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.320s (-37.3% 🟢)1.006s (~)0.687s901.00x
🐘 PostgresNitro0.340s (-31.6% 🟢)1.007s (~)0.667s901.06x
🌐 RedisNext.js (Turbopack)0.408s1.004s0.596s901.28x
🐘 PostgresNext.js (Turbopack)0.895s1.442s0.547s632.80x
💻 LocalNitro2.133s (-16.0% 🟢)2.883s (-4.2%)0.750s326.67x
💻 LocalExpress2.304s (-8.3% 🟢)2.943s (-2.2%)0.639s317.21x
🌐 MongoDBNext.js (Turbopack)2.605s3.006s0.401s308.15x
💻 LocalNext.js (Turbopack)2.641s3.183s0.542s298.26x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro8.322s (+158.0% 🔺)10.481s (+117.4% 🔺)2.158s91.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.647s (-21.0% 🟢)1.006s (-1.1%)0.359s1201.00x
🐘 PostgresNitro0.698s (-11.6% 🟢)1.007s (~)0.308s1201.08x
🌐 RedisNext.js (Turbopack)0.762s1.004s0.242s1201.18x
🐘 PostgresNext.js (Turbopack)1.313s1.779s0.467s682.03x
🌐 MongoDBNext.js (Turbopack)5.386s6.013s0.627s208.33x
💻 LocalNitro10.093s (-9.8% 🟢)10.612s (-9.0% 🟢)0.519s1215.61x
💻 LocalExpress10.309s (-7.9% 🟢)10.864s (-9.0% 🟢)0.555s1215.94x
💻 LocalNext.js (Turbopack)11.645s12.330s0.684s1018.01x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro21.406s (+177.2% 🔺)23.498s (+150.0% 🔺)2.092s61.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.134s (+452.9% 🔺)1.998s (+100.1% 🔺)0.002s (-6.3% 🟢)2.010s (+98.7% 🔺)0.876s101.00x
💻 LocalExpress1.134s (+469.8% 🔺)2.005s (+99.6% 🔺)0.012s (+0.8%)2.020s (+98.4% 🔺)0.885s101.00x
💻 LocalNitro1.136s (+431.6% 🔺)2.005s (+99.6% 🔺)0.012s (-1.6%)2.020s (+98.2% 🔺)0.883s101.00x
🐘 PostgresNitro1.154s (+462.9% 🔺)2.002s (+100.2% 🔺)0.002s (~)2.013s (+99.0% 🔺)0.859s101.02x
💻 LocalNext.js (Turbopack)1.198s2.003s0.013s2.020s0.823s101.06x
🐘 PostgresNext.js (Turbopack)1.533s1.999s0.410s2.441s0.908s101.35x
🌐 MongoDBNext.js (Turbopack)⚠️missing-----
🌐 RedisNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.173s (-43.3% 🟢)3.567s (-32.4% 🟢)1.733s (+133.5% 🔺)5.800s (-10.5% 🟢)3.628s101.00x
▲ VercelExpress⚠️missing-----
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.509s (+139.5% 🔺)2.003s (+99.1% 🔺)0.004s (-2.5%)2.024s (+97.9% 🔺)0.515s301.00x
💻 LocalNitro1.528s (+82.1% 🔺)2.011s (+98.7% 🔺)0.009s (-5.7% 🟢)2.022s (+81.2% 🔺)0.494s301.01x
💻 LocalExpress1.530s (+102.1% 🔺)2.011s (+95.5% 🔺)0.010s (+4.5%)2.023s (+94.6% 🔺)0.493s301.01x
🐘 PostgresNitro1.576s (+152.5% 🔺)2.005s (+99.2% 🔺)0.004s (-3.3%)2.027s (+98.3% 🔺)0.451s301.04x
💻 LocalNext.js (Turbopack)1.698s2.010s0.011s2.024s0.327s301.13x
🐘 PostgresNext.js (Turbopack)2.628s3.008s0.004s3.036s0.408s201.74x
🌐 MongoDBNext.js (Turbopack)⚠️missing-----
🌐 RedisNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.770s (-80.4% 🟢)7.572s (-75.4% 🟢)0.449s (+300.9% 🔺)8.603s (-72.9% 🟢)2.833s71.00x
▲ VercelExpress⚠️missing-----
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.668s (-30.5% 🟢)1.049s (-17.9% 🟢)0.000s (-59.6% 🟢)1.060s (-18.9% 🟢)0.392s571.00x
🐘 PostgresNitro0.716s (-26.0% 🟢)1.050s (-15.8% 🟢)0.000s (-57.9% 🟢)1.068s (-15.1% 🟢)0.351s571.07x
💻 LocalExpress1.350s (+10.2% 🔺)2.015s (~)0.001s (+70.0% 🔺)2.017s (~)0.667s302.02x
💻 LocalNitro1.358s (+11.1% 🔺)2.015s (~)0.000s (+133.3% 🔺)2.017s (~)0.659s302.03x
🐘 PostgresNext.js (Turbopack)1.370s1.659s0.000s1.724s0.354s362.05x
💻 LocalNext.js (Turbopack)1.498s2.014s0.000s2.017s0.519s302.24x
🌐 MongoDBNext.js (Turbopack)⚠️missing-----
🌐 RedisNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.519s (+15.4% 🔺)5.014s (+14.1% 🔺)0.000s (-100.0% 🟢)5.521s (+14.8% 🔺)2.002s111.00x
▲ VercelExpress⚠️missing-----
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro

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

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.332s (-24.8% 🟢)1.998s (-8.3% 🟢)0.000s (+Infinity% 🔺)2.018s (-8.2% 🟢)0.686s301.00x
🐘 PostgresNitro1.458s (-18.6% 🟢)2.100s (-1.9%)0.000s (-3.4%)2.115s (-2.7%)0.656s291.09x
💻 LocalNext.js (Turbopack)2.949s3.357s0.000s3.367s0.418s182.21x
💻 LocalNitro3.144s (-7.2% 🟢)4.029s (~)0.000s (-25.0% 🟢)4.032s (~)0.888s152.36x
💻 LocalExpress3.177s (-8.4% 🟢)3.967s (-1.7%)0.000s (-84.4% 🟢)3.969s (-1.7%)0.792s162.39x
🐘 PostgresNext.js (Turbopack)3.436s3.946s0.000s4.008s0.572s152.58x
🌐 MongoDBNext.js (Turbopack)⚠️missing-----
🌐 RedisNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.077s (+48.4% 🔺)7.737s (+44.0% 🔺)0.000s (-54.2% 🟢)8.390s (+44.8% 🔺)2.313s81.00x
▲ VercelExpress⚠️missing-----
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress10/21
🐘 PostgresExpress21/21
▲ VercelNitro21/21
Fastest World by Framework

Winner determined by most benchmark wins

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

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)

📋 View full workflow run


Some benchmark jobs failed:

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

Check the workflow run for details.

@github-actions

github-actionsBot commented May 5, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production92502191144
✅ 💻 Local Development123702191456
✅ 📦 Local Production123702191456
✅ 🐘 Local Postgres123702191456
✅ 🪟 Windows10400104
✅ 📋 Other5520176728
Total5292010526344

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro78026
✅ example78026
✅ express78026
✅ fastify78026
✅ hono78026
✅ nextjs-turbopack10202
✅ nextjs-webpack10202
✅ nitro78026
✅ nuxt78026
✅ sveltekit9707
✅ vite78026
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack10400
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable79025
✅ e2e-local-dev-tanstack-start-79025
✅ e2e-local-postgres-nest-stable79025
✅ e2e-local-postgres-tanstack-start-79025
✅ e2e-local-prod-nest-stable79025
✅ e2e-local-prod-tanstack-start-79025
✅ e2e-vercel-prod-tanstack-start78026

📋 View full workflow run

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

Updates @workflow/swc-plugin to correctly preserve lexical this for nested arrow "use step" functions (common in “method returns object literal with arrow steps” patterns), by detecting this usage, binding step proxies in workflow mode, and hoisting step bodies as regular functions in step mode so runtime apply(thisVal, args) rebinding works.

Changes:

  • Add lexical-this detection for nested arrow step bodies and thread a references_lexical_this flag through nested-step tracking.
  • Emit .bind(this) on workflow-mode step proxies when lexical this is referenced; adjust step-mode hoisting to emit function instead of arrow for those steps.
  • Add SWC fixture coverage + a core runtime test; document the new behavior and ship a changeset.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
packages/swc-plugin-workflow/transform/src/lib.rsImplements lexical-this detection, proxy binding, and step-mode hoist changes.
packages/swc-plugin-workflow/spec.mdDocuments lexical this capture behavior and updates notes about this in steps.
packages/core/src/step.test.tsAdds runtime test ensuring .bind(this) captures thisVal onto the invocation queue item.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this/input.jsNew fixture input covering nested arrow step referencing this in a method.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this/output-workflow.jsExpected workflow-mode output showing .bind(this) on the step proxy.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this/output-step.jsExpected step-mode output showing hoisting as async function using this.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this-var-decl/input.jsNew fixture input for var-declarator arrow step capturing this.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this-var-decl/output-workflow.jsExpected workflow-mode output with bound proxy for var-declarator case.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this-var-decl/output-step.jsExpected step-mode output hoisting as function for var-declarator case.
.changeset/swc-lexical-this-capture.mdPatch changeset describing the new lexical-this behavior.

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

Comment threadpackages/swc-plugin-workflow/transform/src/lib.rs
Comment threadpackages/swc-plugin-workflow/transform/src/lib.rs
Comment threadpackages/swc-plugin-workflow/transform/src/lib.rs
Comment threadpackages/swc-plugin-workflow/spec.md Outdated
Comment threadpackages/core/src/step.test.ts Outdated
…ction
- core: Override `.bind` on step proxies so the bound function retains
`stepId` and `__closureVarsFn`. Without this, a bound proxy that flows
through workflow serialization (e.g. as a step argument) would be
treated as a non-serializable plain function by `getStepFunctionReducer`.
- swc-plugin: Detector now also walks `arrow.params` so `this` references
in default values / destructuring initializers (e.g. `(x = this.foo) =>
...`) trigger the `.bind(this)` path.
- swc-plugin: Class bodies inside the arrow body are now treated as
`this`-binding boundaries — `this` inside class field initializers,
methods, etc. is bound to the class instance, not the outer arrow. The
detector still walks `extends` clauses and computed property keys
because those are evaluated in the surrounding scope.
- spec.md: Sharpen the note about `this` in step bodies — it's
syntactically allowed but only meaningful for instance-method steps and
lexical-`this` arrow steps; other shapes compile but `this` will be
whatever the caller of the step proxy passes.
- Add `lexical-this-detector-edge-cases` fixture covering both the
default-param positive case and the inner-class false-positive guard.
- Strengthen the runtime test to assert `stepId` / `__closureVarsFn`
survive `.bind(...)`.
… coverage
Without this, a `useStep(...).bind(thisArg)` proxy that flows through
workflow serialization (e.g. passed as a step argument) would lose its
receiver: the reducer captured `stepId` but not the bound `this`, and
the step-bundle reviver returned the raw registered step body which
ignores any `this` the caller passes.
Now:
- step.ts `.bind` override stashes the bound value on the result as
`__boundThis` so the reducer can see it.
- The reducer serializes `boundThis` (using property presence so
`bind(null)`/`bind(undefined)` round-trip faithfully).
- The workflow-bundle reviver re-binds the freshly created proxy.
- The step-bundle reviver wraps the registered body so it's invoked
with `apply(boundThis, args)` (and still runs inside the closure-vars
AsyncLocalStorage frame when `closureVars` is present).
E2E coverage extends `instanceMethodStepWorkflow` with two new shapes:
- `counter.makeAdder(7).add(2)` — direct invocation of a lexical-`this`
arrow step. Verifies `bind(this)` carries `thisVal` to the queue and
the step body sees `this.value` correctly.
- `invokeAdderFromStep(adder.add, 3)` — passes the bound proxy as a
step argument so the round-trip path exercises both the reducer and
the step-bundle reviver, with the inner call running inline. Without
the new `boundThis` plumbing this previously failed with `Cannot read
properties of undefined (reading 'value')`.

@karthikscale3karthikscale3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AI review: Solid implementation — the LexicalThisDetector, the .bind() override, and the serialization round-trip all hang together correctly. No regressions found. The arguments fix is a genuine bug fix and the dead this/arguments error-check removal is safe. All completed CI checks pass. LGTM

Comment threadpackages/core/src/step.ts
Comment threadpackages/core/src/serialization/serialization.test.ts
…+boundThis
- step.ts `.bind` override now also stashes `__boundArgs` when the caller
supplied prefilled args (`useStep(...).bind(thisArg, x, y)`). The
reducer serializes them as `boundArgs`, and both the workflow- and
step-bundle revivers re-apply them (the workflow reviver via
`bind(boundThis, ...boundArgs)`, the step-bundle reviver by
prepending to the runtime args). The SWC plugin only ever emits
`.bind(this)` today, but this keeps partial application faithful in
case hand-written code ever calls `.bind` with extra args.
- Add two new unit tests in `serialization.test.ts`:
- `closureVars + boundThis` combo: exercises the step-bundle reviver's
inner branch (`contextStorage.run(newContext, () =>
stepFn.apply(callThis, callArgs))`) in isolation — previously only
covered end-to-end by the `instanceMethodStepWorkflow` e2e test.
- `boundArgs` round-trip: codifies that prefilled args survive
serialization.
@github-actions

Copy link
Copy Markdown
Contributor

Backport PR opened against stable: #1945. Merge conflicts were resolved by AI — please review carefully.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-stableCherry-pick this PR to the stable branch when merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

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

[swc-plugin] Capture lexical this for nested arrow step functions - #1935

Merged
TooTallNate merged 6 commits into
mainfrom
feat/swc-lexical-this-capture
May 5, 2026
Merged

[swc-plugin] Capture lexical this for nested arrow step functions#1935
TooTallNate merged 6 commits into
mainfrom
feat/swc-lexical-this-capture

Conversation

@TooTallNate

Copy link
Copy Markdown
Member

Summary

Teaches the SWC plugin to handle "use step" arrow functions whose body lexically captures this from an enclosing method/function — a pattern that comes up naturally with the AI SDK tool({...}) factory and other "method that returns an object literal containing arrow steps" shapes.

Before this PR:

classReadFileTool{// serializable...createTool(context){returntool({execute: async(input)=>{'use step';returnthis.service.readFileContent(input,context);// `this` was lost},});}}

The compiler would hoist execute to module scope as an arrow with a bare return this.service…, so at runtime this was undefined. Workflow mode produced a step proxy with no this plumbing.

After this PR:

  • Workflow mode wraps the proxy with .bind(this) so invoking it captures the caller's this as thisVal on the queue item.
  • Step mode hoists the body as a regular function (not an arrow) so the runtime's existing stepFn.apply(thisVal, args) rebinds this inside the hoisted body.

The runtime already had thisVal plumbing for instance-method steps; this PR is purely a compiler change feeding that existing pipeline.

How

  • New LexicalThisDetector (Visit) walks an arrow body looking for ThisExpr. Recurses through nested arrows but stops at Function/Constructor/MethodProp/GetterProp/SetterProp/ClassMethod/PrivateMethod/StaticBlock because those introduce their own this.
  • nested_step_functions gains a references_lexical_this: bool field, threaded through every push site.
  • Two new helpers: wrap_with_bind_this(expr) and create_step_proxy_reference_maybe_bound(...).
  • Step-mode hoister checks was_arrow && !references_lexical_this to decide arrow vs. function emission.

Caveat

Capture only works at runtime if the captured this is serializable across the workflow→step boundary — i.e. the enclosing class implements WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE. Same precondition as the existing instance-method-step support. NestJS @Injectable() instances still need a separate provider rehydration path (the third option from the original issue).

Output examples

Workflow mode:

execute: globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//./input//_anonymousStep0",()=>({ context })).bind(this)

Step mode (hoisted as function, not arrow):

asyncfunction_anonymousStep0(input){const{ context }=(function(){/* closure-var IIFE */})();returnthis.service.readFileContent(input,context);}

Tests

  • 4 new SWC fixture tests (nested-arrow-step-lexical-this, nested-arrow-step-lexical-this-var-decl, both modes). Total fixture tests: 114 → 118.
  • New step.test.ts runtime test exercising useStep(stepId).bind(instance) and asserting thisVal: instance on the queue item.
  • All 843 @workflow/core tests pass.
  • pnpm typecheck clean; workbench/example builds against the rebuilt wasm.

Refs

#1865 (specifically the linked comment)

When a nested arrow `"use step"` references the enclosing function/method's
`this`, plumb that `this` through the workflow runtime so the step body
sees the correct receiver.
- Workflow mode wraps the step proxy with `.bind(this)`, so invoking the
proxy captures the caller's `this` as `thisVal` on the queue item.
- Step mode hoists the body as a regular `function` (not an arrow) so the
runtime's `stepFn.apply(thisVal, args)` rebinds `this` inside the
hoisted body.
Detection only fires for arrows, since arrows inherit `this` lexically.
Nested non-arrow functions/methods/getters/setters introduce their own
`this`, so the detector stops at those boundaries.
The runtime already supported `thisVal` for instance-method steps; this
PR is purely a compiler change to feed the existing pipeline.
Caveat: capture works at runtime only when the captured value is
serializable across the workflow->step boundary (i.e. the enclosing
class implements `WORKFLOW_SERIALIZE`/`WORKFLOW_DESERIALIZE`).
Refs #1865
CopilotAI review requested due to automatic review settings May 5, 2026 05:51
@changeset-bot

changeset-botBot commented May 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a19805f

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

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

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 May 5, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented May 5, 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.031s (-29.6% 🟢)1.006s (~)0.974s101.00x
💻 LocalNitro0.031s (-27.6% 🟢)1.005s (~)0.974s101.00x
🐘 PostgresExpress0.046s (-21.4% 🟢)1.012s (~)0.966s101.46x
💻 LocalNext.js (Turbopack)0.050s1.006s0.956s101.60x
🌐 RedisNext.js (Turbopack)0.052s1.005s0.953s101.67x
🐘 PostgresNitro0.053s (-44.3% 🟢)1.013s (-2.9%)0.960s101.70x
🌐 MongoDBNext.js (Turbopack)0.100s1.008s0.908s103.20x
🐘 PostgresNext.js (Turbopack)0.177s1.043s0.866s105.68x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro0.242s (-40.8% 🟢)2.094s (-16.5% 🟢)1.852s101.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.069s (-5.0% 🟢)2.007s (~)0.938s101.00x
💻 LocalNitro1.073s (-5.1% 🟢)2.006s (~)0.932s101.00x
🐘 PostgresExpress1.084s (-5.5% 🟢)2.010s (~)0.926s101.01x
🐘 PostgresNitro1.084s (-4.9%)2.010s (~)0.926s101.01x
🌐 RedisNext.js (Turbopack)1.111s2.007s0.895s101.04x
💻 LocalNext.js (Turbopack)1.113s2.007s0.894s101.04x
🌐 MongoDBNext.js (Turbopack)1.169s2.009s0.840s101.09x
🐘 PostgresNext.js (Turbopack)1.229s2.048s0.819s101.15x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.643s (-57.8% 🟢)3.930s (-33.5% 🟢)2.287s101.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro10.404s (-4.9%)11.021s (~)0.617s31.00x
💻 LocalExpress10.413s (-4.7%)11.022s (~)0.610s31.00x
🐘 PostgresExpress10.426s (-4.9%)11.018s (~)0.592s31.00x
🐘 PostgresNitro10.482s (-3.6%)11.020s (~)0.538s31.01x
🌐 RedisNext.js (Turbopack)10.642s11.023s0.381s31.02x
💻 LocalNext.js (Turbopack)10.680s11.024s0.344s31.03x
🌐 MongoDBNext.js (Turbopack)10.784s11.021s0.238s31.04x
🐘 PostgresNext.js (Turbopack)11.142s11.680s0.538s31.07x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro13.533s (-43.0% 🟢)15.534s (-38.2% 🟢)2.001s21.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express13.449s (-10.2% 🟢)14.027s (-6.7% 🟢)0.578s51.00x
🐘 PostgresExpress13.453s (-7.8% 🟢)14.018s (-6.7% 🟢)0.565s51.00x
💻 LocalNitro13.485s (-10.5% 🟢)14.026s (-12.5% 🟢)0.541s51.00x
🐘 PostgresNitro13.521s (-7.4% 🟢)14.023s (-6.7% 🟢)0.502s51.01x
🌐 RedisNext.js (Turbopack)14.030s14.428s0.398s51.04x
💻 LocalNext.js (Turbopack)14.125s15.030s0.905s41.05x
🌐 MongoDBNext.js (Turbopack)14.212s15.023s0.811s41.06x
🐘 PostgresNext.js (Turbopack)15.396s16.019s0.624s41.14x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro22.032s (-65.8% 🟢)24.542s (-63.2% 🟢)2.510s31.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro11.936s (-28.9% 🟢)12.147s (-28.7% 🟢)0.211s81.00x
🐘 PostgresExpress11.971s (-14.5% 🟢)12.516s (-14.2% 🟢)0.545s81.00x
💻 LocalExpress11.979s (-27.8% 🟢)12.273s (-27.9% 🟢)0.294s81.00x
🐘 PostgresNitro12.038s (-13.8% 🟢)12.766s (-10.8% 🟢)0.728s81.01x
🌐 RedisNext.js (Turbopack)13.002s13.311s0.309s71.09x
🌐 MongoDBNext.js (Turbopack)13.250s14.020s0.770s71.11x
💻 LocalNext.js (Turbopack)13.313s14.026s0.713s71.12x
🐘 PostgresNext.js (Turbopack)16.668s17.052s0.385s61.40x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro33.281s (-92.1% 🟢)35.586s (-91.6% 🟢)2.305s31.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.148s (-8.9% 🟢)2.008s (~)0.860s151.00x
🐘 PostgresNitro1.164s (-8.7% 🟢)2.007s (~)0.843s151.01x
💻 LocalExpress1.184s (-20.5% 🟢)2.006s (~)0.822s151.03x
💻 LocalNitro1.185s (-27.4% 🟢)2.006s (-3.3%)0.821s151.03x
🌐 RedisNext.js (Turbopack)1.249s2.006s0.757s151.09x
💻 LocalNext.js (Turbopack)1.307s2.006s0.699s151.14x
🐘 PostgresNext.js (Turbopack)1.785s2.202s0.417s141.55x
🌐 MongoDBNext.js (Turbopack)2.029s2.827s0.798s111.77x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.530s (-10.2% 🟢)4.390s (+1.6%)1.860s71.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.231s (-47.8% 🟢)2.009s (-33.2% 🟢)0.778s151.00x
🐘 PostgresNitro1.247s (-47.0% 🟢)2.008s (-33.3% 🟢)0.761s151.01x
🐘 PostgresNext.js (Turbopack)1.696s2.200s0.504s141.38x
💻 LocalNitro1.796s (-42.9% 🟢)2.072s (-46.7% 🟢)0.276s151.46x
💻 LocalExpress1.798s (-39.1% 🟢)2.073s (-40.0% 🟢)0.275s151.46x
💻 LocalNext.js (Turbopack)1.864s2.221s0.357s141.51x
🌐 RedisNext.js (Turbopack)2.370s3.008s0.637s101.92x
🌐 MongoDBNext.js (Turbopack)3.591s4.010s0.419s82.92x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.386s (-16.4% 🟢)5.511s (-6.9% 🟢)2.125s61.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.373s (-60.6% 🟢)2.007s (-49.9% 🟢)0.634s151.00x
🐘 PostgresNitro1.444s (-58.5% 🟢)2.008s (-49.9% 🟢)0.565s151.05x
🐘 PostgresNext.js (Turbopack)2.200s3.032s0.832s101.60x
🌐 RedisNext.js (Turbopack)3.616s4.010s0.394s82.63x
💻 LocalExpress5.169s (-38.0% 🟢)5.849s (-35.2% 🟢)0.680s63.76x
💻 LocalNitro5.416s (-35.1% 🟢)6.016s (-33.3% 🟢)0.600s53.94x
💻 LocalNext.js (Turbopack)5.970s6.414s0.444s54.35x
🌐 MongoDBNext.js (Turbopack)6.338s7.014s0.676s54.62x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro7.725s (+119.1% 🔺)10.152s (+83.5% 🔺)2.427s31.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.152s (-8.4% 🟢)2.007s (~)0.856s151.00x
🐘 PostgresNitro1.174s (-6.6% 🟢)2.009s (~)0.835s151.02x
🌐 RedisNext.js (Turbopack)1.229s2.006s0.777s151.07x
💻 LocalNitro1.325s (-29.0% 🟢)2.006s (-14.3% 🟢)0.681s151.15x
💻 LocalNext.js (Turbopack)1.353s2.007s0.654s151.17x
💻 LocalExpress1.413s (-25.4% 🟢)2.007s (-15.1% 🟢)0.594s151.23x
🐘 PostgresNext.js (Turbopack)1.630s2.184s0.554s141.42x
🌐 MongoDBNext.js (Turbopack)2.057s3.008s0.952s101.79x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.585s (+5.1% 🔺)4.490s (+7.7% 🔺)1.905s71.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.239s (-47.1% 🟢)2.008s (-33.3% 🟢)0.768s151.00x
🐘 PostgresNitro1.273s (-45.6% 🟢)2.009s (-33.3% 🟢)0.737s151.03x
🐘 PostgresNext.js (Turbopack)1.770s2.594s0.824s121.43x
💻 LocalNitro1.982s (-35.3% 🟢)2.393s (-38.4% 🟢)0.411s131.60x
💻 LocalExpress2.047s (-34.6% 🟢)2.394s (-36.4% 🟢)0.346s131.65x
💻 LocalNext.js (Turbopack)2.054s2.508s0.455s121.66x
🌐 RedisNext.js (Turbopack)2.354s3.008s0.654s101.90x
🌐 MongoDBNext.js (Turbopack)3.576s4.009s0.433s82.89x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro54.294s (+1579.3% 🔺)56.491s (+1012.7% 🔺)2.197s61.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.379s (-60.6% 🟢)2.008s (-49.9% 🟢)0.629s151.00x
🐘 PostgresNitro1.414s (-59.4% 🟢)2.008s (-49.9% 🟢)0.594s151.03x
🐘 PostgresNext.js (Turbopack)2.116s2.606s0.490s121.53x
🌐 RedisNext.js (Turbopack)3.586s4.010s0.424s82.60x
💻 LocalNitro5.772s (-36.9% 🟢)6.214s (-38.0% 🟢)0.441s54.18x
💻 LocalExpress5.890s (-33.1% 🟢)6.214s (-33.0% 🟢)0.324s54.27x
💻 LocalNext.js (Turbopack)6.126s6.618s0.492s54.44x
🌐 MongoDBNext.js (Turbopack)6.338s7.011s0.673s54.60x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.693s (+31.4% 🔺)9.252s (+35.7% 🔺)2.560s41.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.460s (-45.2% 🟢)1.024s (~)0.564s591.00x
💻 LocalNitro0.484s (-50.7% 🟢)1.021s (-6.6% 🟢)0.538s591.05x
🐘 PostgresNitro0.491s (-40.1% 🟢)1.024s (+1.8%)0.533s591.07x
💻 LocalExpress0.500s (-49.2% 🟢)1.004s (-6.7% 🟢)0.505s601.09x
🌐 RedisNext.js (Turbopack)0.619s1.004s0.386s601.35x
🌐 MongoDBNext.js (Turbopack)0.724s1.005s0.281s601.57x
💻 LocalNext.js (Turbopack)0.748s1.005s0.256s601.63x
🐘 PostgresNext.js (Turbopack)0.860s1.301s0.441s471.87x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.067s (-77.0% 🟢)6.854s (-71.5% 🟢)1.787s91.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.017s (-48.6% 🟢)1.586s (-29.8% 🟢)0.569s571.00x
🐘 PostgresNitro1.143s (-40.7% 🟢)1.986s (-5.4% 🟢)0.843s461.12x
💻 LocalExpress1.210s (-59.9% 🟢)2.005s (-44.1% 🟢)0.795s451.19x
💻 LocalNitro1.212s (-60.1% 🟢)2.006s (-46.6% 🟢)0.793s451.19x
🌐 RedisNext.js (Turbopack)1.513s2.006s0.493s451.49x
🌐 MongoDBNext.js (Turbopack)1.821s2.007s0.186s451.79x
💻 LocalNext.js (Turbopack)1.825s2.006s0.181s451.80x
🐘 PostgresNext.js (Turbopack)2.605s3.071s0.465s302.56x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro13.232s (-66.5% 🟢)15.905s (-61.5% 🟢)2.672s61.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express2.137s (-46.4% 🟢)2.604s (-40.4% 🟢)0.466s471.00x
🐘 PostgresNitro2.235s (-45.5% 🟢)3.034s (-34.1% 🟢)0.799s401.05x
💻 LocalExpress2.693s (-70.8% 🟢)3.008s (-70.0% 🟢)0.314s401.26x
💻 LocalNitro2.712s (-70.8% 🟢)3.057s (-69.5% 🟢)0.346s401.27x
🌐 RedisNext.js (Turbopack)2.959s3.135s0.176s391.38x
💻 LocalNext.js (Turbopack)3.941s4.366s0.426s281.84x
🌐 MongoDBNext.js (Turbopack)4.155s5.012s0.857s241.94x
🐘 PostgresNext.js (Turbopack)4.540s5.107s0.567s252.12x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro41.246s (-57.4% 🟢)43.425s (-55.9% 🟢)2.180s31.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.189s (-33.0% 🟢)1.005s (~)0.816s601.00x
🐘 PostgresNitro0.198s (-30.3% 🟢)1.006s (~)0.809s601.04x
🌐 RedisNext.js (Turbopack)0.242s1.004s0.763s601.28x
💻 LocalExpress0.473s (-15.7% 🟢)1.004s (~)0.532s602.50x
💻 LocalNitro0.539s (-10.9% 🟢)1.096s (+7.3% 🔺)0.557s552.85x
💻 LocalNext.js (Turbopack)0.592s1.022s0.429s593.13x
🐘 PostgresNext.js (Turbopack)0.679s1.259s0.580s483.59x
🌐 MongoDBNext.js (Turbopack)1.029s1.749s0.720s355.44x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.252s (+35.6% 🔺)4.327s (+29.1% 🔺)2.074s141.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.320s (-37.3% 🟢)1.006s (~)0.687s901.00x
🐘 PostgresNitro0.340s (-31.6% 🟢)1.007s (~)0.667s901.06x
🌐 RedisNext.js (Turbopack)0.408s1.004s0.596s901.28x
🐘 PostgresNext.js (Turbopack)0.895s1.442s0.547s632.80x
💻 LocalNitro2.133s (-16.0% 🟢)2.883s (-4.2%)0.750s326.67x
💻 LocalExpress2.304s (-8.3% 🟢)2.943s (-2.2%)0.639s317.21x
🌐 MongoDBNext.js (Turbopack)2.605s3.006s0.401s308.15x
💻 LocalNext.js (Turbopack)2.641s3.183s0.542s298.26x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro8.322s (+158.0% 🔺)10.481s (+117.4% 🔺)2.158s91.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.647s (-21.0% 🟢)1.006s (-1.1%)0.359s1201.00x
🐘 PostgresNitro0.698s (-11.6% 🟢)1.007s (~)0.308s1201.08x
🌐 RedisNext.js (Turbopack)0.762s1.004s0.242s1201.18x
🐘 PostgresNext.js (Turbopack)1.313s1.779s0.467s682.03x
🌐 MongoDBNext.js (Turbopack)5.386s6.013s0.627s208.33x
💻 LocalNitro10.093s (-9.8% 🟢)10.612s (-9.0% 🟢)0.519s1215.61x
💻 LocalExpress10.309s (-7.9% 🟢)10.864s (-9.0% 🟢)0.555s1215.94x
💻 LocalNext.js (Turbopack)11.645s12.330s0.684s1018.01x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro21.406s (+177.2% 🔺)23.498s (+150.0% 🔺)2.092s61.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.134s (+452.9% 🔺)1.998s (+100.1% 🔺)0.002s (-6.3% 🟢)2.010s (+98.7% 🔺)0.876s101.00x
💻 LocalExpress1.134s (+469.8% 🔺)2.005s (+99.6% 🔺)0.012s (+0.8%)2.020s (+98.4% 🔺)0.885s101.00x
💻 LocalNitro1.136s (+431.6% 🔺)2.005s (+99.6% 🔺)0.012s (-1.6%)2.020s (+98.2% 🔺)0.883s101.00x
🐘 PostgresNitro1.154s (+462.9% 🔺)2.002s (+100.2% 🔺)0.002s (~)2.013s (+99.0% 🔺)0.859s101.02x
💻 LocalNext.js (Turbopack)1.198s2.003s0.013s2.020s0.823s101.06x
🐘 PostgresNext.js (Turbopack)1.533s1.999s0.410s2.441s0.908s101.35x
🌐 MongoDBNext.js (Turbopack)⚠️missing-----
🌐 RedisNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.173s (-43.3% 🟢)3.567s (-32.4% 🟢)1.733s (+133.5% 🔺)5.800s (-10.5% 🟢)3.628s101.00x
▲ VercelExpress⚠️missing-----
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.509s (+139.5% 🔺)2.003s (+99.1% 🔺)0.004s (-2.5%)2.024s (+97.9% 🔺)0.515s301.00x
💻 LocalNitro1.528s (+82.1% 🔺)2.011s (+98.7% 🔺)0.009s (-5.7% 🟢)2.022s (+81.2% 🔺)0.494s301.01x
💻 LocalExpress1.530s (+102.1% 🔺)2.011s (+95.5% 🔺)0.010s (+4.5%)2.023s (+94.6% 🔺)0.493s301.01x
🐘 PostgresNitro1.576s (+152.5% 🔺)2.005s (+99.2% 🔺)0.004s (-3.3%)2.027s (+98.3% 🔺)0.451s301.04x
💻 LocalNext.js (Turbopack)1.698s2.010s0.011s2.024s0.327s301.13x
🐘 PostgresNext.js (Turbopack)2.628s3.008s0.004s3.036s0.408s201.74x
🌐 MongoDBNext.js (Turbopack)⚠️missing-----
🌐 RedisNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.770s (-80.4% 🟢)7.572s (-75.4% 🟢)0.449s (+300.9% 🔺)8.603s (-72.9% 🟢)2.833s71.00x
▲ VercelExpress⚠️missing-----
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.668s (-30.5% 🟢)1.049s (-17.9% 🟢)0.000s (-59.6% 🟢)1.060s (-18.9% 🟢)0.392s571.00x
🐘 PostgresNitro0.716s (-26.0% 🟢)1.050s (-15.8% 🟢)0.000s (-57.9% 🟢)1.068s (-15.1% 🟢)0.351s571.07x
💻 LocalExpress1.350s (+10.2% 🔺)2.015s (~)0.001s (+70.0% 🔺)2.017s (~)0.667s302.02x
💻 LocalNitro1.358s (+11.1% 🔺)2.015s (~)0.000s (+133.3% 🔺)2.017s (~)0.659s302.03x
🐘 PostgresNext.js (Turbopack)1.370s1.659s0.000s1.724s0.354s362.05x
💻 LocalNext.js (Turbopack)1.498s2.014s0.000s2.017s0.519s302.24x
🌐 MongoDBNext.js (Turbopack)⚠️missing-----
🌐 RedisNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.519s (+15.4% 🔺)5.014s (+14.1% 🔺)0.000s (-100.0% 🟢)5.521s (+14.8% 🔺)2.002s111.00x
▲ VercelExpress⚠️missing-----
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro

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

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.332s (-24.8% 🟢)1.998s (-8.3% 🟢)0.000s (+Infinity% 🔺)2.018s (-8.2% 🟢)0.686s301.00x
🐘 PostgresNitro1.458s (-18.6% 🟢)2.100s (-1.9%)0.000s (-3.4%)2.115s (-2.7%)0.656s291.09x
💻 LocalNext.js (Turbopack)2.949s3.357s0.000s3.367s0.418s182.21x
💻 LocalNitro3.144s (-7.2% 🟢)4.029s (~)0.000s (-25.0% 🟢)4.032s (~)0.888s152.36x
💻 LocalExpress3.177s (-8.4% 🟢)3.967s (-1.7%)0.000s (-84.4% 🟢)3.969s (-1.7%)0.792s162.39x
🐘 PostgresNext.js (Turbopack)3.436s3.946s0.000s4.008s0.572s152.58x
🌐 MongoDBNext.js (Turbopack)⚠️missing-----
🌐 RedisNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.077s (+48.4% 🔺)7.737s (+44.0% 🔺)0.000s (-54.2% 🟢)8.390s (+44.8% 🔺)2.313s81.00x
▲ VercelExpress⚠️missing-----
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress10/21
🐘 PostgresExpress21/21
▲ VercelNitro21/21
Fastest World by Framework

Winner determined by most benchmark wins

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

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)

📋 View full workflow run


Some benchmark jobs failed:

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

Check the workflow run for details.

@github-actions

github-actionsBot commented May 5, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production92502191144
✅ 💻 Local Development123702191456
✅ 📦 Local Production123702191456
✅ 🐘 Local Postgres123702191456
✅ 🪟 Windows10400104
✅ 📋 Other5520176728
Total5292010526344

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro78026
✅ example78026
✅ express78026
✅ fastify78026
✅ hono78026
✅ nextjs-turbopack10202
✅ nextjs-webpack10202
✅ nitro78026
✅ nuxt78026
✅ sveltekit9707
✅ vite78026
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack10400
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable79025
✅ e2e-local-dev-tanstack-start-79025
✅ e2e-local-postgres-nest-stable79025
✅ e2e-local-postgres-tanstack-start-79025
✅ e2e-local-prod-nest-stable79025
✅ e2e-local-prod-tanstack-start-79025
✅ e2e-vercel-prod-tanstack-start78026

📋 View full workflow run

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

Updates @workflow/swc-plugin to correctly preserve lexical this for nested arrow "use step" functions (common in “method returns object literal with arrow steps” patterns), by detecting this usage, binding step proxies in workflow mode, and hoisting step bodies as regular functions in step mode so runtime apply(thisVal, args) rebinding works.

Changes:

  • Add lexical-this detection for nested arrow step bodies and thread a references_lexical_this flag through nested-step tracking.
  • Emit .bind(this) on workflow-mode step proxies when lexical this is referenced; adjust step-mode hoisting to emit function instead of arrow for those steps.
  • Add SWC fixture coverage + a core runtime test; document the new behavior and ship a changeset.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
packages/swc-plugin-workflow/transform/src/lib.rsImplements lexical-this detection, proxy binding, and step-mode hoist changes.
packages/swc-plugin-workflow/spec.mdDocuments lexical this capture behavior and updates notes about this in steps.
packages/core/src/step.test.tsAdds runtime test ensuring .bind(this) captures thisVal onto the invocation queue item.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this/input.jsNew fixture input covering nested arrow step referencing this in a method.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this/output-workflow.jsExpected workflow-mode output showing .bind(this) on the step proxy.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this/output-step.jsExpected step-mode output showing hoisting as async function using this.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this-var-decl/input.jsNew fixture input for var-declarator arrow step capturing this.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this-var-decl/output-workflow.jsExpected workflow-mode output with bound proxy for var-declarator case.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this-var-decl/output-step.jsExpected step-mode output hoisting as function for var-declarator case.
.changeset/swc-lexical-this-capture.mdPatch changeset describing the new lexical-this behavior.

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

Comment threadpackages/swc-plugin-workflow/transform/src/lib.rs
Comment threadpackages/swc-plugin-workflow/transform/src/lib.rs
Comment threadpackages/swc-plugin-workflow/transform/src/lib.rs
Comment threadpackages/swc-plugin-workflow/spec.md Outdated
Comment threadpackages/core/src/step.test.ts Outdated
…ction
- core: Override `.bind` on step proxies so the bound function retains
`stepId` and `__closureVarsFn`. Without this, a bound proxy that flows
through workflow serialization (e.g. as a step argument) would be
treated as a non-serializable plain function by `getStepFunctionReducer`.
- swc-plugin: Detector now also walks `arrow.params` so `this` references
in default values / destructuring initializers (e.g. `(x = this.foo) =>
...`) trigger the `.bind(this)` path.
- swc-plugin: Class bodies inside the arrow body are now treated as
`this`-binding boundaries — `this` inside class field initializers,
methods, etc. is bound to the class instance, not the outer arrow. The
detector still walks `extends` clauses and computed property keys
because those are evaluated in the surrounding scope.
- spec.md: Sharpen the note about `this` in step bodies — it's
syntactically allowed but only meaningful for instance-method steps and
lexical-`this` arrow steps; other shapes compile but `this` will be
whatever the caller of the step proxy passes.
- Add `lexical-this-detector-edge-cases` fixture covering both the
default-param positive case and the inner-class false-positive guard.
- Strengthen the runtime test to assert `stepId` / `__closureVarsFn`
survive `.bind(...)`.
… coverage
Without this, a `useStep(...).bind(thisArg)` proxy that flows through
workflow serialization (e.g. passed as a step argument) would lose its
receiver: the reducer captured `stepId` but not the bound `this`, and
the step-bundle reviver returned the raw registered step body which
ignores any `this` the caller passes.
Now:
- step.ts `.bind` override stashes the bound value on the result as
`__boundThis` so the reducer can see it.
- The reducer serializes `boundThis` (using property presence so
`bind(null)`/`bind(undefined)` round-trip faithfully).
- The workflow-bundle reviver re-binds the freshly created proxy.
- The step-bundle reviver wraps the registered body so it's invoked
with `apply(boundThis, args)` (and still runs inside the closure-vars
AsyncLocalStorage frame when `closureVars` is present).
E2E coverage extends `instanceMethodStepWorkflow` with two new shapes:
- `counter.makeAdder(7).add(2)` — direct invocation of a lexical-`this`
arrow step. Verifies `bind(this)` carries `thisVal` to the queue and
the step body sees `this.value` correctly.
- `invokeAdderFromStep(adder.add, 3)` — passes the bound proxy as a
step argument so the round-trip path exercises both the reducer and
the step-bundle reviver, with the inner call running inline. Without
the new `boundThis` plumbing this previously failed with `Cannot read
properties of undefined (reading 'value')`.

@karthikscale3karthikscale3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AI review: Solid implementation — the LexicalThisDetector, the .bind() override, and the serialization round-trip all hang together correctly. No regressions found. The arguments fix is a genuine bug fix and the dead this/arguments error-check removal is safe. All completed CI checks pass. LGTM

Comment threadpackages/core/src/step.ts
Comment threadpackages/core/src/serialization/serialization.test.ts
…+boundThis
- step.ts `.bind` override now also stashes `__boundArgs` when the caller
supplied prefilled args (`useStep(...).bind(thisArg, x, y)`). The
reducer serializes them as `boundArgs`, and both the workflow- and
step-bundle revivers re-apply them (the workflow reviver via
`bind(boundThis, ...boundArgs)`, the step-bundle reviver by
prepending to the runtime args). The SWC plugin only ever emits
`.bind(this)` today, but this keeps partial application faithful in
case hand-written code ever calls `.bind` with extra args.
- Add two new unit tests in `serialization.test.ts`:
- `closureVars + boundThis` combo: exercises the step-bundle reviver's
inner branch (`contextStorage.run(newContext, () =>
stepFn.apply(callThis, callArgs))`) in isolation — previously only
covered end-to-end by the `instanceMethodStepWorkflow` e2e test.
- `boundArgs` round-trip: codifies that prefilled args survive
serialization.
@github-actions

Copy link
Copy Markdown
Contributor

Backport PR opened against stable: #1945. Merge conflicts were resolved by AI — please review carefully.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-stableCherry-pick this PR to the stable branch when merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

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

[swc-plugin] Capture lexical this for nested arrow step functions - #1935

Merged
TooTallNate merged 6 commits into
mainfrom
feat/swc-lexical-this-capture
May 5, 2026
Merged

[swc-plugin] Capture lexical this for nested arrow step functions#1935
TooTallNate merged 6 commits into
mainfrom
feat/swc-lexical-this-capture

Conversation

@TooTallNate

Copy link
Copy Markdown
Member

Summary

Teaches the SWC plugin to handle "use step" arrow functions whose body lexically captures this from an enclosing method/function — a pattern that comes up naturally with the AI SDK tool({...}) factory and other "method that returns an object literal containing arrow steps" shapes.

Before this PR:

classReadFileTool{// serializable...createTool(context){returntool({execute: async(input)=>{'use step';returnthis.service.readFileContent(input,context);// `this` was lost},});}}

The compiler would hoist execute to module scope as an arrow with a bare return this.service…, so at runtime this was undefined. Workflow mode produced a step proxy with no this plumbing.

After this PR:

  • Workflow mode wraps the proxy with .bind(this) so invoking it captures the caller's this as thisVal on the queue item.
  • Step mode hoists the body as a regular function (not an arrow) so the runtime's existing stepFn.apply(thisVal, args) rebinds this inside the hoisted body.

The runtime already had thisVal plumbing for instance-method steps; this PR is purely a compiler change feeding that existing pipeline.

How

  • New LexicalThisDetector (Visit) walks an arrow body looking for ThisExpr. Recurses through nested arrows but stops at Function/Constructor/MethodProp/GetterProp/SetterProp/ClassMethod/PrivateMethod/StaticBlock because those introduce their own this.
  • nested_step_functions gains a references_lexical_this: bool field, threaded through every push site.
  • Two new helpers: wrap_with_bind_this(expr) and create_step_proxy_reference_maybe_bound(...).
  • Step-mode hoister checks was_arrow && !references_lexical_this to decide arrow vs. function emission.

Caveat

Capture only works at runtime if the captured this is serializable across the workflow→step boundary — i.e. the enclosing class implements WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE. Same precondition as the existing instance-method-step support. NestJS @Injectable() instances still need a separate provider rehydration path (the third option from the original issue).

Output examples

Workflow mode:

execute: globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//./input//_anonymousStep0",()=>({ context })).bind(this)

Step mode (hoisted as function, not arrow):

asyncfunction_anonymousStep0(input){const{ context }=(function(){/* closure-var IIFE */})();returnthis.service.readFileContent(input,context);}

Tests

  • 4 new SWC fixture tests (nested-arrow-step-lexical-this, nested-arrow-step-lexical-this-var-decl, both modes). Total fixture tests: 114 → 118.
  • New step.test.ts runtime test exercising useStep(stepId).bind(instance) and asserting thisVal: instance on the queue item.
  • All 843 @workflow/core tests pass.
  • pnpm typecheck clean; workbench/example builds against the rebuilt wasm.

Refs

#1865 (specifically the linked comment)

When a nested arrow `"use step"` references the enclosing function/method's
`this`, plumb that `this` through the workflow runtime so the step body
sees the correct receiver.
- Workflow mode wraps the step proxy with `.bind(this)`, so invoking the
proxy captures the caller's `this` as `thisVal` on the queue item.
- Step mode hoists the body as a regular `function` (not an arrow) so the
runtime's `stepFn.apply(thisVal, args)` rebinds `this` inside the
hoisted body.
Detection only fires for arrows, since arrows inherit `this` lexically.
Nested non-arrow functions/methods/getters/setters introduce their own
`this`, so the detector stops at those boundaries.
The runtime already supported `thisVal` for instance-method steps; this
PR is purely a compiler change to feed the existing pipeline.
Caveat: capture works at runtime only when the captured value is
serializable across the workflow->step boundary (i.e. the enclosing
class implements `WORKFLOW_SERIALIZE`/`WORKFLOW_DESERIALIZE`).
Refs #1865
CopilotAI review requested due to automatic review settings May 5, 2026 05:51
@changeset-bot

changeset-botBot commented May 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a19805f

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

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

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 May 5, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented May 5, 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.031s (-29.6% 🟢)1.006s (~)0.974s101.00x
💻 LocalNitro0.031s (-27.6% 🟢)1.005s (~)0.974s101.00x
🐘 PostgresExpress0.046s (-21.4% 🟢)1.012s (~)0.966s101.46x
💻 LocalNext.js (Turbopack)0.050s1.006s0.956s101.60x
🌐 RedisNext.js (Turbopack)0.052s1.005s0.953s101.67x
🐘 PostgresNitro0.053s (-44.3% 🟢)1.013s (-2.9%)0.960s101.70x
🌐 MongoDBNext.js (Turbopack)0.100s1.008s0.908s103.20x
🐘 PostgresNext.js (Turbopack)0.177s1.043s0.866s105.68x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro0.242s (-40.8% 🟢)2.094s (-16.5% 🟢)1.852s101.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.069s (-5.0% 🟢)2.007s (~)0.938s101.00x
💻 LocalNitro1.073s (-5.1% 🟢)2.006s (~)0.932s101.00x
🐘 PostgresExpress1.084s (-5.5% 🟢)2.010s (~)0.926s101.01x
🐘 PostgresNitro1.084s (-4.9%)2.010s (~)0.926s101.01x
🌐 RedisNext.js (Turbopack)1.111s2.007s0.895s101.04x
💻 LocalNext.js (Turbopack)1.113s2.007s0.894s101.04x
🌐 MongoDBNext.js (Turbopack)1.169s2.009s0.840s101.09x
🐘 PostgresNext.js (Turbopack)1.229s2.048s0.819s101.15x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.643s (-57.8% 🟢)3.930s (-33.5% 🟢)2.287s101.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro10.404s (-4.9%)11.021s (~)0.617s31.00x
💻 LocalExpress10.413s (-4.7%)11.022s (~)0.610s31.00x
🐘 PostgresExpress10.426s (-4.9%)11.018s (~)0.592s31.00x
🐘 PostgresNitro10.482s (-3.6%)11.020s (~)0.538s31.01x
🌐 RedisNext.js (Turbopack)10.642s11.023s0.381s31.02x
💻 LocalNext.js (Turbopack)10.680s11.024s0.344s31.03x
🌐 MongoDBNext.js (Turbopack)10.784s11.021s0.238s31.04x
🐘 PostgresNext.js (Turbopack)11.142s11.680s0.538s31.07x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro13.533s (-43.0% 🟢)15.534s (-38.2% 🟢)2.001s21.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express13.449s (-10.2% 🟢)14.027s (-6.7% 🟢)0.578s51.00x
🐘 PostgresExpress13.453s (-7.8% 🟢)14.018s (-6.7% 🟢)0.565s51.00x
💻 LocalNitro13.485s (-10.5% 🟢)14.026s (-12.5% 🟢)0.541s51.00x
🐘 PostgresNitro13.521s (-7.4% 🟢)14.023s (-6.7% 🟢)0.502s51.01x
🌐 RedisNext.js (Turbopack)14.030s14.428s0.398s51.04x
💻 LocalNext.js (Turbopack)14.125s15.030s0.905s41.05x
🌐 MongoDBNext.js (Turbopack)14.212s15.023s0.811s41.06x
🐘 PostgresNext.js (Turbopack)15.396s16.019s0.624s41.14x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro22.032s (-65.8% 🟢)24.542s (-63.2% 🟢)2.510s31.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro11.936s (-28.9% 🟢)12.147s (-28.7% 🟢)0.211s81.00x
🐘 PostgresExpress11.971s (-14.5% 🟢)12.516s (-14.2% 🟢)0.545s81.00x
💻 LocalExpress11.979s (-27.8% 🟢)12.273s (-27.9% 🟢)0.294s81.00x
🐘 PostgresNitro12.038s (-13.8% 🟢)12.766s (-10.8% 🟢)0.728s81.01x
🌐 RedisNext.js (Turbopack)13.002s13.311s0.309s71.09x
🌐 MongoDBNext.js (Turbopack)13.250s14.020s0.770s71.11x
💻 LocalNext.js (Turbopack)13.313s14.026s0.713s71.12x
🐘 PostgresNext.js (Turbopack)16.668s17.052s0.385s61.40x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro33.281s (-92.1% 🟢)35.586s (-91.6% 🟢)2.305s31.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.148s (-8.9% 🟢)2.008s (~)0.860s151.00x
🐘 PostgresNitro1.164s (-8.7% 🟢)2.007s (~)0.843s151.01x
💻 LocalExpress1.184s (-20.5% 🟢)2.006s (~)0.822s151.03x
💻 LocalNitro1.185s (-27.4% 🟢)2.006s (-3.3%)0.821s151.03x
🌐 RedisNext.js (Turbopack)1.249s2.006s0.757s151.09x
💻 LocalNext.js (Turbopack)1.307s2.006s0.699s151.14x
🐘 PostgresNext.js (Turbopack)1.785s2.202s0.417s141.55x
🌐 MongoDBNext.js (Turbopack)2.029s2.827s0.798s111.77x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.530s (-10.2% 🟢)4.390s (+1.6%)1.860s71.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.231s (-47.8% 🟢)2.009s (-33.2% 🟢)0.778s151.00x
🐘 PostgresNitro1.247s (-47.0% 🟢)2.008s (-33.3% 🟢)0.761s151.01x
🐘 PostgresNext.js (Turbopack)1.696s2.200s0.504s141.38x
💻 LocalNitro1.796s (-42.9% 🟢)2.072s (-46.7% 🟢)0.276s151.46x
💻 LocalExpress1.798s (-39.1% 🟢)2.073s (-40.0% 🟢)0.275s151.46x
💻 LocalNext.js (Turbopack)1.864s2.221s0.357s141.51x
🌐 RedisNext.js (Turbopack)2.370s3.008s0.637s101.92x
🌐 MongoDBNext.js (Turbopack)3.591s4.010s0.419s82.92x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.386s (-16.4% 🟢)5.511s (-6.9% 🟢)2.125s61.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.373s (-60.6% 🟢)2.007s (-49.9% 🟢)0.634s151.00x
🐘 PostgresNitro1.444s (-58.5% 🟢)2.008s (-49.9% 🟢)0.565s151.05x
🐘 PostgresNext.js (Turbopack)2.200s3.032s0.832s101.60x
🌐 RedisNext.js (Turbopack)3.616s4.010s0.394s82.63x
💻 LocalExpress5.169s (-38.0% 🟢)5.849s (-35.2% 🟢)0.680s63.76x
💻 LocalNitro5.416s (-35.1% 🟢)6.016s (-33.3% 🟢)0.600s53.94x
💻 LocalNext.js (Turbopack)5.970s6.414s0.444s54.35x
🌐 MongoDBNext.js (Turbopack)6.338s7.014s0.676s54.62x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro7.725s (+119.1% 🔺)10.152s (+83.5% 🔺)2.427s31.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.152s (-8.4% 🟢)2.007s (~)0.856s151.00x
🐘 PostgresNitro1.174s (-6.6% 🟢)2.009s (~)0.835s151.02x
🌐 RedisNext.js (Turbopack)1.229s2.006s0.777s151.07x
💻 LocalNitro1.325s (-29.0% 🟢)2.006s (-14.3% 🟢)0.681s151.15x
💻 LocalNext.js (Turbopack)1.353s2.007s0.654s151.17x
💻 LocalExpress1.413s (-25.4% 🟢)2.007s (-15.1% 🟢)0.594s151.23x
🐘 PostgresNext.js (Turbopack)1.630s2.184s0.554s141.42x
🌐 MongoDBNext.js (Turbopack)2.057s3.008s0.952s101.79x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.585s (+5.1% 🔺)4.490s (+7.7% 🔺)1.905s71.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.239s (-47.1% 🟢)2.008s (-33.3% 🟢)0.768s151.00x
🐘 PostgresNitro1.273s (-45.6% 🟢)2.009s (-33.3% 🟢)0.737s151.03x
🐘 PostgresNext.js (Turbopack)1.770s2.594s0.824s121.43x
💻 LocalNitro1.982s (-35.3% 🟢)2.393s (-38.4% 🟢)0.411s131.60x
💻 LocalExpress2.047s (-34.6% 🟢)2.394s (-36.4% 🟢)0.346s131.65x
💻 LocalNext.js (Turbopack)2.054s2.508s0.455s121.66x
🌐 RedisNext.js (Turbopack)2.354s3.008s0.654s101.90x
🌐 MongoDBNext.js (Turbopack)3.576s4.009s0.433s82.89x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro54.294s (+1579.3% 🔺)56.491s (+1012.7% 🔺)2.197s61.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.379s (-60.6% 🟢)2.008s (-49.9% 🟢)0.629s151.00x
🐘 PostgresNitro1.414s (-59.4% 🟢)2.008s (-49.9% 🟢)0.594s151.03x
🐘 PostgresNext.js (Turbopack)2.116s2.606s0.490s121.53x
🌐 RedisNext.js (Turbopack)3.586s4.010s0.424s82.60x
💻 LocalNitro5.772s (-36.9% 🟢)6.214s (-38.0% 🟢)0.441s54.18x
💻 LocalExpress5.890s (-33.1% 🟢)6.214s (-33.0% 🟢)0.324s54.27x
💻 LocalNext.js (Turbopack)6.126s6.618s0.492s54.44x
🌐 MongoDBNext.js (Turbopack)6.338s7.011s0.673s54.60x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.693s (+31.4% 🔺)9.252s (+35.7% 🔺)2.560s41.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.460s (-45.2% 🟢)1.024s (~)0.564s591.00x
💻 LocalNitro0.484s (-50.7% 🟢)1.021s (-6.6% 🟢)0.538s591.05x
🐘 PostgresNitro0.491s (-40.1% 🟢)1.024s (+1.8%)0.533s591.07x
💻 LocalExpress0.500s (-49.2% 🟢)1.004s (-6.7% 🟢)0.505s601.09x
🌐 RedisNext.js (Turbopack)0.619s1.004s0.386s601.35x
🌐 MongoDBNext.js (Turbopack)0.724s1.005s0.281s601.57x
💻 LocalNext.js (Turbopack)0.748s1.005s0.256s601.63x
🐘 PostgresNext.js (Turbopack)0.860s1.301s0.441s471.87x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.067s (-77.0% 🟢)6.854s (-71.5% 🟢)1.787s91.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.017s (-48.6% 🟢)1.586s (-29.8% 🟢)0.569s571.00x
🐘 PostgresNitro1.143s (-40.7% 🟢)1.986s (-5.4% 🟢)0.843s461.12x
💻 LocalExpress1.210s (-59.9% 🟢)2.005s (-44.1% 🟢)0.795s451.19x
💻 LocalNitro1.212s (-60.1% 🟢)2.006s (-46.6% 🟢)0.793s451.19x
🌐 RedisNext.js (Turbopack)1.513s2.006s0.493s451.49x
🌐 MongoDBNext.js (Turbopack)1.821s2.007s0.186s451.79x
💻 LocalNext.js (Turbopack)1.825s2.006s0.181s451.80x
🐘 PostgresNext.js (Turbopack)2.605s3.071s0.465s302.56x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro13.232s (-66.5% 🟢)15.905s (-61.5% 🟢)2.672s61.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express2.137s (-46.4% 🟢)2.604s (-40.4% 🟢)0.466s471.00x
🐘 PostgresNitro2.235s (-45.5% 🟢)3.034s (-34.1% 🟢)0.799s401.05x
💻 LocalExpress2.693s (-70.8% 🟢)3.008s (-70.0% 🟢)0.314s401.26x
💻 LocalNitro2.712s (-70.8% 🟢)3.057s (-69.5% 🟢)0.346s401.27x
🌐 RedisNext.js (Turbopack)2.959s3.135s0.176s391.38x
💻 LocalNext.js (Turbopack)3.941s4.366s0.426s281.84x
🌐 MongoDBNext.js (Turbopack)4.155s5.012s0.857s241.94x
🐘 PostgresNext.js (Turbopack)4.540s5.107s0.567s252.12x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro41.246s (-57.4% 🟢)43.425s (-55.9% 🟢)2.180s31.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.189s (-33.0% 🟢)1.005s (~)0.816s601.00x
🐘 PostgresNitro0.198s (-30.3% 🟢)1.006s (~)0.809s601.04x
🌐 RedisNext.js (Turbopack)0.242s1.004s0.763s601.28x
💻 LocalExpress0.473s (-15.7% 🟢)1.004s (~)0.532s602.50x
💻 LocalNitro0.539s (-10.9% 🟢)1.096s (+7.3% 🔺)0.557s552.85x
💻 LocalNext.js (Turbopack)0.592s1.022s0.429s593.13x
🐘 PostgresNext.js (Turbopack)0.679s1.259s0.580s483.59x
🌐 MongoDBNext.js (Turbopack)1.029s1.749s0.720s355.44x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.252s (+35.6% 🔺)4.327s (+29.1% 🔺)2.074s141.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.320s (-37.3% 🟢)1.006s (~)0.687s901.00x
🐘 PostgresNitro0.340s (-31.6% 🟢)1.007s (~)0.667s901.06x
🌐 RedisNext.js (Turbopack)0.408s1.004s0.596s901.28x
🐘 PostgresNext.js (Turbopack)0.895s1.442s0.547s632.80x
💻 LocalNitro2.133s (-16.0% 🟢)2.883s (-4.2%)0.750s326.67x
💻 LocalExpress2.304s (-8.3% 🟢)2.943s (-2.2%)0.639s317.21x
🌐 MongoDBNext.js (Turbopack)2.605s3.006s0.401s308.15x
💻 LocalNext.js (Turbopack)2.641s3.183s0.542s298.26x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro8.322s (+158.0% 🔺)10.481s (+117.4% 🔺)2.158s91.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.647s (-21.0% 🟢)1.006s (-1.1%)0.359s1201.00x
🐘 PostgresNitro0.698s (-11.6% 🟢)1.007s (~)0.308s1201.08x
🌐 RedisNext.js (Turbopack)0.762s1.004s0.242s1201.18x
🐘 PostgresNext.js (Turbopack)1.313s1.779s0.467s682.03x
🌐 MongoDBNext.js (Turbopack)5.386s6.013s0.627s208.33x
💻 LocalNitro10.093s (-9.8% 🟢)10.612s (-9.0% 🟢)0.519s1215.61x
💻 LocalExpress10.309s (-7.9% 🟢)10.864s (-9.0% 🟢)0.555s1215.94x
💻 LocalNext.js (Turbopack)11.645s12.330s0.684s1018.01x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro21.406s (+177.2% 🔺)23.498s (+150.0% 🔺)2.092s61.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.134s (+452.9% 🔺)1.998s (+100.1% 🔺)0.002s (-6.3% 🟢)2.010s (+98.7% 🔺)0.876s101.00x
💻 LocalExpress1.134s (+469.8% 🔺)2.005s (+99.6% 🔺)0.012s (+0.8%)2.020s (+98.4% 🔺)0.885s101.00x
💻 LocalNitro1.136s (+431.6% 🔺)2.005s (+99.6% 🔺)0.012s (-1.6%)2.020s (+98.2% 🔺)0.883s101.00x
🐘 PostgresNitro1.154s (+462.9% 🔺)2.002s (+100.2% 🔺)0.002s (~)2.013s (+99.0% 🔺)0.859s101.02x
💻 LocalNext.js (Turbopack)1.198s2.003s0.013s2.020s0.823s101.06x
🐘 PostgresNext.js (Turbopack)1.533s1.999s0.410s2.441s0.908s101.35x
🌐 MongoDBNext.js (Turbopack)⚠️missing-----
🌐 RedisNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.173s (-43.3% 🟢)3.567s (-32.4% 🟢)1.733s (+133.5% 🔺)5.800s (-10.5% 🟢)3.628s101.00x
▲ VercelExpress⚠️missing-----
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.509s (+139.5% 🔺)2.003s (+99.1% 🔺)0.004s (-2.5%)2.024s (+97.9% 🔺)0.515s301.00x
💻 LocalNitro1.528s (+82.1% 🔺)2.011s (+98.7% 🔺)0.009s (-5.7% 🟢)2.022s (+81.2% 🔺)0.494s301.01x
💻 LocalExpress1.530s (+102.1% 🔺)2.011s (+95.5% 🔺)0.010s (+4.5%)2.023s (+94.6% 🔺)0.493s301.01x
🐘 PostgresNitro1.576s (+152.5% 🔺)2.005s (+99.2% 🔺)0.004s (-3.3%)2.027s (+98.3% 🔺)0.451s301.04x
💻 LocalNext.js (Turbopack)1.698s2.010s0.011s2.024s0.327s301.13x
🐘 PostgresNext.js (Turbopack)2.628s3.008s0.004s3.036s0.408s201.74x
🌐 MongoDBNext.js (Turbopack)⚠️missing-----
🌐 RedisNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.770s (-80.4% 🟢)7.572s (-75.4% 🟢)0.449s (+300.9% 🔺)8.603s (-72.9% 🟢)2.833s71.00x
▲ VercelExpress⚠️missing-----
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.668s (-30.5% 🟢)1.049s (-17.9% 🟢)0.000s (-59.6% 🟢)1.060s (-18.9% 🟢)0.392s571.00x
🐘 PostgresNitro0.716s (-26.0% 🟢)1.050s (-15.8% 🟢)0.000s (-57.9% 🟢)1.068s (-15.1% 🟢)0.351s571.07x
💻 LocalExpress1.350s (+10.2% 🔺)2.015s (~)0.001s (+70.0% 🔺)2.017s (~)0.667s302.02x
💻 LocalNitro1.358s (+11.1% 🔺)2.015s (~)0.000s (+133.3% 🔺)2.017s (~)0.659s302.03x
🐘 PostgresNext.js (Turbopack)1.370s1.659s0.000s1.724s0.354s362.05x
💻 LocalNext.js (Turbopack)1.498s2.014s0.000s2.017s0.519s302.24x
🌐 MongoDBNext.js (Turbopack)⚠️missing-----
🌐 RedisNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.519s (+15.4% 🔺)5.014s (+14.1% 🔺)0.000s (-100.0% 🟢)5.521s (+14.8% 🔺)2.002s111.00x
▲ VercelExpress⚠️missing-----
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro

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

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.332s (-24.8% 🟢)1.998s (-8.3% 🟢)0.000s (+Infinity% 🔺)2.018s (-8.2% 🟢)0.686s301.00x
🐘 PostgresNitro1.458s (-18.6% 🟢)2.100s (-1.9%)0.000s (-3.4%)2.115s (-2.7%)0.656s291.09x
💻 LocalNext.js (Turbopack)2.949s3.357s0.000s3.367s0.418s182.21x
💻 LocalNitro3.144s (-7.2% 🟢)4.029s (~)0.000s (-25.0% 🟢)4.032s (~)0.888s152.36x
💻 LocalExpress3.177s (-8.4% 🟢)3.967s (-1.7%)0.000s (-84.4% 🟢)3.969s (-1.7%)0.792s162.39x
🐘 PostgresNext.js (Turbopack)3.436s3.946s0.000s4.008s0.572s152.58x
🌐 MongoDBNext.js (Turbopack)⚠️missing-----
🌐 RedisNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.077s (+48.4% 🔺)7.737s (+44.0% 🔺)0.000s (-54.2% 🟢)8.390s (+44.8% 🔺)2.313s81.00x
▲ VercelExpress⚠️missing-----
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress10/21
🐘 PostgresExpress21/21
▲ VercelNitro21/21
Fastest World by Framework

Winner determined by most benchmark wins

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

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)

📋 View full workflow run


Some benchmark jobs failed:

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

Check the workflow run for details.

@github-actions

github-actionsBot commented May 5, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production92502191144
✅ 💻 Local Development123702191456
✅ 📦 Local Production123702191456
✅ 🐘 Local Postgres123702191456
✅ 🪟 Windows10400104
✅ 📋 Other5520176728
Total5292010526344

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro78026
✅ example78026
✅ express78026
✅ fastify78026
✅ hono78026
✅ nextjs-turbopack10202
✅ nextjs-webpack10202
✅ nitro78026
✅ nuxt78026
✅ sveltekit9707
✅ vite78026
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack10400
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable79025
✅ e2e-local-dev-tanstack-start-79025
✅ e2e-local-postgres-nest-stable79025
✅ e2e-local-postgres-tanstack-start-79025
✅ e2e-local-prod-nest-stable79025
✅ e2e-local-prod-tanstack-start-79025
✅ e2e-vercel-prod-tanstack-start78026

📋 View full workflow run

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

Updates @workflow/swc-plugin to correctly preserve lexical this for nested arrow "use step" functions (common in “method returns object literal with arrow steps” patterns), by detecting this usage, binding step proxies in workflow mode, and hoisting step bodies as regular functions in step mode so runtime apply(thisVal, args) rebinding works.

Changes:

  • Add lexical-this detection for nested arrow step bodies and thread a references_lexical_this flag through nested-step tracking.
  • Emit .bind(this) on workflow-mode step proxies when lexical this is referenced; adjust step-mode hoisting to emit function instead of arrow for those steps.
  • Add SWC fixture coverage + a core runtime test; document the new behavior and ship a changeset.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
packages/swc-plugin-workflow/transform/src/lib.rsImplements lexical-this detection, proxy binding, and step-mode hoist changes.
packages/swc-plugin-workflow/spec.mdDocuments lexical this capture behavior and updates notes about this in steps.
packages/core/src/step.test.tsAdds runtime test ensuring .bind(this) captures thisVal onto the invocation queue item.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this/input.jsNew fixture input covering nested arrow step referencing this in a method.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this/output-workflow.jsExpected workflow-mode output showing .bind(this) on the step proxy.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this/output-step.jsExpected step-mode output showing hoisting as async function using this.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this-var-decl/input.jsNew fixture input for var-declarator arrow step capturing this.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this-var-decl/output-workflow.jsExpected workflow-mode output with bound proxy for var-declarator case.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this-var-decl/output-step.jsExpected step-mode output hoisting as function for var-declarator case.
.changeset/swc-lexical-this-capture.mdPatch changeset describing the new lexical-this behavior.

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

Comment threadpackages/swc-plugin-workflow/transform/src/lib.rs
Comment threadpackages/swc-plugin-workflow/transform/src/lib.rs
Comment threadpackages/swc-plugin-workflow/transform/src/lib.rs
Comment threadpackages/swc-plugin-workflow/spec.md Outdated
Comment threadpackages/core/src/step.test.ts Outdated
…ction
- core: Override `.bind` on step proxies so the bound function retains
`stepId` and `__closureVarsFn`. Without this, a bound proxy that flows
through workflow serialization (e.g. as a step argument) would be
treated as a non-serializable plain function by `getStepFunctionReducer`.
- swc-plugin: Detector now also walks `arrow.params` so `this` references
in default values / destructuring initializers (e.g. `(x = this.foo) =>
...`) trigger the `.bind(this)` path.
- swc-plugin: Class bodies inside the arrow body are now treated as
`this`-binding boundaries — `this` inside class field initializers,
methods, etc. is bound to the class instance, not the outer arrow. The
detector still walks `extends` clauses and computed property keys
because those are evaluated in the surrounding scope.
- spec.md: Sharpen the note about `this` in step bodies — it's
syntactically allowed but only meaningful for instance-method steps and
lexical-`this` arrow steps; other shapes compile but `this` will be
whatever the caller of the step proxy passes.
- Add `lexical-this-detector-edge-cases` fixture covering both the
default-param positive case and the inner-class false-positive guard.
- Strengthen the runtime test to assert `stepId` / `__closureVarsFn`
survive `.bind(...)`.
… coverage
Without this, a `useStep(...).bind(thisArg)` proxy that flows through
workflow serialization (e.g. passed as a step argument) would lose its
receiver: the reducer captured `stepId` but not the bound `this`, and
the step-bundle reviver returned the raw registered step body which
ignores any `this` the caller passes.
Now:
- step.ts `.bind` override stashes the bound value on the result as
`__boundThis` so the reducer can see it.
- The reducer serializes `boundThis` (using property presence so
`bind(null)`/`bind(undefined)` round-trip faithfully).
- The workflow-bundle reviver re-binds the freshly created proxy.
- The step-bundle reviver wraps the registered body so it's invoked
with `apply(boundThis, args)` (and still runs inside the closure-vars
AsyncLocalStorage frame when `closureVars` is present).
E2E coverage extends `instanceMethodStepWorkflow` with two new shapes:
- `counter.makeAdder(7).add(2)` — direct invocation of a lexical-`this`
arrow step. Verifies `bind(this)` carries `thisVal` to the queue and
the step body sees `this.value` correctly.
- `invokeAdderFromStep(adder.add, 3)` — passes the bound proxy as a
step argument so the round-trip path exercises both the reducer and
the step-bundle reviver, with the inner call running inline. Without
the new `boundThis` plumbing this previously failed with `Cannot read
properties of undefined (reading 'value')`.

@karthikscale3karthikscale3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AI review: Solid implementation — the LexicalThisDetector, the .bind() override, and the serialization round-trip all hang together correctly. No regressions found. The arguments fix is a genuine bug fix and the dead this/arguments error-check removal is safe. All completed CI checks pass. LGTM

Comment threadpackages/core/src/step.ts
Comment threadpackages/core/src/serialization/serialization.test.ts
…+boundThis
- step.ts `.bind` override now also stashes `__boundArgs` when the caller
supplied prefilled args (`useStep(...).bind(thisArg, x, y)`). The
reducer serializes them as `boundArgs`, and both the workflow- and
step-bundle revivers re-apply them (the workflow reviver via
`bind(boundThis, ...boundArgs)`, the step-bundle reviver by
prepending to the runtime args). The SWC plugin only ever emits
`.bind(this)` today, but this keeps partial application faithful in
case hand-written code ever calls `.bind` with extra args.
- Add two new unit tests in `serialization.test.ts`:
- `closureVars + boundThis` combo: exercises the step-bundle reviver's
inner branch (`contextStorage.run(newContext, () =>
stepFn.apply(callThis, callArgs))`) in isolation — previously only
covered end-to-end by the `instanceMethodStepWorkflow` e2e test.
- `boundArgs` round-trip: codifies that prefilled args survive
serialization.
@github-actions

Copy link
Copy Markdown
Contributor

Backport PR opened against stable: #1945. Merge conflicts were resolved by AI — please review carefully.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-stableCherry-pick this PR to the stable branch when merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

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

[swc-plugin] Capture lexical this for nested arrow step functions - #1935

Merged
TooTallNate merged 6 commits into
mainfrom
feat/swc-lexical-this-capture
May 5, 2026
Merged

[swc-plugin] Capture lexical this for nested arrow step functions#1935
TooTallNate merged 6 commits into
mainfrom
feat/swc-lexical-this-capture

Conversation

@TooTallNate

Copy link
Copy Markdown
Member

Summary

Teaches the SWC plugin to handle "use step" arrow functions whose body lexically captures this from an enclosing method/function — a pattern that comes up naturally with the AI SDK tool({...}) factory and other "method that returns an object literal containing arrow steps" shapes.

Before this PR:

classReadFileTool{// serializable...createTool(context){returntool({execute: async(input)=>{'use step';returnthis.service.readFileContent(input,context);// `this` was lost},});}}

The compiler would hoist execute to module scope as an arrow with a bare return this.service…, so at runtime this was undefined. Workflow mode produced a step proxy with no this plumbing.

After this PR:

  • Workflow mode wraps the proxy with .bind(this) so invoking it captures the caller's this as thisVal on the queue item.
  • Step mode hoists the body as a regular function (not an arrow) so the runtime's existing stepFn.apply(thisVal, args) rebinds this inside the hoisted body.

The runtime already had thisVal plumbing for instance-method steps; this PR is purely a compiler change feeding that existing pipeline.

How

  • New LexicalThisDetector (Visit) walks an arrow body looking for ThisExpr. Recurses through nested arrows but stops at Function/Constructor/MethodProp/GetterProp/SetterProp/ClassMethod/PrivateMethod/StaticBlock because those introduce their own this.
  • nested_step_functions gains a references_lexical_this: bool field, threaded through every push site.
  • Two new helpers: wrap_with_bind_this(expr) and create_step_proxy_reference_maybe_bound(...).
  • Step-mode hoister checks was_arrow && !references_lexical_this to decide arrow vs. function emission.

Caveat

Capture only works at runtime if the captured this is serializable across the workflow→step boundary — i.e. the enclosing class implements WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE. Same precondition as the existing instance-method-step support. NestJS @Injectable() instances still need a separate provider rehydration path (the third option from the original issue).

Output examples

Workflow mode:

execute: globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//./input//_anonymousStep0",()=>({ context })).bind(this)

Step mode (hoisted as function, not arrow):

asyncfunction_anonymousStep0(input){const{ context }=(function(){/* closure-var IIFE */})();returnthis.service.readFileContent(input,context);}

Tests

  • 4 new SWC fixture tests (nested-arrow-step-lexical-this, nested-arrow-step-lexical-this-var-decl, both modes). Total fixture tests: 114 → 118.
  • New step.test.ts runtime test exercising useStep(stepId).bind(instance) and asserting thisVal: instance on the queue item.
  • All 843 @workflow/core tests pass.
  • pnpm typecheck clean; workbench/example builds against the rebuilt wasm.

Refs

#1865 (specifically the linked comment)

When a nested arrow `"use step"` references the enclosing function/method's
`this`, plumb that `this` through the workflow runtime so the step body
sees the correct receiver.
- Workflow mode wraps the step proxy with `.bind(this)`, so invoking the
proxy captures the caller's `this` as `thisVal` on the queue item.
- Step mode hoists the body as a regular `function` (not an arrow) so the
runtime's `stepFn.apply(thisVal, args)` rebinds `this` inside the
hoisted body.
Detection only fires for arrows, since arrows inherit `this` lexically.
Nested non-arrow functions/methods/getters/setters introduce their own
`this`, so the detector stops at those boundaries.
The runtime already supported `thisVal` for instance-method steps; this
PR is purely a compiler change to feed the existing pipeline.
Caveat: capture works at runtime only when the captured value is
serializable across the workflow->step boundary (i.e. the enclosing
class implements `WORKFLOW_SERIALIZE`/`WORKFLOW_DESERIALIZE`).
Refs #1865
CopilotAI review requested due to automatic review settings May 5, 2026 05:51
@changeset-bot

changeset-botBot commented May 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a19805f

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

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

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 May 5, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented May 5, 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.031s (-29.6% 🟢)1.006s (~)0.974s101.00x
💻 LocalNitro0.031s (-27.6% 🟢)1.005s (~)0.974s101.00x
🐘 PostgresExpress0.046s (-21.4% 🟢)1.012s (~)0.966s101.46x
💻 LocalNext.js (Turbopack)0.050s1.006s0.956s101.60x
🌐 RedisNext.js (Turbopack)0.052s1.005s0.953s101.67x
🐘 PostgresNitro0.053s (-44.3% 🟢)1.013s (-2.9%)0.960s101.70x
🌐 MongoDBNext.js (Turbopack)0.100s1.008s0.908s103.20x
🐘 PostgresNext.js (Turbopack)0.177s1.043s0.866s105.68x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro0.242s (-40.8% 🟢)2.094s (-16.5% 🟢)1.852s101.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.069s (-5.0% 🟢)2.007s (~)0.938s101.00x
💻 LocalNitro1.073s (-5.1% 🟢)2.006s (~)0.932s101.00x
🐘 PostgresExpress1.084s (-5.5% 🟢)2.010s (~)0.926s101.01x
🐘 PostgresNitro1.084s (-4.9%)2.010s (~)0.926s101.01x
🌐 RedisNext.js (Turbopack)1.111s2.007s0.895s101.04x
💻 LocalNext.js (Turbopack)1.113s2.007s0.894s101.04x
🌐 MongoDBNext.js (Turbopack)1.169s2.009s0.840s101.09x
🐘 PostgresNext.js (Turbopack)1.229s2.048s0.819s101.15x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.643s (-57.8% 🟢)3.930s (-33.5% 🟢)2.287s101.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro10.404s (-4.9%)11.021s (~)0.617s31.00x
💻 LocalExpress10.413s (-4.7%)11.022s (~)0.610s31.00x
🐘 PostgresExpress10.426s (-4.9%)11.018s (~)0.592s31.00x
🐘 PostgresNitro10.482s (-3.6%)11.020s (~)0.538s31.01x
🌐 RedisNext.js (Turbopack)10.642s11.023s0.381s31.02x
💻 LocalNext.js (Turbopack)10.680s11.024s0.344s31.03x
🌐 MongoDBNext.js (Turbopack)10.784s11.021s0.238s31.04x
🐘 PostgresNext.js (Turbopack)11.142s11.680s0.538s31.07x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro13.533s (-43.0% 🟢)15.534s (-38.2% 🟢)2.001s21.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express13.449s (-10.2% 🟢)14.027s (-6.7% 🟢)0.578s51.00x
🐘 PostgresExpress13.453s (-7.8% 🟢)14.018s (-6.7% 🟢)0.565s51.00x
💻 LocalNitro13.485s (-10.5% 🟢)14.026s (-12.5% 🟢)0.541s51.00x
🐘 PostgresNitro13.521s (-7.4% 🟢)14.023s (-6.7% 🟢)0.502s51.01x
🌐 RedisNext.js (Turbopack)14.030s14.428s0.398s51.04x
💻 LocalNext.js (Turbopack)14.125s15.030s0.905s41.05x
🌐 MongoDBNext.js (Turbopack)14.212s15.023s0.811s41.06x
🐘 PostgresNext.js (Turbopack)15.396s16.019s0.624s41.14x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro22.032s (-65.8% 🟢)24.542s (-63.2% 🟢)2.510s31.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro11.936s (-28.9% 🟢)12.147s (-28.7% 🟢)0.211s81.00x
🐘 PostgresExpress11.971s (-14.5% 🟢)12.516s (-14.2% 🟢)0.545s81.00x
💻 LocalExpress11.979s (-27.8% 🟢)12.273s (-27.9% 🟢)0.294s81.00x
🐘 PostgresNitro12.038s (-13.8% 🟢)12.766s (-10.8% 🟢)0.728s81.01x
🌐 RedisNext.js (Turbopack)13.002s13.311s0.309s71.09x
🌐 MongoDBNext.js (Turbopack)13.250s14.020s0.770s71.11x
💻 LocalNext.js (Turbopack)13.313s14.026s0.713s71.12x
🐘 PostgresNext.js (Turbopack)16.668s17.052s0.385s61.40x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro33.281s (-92.1% 🟢)35.586s (-91.6% 🟢)2.305s31.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.148s (-8.9% 🟢)2.008s (~)0.860s151.00x
🐘 PostgresNitro1.164s (-8.7% 🟢)2.007s (~)0.843s151.01x
💻 LocalExpress1.184s (-20.5% 🟢)2.006s (~)0.822s151.03x
💻 LocalNitro1.185s (-27.4% 🟢)2.006s (-3.3%)0.821s151.03x
🌐 RedisNext.js (Turbopack)1.249s2.006s0.757s151.09x
💻 LocalNext.js (Turbopack)1.307s2.006s0.699s151.14x
🐘 PostgresNext.js (Turbopack)1.785s2.202s0.417s141.55x
🌐 MongoDBNext.js (Turbopack)2.029s2.827s0.798s111.77x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.530s (-10.2% 🟢)4.390s (+1.6%)1.860s71.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.231s (-47.8% 🟢)2.009s (-33.2% 🟢)0.778s151.00x
🐘 PostgresNitro1.247s (-47.0% 🟢)2.008s (-33.3% 🟢)0.761s151.01x
🐘 PostgresNext.js (Turbopack)1.696s2.200s0.504s141.38x
💻 LocalNitro1.796s (-42.9% 🟢)2.072s (-46.7% 🟢)0.276s151.46x
💻 LocalExpress1.798s (-39.1% 🟢)2.073s (-40.0% 🟢)0.275s151.46x
💻 LocalNext.js (Turbopack)1.864s2.221s0.357s141.51x
🌐 RedisNext.js (Turbopack)2.370s3.008s0.637s101.92x
🌐 MongoDBNext.js (Turbopack)3.591s4.010s0.419s82.92x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.386s (-16.4% 🟢)5.511s (-6.9% 🟢)2.125s61.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.373s (-60.6% 🟢)2.007s (-49.9% 🟢)0.634s151.00x
🐘 PostgresNitro1.444s (-58.5% 🟢)2.008s (-49.9% 🟢)0.565s151.05x
🐘 PostgresNext.js (Turbopack)2.200s3.032s0.832s101.60x
🌐 RedisNext.js (Turbopack)3.616s4.010s0.394s82.63x
💻 LocalExpress5.169s (-38.0% 🟢)5.849s (-35.2% 🟢)0.680s63.76x
💻 LocalNitro5.416s (-35.1% 🟢)6.016s (-33.3% 🟢)0.600s53.94x
💻 LocalNext.js (Turbopack)5.970s6.414s0.444s54.35x
🌐 MongoDBNext.js (Turbopack)6.338s7.014s0.676s54.62x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro7.725s (+119.1% 🔺)10.152s (+83.5% 🔺)2.427s31.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.152s (-8.4% 🟢)2.007s (~)0.856s151.00x
🐘 PostgresNitro1.174s (-6.6% 🟢)2.009s (~)0.835s151.02x
🌐 RedisNext.js (Turbopack)1.229s2.006s0.777s151.07x
💻 LocalNitro1.325s (-29.0% 🟢)2.006s (-14.3% 🟢)0.681s151.15x
💻 LocalNext.js (Turbopack)1.353s2.007s0.654s151.17x
💻 LocalExpress1.413s (-25.4% 🟢)2.007s (-15.1% 🟢)0.594s151.23x
🐘 PostgresNext.js (Turbopack)1.630s2.184s0.554s141.42x
🌐 MongoDBNext.js (Turbopack)2.057s3.008s0.952s101.79x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.585s (+5.1% 🔺)4.490s (+7.7% 🔺)1.905s71.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.239s (-47.1% 🟢)2.008s (-33.3% 🟢)0.768s151.00x
🐘 PostgresNitro1.273s (-45.6% 🟢)2.009s (-33.3% 🟢)0.737s151.03x
🐘 PostgresNext.js (Turbopack)1.770s2.594s0.824s121.43x
💻 LocalNitro1.982s (-35.3% 🟢)2.393s (-38.4% 🟢)0.411s131.60x
💻 LocalExpress2.047s (-34.6% 🟢)2.394s (-36.4% 🟢)0.346s131.65x
💻 LocalNext.js (Turbopack)2.054s2.508s0.455s121.66x
🌐 RedisNext.js (Turbopack)2.354s3.008s0.654s101.90x
🌐 MongoDBNext.js (Turbopack)3.576s4.009s0.433s82.89x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro54.294s (+1579.3% 🔺)56.491s (+1012.7% 🔺)2.197s61.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.379s (-60.6% 🟢)2.008s (-49.9% 🟢)0.629s151.00x
🐘 PostgresNitro1.414s (-59.4% 🟢)2.008s (-49.9% 🟢)0.594s151.03x
🐘 PostgresNext.js (Turbopack)2.116s2.606s0.490s121.53x
🌐 RedisNext.js (Turbopack)3.586s4.010s0.424s82.60x
💻 LocalNitro5.772s (-36.9% 🟢)6.214s (-38.0% 🟢)0.441s54.18x
💻 LocalExpress5.890s (-33.1% 🟢)6.214s (-33.0% 🟢)0.324s54.27x
💻 LocalNext.js (Turbopack)6.126s6.618s0.492s54.44x
🌐 MongoDBNext.js (Turbopack)6.338s7.011s0.673s54.60x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.693s (+31.4% 🔺)9.252s (+35.7% 🔺)2.560s41.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.460s (-45.2% 🟢)1.024s (~)0.564s591.00x
💻 LocalNitro0.484s (-50.7% 🟢)1.021s (-6.6% 🟢)0.538s591.05x
🐘 PostgresNitro0.491s (-40.1% 🟢)1.024s (+1.8%)0.533s591.07x
💻 LocalExpress0.500s (-49.2% 🟢)1.004s (-6.7% 🟢)0.505s601.09x
🌐 RedisNext.js (Turbopack)0.619s1.004s0.386s601.35x
🌐 MongoDBNext.js (Turbopack)0.724s1.005s0.281s601.57x
💻 LocalNext.js (Turbopack)0.748s1.005s0.256s601.63x
🐘 PostgresNext.js (Turbopack)0.860s1.301s0.441s471.87x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.067s (-77.0% 🟢)6.854s (-71.5% 🟢)1.787s91.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.017s (-48.6% 🟢)1.586s (-29.8% 🟢)0.569s571.00x
🐘 PostgresNitro1.143s (-40.7% 🟢)1.986s (-5.4% 🟢)0.843s461.12x
💻 LocalExpress1.210s (-59.9% 🟢)2.005s (-44.1% 🟢)0.795s451.19x
💻 LocalNitro1.212s (-60.1% 🟢)2.006s (-46.6% 🟢)0.793s451.19x
🌐 RedisNext.js (Turbopack)1.513s2.006s0.493s451.49x
🌐 MongoDBNext.js (Turbopack)1.821s2.007s0.186s451.79x
💻 LocalNext.js (Turbopack)1.825s2.006s0.181s451.80x
🐘 PostgresNext.js (Turbopack)2.605s3.071s0.465s302.56x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro13.232s (-66.5% 🟢)15.905s (-61.5% 🟢)2.672s61.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express2.137s (-46.4% 🟢)2.604s (-40.4% 🟢)0.466s471.00x
🐘 PostgresNitro2.235s (-45.5% 🟢)3.034s (-34.1% 🟢)0.799s401.05x
💻 LocalExpress2.693s (-70.8% 🟢)3.008s (-70.0% 🟢)0.314s401.26x
💻 LocalNitro2.712s (-70.8% 🟢)3.057s (-69.5% 🟢)0.346s401.27x
🌐 RedisNext.js (Turbopack)2.959s3.135s0.176s391.38x
💻 LocalNext.js (Turbopack)3.941s4.366s0.426s281.84x
🌐 MongoDBNext.js (Turbopack)4.155s5.012s0.857s241.94x
🐘 PostgresNext.js (Turbopack)4.540s5.107s0.567s252.12x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro41.246s (-57.4% 🟢)43.425s (-55.9% 🟢)2.180s31.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.189s (-33.0% 🟢)1.005s (~)0.816s601.00x
🐘 PostgresNitro0.198s (-30.3% 🟢)1.006s (~)0.809s601.04x
🌐 RedisNext.js (Turbopack)0.242s1.004s0.763s601.28x
💻 LocalExpress0.473s (-15.7% 🟢)1.004s (~)0.532s602.50x
💻 LocalNitro0.539s (-10.9% 🟢)1.096s (+7.3% 🔺)0.557s552.85x
💻 LocalNext.js (Turbopack)0.592s1.022s0.429s593.13x
🐘 PostgresNext.js (Turbopack)0.679s1.259s0.580s483.59x
🌐 MongoDBNext.js (Turbopack)1.029s1.749s0.720s355.44x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.252s (+35.6% 🔺)4.327s (+29.1% 🔺)2.074s141.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.320s (-37.3% 🟢)1.006s (~)0.687s901.00x
🐘 PostgresNitro0.340s (-31.6% 🟢)1.007s (~)0.667s901.06x
🌐 RedisNext.js (Turbopack)0.408s1.004s0.596s901.28x
🐘 PostgresNext.js (Turbopack)0.895s1.442s0.547s632.80x
💻 LocalNitro2.133s (-16.0% 🟢)2.883s (-4.2%)0.750s326.67x
💻 LocalExpress2.304s (-8.3% 🟢)2.943s (-2.2%)0.639s317.21x
🌐 MongoDBNext.js (Turbopack)2.605s3.006s0.401s308.15x
💻 LocalNext.js (Turbopack)2.641s3.183s0.542s298.26x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro8.322s (+158.0% 🔺)10.481s (+117.4% 🔺)2.158s91.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.647s (-21.0% 🟢)1.006s (-1.1%)0.359s1201.00x
🐘 PostgresNitro0.698s (-11.6% 🟢)1.007s (~)0.308s1201.08x
🌐 RedisNext.js (Turbopack)0.762s1.004s0.242s1201.18x
🐘 PostgresNext.js (Turbopack)1.313s1.779s0.467s682.03x
🌐 MongoDBNext.js (Turbopack)5.386s6.013s0.627s208.33x
💻 LocalNitro10.093s (-9.8% 🟢)10.612s (-9.0% 🟢)0.519s1215.61x
💻 LocalExpress10.309s (-7.9% 🟢)10.864s (-9.0% 🟢)0.555s1215.94x
💻 LocalNext.js (Turbopack)11.645s12.330s0.684s1018.01x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro21.406s (+177.2% 🔺)23.498s (+150.0% 🔺)2.092s61.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.134s (+452.9% 🔺)1.998s (+100.1% 🔺)0.002s (-6.3% 🟢)2.010s (+98.7% 🔺)0.876s101.00x
💻 LocalExpress1.134s (+469.8% 🔺)2.005s (+99.6% 🔺)0.012s (+0.8%)2.020s (+98.4% 🔺)0.885s101.00x
💻 LocalNitro1.136s (+431.6% 🔺)2.005s (+99.6% 🔺)0.012s (-1.6%)2.020s (+98.2% 🔺)0.883s101.00x
🐘 PostgresNitro1.154s (+462.9% 🔺)2.002s (+100.2% 🔺)0.002s (~)2.013s (+99.0% 🔺)0.859s101.02x
💻 LocalNext.js (Turbopack)1.198s2.003s0.013s2.020s0.823s101.06x
🐘 PostgresNext.js (Turbopack)1.533s1.999s0.410s2.441s0.908s101.35x
🌐 MongoDBNext.js (Turbopack)⚠️missing-----
🌐 RedisNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.173s (-43.3% 🟢)3.567s (-32.4% 🟢)1.733s (+133.5% 🔺)5.800s (-10.5% 🟢)3.628s101.00x
▲ VercelExpress⚠️missing-----
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.509s (+139.5% 🔺)2.003s (+99.1% 🔺)0.004s (-2.5%)2.024s (+97.9% 🔺)0.515s301.00x
💻 LocalNitro1.528s (+82.1% 🔺)2.011s (+98.7% 🔺)0.009s (-5.7% 🟢)2.022s (+81.2% 🔺)0.494s301.01x
💻 LocalExpress1.530s (+102.1% 🔺)2.011s (+95.5% 🔺)0.010s (+4.5%)2.023s (+94.6% 🔺)0.493s301.01x
🐘 PostgresNitro1.576s (+152.5% 🔺)2.005s (+99.2% 🔺)0.004s (-3.3%)2.027s (+98.3% 🔺)0.451s301.04x
💻 LocalNext.js (Turbopack)1.698s2.010s0.011s2.024s0.327s301.13x
🐘 PostgresNext.js (Turbopack)2.628s3.008s0.004s3.036s0.408s201.74x
🌐 MongoDBNext.js (Turbopack)⚠️missing-----
🌐 RedisNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.770s (-80.4% 🟢)7.572s (-75.4% 🟢)0.449s (+300.9% 🔺)8.603s (-72.9% 🟢)2.833s71.00x
▲ VercelExpress⚠️missing-----
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.668s (-30.5% 🟢)1.049s (-17.9% 🟢)0.000s (-59.6% 🟢)1.060s (-18.9% 🟢)0.392s571.00x
🐘 PostgresNitro0.716s (-26.0% 🟢)1.050s (-15.8% 🟢)0.000s (-57.9% 🟢)1.068s (-15.1% 🟢)0.351s571.07x
💻 LocalExpress1.350s (+10.2% 🔺)2.015s (~)0.001s (+70.0% 🔺)2.017s (~)0.667s302.02x
💻 LocalNitro1.358s (+11.1% 🔺)2.015s (~)0.000s (+133.3% 🔺)2.017s (~)0.659s302.03x
🐘 PostgresNext.js (Turbopack)1.370s1.659s0.000s1.724s0.354s362.05x
💻 LocalNext.js (Turbopack)1.498s2.014s0.000s2.017s0.519s302.24x
🌐 MongoDBNext.js (Turbopack)⚠️missing-----
🌐 RedisNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.519s (+15.4% 🔺)5.014s (+14.1% 🔺)0.000s (-100.0% 🟢)5.521s (+14.8% 🔺)2.002s111.00x
▲ VercelExpress⚠️missing-----
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro

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

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.332s (-24.8% 🟢)1.998s (-8.3% 🟢)0.000s (+Infinity% 🔺)2.018s (-8.2% 🟢)0.686s301.00x
🐘 PostgresNitro1.458s (-18.6% 🟢)2.100s (-1.9%)0.000s (-3.4%)2.115s (-2.7%)0.656s291.09x
💻 LocalNext.js (Turbopack)2.949s3.357s0.000s3.367s0.418s182.21x
💻 LocalNitro3.144s (-7.2% 🟢)4.029s (~)0.000s (-25.0% 🟢)4.032s (~)0.888s152.36x
💻 LocalExpress3.177s (-8.4% 🟢)3.967s (-1.7%)0.000s (-84.4% 🟢)3.969s (-1.7%)0.792s162.39x
🐘 PostgresNext.js (Turbopack)3.436s3.946s0.000s4.008s0.572s152.58x
🌐 MongoDBNext.js (Turbopack)⚠️missing-----
🌐 RedisNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.077s (+48.4% 🔺)7.737s (+44.0% 🔺)0.000s (-54.2% 🟢)8.390s (+44.8% 🔺)2.313s81.00x
▲ VercelExpress⚠️missing-----
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress10/21
🐘 PostgresExpress21/21
▲ VercelNitro21/21
Fastest World by Framework

Winner determined by most benchmark wins

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

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)

📋 View full workflow run


Some benchmark jobs failed:

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

Check the workflow run for details.

@github-actions

github-actionsBot commented May 5, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production92502191144
✅ 💻 Local Development123702191456
✅ 📦 Local Production123702191456
✅ 🐘 Local Postgres123702191456
✅ 🪟 Windows10400104
✅ 📋 Other5520176728
Total5292010526344

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro78026
✅ example78026
✅ express78026
✅ fastify78026
✅ hono78026
✅ nextjs-turbopack10202
✅ nextjs-webpack10202
✅ nitro78026
✅ nuxt78026
✅ sveltekit9707
✅ vite78026
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack10400
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable79025
✅ e2e-local-dev-tanstack-start-79025
✅ e2e-local-postgres-nest-stable79025
✅ e2e-local-postgres-tanstack-start-79025
✅ e2e-local-prod-nest-stable79025
✅ e2e-local-prod-tanstack-start-79025
✅ e2e-vercel-prod-tanstack-start78026

📋 View full workflow run

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

Updates @workflow/swc-plugin to correctly preserve lexical this for nested arrow "use step" functions (common in “method returns object literal with arrow steps” patterns), by detecting this usage, binding step proxies in workflow mode, and hoisting step bodies as regular functions in step mode so runtime apply(thisVal, args) rebinding works.

Changes:

  • Add lexical-this detection for nested arrow step bodies and thread a references_lexical_this flag through nested-step tracking.
  • Emit .bind(this) on workflow-mode step proxies when lexical this is referenced; adjust step-mode hoisting to emit function instead of arrow for those steps.
  • Add SWC fixture coverage + a core runtime test; document the new behavior and ship a changeset.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
packages/swc-plugin-workflow/transform/src/lib.rsImplements lexical-this detection, proxy binding, and step-mode hoist changes.
packages/swc-plugin-workflow/spec.mdDocuments lexical this capture behavior and updates notes about this in steps.
packages/core/src/step.test.tsAdds runtime test ensuring .bind(this) captures thisVal onto the invocation queue item.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this/input.jsNew fixture input covering nested arrow step referencing this in a method.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this/output-workflow.jsExpected workflow-mode output showing .bind(this) on the step proxy.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this/output-step.jsExpected step-mode output showing hoisting as async function using this.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this-var-decl/input.jsNew fixture input for var-declarator arrow step capturing this.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this-var-decl/output-workflow.jsExpected workflow-mode output with bound proxy for var-declarator case.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this-var-decl/output-step.jsExpected step-mode output hoisting as function for var-declarator case.
.changeset/swc-lexical-this-capture.mdPatch changeset describing the new lexical-this behavior.

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

Comment threadpackages/swc-plugin-workflow/transform/src/lib.rs
Comment threadpackages/swc-plugin-workflow/transform/src/lib.rs
Comment threadpackages/swc-plugin-workflow/transform/src/lib.rs
Comment threadpackages/swc-plugin-workflow/spec.md Outdated
Comment threadpackages/core/src/step.test.ts Outdated
…ction
- core: Override `.bind` on step proxies so the bound function retains
`stepId` and `__closureVarsFn`. Without this, a bound proxy that flows
through workflow serialization (e.g. as a step argument) would be
treated as a non-serializable plain function by `getStepFunctionReducer`.
- swc-plugin: Detector now also walks `arrow.params` so `this` references
in default values / destructuring initializers (e.g. `(x = this.foo) =>
...`) trigger the `.bind(this)` path.
- swc-plugin: Class bodies inside the arrow body are now treated as
`this`-binding boundaries — `this` inside class field initializers,
methods, etc. is bound to the class instance, not the outer arrow. The
detector still walks `extends` clauses and computed property keys
because those are evaluated in the surrounding scope.
- spec.md: Sharpen the note about `this` in step bodies — it's
syntactically allowed but only meaningful for instance-method steps and
lexical-`this` arrow steps; other shapes compile but `this` will be
whatever the caller of the step proxy passes.
- Add `lexical-this-detector-edge-cases` fixture covering both the
default-param positive case and the inner-class false-positive guard.
- Strengthen the runtime test to assert `stepId` / `__closureVarsFn`
survive `.bind(...)`.
… coverage
Without this, a `useStep(...).bind(thisArg)` proxy that flows through
workflow serialization (e.g. passed as a step argument) would lose its
receiver: the reducer captured `stepId` but not the bound `this`, and
the step-bundle reviver returned the raw registered step body which
ignores any `this` the caller passes.
Now:
- step.ts `.bind` override stashes the bound value on the result as
`__boundThis` so the reducer can see it.
- The reducer serializes `boundThis` (using property presence so
`bind(null)`/`bind(undefined)` round-trip faithfully).
- The workflow-bundle reviver re-binds the freshly created proxy.
- The step-bundle reviver wraps the registered body so it's invoked
with `apply(boundThis, args)` (and still runs inside the closure-vars
AsyncLocalStorage frame when `closureVars` is present).
E2E coverage extends `instanceMethodStepWorkflow` with two new shapes:
- `counter.makeAdder(7).add(2)` — direct invocation of a lexical-`this`
arrow step. Verifies `bind(this)` carries `thisVal` to the queue and
the step body sees `this.value` correctly.
- `invokeAdderFromStep(adder.add, 3)` — passes the bound proxy as a
step argument so the round-trip path exercises both the reducer and
the step-bundle reviver, with the inner call running inline. Without
the new `boundThis` plumbing this previously failed with `Cannot read
properties of undefined (reading 'value')`.

@karthikscale3karthikscale3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AI review: Solid implementation — the LexicalThisDetector, the .bind() override, and the serialization round-trip all hang together correctly. No regressions found. The arguments fix is a genuine bug fix and the dead this/arguments error-check removal is safe. All completed CI checks pass. LGTM

Comment threadpackages/core/src/step.ts
Comment threadpackages/core/src/serialization/serialization.test.ts
…+boundThis
- step.ts `.bind` override now also stashes `__boundArgs` when the caller
supplied prefilled args (`useStep(...).bind(thisArg, x, y)`). The
reducer serializes them as `boundArgs`, and both the workflow- and
step-bundle revivers re-apply them (the workflow reviver via
`bind(boundThis, ...boundArgs)`, the step-bundle reviver by
prepending to the runtime args). The SWC plugin only ever emits
`.bind(this)` today, but this keeps partial application faithful in
case hand-written code ever calls `.bind` with extra args.
- Add two new unit tests in `serialization.test.ts`:
- `closureVars + boundThis` combo: exercises the step-bundle reviver's
inner branch (`contextStorage.run(newContext, () =>
stepFn.apply(callThis, callArgs))`) in isolation — previously only
covered end-to-end by the `instanceMethodStepWorkflow` e2e test.
- `boundArgs` round-trip: codifies that prefilled args survive
serialization.
@github-actions

Copy link
Copy Markdown
Contributor

Backport PR opened against stable: #1945. Merge conflicts were resolved by AI — please review carefully.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-stableCherry-pick this PR to the stable branch when merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

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

[swc-plugin] Capture lexical this for nested arrow step functions - #1935

Merged
TooTallNate merged 6 commits into
mainfrom
feat/swc-lexical-this-capture
May 5, 2026
Merged

[swc-plugin] Capture lexical this for nested arrow step functions#1935
TooTallNate merged 6 commits into
mainfrom
feat/swc-lexical-this-capture

Conversation

@TooTallNate

Copy link
Copy Markdown
Member

Summary

Teaches the SWC plugin to handle "use step" arrow functions whose body lexically captures this from an enclosing method/function — a pattern that comes up naturally with the AI SDK tool({...}) factory and other "method that returns an object literal containing arrow steps" shapes.

Before this PR:

classReadFileTool{// serializable...createTool(context){returntool({execute: async(input)=>{'use step';returnthis.service.readFileContent(input,context);// `this` was lost},});}}

The compiler would hoist execute to module scope as an arrow with a bare return this.service…, so at runtime this was undefined. Workflow mode produced a step proxy with no this plumbing.

After this PR:

  • Workflow mode wraps the proxy with .bind(this) so invoking it captures the caller's this as thisVal on the queue item.
  • Step mode hoists the body as a regular function (not an arrow) so the runtime's existing stepFn.apply(thisVal, args) rebinds this inside the hoisted body.

The runtime already had thisVal plumbing for instance-method steps; this PR is purely a compiler change feeding that existing pipeline.

How

  • New LexicalThisDetector (Visit) walks an arrow body looking for ThisExpr. Recurses through nested arrows but stops at Function/Constructor/MethodProp/GetterProp/SetterProp/ClassMethod/PrivateMethod/StaticBlock because those introduce their own this.
  • nested_step_functions gains a references_lexical_this: bool field, threaded through every push site.
  • Two new helpers: wrap_with_bind_this(expr) and create_step_proxy_reference_maybe_bound(...).
  • Step-mode hoister checks was_arrow && !references_lexical_this to decide arrow vs. function emission.

Caveat

Capture only works at runtime if the captured this is serializable across the workflow→step boundary — i.e. the enclosing class implements WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE. Same precondition as the existing instance-method-step support. NestJS @Injectable() instances still need a separate provider rehydration path (the third option from the original issue).

Output examples

Workflow mode:

execute: globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//./input//_anonymousStep0",()=>({ context })).bind(this)

Step mode (hoisted as function, not arrow):

asyncfunction_anonymousStep0(input){const{ context }=(function(){/* closure-var IIFE */})();returnthis.service.readFileContent(input,context);}

Tests

  • 4 new SWC fixture tests (nested-arrow-step-lexical-this, nested-arrow-step-lexical-this-var-decl, both modes). Total fixture tests: 114 → 118.
  • New step.test.ts runtime test exercising useStep(stepId).bind(instance) and asserting thisVal: instance on the queue item.
  • All 843 @workflow/core tests pass.
  • pnpm typecheck clean; workbench/example builds against the rebuilt wasm.

Refs

#1865 (specifically the linked comment)

When a nested arrow `"use step"` references the enclosing function/method's
`this`, plumb that `this` through the workflow runtime so the step body
sees the correct receiver.
- Workflow mode wraps the step proxy with `.bind(this)`, so invoking the
proxy captures the caller's `this` as `thisVal` on the queue item.
- Step mode hoists the body as a regular `function` (not an arrow) so the
runtime's `stepFn.apply(thisVal, args)` rebinds `this` inside the
hoisted body.
Detection only fires for arrows, since arrows inherit `this` lexically.
Nested non-arrow functions/methods/getters/setters introduce their own
`this`, so the detector stops at those boundaries.
The runtime already supported `thisVal` for instance-method steps; this
PR is purely a compiler change to feed the existing pipeline.
Caveat: capture works at runtime only when the captured value is
serializable across the workflow->step boundary (i.e. the enclosing
class implements `WORKFLOW_SERIALIZE`/`WORKFLOW_DESERIALIZE`).
Refs #1865
CopilotAI review requested due to automatic review settings May 5, 2026 05:51
@changeset-bot

changeset-botBot commented May 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a19805f

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

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

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 May 5, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented May 5, 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.031s (-29.6% 🟢)1.006s (~)0.974s101.00x
💻 LocalNitro0.031s (-27.6% 🟢)1.005s (~)0.974s101.00x
🐘 PostgresExpress0.046s (-21.4% 🟢)1.012s (~)0.966s101.46x
💻 LocalNext.js (Turbopack)0.050s1.006s0.956s101.60x
🌐 RedisNext.js (Turbopack)0.052s1.005s0.953s101.67x
🐘 PostgresNitro0.053s (-44.3% 🟢)1.013s (-2.9%)0.960s101.70x
🌐 MongoDBNext.js (Turbopack)0.100s1.008s0.908s103.20x
🐘 PostgresNext.js (Turbopack)0.177s1.043s0.866s105.68x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro0.242s (-40.8% 🟢)2.094s (-16.5% 🟢)1.852s101.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.069s (-5.0% 🟢)2.007s (~)0.938s101.00x
💻 LocalNitro1.073s (-5.1% 🟢)2.006s (~)0.932s101.00x
🐘 PostgresExpress1.084s (-5.5% 🟢)2.010s (~)0.926s101.01x
🐘 PostgresNitro1.084s (-4.9%)2.010s (~)0.926s101.01x
🌐 RedisNext.js (Turbopack)1.111s2.007s0.895s101.04x
💻 LocalNext.js (Turbopack)1.113s2.007s0.894s101.04x
🌐 MongoDBNext.js (Turbopack)1.169s2.009s0.840s101.09x
🐘 PostgresNext.js (Turbopack)1.229s2.048s0.819s101.15x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.643s (-57.8% 🟢)3.930s (-33.5% 🟢)2.287s101.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro10.404s (-4.9%)11.021s (~)0.617s31.00x
💻 LocalExpress10.413s (-4.7%)11.022s (~)0.610s31.00x
🐘 PostgresExpress10.426s (-4.9%)11.018s (~)0.592s31.00x
🐘 PostgresNitro10.482s (-3.6%)11.020s (~)0.538s31.01x
🌐 RedisNext.js (Turbopack)10.642s11.023s0.381s31.02x
💻 LocalNext.js (Turbopack)10.680s11.024s0.344s31.03x
🌐 MongoDBNext.js (Turbopack)10.784s11.021s0.238s31.04x
🐘 PostgresNext.js (Turbopack)11.142s11.680s0.538s31.07x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro13.533s (-43.0% 🟢)15.534s (-38.2% 🟢)2.001s21.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express13.449s (-10.2% 🟢)14.027s (-6.7% 🟢)0.578s51.00x
🐘 PostgresExpress13.453s (-7.8% 🟢)14.018s (-6.7% 🟢)0.565s51.00x
💻 LocalNitro13.485s (-10.5% 🟢)14.026s (-12.5% 🟢)0.541s51.00x
🐘 PostgresNitro13.521s (-7.4% 🟢)14.023s (-6.7% 🟢)0.502s51.01x
🌐 RedisNext.js (Turbopack)14.030s14.428s0.398s51.04x
💻 LocalNext.js (Turbopack)14.125s15.030s0.905s41.05x
🌐 MongoDBNext.js (Turbopack)14.212s15.023s0.811s41.06x
🐘 PostgresNext.js (Turbopack)15.396s16.019s0.624s41.14x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro22.032s (-65.8% 🟢)24.542s (-63.2% 🟢)2.510s31.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro11.936s (-28.9% 🟢)12.147s (-28.7% 🟢)0.211s81.00x
🐘 PostgresExpress11.971s (-14.5% 🟢)12.516s (-14.2% 🟢)0.545s81.00x
💻 LocalExpress11.979s (-27.8% 🟢)12.273s (-27.9% 🟢)0.294s81.00x
🐘 PostgresNitro12.038s (-13.8% 🟢)12.766s (-10.8% 🟢)0.728s81.01x
🌐 RedisNext.js (Turbopack)13.002s13.311s0.309s71.09x
🌐 MongoDBNext.js (Turbopack)13.250s14.020s0.770s71.11x
💻 LocalNext.js (Turbopack)13.313s14.026s0.713s71.12x
🐘 PostgresNext.js (Turbopack)16.668s17.052s0.385s61.40x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro33.281s (-92.1% 🟢)35.586s (-91.6% 🟢)2.305s31.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.148s (-8.9% 🟢)2.008s (~)0.860s151.00x
🐘 PostgresNitro1.164s (-8.7% 🟢)2.007s (~)0.843s151.01x
💻 LocalExpress1.184s (-20.5% 🟢)2.006s (~)0.822s151.03x
💻 LocalNitro1.185s (-27.4% 🟢)2.006s (-3.3%)0.821s151.03x
🌐 RedisNext.js (Turbopack)1.249s2.006s0.757s151.09x
💻 LocalNext.js (Turbopack)1.307s2.006s0.699s151.14x
🐘 PostgresNext.js (Turbopack)1.785s2.202s0.417s141.55x
🌐 MongoDBNext.js (Turbopack)2.029s2.827s0.798s111.77x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.530s (-10.2% 🟢)4.390s (+1.6%)1.860s71.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.231s (-47.8% 🟢)2.009s (-33.2% 🟢)0.778s151.00x
🐘 PostgresNitro1.247s (-47.0% 🟢)2.008s (-33.3% 🟢)0.761s151.01x
🐘 PostgresNext.js (Turbopack)1.696s2.200s0.504s141.38x
💻 LocalNitro1.796s (-42.9% 🟢)2.072s (-46.7% 🟢)0.276s151.46x
💻 LocalExpress1.798s (-39.1% 🟢)2.073s (-40.0% 🟢)0.275s151.46x
💻 LocalNext.js (Turbopack)1.864s2.221s0.357s141.51x
🌐 RedisNext.js (Turbopack)2.370s3.008s0.637s101.92x
🌐 MongoDBNext.js (Turbopack)3.591s4.010s0.419s82.92x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.386s (-16.4% 🟢)5.511s (-6.9% 🟢)2.125s61.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.373s (-60.6% 🟢)2.007s (-49.9% 🟢)0.634s151.00x
🐘 PostgresNitro1.444s (-58.5% 🟢)2.008s (-49.9% 🟢)0.565s151.05x
🐘 PostgresNext.js (Turbopack)2.200s3.032s0.832s101.60x
🌐 RedisNext.js (Turbopack)3.616s4.010s0.394s82.63x
💻 LocalExpress5.169s (-38.0% 🟢)5.849s (-35.2% 🟢)0.680s63.76x
💻 LocalNitro5.416s (-35.1% 🟢)6.016s (-33.3% 🟢)0.600s53.94x
💻 LocalNext.js (Turbopack)5.970s6.414s0.444s54.35x
🌐 MongoDBNext.js (Turbopack)6.338s7.014s0.676s54.62x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro7.725s (+119.1% 🔺)10.152s (+83.5% 🔺)2.427s31.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.152s (-8.4% 🟢)2.007s (~)0.856s151.00x
🐘 PostgresNitro1.174s (-6.6% 🟢)2.009s (~)0.835s151.02x
🌐 RedisNext.js (Turbopack)1.229s2.006s0.777s151.07x
💻 LocalNitro1.325s (-29.0% 🟢)2.006s (-14.3% 🟢)0.681s151.15x
💻 LocalNext.js (Turbopack)1.353s2.007s0.654s151.17x
💻 LocalExpress1.413s (-25.4% 🟢)2.007s (-15.1% 🟢)0.594s151.23x
🐘 PostgresNext.js (Turbopack)1.630s2.184s0.554s141.42x
🌐 MongoDBNext.js (Turbopack)2.057s3.008s0.952s101.79x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.585s (+5.1% 🔺)4.490s (+7.7% 🔺)1.905s71.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.239s (-47.1% 🟢)2.008s (-33.3% 🟢)0.768s151.00x
🐘 PostgresNitro1.273s (-45.6% 🟢)2.009s (-33.3% 🟢)0.737s151.03x
🐘 PostgresNext.js (Turbopack)1.770s2.594s0.824s121.43x
💻 LocalNitro1.982s (-35.3% 🟢)2.393s (-38.4% 🟢)0.411s131.60x
💻 LocalExpress2.047s (-34.6% 🟢)2.394s (-36.4% 🟢)0.346s131.65x
💻 LocalNext.js (Turbopack)2.054s2.508s0.455s121.66x
🌐 RedisNext.js (Turbopack)2.354s3.008s0.654s101.90x
🌐 MongoDBNext.js (Turbopack)3.576s4.009s0.433s82.89x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro54.294s (+1579.3% 🔺)56.491s (+1012.7% 🔺)2.197s61.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.379s (-60.6% 🟢)2.008s (-49.9% 🟢)0.629s151.00x
🐘 PostgresNitro1.414s (-59.4% 🟢)2.008s (-49.9% 🟢)0.594s151.03x
🐘 PostgresNext.js (Turbopack)2.116s2.606s0.490s121.53x
🌐 RedisNext.js (Turbopack)3.586s4.010s0.424s82.60x
💻 LocalNitro5.772s (-36.9% 🟢)6.214s (-38.0% 🟢)0.441s54.18x
💻 LocalExpress5.890s (-33.1% 🟢)6.214s (-33.0% 🟢)0.324s54.27x
💻 LocalNext.js (Turbopack)6.126s6.618s0.492s54.44x
🌐 MongoDBNext.js (Turbopack)6.338s7.011s0.673s54.60x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.693s (+31.4% 🔺)9.252s (+35.7% 🔺)2.560s41.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.460s (-45.2% 🟢)1.024s (~)0.564s591.00x
💻 LocalNitro0.484s (-50.7% 🟢)1.021s (-6.6% 🟢)0.538s591.05x
🐘 PostgresNitro0.491s (-40.1% 🟢)1.024s (+1.8%)0.533s591.07x
💻 LocalExpress0.500s (-49.2% 🟢)1.004s (-6.7% 🟢)0.505s601.09x
🌐 RedisNext.js (Turbopack)0.619s1.004s0.386s601.35x
🌐 MongoDBNext.js (Turbopack)0.724s1.005s0.281s601.57x
💻 LocalNext.js (Turbopack)0.748s1.005s0.256s601.63x
🐘 PostgresNext.js (Turbopack)0.860s1.301s0.441s471.87x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.067s (-77.0% 🟢)6.854s (-71.5% 🟢)1.787s91.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.017s (-48.6% 🟢)1.586s (-29.8% 🟢)0.569s571.00x
🐘 PostgresNitro1.143s (-40.7% 🟢)1.986s (-5.4% 🟢)0.843s461.12x
💻 LocalExpress1.210s (-59.9% 🟢)2.005s (-44.1% 🟢)0.795s451.19x
💻 LocalNitro1.212s (-60.1% 🟢)2.006s (-46.6% 🟢)0.793s451.19x
🌐 RedisNext.js (Turbopack)1.513s2.006s0.493s451.49x
🌐 MongoDBNext.js (Turbopack)1.821s2.007s0.186s451.79x
💻 LocalNext.js (Turbopack)1.825s2.006s0.181s451.80x
🐘 PostgresNext.js (Turbopack)2.605s3.071s0.465s302.56x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro13.232s (-66.5% 🟢)15.905s (-61.5% 🟢)2.672s61.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express2.137s (-46.4% 🟢)2.604s (-40.4% 🟢)0.466s471.00x
🐘 PostgresNitro2.235s (-45.5% 🟢)3.034s (-34.1% 🟢)0.799s401.05x
💻 LocalExpress2.693s (-70.8% 🟢)3.008s (-70.0% 🟢)0.314s401.26x
💻 LocalNitro2.712s (-70.8% 🟢)3.057s (-69.5% 🟢)0.346s401.27x
🌐 RedisNext.js (Turbopack)2.959s3.135s0.176s391.38x
💻 LocalNext.js (Turbopack)3.941s4.366s0.426s281.84x
🌐 MongoDBNext.js (Turbopack)4.155s5.012s0.857s241.94x
🐘 PostgresNext.js (Turbopack)4.540s5.107s0.567s252.12x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro41.246s (-57.4% 🟢)43.425s (-55.9% 🟢)2.180s31.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.189s (-33.0% 🟢)1.005s (~)0.816s601.00x
🐘 PostgresNitro0.198s (-30.3% 🟢)1.006s (~)0.809s601.04x
🌐 RedisNext.js (Turbopack)0.242s1.004s0.763s601.28x
💻 LocalExpress0.473s (-15.7% 🟢)1.004s (~)0.532s602.50x
💻 LocalNitro0.539s (-10.9% 🟢)1.096s (+7.3% 🔺)0.557s552.85x
💻 LocalNext.js (Turbopack)0.592s1.022s0.429s593.13x
🐘 PostgresNext.js (Turbopack)0.679s1.259s0.580s483.59x
🌐 MongoDBNext.js (Turbopack)1.029s1.749s0.720s355.44x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.252s (+35.6% 🔺)4.327s (+29.1% 🔺)2.074s141.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.320s (-37.3% 🟢)1.006s (~)0.687s901.00x
🐘 PostgresNitro0.340s (-31.6% 🟢)1.007s (~)0.667s901.06x
🌐 RedisNext.js (Turbopack)0.408s1.004s0.596s901.28x
🐘 PostgresNext.js (Turbopack)0.895s1.442s0.547s632.80x
💻 LocalNitro2.133s (-16.0% 🟢)2.883s (-4.2%)0.750s326.67x
💻 LocalExpress2.304s (-8.3% 🟢)2.943s (-2.2%)0.639s317.21x
🌐 MongoDBNext.js (Turbopack)2.605s3.006s0.401s308.15x
💻 LocalNext.js (Turbopack)2.641s3.183s0.542s298.26x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro8.322s (+158.0% 🔺)10.481s (+117.4% 🔺)2.158s91.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.647s (-21.0% 🟢)1.006s (-1.1%)0.359s1201.00x
🐘 PostgresNitro0.698s (-11.6% 🟢)1.007s (~)0.308s1201.08x
🌐 RedisNext.js (Turbopack)0.762s1.004s0.242s1201.18x
🐘 PostgresNext.js (Turbopack)1.313s1.779s0.467s682.03x
🌐 MongoDBNext.js (Turbopack)5.386s6.013s0.627s208.33x
💻 LocalNitro10.093s (-9.8% 🟢)10.612s (-9.0% 🟢)0.519s1215.61x
💻 LocalExpress10.309s (-7.9% 🟢)10.864s (-9.0% 🟢)0.555s1215.94x
💻 LocalNext.js (Turbopack)11.645s12.330s0.684s1018.01x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro21.406s (+177.2% 🔺)23.498s (+150.0% 🔺)2.092s61.00x
▲ VercelExpress⚠️missing----
▲ VercelNext.js (Turbopack)⚠️missing----

🔍 Observability: Nitro

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.134s (+452.9% 🔺)1.998s (+100.1% 🔺)0.002s (-6.3% 🟢)2.010s (+98.7% 🔺)0.876s101.00x
💻 LocalExpress1.134s (+469.8% 🔺)2.005s (+99.6% 🔺)0.012s (+0.8%)2.020s (+98.4% 🔺)0.885s101.00x
💻 LocalNitro1.136s (+431.6% 🔺)2.005s (+99.6% 🔺)0.012s (-1.6%)2.020s (+98.2% 🔺)0.883s101.00x
🐘 PostgresNitro1.154s (+462.9% 🔺)2.002s (+100.2% 🔺)0.002s (~)2.013s (+99.0% 🔺)0.859s101.02x
💻 LocalNext.js (Turbopack)1.198s2.003s0.013s2.020s0.823s101.06x
🐘 PostgresNext.js (Turbopack)1.533s1.999s0.410s2.441s0.908s101.35x
🌐 MongoDBNext.js (Turbopack)⚠️missing-----
🌐 RedisNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.173s (-43.3% 🟢)3.567s (-32.4% 🟢)1.733s (+133.5% 🔺)5.800s (-10.5% 🟢)3.628s101.00x
▲ VercelExpress⚠️missing-----
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.509s (+139.5% 🔺)2.003s (+99.1% 🔺)0.004s (-2.5%)2.024s (+97.9% 🔺)0.515s301.00x
💻 LocalNitro1.528s (+82.1% 🔺)2.011s (+98.7% 🔺)0.009s (-5.7% 🟢)2.022s (+81.2% 🔺)0.494s301.01x
💻 LocalExpress1.530s (+102.1% 🔺)2.011s (+95.5% 🔺)0.010s (+4.5%)2.023s (+94.6% 🔺)0.493s301.01x
🐘 PostgresNitro1.576s (+152.5% 🔺)2.005s (+99.2% 🔺)0.004s (-3.3%)2.027s (+98.3% 🔺)0.451s301.04x
💻 LocalNext.js (Turbopack)1.698s2.010s0.011s2.024s0.327s301.13x
🐘 PostgresNext.js (Turbopack)2.628s3.008s0.004s3.036s0.408s201.74x
🌐 MongoDBNext.js (Turbopack)⚠️missing-----
🌐 RedisNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.770s (-80.4% 🟢)7.572s (-75.4% 🟢)0.449s (+300.9% 🔺)8.603s (-72.9% 🟢)2.833s71.00x
▲ VercelExpress⚠️missing-----
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.668s (-30.5% 🟢)1.049s (-17.9% 🟢)0.000s (-59.6% 🟢)1.060s (-18.9% 🟢)0.392s571.00x
🐘 PostgresNitro0.716s (-26.0% 🟢)1.050s (-15.8% 🟢)0.000s (-57.9% 🟢)1.068s (-15.1% 🟢)0.351s571.07x
💻 LocalExpress1.350s (+10.2% 🔺)2.015s (~)0.001s (+70.0% 🔺)2.017s (~)0.667s302.02x
💻 LocalNitro1.358s (+11.1% 🔺)2.015s (~)0.000s (+133.3% 🔺)2.017s (~)0.659s302.03x
🐘 PostgresNext.js (Turbopack)1.370s1.659s0.000s1.724s0.354s362.05x
💻 LocalNext.js (Turbopack)1.498s2.014s0.000s2.017s0.519s302.24x
🌐 MongoDBNext.js (Turbopack)⚠️missing-----
🌐 RedisNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.519s (+15.4% 🔺)5.014s (+14.1% 🔺)0.000s (-100.0% 🟢)5.521s (+14.8% 🔺)2.002s111.00x
▲ VercelExpress⚠️missing-----
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro

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

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.332s (-24.8% 🟢)1.998s (-8.3% 🟢)0.000s (+Infinity% 🔺)2.018s (-8.2% 🟢)0.686s301.00x
🐘 PostgresNitro1.458s (-18.6% 🟢)2.100s (-1.9%)0.000s (-3.4%)2.115s (-2.7%)0.656s291.09x
💻 LocalNext.js (Turbopack)2.949s3.357s0.000s3.367s0.418s182.21x
💻 LocalNitro3.144s (-7.2% 🟢)4.029s (~)0.000s (-25.0% 🟢)4.032s (~)0.888s152.36x
💻 LocalExpress3.177s (-8.4% 🟢)3.967s (-1.7%)0.000s (-84.4% 🟢)3.969s (-1.7%)0.792s162.39x
🐘 PostgresNext.js (Turbopack)3.436s3.946s0.000s4.008s0.572s152.58x
🌐 MongoDBNext.js (Turbopack)⚠️missing-----
🌐 RedisNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.077s (+48.4% 🔺)7.737s (+44.0% 🔺)0.000s (-54.2% 🟢)8.390s (+44.8% 🔺)2.313s81.00x
▲ VercelExpress⚠️missing-----
▲ VercelNext.js (Turbopack)⚠️missing-----

🔍 Observability: Nitro

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress10/21
🐘 PostgresExpress21/21
▲ VercelNitro21/21
Fastest World by Framework

Winner determined by most benchmark wins

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

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)

📋 View full workflow run


Some benchmark jobs failed:

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

Check the workflow run for details.

@github-actions

github-actionsBot commented May 5, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production92502191144
✅ 💻 Local Development123702191456
✅ 📦 Local Production123702191456
✅ 🐘 Local Postgres123702191456
✅ 🪟 Windows10400104
✅ 📋 Other5520176728
Total5292010526344

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro78026
✅ example78026
✅ express78026
✅ fastify78026
✅ hono78026
✅ nextjs-turbopack10202
✅ nextjs-webpack10202
✅ nitro78026
✅ nuxt78026
✅ sveltekit9707
✅ vite78026
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack10400
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable79025
✅ e2e-local-dev-tanstack-start-79025
✅ e2e-local-postgres-nest-stable79025
✅ e2e-local-postgres-tanstack-start-79025
✅ e2e-local-prod-nest-stable79025
✅ e2e-local-prod-tanstack-start-79025
✅ e2e-vercel-prod-tanstack-start78026

📋 View full workflow run

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

Updates @workflow/swc-plugin to correctly preserve lexical this for nested arrow "use step" functions (common in “method returns object literal with arrow steps” patterns), by detecting this usage, binding step proxies in workflow mode, and hoisting step bodies as regular functions in step mode so runtime apply(thisVal, args) rebinding works.

Changes:

  • Add lexical-this detection for nested arrow step bodies and thread a references_lexical_this flag through nested-step tracking.
  • Emit .bind(this) on workflow-mode step proxies when lexical this is referenced; adjust step-mode hoisting to emit function instead of arrow for those steps.
  • Add SWC fixture coverage + a core runtime test; document the new behavior and ship a changeset.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
packages/swc-plugin-workflow/transform/src/lib.rsImplements lexical-this detection, proxy binding, and step-mode hoist changes.
packages/swc-plugin-workflow/spec.mdDocuments lexical this capture behavior and updates notes about this in steps.
packages/core/src/step.test.tsAdds runtime test ensuring .bind(this) captures thisVal onto the invocation queue item.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this/input.jsNew fixture input covering nested arrow step referencing this in a method.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this/output-workflow.jsExpected workflow-mode output showing .bind(this) on the step proxy.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this/output-step.jsExpected step-mode output showing hoisting as async function using this.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this-var-decl/input.jsNew fixture input for var-declarator arrow step capturing this.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this-var-decl/output-workflow.jsExpected workflow-mode output with bound proxy for var-declarator case.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this-var-decl/output-step.jsExpected step-mode output hoisting as function for var-declarator case.
.changeset/swc-lexical-this-capture.mdPatch changeset describing the new lexical-this behavior.

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

Comment threadpackages/swc-plugin-workflow/transform/src/lib.rs
Comment threadpackages/swc-plugin-workflow/transform/src/lib.rs
Comment threadpackages/swc-plugin-workflow/transform/src/lib.rs
Comment threadpackages/swc-plugin-workflow/spec.md Outdated
Comment threadpackages/core/src/step.test.ts Outdated
…ction
- core: Override `.bind` on step proxies so the bound function retains
`stepId` and `__closureVarsFn`. Without this, a bound proxy that flows
through workflow serialization (e.g. as a step argument) would be
treated as a non-serializable plain function by `getStepFunctionReducer`.
- swc-plugin: Detector now also walks `arrow.params` so `this` references
in default values / destructuring initializers (e.g. `(x = this.foo) =>
...`) trigger the `.bind(this)` path.
- swc-plugin: Class bodies inside the arrow body are now treated as
`this`-binding boundaries — `this` inside class field initializers,
methods, etc. is bound to the class instance, not the outer arrow. The
detector still walks `extends` clauses and computed property keys
because those are evaluated in the surrounding scope.
- spec.md: Sharpen the note about `this` in step bodies — it's
syntactically allowed but only meaningful for instance-method steps and
lexical-`this` arrow steps; other shapes compile but `this` will be
whatever the caller of the step proxy passes.
- Add `lexical-this-detector-edge-cases` fixture covering both the
default-param positive case and the inner-class false-positive guard.
- Strengthen the runtime test to assert `stepId` / `__closureVarsFn`
survive `.bind(...)`.
… coverage
Without this, a `useStep(...).bind(thisArg)` proxy that flows through
workflow serialization (e.g. passed as a step argument) would lose its
receiver: the reducer captured `stepId` but not the bound `this`, and
the step-bundle reviver returned the raw registered step body which
ignores any `this` the caller passes.
Now:
- step.ts `.bind` override stashes the bound value on the result as
`__boundThis` so the reducer can see it.
- The reducer serializes `boundThis` (using property presence so
`bind(null)`/`bind(undefined)` round-trip faithfully).
- The workflow-bundle reviver re-binds the freshly created proxy.
- The step-bundle reviver wraps the registered body so it's invoked
with `apply(boundThis, args)` (and still runs inside the closure-vars
AsyncLocalStorage frame when `closureVars` is present).
E2E coverage extends `instanceMethodStepWorkflow` with two new shapes:
- `counter.makeAdder(7).add(2)` — direct invocation of a lexical-`this`
arrow step. Verifies `bind(this)` carries `thisVal` to the queue and
the step body sees `this.value` correctly.
- `invokeAdderFromStep(adder.add, 3)` — passes the bound proxy as a
step argument so the round-trip path exercises both the reducer and
the step-bundle reviver, with the inner call running inline. Without
the new `boundThis` plumbing this previously failed with `Cannot read
properties of undefined (reading 'value')`.

@karthikscale3karthikscale3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AI review: Solid implementation — the LexicalThisDetector, the .bind() override, and the serialization round-trip all hang together correctly. No regressions found. The arguments fix is a genuine bug fix and the dead this/arguments error-check removal is safe. All completed CI checks pass. LGTM

Comment threadpackages/core/src/step.ts
Comment threadpackages/core/src/serialization/serialization.test.ts
…+boundThis
- step.ts `.bind` override now also stashes `__boundArgs` when the caller
supplied prefilled args (`useStep(...).bind(thisArg, x, y)`). The
reducer serializes them as `boundArgs`, and both the workflow- and
step-bundle revivers re-apply them (the workflow reviver via
`bind(boundThis, ...boundArgs)`, the step-bundle reviver by
prepending to the runtime args). The SWC plugin only ever emits
`.bind(this)` today, but this keeps partial application faithful in
case hand-written code ever calls `.bind` with extra args.
- Add two new unit tests in `serialization.test.ts`:
- `closureVars + boundThis` combo: exercises the step-bundle reviver's
inner branch (`contextStorage.run(newContext, () =>
stepFn.apply(callThis, callArgs))`) in isolation — previously only
covered end-to-end by the `instanceMethodStepWorkflow` e2e test.
- `boundArgs` round-trip: codifies that prefilled args survive
serialization.
@github-actions

Copy link
Copy Markdown
Contributor

Backport PR opened against stable: #1945. Merge conflicts were resolved by AI — please review carefully.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-stableCherry-pick this PR to the stable branch when merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@TooTallNate@karthikscale3