Skip to content

Add experimental dynamic workflow source start#2062

Draft
pranaygp wants to merge 4 commits into
mainfrom
pranaygp/codex/dynamic-workflow-source
Draft

Add experimental dynamic workflow source start#2062
pranaygp wants to merge 4 commits into
mainfrom
pranaygp/codex/dynamic-workflow-source

Conversation

@pranaygp
Copy link
Copy Markdown
Contributor

@pranaygp pranaygp commented May 21, 2026

Discussion RFC: #2063

RFC: Dynamic Workflow Source MVP

Summary

This PR adds an experimental start() overload that accepts trusted workflow source as a string, compiles it into workflow VM code for that run, persists that generated code with the run, and executes it without requiring the workflow function to be present in the build-time manifest.

The intended MVP use case is dynamic or AI-generated orchestration over step functions that were already registered by the deployment. It does not add runtime step registration.

import { start } from 'workflow/api';
import { fetchUser, sendEmail } from './steps';

const run = await start(
  `
async function workflow(input) {
  "use workflow";

  const user = await steps.fetchUser(input.userId);
  await steps.sendEmail(user.email);

  return { ok: true };
}
`,
  [{ userId: 'user_123' }],
  {
    dynamic: {
      steps: { fetchUser, sendEmail },
    },
  }
);

What Changed In This Prototype

  • Adds start(source, args, { dynamic }) overloads returning Run<unknown>.
  • Adds dynamic workflow source validation and VM-code generation in @workflow/core.
  • Generates dynamic workflow IDs from a hash of the source and referenced steps: workflow//dynamic/<source-hash>//<exportName>.
  • Injects predefined runtime bindings: steps, sleep, and createHook.
  • Persists { version, workflowCode, sourceHash, exportName } at executionContext.dynamicWorkflow on run_created and resilient queue input.
  • Updates workflowEntrypoint to execute persisted dynamic workflow code for dynamic runs, falling back to static bundle code for all existing workflows.
  • Exports dynamic start types from @workflow/core and workflow/api.
  • Adds v5 docs for dynamic workflows, including a workflow-builder/LLM generation example.

Preferred Source Storage Design

The production shape should store dynamic source/code as encrypted ref-backed run data, not as plaintext executionContext metadata.

Preferred model:

  • Keep small non-sensitive metadata in executionContext.dynamicWorkflow: version, sourceHash, exportName, generated workflow ID, and step aliases/step IDs.
  • Add a dynamicWorkflowCode or dynamicWorkflowSource serialized data field to run_created.eventData and the run entity.
  • Serialize and encrypt that field with the same run encryption key used for workflow inputs, step inputs/outputs, hook payloads, streams, and workflow outputs.
  • Let worlds that support remote refs store the encrypted bytes behind a ref descriptor, instead of inlining the source/code into the run item.
  • During replay, resolve the ref, decrypt/hydrate the workflow code, then pass it to runWorkflow().
  • In observability, show a locked source/code placeholder by default and reveal it only through the existing decrypt flow.

That design supports sensitive generated source and avoids DynamoDB item-size pressure for larger dynamic workflows. The current inline executionContext.dynamicWorkflow.workflowCode path is only a client-repo prototype because executionContext is already opaque metadata and does not require a world/server storage change. Before shipping beyond experiment, we should move source/code into the encrypted ref-backed field above.

Predefined Runtime Globals

Dynamic workflow source does not use imports in this MVP. The generated VM code predefines this runtime surface:

  • steps: frozen object containing the aliases passed through dynamic.steps; each alias is backed by WORKFLOW_USE_STEP(stepId).
  • sleep: durable waits and timers.
  • createHook: durable external resume signals.

The source also runs inside the normal deterministic workflow VM, so sandbox globals such as Date, Math.random, crypto, URL, URLSearchParams, TextEncoder, TextDecoder, structuredClone, atob, and btoa are available with the same constraints as static workflows.

For the MVP, createWebhook and getWritable are not predefined in dynamic source.

const run = await start(
  `
async function workflow(input) {
  "use workflow";

  await sleep("15m");

  const approval = createHook({ token: "approval-" + input.userId });
  const result = await Promise.race([
    approval,
    sleep("1d").then(() => ({ approved: false, timedOut: true })),
  ]);

  if (result.approved) {
    await steps.sendEmail(input.email);
  }

  return result;
}
`,
  [{ userId: 'user_123', email: 'ada@example.com' }],
  {
    dynamic: {
      steps: { sendEmail },
    },
  }
);

Step Exposure and Safety Model

steps is injected as a lexical capability object inside the generated VM code. It is frozen and only includes aliases passed through dynamic.steps, so normal generated code that calls steps.notAllowed() fails the run instead of dispatching an arbitrary step.

This is an allowlist convenience, not a hard malicious-code sandbox. Dynamic source is still treated as trusted application code in this MVP. A stronger version should gate the underlying dynamic step primitive so only allowed step IDs can be materialized even if source attempts to reach private VM symbols directly.

Longer-term we may still consider virtual imports like workflow:steps, but the MVP API should stay with the predefined runtime bindings above.

Observability Plan

Dynamic runs should show the code that actually executed from the run detail view:

  • detect executionContext.dynamicWorkflow.version === 1,
  • display workflow ID, export name, source hash, step aliases, and source/generated code,
  • use a syntax-highlighted code viewer with copy/download affordances,
  • label whether code is plaintext prototype metadata or encrypted/decrypted ref-backed data,
  • keep dynamic source out of timeline summaries/logs by default.

With encrypted source refs, observability should use the existing encrypted-field UX: locked placeholder by default, reveal only after the user chooses to decrypt.

MVP Constraints

  • JavaScript source only.
  • Requires async function <exportName>(...) { "use workflow"; ... }.
  • No caller-provided dynamic workflow IDs; IDs are generated from the source and step references.
  • No import or export syntax.
  • No TypeScript syntax, runtime bundling, npm dependencies, inline "use step" functions, or runtime step registration.
  • All referenced steps must already be registered in the target deployment.
  • Current prototype stores source/code inline in metadata; production design should use encrypted source refs before broad release.
  • The workflow VM is a determinism sandbox, not a complete security sandbox for malicious JavaScript. Treat source as trusted or reviewed application code.

Review Notes

A few areas worth extra review:

  • Whether the public type shape should stay as { dynamic: { steps, exportName? } } or move behind a more explicit experimental namespace before release.
  • Whether workflow//dynamic/<source-hash>//<exportName> is the right durable ID shape for queue names and observability.
  • Whether encrypted ref-backed source storage should be part of the first merge rather than a follow-up.
  • Whether the MVP should keep the simple source validation approach, or introduce a parser before exposing this more broadly.
  • Whether future versions should add virtual imports, or keep predefined runtime globals as the only dynamic-source surface.

Tests

  • pnpm --filter @workflow/core exec vitest run src/runtime/start.test.ts src/runtime.test.ts
  • pnpm --filter @workflow/core typecheck
  • pnpm exec biome check packages/core/src/runtime/start.ts packages/core/src/runtime.ts packages/core/src/runtime/start.test.ts packages/core/src/runtime.test.ts packages/workflow/src/api.ts docs/content/docs/v5/foundations/dynamic-workflows.mdx docs/content/docs/v5/foundations/index.mdx docs/content/docs/v5/foundations/meta.json docs/content/docs/v5/api-reference/workflow-api/start.mdx .changeset/dynamic-workflow-source.md --diagnostic-level=error
  • pnpm --filter @workflow/docs-typecheck test:docs

Note: local validation ran on Node v25.2.1, so pnpm printed the repo engine warning (^18 || ^20 || ^22 || ^24), but the commands above passed.

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 21, 2026

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

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

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 21, 2026

🦋 Changeset detected

Latest commit: c03bb28

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

This PR includes changesets to release 16 packages
Name Type
@workflow/core Patch
workflow Patch
@workflow/builders Patch
@workflow/cli Patch
@workflow/next Patch
@workflow/nitro Patch
@workflow/vitest Patch
@workflow/web-shared Patch
@workflow/web Patch
@workflow/world-testing Patch
@workflow/astro Patch
@workflow/nest Patch
@workflow/rollup Patch
@workflow/sveltekit Patch
@workflow/vite Patch
@workflow/nuxt 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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 21, 2026

🧪 E2E Test Results

All tests passed

Summary

Passed Failed Skipped Total
✅ ▲ Vercel Production 1200 0 219 1419
✅ 💻 Local Development 1587 0 219 1806
✅ 📦 Local Production 1587 0 219 1806
✅ 🐘 Local Postgres 1587 0 219 1806
✅ 🪟 Windows 129 0 0 129
✅ 📋 Other 727 0 176 903
Total 6817 0 1052 7869

Details by Category

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

📋 View full workflow run

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 21, 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.030s (-33.0% 🟢) 1.005s (~) 0.976s 10 1.00x
💻 Local Nitro 0.032s (-25.8% 🟢) 1.006s (~) 0.974s 10 1.08x
💻 Local Next.js (Turbopack) 0.045s 1.004s 0.959s 10 1.53x
🐘 Postgres Nitro 0.049s (-48.1% 🟢) 1.013s (-2.9%) 0.963s 10 1.66x
🐘 Postgres Express 0.052s (-10.0% 🟢) 1.011s (~) 0.959s 10 1.76x
🐘 Postgres Next.js (Turbopack) 0.095s 1.055s 0.960s 10 3.20x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 0.280s (+18.9% 🔺) 2.120s (-0.7%) 1.840s 10 1.00x
▲ Vercel Next.js (Turbopack) 0.325s (+29.1% 🔺) 2.426s (+4.0%) 2.101s 10 1.16x
▲ Vercel Nitro 0.355s (-13.3% 🟢) 2.495s (-0.6%) 2.140s 10 1.27x

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

workflow with 1 step

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Express 1.070s (-4.9%) 2.006s (~) 0.936s 10 1.00x
💻 Local Nitro 1.075s (-5.0%) 2.006s (~) 0.931s 10 1.00x
🐘 Postgres Nitro 1.082s (-5.1% 🟢) 2.009s (~) 0.927s 10 1.01x
💻 Local Next.js (Turbopack) 1.084s 2.005s 0.921s 10 1.01x
🐘 Postgres Express 1.090s (-4.9%) 2.010s (~) 0.919s 10 1.02x
🐘 Postgres Next.js (Turbopack) 1.195s 2.069s 0.874s 10 1.12x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 1.613s (-58.6% 🟢) 3.689s (-37.6% 🟢) 2.076s 10 1.00x
▲ Vercel Express 1.649s (-12.0% 🟢) 3.990s (+4.8%) 2.341s 10 1.02x
▲ Vercel Next.js (Turbopack) 1.779s (-12.6% 🟢) 3.634s (-5.1% 🟢) 1.855s 10 1.10x

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

workflow with 10 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Express 10.413s (-4.7%) 11.023s (~) 0.610s 3 1.00x
🐘 Postgres Express 10.420s (-5.0%) 11.017s (~) 0.597s 3 1.00x
💻 Local Nitro 10.421s (-4.8%) 11.023s (~) 0.602s 3 1.00x
🐘 Postgres Nitro 10.425s (-4.1%) 11.016s (~) 0.591s 3 1.00x
💻 Local Next.js (Turbopack) 10.532s 11.021s 0.488s 3 1.01x
🐘 Postgres Next.js (Turbopack) 10.975s 11.352s 0.376s 3 1.05x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 13.816s (-41.8% 🟢) 15.563s (-38.0% 🟢) 1.747s 2 1.00x
▲ Vercel Next.js (Turbopack) 14.123s (-18.5% 🟢) 16.266s (-16.1% 🟢) 2.143s 2 1.02x
▲ Vercel Express 14.915s (-12.2% 🟢) 16.962s (-15.3% 🟢) 2.048s 2 1.08x

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

workflow with 25 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Express 13.444s (-10.2% 🟢) 14.028s (-6.7% 🟢) 0.584s 5 1.00x
🐘 Postgres Nitro 13.473s (-7.7% 🟢) 14.019s (-6.7% 🟢) 0.547s 5 1.00x
🐘 Postgres Express 13.474s (-7.6% 🟢) 14.016s (-6.7% 🟢) 0.542s 5 1.00x
💻 Local Nitro 13.525s (-10.2% 🟢) 14.028s (-12.5% 🟢) 0.503s 5 1.01x
💻 Local Next.js (Turbopack) 13.746s 14.025s 0.279s 5 1.02x
🐘 Postgres Next.js (Turbopack) 14.708s 15.265s 0.557s 4 1.09x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 22.211s (-65.5% 🟢) 24.523s (-63.2% 🟢) 2.312s 3 1.00x
▲ Vercel Express 22.663s (-55.0% 🟢) 24.338s (-53.7% 🟢) 1.674s 3 1.02x
▲ Vercel Next.js (Turbopack) 23.424s (-55.4% 🟢) 24.976s (-54.3% 🟢) 1.552s 3 1.05x

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

workflow with 50 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 11.926s (-14.9% 🟢) 12.141s (-16.8% 🟢) 0.215s 8 1.00x
💻 Local Express 11.936s (-28.1% 🟢) 12.148s (-28.7% 🟢) 0.212s 8 1.00x
💻 Local Nitro 11.946s (-28.8% 🟢) 12.147s (-28.7% 🟢) 0.200s 8 1.00x
🐘 Postgres Nitro 11.952s (-14.4% 🟢) 12.267s (-14.3% 🟢) 0.315s 8 1.00x
💻 Local Next.js (Turbopack) 12.435s 13.021s 0.586s 7 1.04x
🐘 Postgres Next.js (Turbopack) 14.047s 14.727s 0.680s 7 1.18x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 33.347s (-92.1% 🟢) 35.833s (-91.6% 🟢) 2.486s 3 1.00x
▲ Vercel Express 36.755s (-69.7% 🟢) 38.906s (-68.5% 🟢) 2.151s 3 1.10x
▲ Vercel Next.js (Turbopack) 39.541s (-90.0% 🟢) 41.231s (-89.6% 🟢) 1.690s 3 1.19x

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

Promise.all with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.146s (-9.1% 🟢) 2.007s (~) 0.861s 15 1.00x
🐘 Postgres Nitro 1.148s (-10.0% 🟢) 2.007s (~) 0.860s 15 1.00x
💻 Local Express 1.171s (-21.4% 🟢) 2.006s (~) 0.835s 15 1.02x
💻 Local Nitro 1.171s (-28.2% 🟢) 2.006s (-3.3%) 0.835s 15 1.02x
💻 Local Next.js (Turbopack) 1.230s 2.005s 0.774s 15 1.07x
🐘 Postgres Next.js (Turbopack) 1.521s 2.053s 0.532s 15 1.33x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 2.937s (+2.7%) 4.776s (+3.3%) 1.839s 7 1.00x
▲ Vercel Next.js (Turbopack) 2.957s (-13.0% 🟢) 4.488s (-9.0% 🟢) 1.530s 7 1.01x
▲ Vercel Nitro 2.992s (+6.2% 🔺) 4.510s (+4.3%) 1.518s 7 1.02x

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

Promise.all with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.206s (-48.9% 🟢) 2.007s (-33.3% 🟢) 0.800s 15 1.00x
🐘 Postgres Nitro 1.214s (-48.4% 🟢) 2.008s (-33.3% 🟢) 0.794s 15 1.01x
🐘 Postgres Next.js (Turbopack) 1.547s 2.234s 0.687s 14 1.28x
💻 Local Next.js (Turbopack) 1.645s 2.005s 0.360s 15 1.36x
💻 Local Express 1.706s (-42.2% 🟢) 2.006s (-41.9% 🟢) 0.299s 15 1.41x
💻 Local Nitro 1.859s (-40.8% 🟢) 2.149s (-44.7% 🟢) 0.290s 14 1.54x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 3.704s (+2.3%) 5.355s (+4.8%) 1.651s 6 1.00x
▲ Vercel Nitro 3.817s (-5.8% 🟢) 5.430s (-8.3% 🟢) 1.613s 6 1.03x
▲ Vercel Next.js (Turbopack) 4.422s (-37.7% 🟢) 6.101s (-31.5% 🟢) 1.678s 5 1.19x

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

Promise.all with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.314s (-62.3% 🟢) 2.009s (-49.9% 🟢) 0.695s 15 1.00x
🐘 Postgres Nitro 1.322s (-62.0% 🟢) 2.010s (-49.9% 🟢) 0.687s 15 1.01x
🐘 Postgres Next.js (Turbopack) 2.024s 2.601s 0.577s 12 1.54x
💻 Local Next.js (Turbopack) 3.647s 4.133s 0.486s 8 2.78x
💻 Local Express 4.767s (-42.8% 🟢) 5.011s (-44.5% 🟢) 0.244s 6 3.63x
💻 Local Nitro 5.166s (-38.1% 🟢) 5.681s (-37.0% 🟢) 0.515s 6 3.93x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 5.668s (+60.8% 🔺) 7.411s (+33.9% 🔺) 1.743s 5 1.00x
▲ Vercel Express 5.766s (+36.0% 🔺) 7.113s (+16.1% 🔺) 1.347s 5 1.02x
▲ Vercel Next.js (Turbopack) 6.040s (-32.2% 🟢) 7.983s (-27.2% 🟢) 1.942s 4 1.07x

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

Promise.race with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.140s (-9.3% 🟢) 2.009s (~) 0.869s 15 1.00x
🐘 Postgres Nitro 1.162s (-7.6% 🟢) 2.008s (~) 0.846s 15 1.02x
💻 Local Next.js (Turbopack) 1.243s 2.005s 0.763s 15 1.09x
🐘 Postgres Next.js (Turbopack) 1.321s 2.006s 0.685s 15 1.16x
💻 Local Nitro 1.404s (-24.8% 🟢) 2.006s (-14.3% 🟢) 0.602s 15 1.23x
💻 Local Express 1.449s (-23.5% 🟢) 2.006s (-15.1% 🟢) 0.557s 15 1.27x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 2.580s (~) 4.102s (-5.7% 🟢) 1.522s 8 1.00x
▲ Vercel Next.js (Turbopack) 2.714s (-7.4% 🟢) 4.363s (-6.0% 🟢) 1.649s 7 1.05x
▲ Vercel Nitro 3.242s (+31.8% 🔺) 4.957s (+18.9% 🔺) 1.715s 7 1.26x

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

Promise.race with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.212s (-48.2% 🟢) 2.009s (-33.3% 🟢) 0.796s 15 1.00x
🐘 Postgres Express 1.215s (-48.1% 🟢) 2.009s (-33.3% 🟢) 0.793s 15 1.00x
🐘 Postgres Next.js (Turbopack) 1.449s 2.008s 0.560s 15 1.20x
💻 Local Next.js (Turbopack) 1.911s 2.291s 0.380s 14 1.58x
💻 Local Express 2.006s (-36.0% 🟢) 2.469s (-34.4% 🟢) 0.463s 13 1.65x
💻 Local Nitro 2.070s (-32.5% 🟢) 2.471s (-36.4% 🟢) 0.402s 13 1.71x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 3.647s (+14.2% 🔺) 5.276s (+10.1% 🔺) 1.628s 6 1.00x
▲ Vercel Next.js (Turbopack) 3.901s (+24.1% 🔺) 5.404s (+19.5% 🔺) 1.504s 6 1.07x
▲ Vercel Nitro 3.997s (+23.6% 🔺) 5.840s (+15.0% 🔺) 1.844s 6 1.10x

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

Promise.race with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.318s (-62.3% 🟢) 2.008s (-49.9% 🟢) 0.690s 15 1.00x
🐘 Postgres Nitro 1.326s (-61.9% 🟢) 2.007s (-49.9% 🟢) 0.682s 15 1.01x
🐘 Postgres Next.js (Turbopack) 1.695s 2.325s 0.629s 13 1.29x
💻 Local Next.js (Turbopack) 4.309s 4.725s 0.415s 7 3.27x
💻 Local Express 5.588s (-36.5% 🟢) 5.849s (-36.9% 🟢) 0.260s 6 4.24x
💻 Local Nitro 5.768s (-36.9% 🟢) 6.216s (-38.0% 🟢) 0.447s 5 4.38x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 5.619s (-12.4% 🟢) 7.241s (-11.5% 🟢) 1.621s 5 1.00x
▲ Vercel Nitro 5.806s (+14.0% 🔺) 7.621s (+11.8% 🔺) 1.814s 4 1.03x
▲ Vercel Next.js (Turbopack) 5.882s (-12.9% 🟢) 7.895s (-7.6% 🟢) 2.013s 4 1.05x

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

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.430s (-47.6% 🟢) 1.007s (~) 0.577s 60 1.00x
🐘 Postgres Express 0.470s (-44.0% 🟢) 1.007s (-1.6%) 0.537s 60 1.09x
💻 Local Express 0.489s (-50.3% 🟢) 1.021s (-5.1% 🟢) 0.532s 59 1.14x
💻 Local Nitro 0.497s (-49.3% 🟢) 1.021s (-6.6% 🟢) 0.524s 59 1.16x
💻 Local Next.js (Turbopack) 0.640s 1.021s 0.380s 59 1.49x
🐘 Postgres Next.js (Turbopack) 0.808s 1.248s 0.440s 49 1.88x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 5.079s (-65.0% 🟢) 6.646s (-58.7% 🟢) 1.567s 10 1.00x
▲ Vercel Nitro 5.126s (-76.8% 🟢) 7.046s (-70.7% 🟢) 1.920s 9 1.01x
▲ Vercel Express 5.381s (-71.7% 🟢) 7.193s (-66.3% 🟢) 1.812s 9 1.06x

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

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.098s (-43.1% 🟢) 1.771s (-15.7% 🟢) 0.674s 51 1.00x
🐘 Postgres Express 1.107s (-44.0% 🟢) 1.882s (-16.6% 🟢) 0.776s 48 1.01x
💻 Local Nitro 1.227s (-59.6% 🟢) 2.006s (-46.6% 🟢) 0.779s 45 1.12x
💻 Local Express 1.244s (-58.8% 🟢) 2.006s (-44.1% 🟢) 0.762s 45 1.13x
💻 Local Next.js (Turbopack) 1.510s 2.005s 0.495s 45 1.38x
🐘 Postgres Next.js (Turbopack) 1.782s 2.256s 0.474s 40 1.62x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 13.569s (-65.6% 🟢) 15.544s (-62.4% 🟢) 1.974s 6 1.00x
▲ Vercel Next.js (Turbopack) 13.835s (-72.2% 🟢) 15.298s (-70.4% 🟢) 1.462s 6 1.02x
▲ Vercel Express 14.749s (-57.3% 🟢) 16.388s (-55.5% 🟢) 1.639s 6 1.09x

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

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 2.043s (-50.2% 🟢) 2.507s (-45.5% 🟢) 0.464s 48 1.00x
🐘 Postgres Express 2.177s (-45.5% 🟢) 3.008s (-31.1% 🟢) 0.831s 40 1.07x
💻 Local Nitro 2.718s (-70.8% 🟢) 3.007s (-70.0% 🟢) 0.289s 40 1.33x
💻 Local Express 2.742s (-70.2% 🟢) 3.032s (-69.7% 🟢) 0.290s 40 1.34x
💻 Local Next.js (Turbopack) 3.341s 4.007s 0.666s 30 1.64x
🐘 Postgres Next.js (Turbopack) 3.557s 4.115s 0.558s 30 1.74x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 41.245s (-68.3% 🟢) 42.971s (-67.5% 🟢) 1.725s 3 1.00x
▲ Vercel Nitro 43.115s (-55.5% 🟢) 44.785s (-54.5% 🟢) 1.670s 3 1.05x
▲ Vercel Next.js (Turbopack) 43.580s (-59.3% 🟢) 44.953s (-58.7% 🟢) 1.373s 3 1.06x

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

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.181s (-35.8% 🟢) 1.007s (~) 0.826s 60 1.00x
🐘 Postgres Nitro 0.208s (-26.6% 🟢) 1.006s (~) 0.798s 60 1.15x
🐘 Postgres Next.js (Turbopack) 0.317s 1.038s 0.721s 58 1.75x
💻 Local Nitro 0.403s (-33.4% 🟢) 1.004s (-1.7%) 0.601s 60 2.22x
💻 Local Express 0.427s (-23.8% 🟢) 1.004s (~) 0.577s 60 2.36x
💻 Local Next.js (Turbopack) 0.508s 1.004s 0.496s 60 2.80x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.290s (+37.9% 🔺) 4.168s (+24.4% 🔺) 1.877s 15 1.00x
▲ Vercel Express 2.301s (+17.7% 🔺) 3.790s (+4.2%) 1.489s 16 1.00x
▲ Vercel Next.js (Turbopack) 2.613s (+29.2% 🔺) 3.964s (+4.5%) 1.351s 16 1.14x

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

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.294s (-42.3% 🟢) 1.006s (~) 0.712s 90 1.00x
🐘 Postgres Nitro 0.298s (-40.0% 🟢) 1.006s (~) 0.708s 90 1.01x
🐘 Postgres Next.js (Turbopack) 0.491s 1.044s 0.553s 87 1.67x
💻 Local Next.js (Turbopack) 1.950s 2.444s 0.494s 37 6.63x
💻 Local Nitro 2.160s (-14.9% 🟢) 2.736s (-9.1% 🟢) 0.576s 33 7.35x
💻 Local Express 2.220s (-11.7% 🟢) 2.822s (-6.2% 🟢) 0.602s 32 7.55x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 9.895s (+206.7% 🔺) 12.010s (+149.1% 🔺) 2.115s 8 1.00x
▲ Vercel Next.js (Turbopack) 10.388s (+193.8% 🔺) 11.879s (+128.7% 🔺) 1.491s 8 1.05x
▲ Vercel Express 11.580s (+280.1% 🔺) 13.236s (+175.3% 🔺) 1.657s 8 1.17x

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

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.624s (-21.0% 🟢) 1.006s (~) 0.382s 120 1.00x
🐘 Postgres Express 0.632s (-22.9% 🟢) 1.006s (-1.1%) 0.374s 120 1.01x
🐘 Postgres Next.js (Turbopack) 1.093s 1.558s 0.465s 78 1.75x
💻 Local Next.js (Turbopack) 8.782s 9.483s 0.701s 13 14.06x
💻 Local Nitro 10.122s (-9.6% 🟢) 10.692s (-8.3% 🟢) 0.570s 12 16.21x
💻 Local Express 10.166s (-9.2% 🟢) 10.696s (-10.4% 🟢) 0.530s 12 16.28x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 26.595s (+157.5% 🔺) 28.057s (+128.4% 🔺) 1.462s 5 1.00x
▲ Vercel Nitro 27.957s (+262.0% 🔺) 30.343s (+222.8% 🔺) 2.385s 4 1.05x
▲ Vercel Express 168.055s (+2164.8% 🔺) 169.517s (+1733.7% 🔺) 1.462s 2 6.32x

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

Stream Benchmarks (includes TTFB metrics)
workflow with stream

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 1.133s (+430.4% 🔺) 2.005s (+99.6% 🔺) 0.013s (~) 2.019s (+98.2% 🔺) 0.886s 10 1.00x
🐘 Postgres Nitro 1.142s (+456.9% 🔺) 1.997s (+99.8% 🔺) 0.001s (-13.3% 🟢) 2.011s (+98.9% 🔺) 0.869s 10 1.01x
💻 Local Next.js (Turbopack) 1.142s 2.003s 0.008s 2.014s 0.872s 10 1.01x
💻 Local Express 1.142s (+473.6% 🔺) 2.006s (+99.7% 🔺) 0.011s (-10.7% 🟢) 2.019s (+98.3% 🔺) 0.877s 10 1.01x
🐘 Postgres Express 1.150s (+460.6% 🔺) 2.003s (+100.6% 🔺) 0.002s (-6.3% 🟢) 2.011s (+98.8% 🔺) 0.861s 10 1.01x
🐘 Postgres Next.js (Turbopack) 1.484s 1.999s 0.379s 2.420s 0.936s 10 1.31x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 2.562s (-62.6% 🟢) 3.497s (-59.6% 🟢) 2.183s (+245.5% 🔺) 6.160s (-37.1% 🟢) 3.598s 10 1.00x
▲ Vercel Express 2.685s (+7.2% 🔺) 3.793s (-7.3% 🟢) 2.807s (+192.1% 🔺) 7.035s (+25.8% 🔺) 4.350s 10 1.05x
▲ Vercel Nitro 2.755s (-28.1% 🟢) 4.308s (-18.4% 🟢) 2.585s (+248.4% 🔺) 7.422s (+14.5% 🔺) 4.667s 10 1.08x

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

stream pipeline with 5 transform steps (1MB)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.518s (+143.3% 🔺) 2.001s (+98.7% 🔺) 0.004s (-3.3%) 2.026s (+98.2% 🔺) 0.508s 30 1.00x
💻 Local Express 1.528s (+101.8% 🔺) 2.010s (+95.3% 🔺) 0.011s (+14.4% 🔺) 2.023s (+94.5% 🔺) 0.495s 30 1.01x
🐘 Postgres Express 1.530s (+142.8% 🔺) 2.006s (+99.3% 🔺) 0.004s (+3.6%) 2.025s (+98.0% 🔺) 0.495s 30 1.01x
💻 Local Nitro 1.546s (+84.3% 🔺) 2.011s (+98.7% 🔺) 0.010s (+4.9%) 2.023s (+81.3% 🔺) 0.477s 30 1.02x
💻 Local Next.js (Turbopack) 1.728s 2.008s 0.006s 2.196s 0.469s 28 1.14x
🐘 Postgres Next.js (Turbopack) 2.750s 3.167s 0.003s 3.231s 0.481s 19 1.81x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 5.910s (-79.9% 🟢) 7.361s (-76.1% 🟢) 0.272s (+143.0% 🔺) 8.148s (-74.4% 🟢) 2.239s 8 1.00x
▲ Vercel Next.js (Turbopack) 6.075s (-64.1% 🟢) 7.045s (-61.4% 🟢) 0.504s (+138.4% 🔺) 7.950s (-58.0% 🟢) 1.875s 8 1.03x
▲ Vercel Express 6.109s (-6.1% 🟢) 7.357s (-8.2% 🟢) 0.516s (+26.2% 🔺) 8.396s (-5.0%) 2.286s 8 1.03x

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

10 parallel streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.662s (-31.1% 🟢) 1.014s (-20.7% 🟢) 0.000s (+55.9% 🔺) 1.029s (-21.2% 🟢) 0.367s 59 1.00x
🐘 Postgres Nitro 0.668s (-31.0% 🟢) 1.031s (-17.4% 🟢) 0.000s (-17.2% 🟢) 1.041s (-17.2% 🟢) 0.373s 58 1.01x
🐘 Postgres Next.js (Turbopack) 1.056s 1.402s 0.000s 1.432s 0.376s 42 1.59x
💻 Local Nitro 1.357s (+11.0% 🔺) 2.016s (~) 0.000s (+233.3% 🔺) 2.018s (~) 0.661s 30 2.05x
💻 Local Express 1.394s (+13.8% 🔺) 2.016s (~) 0.000s (-20.0% 🟢) 2.018s (~) 0.624s 30 2.10x
💻 Local Next.js (Turbopack) ⚠️ missing - - - - -

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 3.669s (-1.9%) 4.701s (-7.9% 🟢) 0.000s (+37.5% 🔺) 5.213s (-5.7% 🟢) 1.545s 12 1.00x
▲ Vercel Nitro 3.678s (+20.6% 🔺) 4.988s (+13.5% 🔺) 0.000s (-100.0% 🟢) 5.539s (+15.2% 🔺) 1.861s 11 1.00x
▲ Vercel Next.js (Turbopack) 4.229s (-58.5% 🟢) 5.284s (-54.1% 🟢) 0.002s (+Infinity% 🔺) 5.702s (-52.7% 🟢) 1.473s 11 1.15x

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

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

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.395s (-22.1% 🟢) 2.101s (-1.9%) 0.000s (-3.4%) 2.114s (-2.8%) 0.718s 29 1.00x
🐘 Postgres Express 1.428s (-19.4% 🟢) 2.178s (~) 0.000s (+Infinity% 🔺) 2.189s (~) 0.760s 28 1.02x
💻 Local Next.js (Turbopack) 2.281s 2.830s 0.000s 2.841s 0.559s 22 1.64x
🐘 Postgres Next.js (Turbopack) 2.901s 3.393s 0.000s 3.490s 0.589s 18 2.08x
💻 Local Nitro 3.030s (-10.5% 🟢) 3.840s (-4.8%) 0.000s (-53.1% 🟢) 3.843s (-4.8%) 0.813s 16 2.17x
💻 Local Express 3.243s (-6.5% 🟢) 4.033s (~) 0.000s (-58.3% 🟢) 4.035s (~) 0.792s 15 2.32x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 5.874s (+28.0% 🔺) 7.232s (+20.1% 🔺) 0.000s (NaN%) 7.692s (+19.1% 🔺) 1.818s 8 1.00x
▲ Vercel Nitro 6.266s (+53.1% 🔺) 7.769s (+44.6% 🔺) 0.000s (+37.5% 🔺) 8.289s (+43.1% 🔺) 2.024s 8 1.07x
▲ Vercel Next.js (Turbopack) 6.570s (+17.0% 🔺) 7.818s (+12.0% 🔺) 0.000s (~) 8.251s (+9.4% 🔺) 1.681s 8 1.12x

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

Summary

Fastest Framework by World

Winner determined by most benchmark wins

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

Winner determined by most benchmark wins

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

Worlds:

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

📋 View full workflow run


⚠️ Community world benchmarks failed (non-blocking):

  • Community Worlds: failure

Check the workflow run for details.

const SAFE_DYNAMIC_ID_SEGMENT = /^[a-zA-Z0-9_.@-]+$/;
const SAFE_DYNAMIC_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
const UNSUPPORTED_DYNAMIC_MODULE_SYNTAX =
/(^|[\s;])(?:import\s*(?:[\w*{]|\(|['"])|export\s+(?:async\s+)?(?:function|const|let|var|class|default|\{|\*))/m;
Copy link
Copy Markdown
Contributor

@vercel vercel Bot May 21, 2026

Choose a reason for hiding this comment

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

The UNSUPPORTED_DYNAMIC_MODULE_SYNTAX regex falsely rejects valid JavaScript code containing identifiers that start with "import" (e.g., importData, importConfig, imports).

Fix on Vercel

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant