Skip to content

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

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

[swc-plugin] Capture lexical this for nested arrow step functions#1935
TooTallNate merged 6 commits intomainfrom
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:

class ReadFileTool {
  // serializable...
  createTool(context) {
    return tool({
      execute: async (input) => {
        'use step';
        return this.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):

async function _anonymousStep0(input) {
  const { context } = (function() { /* closure-var IIFE */ })();
  return this.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
Copilot AI review requested due to automatic review settings May 5, 2026 05:51
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 5, 2026

🦋 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
Name Type
@workflow/core Patch
@workflow/swc-plugin Patch
@workflow/builders Patch
@workflow/cli Patch
@workflow/next Patch
@workflow/nitro Patch
@workflow/vitest Patch
@workflow/web-shared Patch
@workflow/web Patch
workflow Patch
@workflow/world-testing Patch
tarballs Patch
@workflow/astro Patch
@workflow/nest Patch
@workflow/rollup Patch
@workflow/sveltekit Patch
@workflow/vite Patch
@workflow/nuxt Patch
@workflow/ai Patch

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
Copy link
Copy Markdown
Contributor

vercel Bot commented May 5, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
example-nextjs-workflow-turbopack Ready Ready Preview, Comment May 5, 2026 6:42pm
example-nextjs-workflow-webpack Ready Ready Preview, Comment May 5, 2026 6:42pm
example-workflow Ready Ready Preview, Comment May 5, 2026 6:42pm
workbench-astro-workflow Ready Ready Preview, Comment May 5, 2026 6:42pm
workbench-express-workflow Ready Ready Preview, Comment May 5, 2026 6:42pm
workbench-fastify-workflow Ready Ready Preview, Comment May 5, 2026 6:42pm
workbench-hono-workflow Ready Ready Preview, Comment May 5, 2026 6:42pm
workbench-nitro-workflow Ready Ready Preview, Comment May 5, 2026 6:42pm
workbench-nuxt-workflow Ready Ready Preview, Comment May 5, 2026 6:42pm
workbench-sveltekit-workflow Ready Ready Preview, Comment May 5, 2026 6:42pm
workbench-tanstack-start-workflow Ready Ready Preview, Comment May 5, 2026 6:42pm
workbench-vite-workflow Ready Ready Preview, Comment May 5, 2026 6:42pm
workflow-docs Ready Ready Preview, Comment, Open in v0 May 5, 2026 6:42pm
workflow-swc-playground Ready Ready Preview, Comment May 5, 2026 6:42pm
workflow-tarballs Ready Ready Preview, Comment May 5, 2026 6:42pm
workflow-web Ready Ready Preview, Comment May 5, 2026 6:42pm

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

📊 Benchmark Results

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

workflow with no steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Express 0.031s (-29.6% 🟢) 1.006s (~) 0.974s 10 1.00x
💻 Local Nitro 0.031s (-27.6% 🟢) 1.005s (~) 0.974s 10 1.00x
🐘 Postgres Express 0.046s (-21.4% 🟢) 1.012s (~) 0.966s 10 1.46x
💻 Local Next.js (Turbopack) 0.050s 1.006s 0.956s 10 1.60x
🌐 Redis Next.js (Turbopack) 0.052s 1.005s 0.953s 10 1.67x
🐘 Postgres Nitro 0.053s (-44.3% 🟢) 1.013s (-2.9%) 0.960s 10 1.70x
🌐 MongoDB Next.js (Turbopack) 0.100s 1.008s 0.908s 10 3.20x
🐘 Postgres Next.js (Turbopack) 0.177s 1.043s 0.866s 10 5.68x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 0.242s (-40.8% 🟢) 2.094s (-16.5% 🟢) 1.852s 10 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

workflow with 1 step

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Express 1.069s (-5.0% 🟢) 2.007s (~) 0.938s 10 1.00x
💻 Local Nitro 1.073s (-5.1% 🟢) 2.006s (~) 0.932s 10 1.00x
🐘 Postgres Express 1.084s (-5.5% 🟢) 2.010s (~) 0.926s 10 1.01x
🐘 Postgres Nitro 1.084s (-4.9%) 2.010s (~) 0.926s 10 1.01x
🌐 Redis Next.js (Turbopack) 1.111s 2.007s 0.895s 10 1.04x
💻 Local Next.js (Turbopack) 1.113s 2.007s 0.894s 10 1.04x
🌐 MongoDB Next.js (Turbopack) 1.169s 2.009s 0.840s 10 1.09x
🐘 Postgres Next.js (Turbopack) 1.229s 2.048s 0.819s 10 1.15x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 1.643s (-57.8% 🟢) 3.930s (-33.5% 🟢) 2.287s 10 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

workflow with 10 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 10.404s (-4.9%) 11.021s (~) 0.617s 3 1.00x
💻 Local Express 10.413s (-4.7%) 11.022s (~) 0.610s 3 1.00x
🐘 Postgres Express 10.426s (-4.9%) 11.018s (~) 0.592s 3 1.00x
🐘 Postgres Nitro 10.482s (-3.6%) 11.020s (~) 0.538s 3 1.01x
🌐 Redis Next.js (Turbopack) 10.642s 11.023s 0.381s 3 1.02x
💻 Local Next.js (Turbopack) 10.680s 11.024s 0.344s 3 1.03x
🌐 MongoDB Next.js (Turbopack) 10.784s 11.021s 0.238s 3 1.04x
🐘 Postgres Next.js (Turbopack) 11.142s 11.680s 0.538s 3 1.07x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 13.533s (-43.0% 🟢) 15.534s (-38.2% 🟢) 2.001s 2 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

workflow with 25 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Express 13.449s (-10.2% 🟢) 14.027s (-6.7% 🟢) 0.578s 5 1.00x
🐘 Postgres Express 13.453s (-7.8% 🟢) 14.018s (-6.7% 🟢) 0.565s 5 1.00x
💻 Local Nitro 13.485s (-10.5% 🟢) 14.026s (-12.5% 🟢) 0.541s 5 1.00x
🐘 Postgres Nitro 13.521s (-7.4% 🟢) 14.023s (-6.7% 🟢) 0.502s 5 1.01x
🌐 Redis Next.js (Turbopack) 14.030s 14.428s 0.398s 5 1.04x
💻 Local Next.js (Turbopack) 14.125s 15.030s 0.905s 4 1.05x
🌐 MongoDB Next.js (Turbopack) 14.212s 15.023s 0.811s 4 1.06x
🐘 Postgres Next.js (Turbopack) 15.396s 16.019s 0.624s 4 1.14x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 22.032s (-65.8% 🟢) 24.542s (-63.2% 🟢) 2.510s 3 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

workflow with 50 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 11.936s (-28.9% 🟢) 12.147s (-28.7% 🟢) 0.211s 8 1.00x
🐘 Postgres Express 11.971s (-14.5% 🟢) 12.516s (-14.2% 🟢) 0.545s 8 1.00x
💻 Local Express 11.979s (-27.8% 🟢) 12.273s (-27.9% 🟢) 0.294s 8 1.00x
🐘 Postgres Nitro 12.038s (-13.8% 🟢) 12.766s (-10.8% 🟢) 0.728s 8 1.01x
🌐 Redis Next.js (Turbopack) 13.002s 13.311s 0.309s 7 1.09x
🌐 MongoDB Next.js (Turbopack) 13.250s 14.020s 0.770s 7 1.11x
💻 Local Next.js (Turbopack) 13.313s 14.026s 0.713s 7 1.12x
🐘 Postgres Next.js (Turbopack) 16.668s 17.052s 0.385s 6 1.40x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 33.281s (-92.1% 🟢) 35.586s (-91.6% 🟢) 2.305s 3 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

Promise.all with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.148s (-8.9% 🟢) 2.008s (~) 0.860s 15 1.00x
🐘 Postgres Nitro 1.164s (-8.7% 🟢) 2.007s (~) 0.843s 15 1.01x
💻 Local Express 1.184s (-20.5% 🟢) 2.006s (~) 0.822s 15 1.03x
💻 Local Nitro 1.185s (-27.4% 🟢) 2.006s (-3.3%) 0.821s 15 1.03x
🌐 Redis Next.js (Turbopack) 1.249s 2.006s 0.757s 15 1.09x
💻 Local Next.js (Turbopack) 1.307s 2.006s 0.699s 15 1.14x
🐘 Postgres Next.js (Turbopack) 1.785s 2.202s 0.417s 14 1.55x
🌐 MongoDB Next.js (Turbopack) 2.029s 2.827s 0.798s 11 1.77x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.530s (-10.2% 🟢) 4.390s (+1.6%) 1.860s 7 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

Promise.all with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.231s (-47.8% 🟢) 2.009s (-33.2% 🟢) 0.778s 15 1.00x
🐘 Postgres Nitro 1.247s (-47.0% 🟢) 2.008s (-33.3% 🟢) 0.761s 15 1.01x
🐘 Postgres Next.js (Turbopack) 1.696s 2.200s 0.504s 14 1.38x
💻 Local Nitro 1.796s (-42.9% 🟢) 2.072s (-46.7% 🟢) 0.276s 15 1.46x
💻 Local Express 1.798s (-39.1% 🟢) 2.073s (-40.0% 🟢) 0.275s 15 1.46x
💻 Local Next.js (Turbopack) 1.864s 2.221s 0.357s 14 1.51x
🌐 Redis Next.js (Turbopack) 2.370s 3.008s 0.637s 10 1.92x
🌐 MongoDB Next.js (Turbopack) 3.591s 4.010s 0.419s 8 2.92x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 3.386s (-16.4% 🟢) 5.511s (-6.9% 🟢) 2.125s 6 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

Promise.all with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.373s (-60.6% 🟢) 2.007s (-49.9% 🟢) 0.634s 15 1.00x
🐘 Postgres Nitro 1.444s (-58.5% 🟢) 2.008s (-49.9% 🟢) 0.565s 15 1.05x
🐘 Postgres Next.js (Turbopack) 2.200s 3.032s 0.832s 10 1.60x
🌐 Redis Next.js (Turbopack) 3.616s 4.010s 0.394s 8 2.63x
💻 Local Express 5.169s (-38.0% 🟢) 5.849s (-35.2% 🟢) 0.680s 6 3.76x
💻 Local Nitro 5.416s (-35.1% 🟢) 6.016s (-33.3% 🟢) 0.600s 5 3.94x
💻 Local Next.js (Turbopack) 5.970s 6.414s 0.444s 5 4.35x
🌐 MongoDB Next.js (Turbopack) 6.338s 7.014s 0.676s 5 4.62x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 7.725s (+119.1% 🔺) 10.152s (+83.5% 🔺) 2.427s 3 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

Promise.race with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.152s (-8.4% 🟢) 2.007s (~) 0.856s 15 1.00x
🐘 Postgres Nitro 1.174s (-6.6% 🟢) 2.009s (~) 0.835s 15 1.02x
🌐 Redis Next.js (Turbopack) 1.229s 2.006s 0.777s 15 1.07x
💻 Local Nitro 1.325s (-29.0% 🟢) 2.006s (-14.3% 🟢) 0.681s 15 1.15x
💻 Local Next.js (Turbopack) 1.353s 2.007s 0.654s 15 1.17x
💻 Local Express 1.413s (-25.4% 🟢) 2.007s (-15.1% 🟢) 0.594s 15 1.23x
🐘 Postgres Next.js (Turbopack) 1.630s 2.184s 0.554s 14 1.42x
🌐 MongoDB Next.js (Turbopack) 2.057s 3.008s 0.952s 10 1.79x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.585s (+5.1% 🔺) 4.490s (+7.7% 🔺) 1.905s 7 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

Promise.race with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.239s (-47.1% 🟢) 2.008s (-33.3% 🟢) 0.768s 15 1.00x
🐘 Postgres Nitro 1.273s (-45.6% 🟢) 2.009s (-33.3% 🟢) 0.737s 15 1.03x
🐘 Postgres Next.js (Turbopack) 1.770s 2.594s 0.824s 12 1.43x
💻 Local Nitro 1.982s (-35.3% 🟢) 2.393s (-38.4% 🟢) 0.411s 13 1.60x
💻 Local Express 2.047s (-34.6% 🟢) 2.394s (-36.4% 🟢) 0.346s 13 1.65x
💻 Local Next.js (Turbopack) 2.054s 2.508s 0.455s 12 1.66x
🌐 Redis Next.js (Turbopack) 2.354s 3.008s 0.654s 10 1.90x
🌐 MongoDB Next.js (Turbopack) 3.576s 4.009s 0.433s 8 2.89x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 54.294s (+1579.3% 🔺) 56.491s (+1012.7% 🔺) 2.197s 6 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

Promise.race with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.379s (-60.6% 🟢) 2.008s (-49.9% 🟢) 0.629s 15 1.00x
🐘 Postgres Nitro 1.414s (-59.4% 🟢) 2.008s (-49.9% 🟢) 0.594s 15 1.03x
🐘 Postgres Next.js (Turbopack) 2.116s 2.606s 0.490s 12 1.53x
🌐 Redis Next.js (Turbopack) 3.586s 4.010s 0.424s 8 2.60x
💻 Local Nitro 5.772s (-36.9% 🟢) 6.214s (-38.0% 🟢) 0.441s 5 4.18x
💻 Local Express 5.890s (-33.1% 🟢) 6.214s (-33.0% 🟢) 0.324s 5 4.27x
💻 Local Next.js (Turbopack) 6.126s 6.618s 0.492s 5 4.44x
🌐 MongoDB Next.js (Turbopack) 6.338s 7.011s 0.673s 5 4.60x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 6.693s (+31.4% 🔺) 9.252s (+35.7% 🔺) 2.560s 4 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.460s (-45.2% 🟢) 1.024s (~) 0.564s 59 1.00x
💻 Local Nitro 0.484s (-50.7% 🟢) 1.021s (-6.6% 🟢) 0.538s 59 1.05x
🐘 Postgres Nitro 0.491s (-40.1% 🟢) 1.024s (+1.8%) 0.533s 59 1.07x
💻 Local Express 0.500s (-49.2% 🟢) 1.004s (-6.7% 🟢) 0.505s 60 1.09x
🌐 Redis Next.js (Turbopack) 0.619s 1.004s 0.386s 60 1.35x
🌐 MongoDB Next.js (Turbopack) 0.724s 1.005s 0.281s 60 1.57x
💻 Local Next.js (Turbopack) 0.748s 1.005s 0.256s 60 1.63x
🐘 Postgres Next.js (Turbopack) 0.860s 1.301s 0.441s 47 1.87x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 5.067s (-77.0% 🟢) 6.854s (-71.5% 🟢) 1.787s 9 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.017s (-48.6% 🟢) 1.586s (-29.8% 🟢) 0.569s 57 1.00x
🐘 Postgres Nitro 1.143s (-40.7% 🟢) 1.986s (-5.4% 🟢) 0.843s 46 1.12x
💻 Local Express 1.210s (-59.9% 🟢) 2.005s (-44.1% 🟢) 0.795s 45 1.19x
💻 Local Nitro 1.212s (-60.1% 🟢) 2.006s (-46.6% 🟢) 0.793s 45 1.19x
🌐 Redis Next.js (Turbopack) 1.513s 2.006s 0.493s 45 1.49x
🌐 MongoDB Next.js (Turbopack) 1.821s 2.007s 0.186s 45 1.79x
💻 Local Next.js (Turbopack) 1.825s 2.006s 0.181s 45 1.80x
🐘 Postgres Next.js (Turbopack) 2.605s 3.071s 0.465s 30 2.56x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 13.232s (-66.5% 🟢) 15.905s (-61.5% 🟢) 2.672s 6 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 2.137s (-46.4% 🟢) 2.604s (-40.4% 🟢) 0.466s 47 1.00x
🐘 Postgres Nitro 2.235s (-45.5% 🟢) 3.034s (-34.1% 🟢) 0.799s 40 1.05x
💻 Local Express 2.693s (-70.8% 🟢) 3.008s (-70.0% 🟢) 0.314s 40 1.26x
💻 Local Nitro 2.712s (-70.8% 🟢) 3.057s (-69.5% 🟢) 0.346s 40 1.27x
🌐 Redis Next.js (Turbopack) 2.959s 3.135s 0.176s 39 1.38x
💻 Local Next.js (Turbopack) 3.941s 4.366s 0.426s 28 1.84x
🌐 MongoDB Next.js (Turbopack) 4.155s 5.012s 0.857s 24 1.94x
🐘 Postgres Next.js (Turbopack) 4.540s 5.107s 0.567s 25 2.12x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 41.246s (-57.4% 🟢) 43.425s (-55.9% 🟢) 2.180s 3 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.189s (-33.0% 🟢) 1.005s (~) 0.816s 60 1.00x
🐘 Postgres Nitro 0.198s (-30.3% 🟢) 1.006s (~) 0.809s 60 1.04x
🌐 Redis Next.js (Turbopack) 0.242s 1.004s 0.763s 60 1.28x
💻 Local Express 0.473s (-15.7% 🟢) 1.004s (~) 0.532s 60 2.50x
💻 Local Nitro 0.539s (-10.9% 🟢) 1.096s (+7.3% 🔺) 0.557s 55 2.85x
💻 Local Next.js (Turbopack) 0.592s 1.022s 0.429s 59 3.13x
🐘 Postgres Next.js (Turbopack) 0.679s 1.259s 0.580s 48 3.59x
🌐 MongoDB Next.js (Turbopack) 1.029s 1.749s 0.720s 35 5.44x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.252s (+35.6% 🔺) 4.327s (+29.1% 🔺) 2.074s 14 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.320s (-37.3% 🟢) 1.006s (~) 0.687s 90 1.00x
🐘 Postgres Nitro 0.340s (-31.6% 🟢) 1.007s (~) 0.667s 90 1.06x
🌐 Redis Next.js (Turbopack) 0.408s 1.004s 0.596s 90 1.28x
🐘 Postgres Next.js (Turbopack) 0.895s 1.442s 0.547s 63 2.80x
💻 Local Nitro 2.133s (-16.0% 🟢) 2.883s (-4.2%) 0.750s 32 6.67x
💻 Local Express 2.304s (-8.3% 🟢) 2.943s (-2.2%) 0.639s 31 7.21x
🌐 MongoDB Next.js (Turbopack) 2.605s 3.006s 0.401s 30 8.15x
💻 Local Next.js (Turbopack) 2.641s 3.183s 0.542s 29 8.26x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 8.322s (+158.0% 🔺) 10.481s (+117.4% 🔺) 2.158s 9 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.647s (-21.0% 🟢) 1.006s (-1.1%) 0.359s 120 1.00x
🐘 Postgres Nitro 0.698s (-11.6% 🟢) 1.007s (~) 0.308s 120 1.08x
🌐 Redis Next.js (Turbopack) 0.762s 1.004s 0.242s 120 1.18x
🐘 Postgres Next.js (Turbopack) 1.313s 1.779s 0.467s 68 2.03x
🌐 MongoDB Next.js (Turbopack) 5.386s 6.013s 0.627s 20 8.33x
💻 Local Nitro 10.093s (-9.8% 🟢) 10.612s (-9.0% 🟢) 0.519s 12 15.61x
💻 Local Express 10.309s (-7.9% 🟢) 10.864s (-9.0% 🟢) 0.555s 12 15.94x
💻 Local Next.js (Turbopack) 11.645s 12.330s 0.684s 10 18.01x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 21.406s (+177.2% 🔺) 23.498s (+150.0% 🔺) 2.092s 6 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

Stream Benchmarks (includes TTFB metrics)
workflow with stream

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.134s (+452.9% 🔺) 1.998s (+100.1% 🔺) 0.002s (-6.3% 🟢) 2.010s (+98.7% 🔺) 0.876s 10 1.00x
💻 Local Express 1.134s (+469.8% 🔺) 2.005s (+99.6% 🔺) 0.012s (+0.8%) 2.020s (+98.4% 🔺) 0.885s 10 1.00x
💻 Local Nitro 1.136s (+431.6% 🔺) 2.005s (+99.6% 🔺) 0.012s (-1.6%) 2.020s (+98.2% 🔺) 0.883s 10 1.00x
🐘 Postgres Nitro 1.154s (+462.9% 🔺) 2.002s (+100.2% 🔺) 0.002s (~) 2.013s (+99.0% 🔺) 0.859s 10 1.02x
💻 Local Next.js (Turbopack) 1.198s 2.003s 0.013s 2.020s 0.823s 10 1.06x
🐘 Postgres Next.js (Turbopack) 1.533s 1.999s 0.410s 2.441s 0.908s 10 1.35x
🌐 MongoDB Next.js (Turbopack) ⚠️ missing - - - - -
🌐 Redis Next.js (Turbopack) ⚠️ missing - - - - -

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.173s (-43.3% 🟢) 3.567s (-32.4% 🟢) 1.733s (+133.5% 🔺) 5.800s (-10.5% 🟢) 3.628s 10 1.00x
▲ Vercel Express ⚠️ missing - - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - - -

🔍 Observability: Nitro

stream pipeline with 5 transform steps (1MB)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.509s (+139.5% 🔺) 2.003s (+99.1% 🔺) 0.004s (-2.5%) 2.024s (+97.9% 🔺) 0.515s 30 1.00x
💻 Local Nitro 1.528s (+82.1% 🔺) 2.011s (+98.7% 🔺) 0.009s (-5.7% 🟢) 2.022s (+81.2% 🔺) 0.494s 30 1.01x
💻 Local Express 1.530s (+102.1% 🔺) 2.011s (+95.5% 🔺) 0.010s (+4.5%) 2.023s (+94.6% 🔺) 0.493s 30 1.01x
🐘 Postgres Nitro 1.576s (+152.5% 🔺) 2.005s (+99.2% 🔺) 0.004s (-3.3%) 2.027s (+98.3% 🔺) 0.451s 30 1.04x
💻 Local Next.js (Turbopack) 1.698s 2.010s 0.011s 2.024s 0.327s 30 1.13x
🐘 Postgres Next.js (Turbopack) 2.628s 3.008s 0.004s 3.036s 0.408s 20 1.74x
🌐 MongoDB Next.js (Turbopack) ⚠️ missing - - - - -
🌐 Redis Next.js (Turbopack) ⚠️ missing - - - - -

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 5.770s (-80.4% 🟢) 7.572s (-75.4% 🟢) 0.449s (+300.9% 🔺) 8.603s (-72.9% 🟢) 2.833s 7 1.00x
▲ Vercel Express ⚠️ missing - - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - - -

🔍 Observability: Nitro

10 parallel streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.668s (-30.5% 🟢) 1.049s (-17.9% 🟢) 0.000s (-59.6% 🟢) 1.060s (-18.9% 🟢) 0.392s 57 1.00x
🐘 Postgres Nitro 0.716s (-26.0% 🟢) 1.050s (-15.8% 🟢) 0.000s (-57.9% 🟢) 1.068s (-15.1% 🟢) 0.351s 57 1.07x
💻 Local Express 1.350s (+10.2% 🔺) 2.015s (~) 0.001s (+70.0% 🔺) 2.017s (~) 0.667s 30 2.02x
💻 Local Nitro 1.358s (+11.1% 🔺) 2.015s (~) 0.000s (+133.3% 🔺) 2.017s (~) 0.659s 30 2.03x
🐘 Postgres Next.js (Turbopack) 1.370s 1.659s 0.000s 1.724s 0.354s 36 2.05x
💻 Local Next.js (Turbopack) 1.498s 2.014s 0.000s 2.017s 0.519s 30 2.24x
🌐 MongoDB Next.js (Turbopack) ⚠️ missing - - - - -
🌐 Redis Next.js (Turbopack) ⚠️ missing - - - - -

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 3.519s (+15.4% 🔺) 5.014s (+14.1% 🔺) 0.000s (-100.0% 🟢) 5.521s (+14.8% 🔺) 2.002s 11 1.00x
▲ Vercel Express ⚠️ missing - - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - - -

🔍 Observability: Nitro

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

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.332s (-24.8% 🟢) 1.998s (-8.3% 🟢) 0.000s (+Infinity% 🔺) 2.018s (-8.2% 🟢) 0.686s 30 1.00x
🐘 Postgres Nitro 1.458s (-18.6% 🟢) 2.100s (-1.9%) 0.000s (-3.4%) 2.115s (-2.7%) 0.656s 29 1.09x
💻 Local Next.js (Turbopack) 2.949s 3.357s 0.000s 3.367s 0.418s 18 2.21x
💻 Local Nitro 3.144s (-7.2% 🟢) 4.029s (~) 0.000s (-25.0% 🟢) 4.032s (~) 0.888s 15 2.36x
💻 Local Express 3.177s (-8.4% 🟢) 3.967s (-1.7%) 0.000s (-84.4% 🟢) 3.969s (-1.7%) 0.792s 16 2.39x
🐘 Postgres Next.js (Turbopack) 3.436s 3.946s 0.000s 4.008s 0.572s 15 2.58x
🌐 MongoDB Next.js (Turbopack) ⚠️ missing - - - - -
🌐 Redis Next.js (Turbopack) ⚠️ missing - - - - -

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 6.077s (+48.4% 🔺) 7.737s (+44.0% 🔺) 0.000s (-54.2% 🟢) 8.390s (+44.8% 🔺) 2.313s 8 1.00x
▲ Vercel Express ⚠️ missing - - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - - -

🔍 Observability: Nitro

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World 🥇 Fastest Framework Wins
💻 Local Express 10/21
🐘 Postgres Express 21/21
▲ Vercel Nitro 21/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework 🥇 Fastest World Wins
Express 🐘 Postgres 17/21
Next.js (Turbopack) 🌐 Redis 12/21
Nitro 🐘 Postgres 13/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
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

🧪 E2E Test Results

All tests passed

Summary

Passed Failed Skipped Total
✅ ▲ Vercel Production 925 0 219 1144
✅ 💻 Local Development 1237 0 219 1456
✅ 📦 Local Production 1237 0 219 1456
✅ 🐘 Local Postgres 1237 0 219 1456
✅ 🪟 Windows 104 0 0 104
✅ 📋 Other 552 0 176 728
Total 5292 0 1052 6344

Details by Category

✅ ▲ Vercel Production
App Passed Failed Skipped
✅ astro 78 0 26
✅ example 78 0 26
✅ express 78 0 26
✅ fastify 78 0 26
✅ hono 78 0 26
✅ nextjs-turbopack 102 0 2
✅ nextjs-webpack 102 0 2
✅ nitro 78 0 26
✅ nuxt 78 0 26
✅ sveltekit 97 0 7
✅ vite 78 0 26
✅ 💻 Local Development
App Passed Failed Skipped
✅ astro-stable 79 0 25
✅ express-stable 79 0 25
✅ fastify-stable 79 0 25
✅ hono-stable 79 0 25
✅ nextjs-turbopack-canary 85 0 19
✅ nextjs-turbopack-stable-lazy-discovery-disabled 104 0 0
✅ nextjs-turbopack-stable-lazy-discovery-enabled 104 0 0
✅ nextjs-webpack-canary 85 0 19
✅ nextjs-webpack-stable-lazy-discovery-disabled 104 0 0
✅ nextjs-webpack-stable-lazy-discovery-enabled 104 0 0
✅ nitro-stable 79 0 25
✅ nuxt-stable 79 0 25
✅ sveltekit-stable 98 0 6
✅ vite-stable 79 0 25
✅ 📦 Local Production
App Passed Failed Skipped
✅ astro-stable 79 0 25
✅ express-stable 79 0 25
✅ fastify-stable 79 0 25
✅ hono-stable 79 0 25
✅ nextjs-turbopack-canary 85 0 19
✅ nextjs-turbopack-stable-lazy-discovery-disabled 104 0 0
✅ nextjs-turbopack-stable-lazy-discovery-enabled 104 0 0
✅ nextjs-webpack-canary 85 0 19
✅ nextjs-webpack-stable-lazy-discovery-disabled 104 0 0
✅ nextjs-webpack-stable-lazy-discovery-enabled 104 0 0
✅ nitro-stable 79 0 25
✅ nuxt-stable 79 0 25
✅ sveltekit-stable 98 0 6
✅ vite-stable 79 0 25
✅ 🐘 Local Postgres
App Passed Failed Skipped
✅ astro-stable 79 0 25
✅ express-stable 79 0 25
✅ fastify-stable 79 0 25
✅ hono-stable 79 0 25
✅ nextjs-turbopack-canary 85 0 19
✅ nextjs-turbopack-stable-lazy-discovery-disabled 104 0 0
✅ nextjs-turbopack-stable-lazy-discovery-enabled 104 0 0
✅ nextjs-webpack-canary 85 0 19
✅ nextjs-webpack-stable-lazy-discovery-disabled 104 0 0
✅ nextjs-webpack-stable-lazy-discovery-enabled 104 0 0
✅ nitro-stable 79 0 25
✅ nuxt-stable 79 0 25
✅ sveltekit-stable 98 0 6
✅ vite-stable 79 0 25
✅ 🪟 Windows
App Passed Failed Skipped
✅ nextjs-turbopack 104 0 0
✅ 📋 Other
App Passed Failed Skipped
✅ e2e-local-dev-nest-stable 79 0 25
✅ e2e-local-dev-tanstack-start- 79 0 25
✅ e2e-local-postgres-nest-stable 79 0 25
✅ e2e-local-postgres-tanstack-start- 79 0 25
✅ e2e-local-prod-nest-stable 79 0 25
✅ e2e-local-prod-tanstack-start- 79 0 25
✅ e2e-vercel-prod-tanstack-start 78 0 26

📋 View full workflow run

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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
File Description
packages/swc-plugin-workflow/transform/src/lib.rs Implements lexical-this detection, proxy binding, and step-mode hoist changes.
packages/swc-plugin-workflow/spec.md Documents lexical this capture behavior and updates notes about this in steps.
packages/core/src/step.test.ts Adds 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.js New 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.js Expected workflow-mode output showing .bind(this) on the step proxy.
packages/swc-plugin-workflow/transform/tests/fixture/nested-arrow-step-lexical-this/output-step.js Expected 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.js New 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.js Expected 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.js Expected step-mode output hoisting as function for var-declarator case.
.changeset/swc-lexical-this-capture.md Patch changeset describing the new lexical-this behavior.

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

Comment thread packages/swc-plugin-workflow/transform/src/lib.rs
Comment thread packages/swc-plugin-workflow/transform/src/lib.rs
Comment thread packages/swc-plugin-workflow/transform/src/lib.rs
Comment thread packages/swc-plugin-workflow/spec.md Outdated
Comment thread packages/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')`.
Copy link
Copy Markdown
Contributor

@karthikscale3 karthikscale3 left a comment

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 thread packages/core/src/step.ts
Comment thread packages/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

github-actions Bot commented May 5, 2026

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

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

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants