Skip to content

[core] Combine initial run fetch, event fetch, and run_started event creation#1569

Open
VaguelySerious wants to merge 1 commit intomainfrom
peter/skip-runs-get
Open

[core] Combine initial run fetch, event fetch, and run_started event creation#1569
VaguelySerious wants to merge 1 commit intomainfrom
peter/skip-runs-get

Conversation

@VaguelySerious
Copy link
Copy Markdown
Member

@VaguelySerious VaguelySerious commented Mar 31, 2026

This PR extracts some of the safer performance improvements originally added as part of #1537

…creation

Signed-off-by: Peter Wielander <mittgfu@gmail.com>
@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Mar 31, 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 Mar 31, 2026 7:46pm
example-nextjs-workflow-webpack Ready Ready Preview, Comment Mar 31, 2026 7:46pm
example-workflow Ready Ready Preview, Comment Mar 31, 2026 7:46pm
workbench-astro-workflow Ready Ready Preview, Comment Mar 31, 2026 7:46pm
workbench-express-workflow Ready Ready Preview, Comment Mar 31, 2026 7:46pm
workbench-fastify-workflow Ready Ready Preview, Comment Mar 31, 2026 7:46pm
workbench-hono-workflow Ready Ready Preview, Comment Mar 31, 2026 7:46pm
workbench-nitro-workflow Ready Ready Preview, Comment Mar 31, 2026 7:46pm
workbench-nuxt-workflow Ready Ready Preview, Comment Mar 31, 2026 7:46pm
workbench-sveltekit-workflow Ready Ready Preview, Comment Mar 31, 2026 7:46pm
workbench-vite-workflow Ready Ready Preview, Comment Mar 31, 2026 7:46pm
workflow-docs Ready Ready Preview, Comment, Open in v0 Mar 31, 2026 7:46pm
workflow-swc-playground Ready Ready Preview, Comment Mar 31, 2026 7:46pm

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 31, 2026

🦋 Changeset detected

Latest commit: 38b6674

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

This PR includes changesets to release 20 packages
Name Type
@workflow/world Patch
@workflow/core Patch
@workflow/world-local Patch
@workflow/world-postgres Patch
@workflow/cli Patch
@workflow/vitest Patch
@workflow/web-shared Patch
@workflow/world-testing Patch
@workflow/world-vercel Patch
@workflow/builders Patch
@workflow/next Patch
@workflow/nitro Patch
workflow 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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 31, 2026

🧪 E2E Test Results

Some tests failed

Summary

Passed Failed Skipped Total
✅ ▲ Vercel Production 857 0 67 924
✅ 💻 Local Development 830 0 178 1008
✅ 📦 Local Production 830 0 178 1008
✅ 🐘 Local Postgres 830 0 178 1008
✅ 🪟 Windows 76 0 8 84
❌ 🌍 Community Worlds 132 60 24 216
✅ 📋 Other 210 0 42 252
Total 3765 60 675 4500

❌ Failed Tests

🌍 Community Worlds (60 failed)

mongodb (3 failed):

  • hookWorkflow is not resumable via public webhook endpoint | wrun_01KN2PY3613TQRZKK90MYCR0GW
  • webhookWorkflow | wrun_01KN2PYBGY0TRSDB5KHPS8M3JV
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_01KN2Q5S83Q6E3F1VZ00S0GY2D

redis (2 failed):

  • hookWorkflow is not resumable via public webhook endpoint | wrun_01KN2PY3613TQRZKK90MYCR0GW
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_01KN2Q5S83Q6E3F1VZ00S0GY2D

turso (55 failed):

  • addTenWorkflow | wrun_01KN2PWYX0G8D2NS97J7WCFCY8
  • addTenWorkflow | wrun_01KN2PWYX0G8D2NS97J7WCFCY8
  • wellKnownAgentWorkflow (.well-known/agent) | wrun_01KN2PY2S4KYQNXSD8DVK11BBF
  • should work with react rendering in step
  • promiseAllWorkflow | wrun_01KN2PX62M9ETA4S1638AN5XGA
  • promiseRaceWorkflow | wrun_01KN2PXB9CYB5KDR8EX4RJS7SM
  • promiseAnyWorkflow | wrun_01KN2PXDVPW3D7Z1SRQRGPER9X
  • importedStepOnlyWorkflow | wrun_01KN2PYDP7JNWAZKCKJ52DV7Q0
  • hookWorkflow | wrun_01KN2PXSGPNZX3YQXPQ5XXA8HS
  • hookWorkflow is not resumable via public webhook endpoint | wrun_01KN2PY3613TQRZKK90MYCR0GW
  • webhookWorkflow | wrun_01KN2PYBGY0TRSDB5KHPS8M3JV
  • sleepingWorkflow | wrun_01KN2PYJEY51GXPY1QTQTWDDXG
  • parallelSleepWorkflow | wrun_01KN2PYYC569XWNE8ANXCW9VVS
  • nullByteWorkflow | wrun_01KN2PZ2K87MTBJMKXE85GCMFE
  • workflowAndStepMetadataWorkflow | wrun_01KN2PZ4FY5Q35WBQBVT0XKGMW
  • fetchWorkflow | wrun_01KN2Q1W7X24SGXCHS0A2DXHK7
  • promiseRaceStressTestWorkflow | wrun_01KN2Q1Z2CFYJYXSTR89QGJEG2
  • error handling error propagation workflow errors nested function calls preserve message and stack trace
  • error handling error propagation workflow errors cross-file imports preserve message and stack trace
  • error handling error propagation step errors basic step error preserves message and stack trace
  • error handling error propagation step errors cross-file step error preserves message and function names in stack
  • error handling retry behavior regular Error retries until success
  • error handling retry behavior FatalError fails immediately without retries
  • error handling retry behavior RetryableError respects custom retryAfter delay
  • error handling retry behavior maxRetries=0 disables retries
  • error handling catchability FatalError can be caught and detected with FatalError.is()
  • error handling not registered WorkflowNotRegisteredError fails the run when workflow does not exist
  • error handling not registered StepNotRegisteredError fails the step but workflow can catch it
  • error handling not registered StepNotRegisteredError fails the run when not caught in workflow
  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_01KN2Q56E3ZA6CRCXQ2DCP2EV5
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_01KN2Q5S83Q6E3F1VZ00S0GY2D
  • hookDisposeTestWorkflow - hook token reuse after explicit disposal while workflow still running | wrun_01KN2Q6CKNQT4BWC25HQZ30XN8
  • stepFunctionPassingWorkflow - step function references can be passed as arguments (without closure vars) | wrun_01KN2Q6YNYJADH38JJPAHY9EQ2
  • stepFunctionWithClosureWorkflow - step function with closure variables passed as argument | wrun_01KN2Q7617BHRK5KJG5XKAXYBY
  • closureVariableWorkflow - nested step functions with closure variables | wrun_01KN2Q7AVZKGXBKYXQPW610DHD
  • spawnWorkflowFromStepWorkflow - spawning a child workflow using start() inside a step | wrun_01KN2Q7D3397H0F68EJKW4SQDJ
  • health check (queue-based) - workflow and step endpoints respond to health check messages
  • pathsAliasWorkflow - TypeScript path aliases resolve correctly | wrun_01KN2Q7TGBZMPE66NCAA550B1C
  • Calculator.calculate - static workflow method using static step methods from another class | wrun_01KN2Q7ZCR83BC1ZE1X7Y3V9G1
  • AllInOneService.processNumber - static workflow method using sibling static step methods | wrun_01KN2Q8553BEFKW8NPB3QY5WB5
  • ChainableService.processWithThis - static step methods using this to reference the class | wrun_01KN2Q8B52Y3C5DB1X78YM303F
  • thisSerializationWorkflow - step function invoked with .call() and .apply() | wrun_01KN2Q8H4E18YJ5M7HHA7RS92G
  • customSerializationWorkflow - custom class serialization with WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE | wrun_01KN2Q8R6B3V6MDK67H0XQXJ7G
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_01KN2Q8ZWVZ24QNMR1TDGCM099
  • crossContextSerdeWorkflow - classes defined in step code are deserializable in workflow context | wrun_01KN2Q99KZNHW9J0N842WK9KG8
  • stepFunctionAsStartArgWorkflow - step function reference passed as start() argument | wrun_01KN2Q9HQ8MXRG3RB4E58CV9JE
  • cancelRun - cancelling a running workflow | wrun_01KN2Q9R52978D6Y2T7K4W2E9K
  • cancelRun via CLI - cancelling a running workflow | wrun_01KN2QA0PKDDDFJN8EJVD1BN65
  • pages router addTenWorkflow via pages router
  • pages router promiseAllWorkflow via pages router
  • pages router sleepingWorkflow via pages router
  • hookWithSleepWorkflow - hook payloads delivered correctly with concurrent sleep | wrun_01KN2QABPM67KPCSB0T7RK9ACQ
  • sleepInLoopWorkflow - sleep inside loop with steps actually delays each iteration | wrun_01KN2QAXWY0E1ESTJ163PJ3XR9
  • sleepWithSequentialStepsWorkflow - sequential steps work with concurrent sleep (control) | wrun_01KN2QB8PRNSFYQED9PRSW9HMP
  • importMetaUrlWorkflow - import.meta.url is available in step bundles | wrun_01KN2QBEQRG1TQHVPHFQ8GRAEQ

Details by Category

✅ ▲ Vercel Production
App Passed Failed Skipped
✅ astro 77 0 7
✅ example 77 0 7
✅ express 77 0 7
✅ fastify 77 0 7
✅ hono 77 0 7
✅ nextjs-turbopack 82 0 2
✅ nextjs-webpack 82 0 2
✅ nitro 77 0 7
✅ nuxt 77 0 7
✅ sveltekit 77 0 7
✅ vite 77 0 7
✅ 💻 Local Development
App Passed Failed Skipped
✅ astro-stable 70 0 14
✅ express-stable 70 0 14
✅ fastify-stable 70 0 14
✅ hono-stable 70 0 14
✅ nextjs-turbopack-canary 59 0 25
✅ nextjs-turbopack-stable 76 0 8
✅ nextjs-webpack-canary 59 0 25
✅ nextjs-webpack-stable 76 0 8
✅ nitro-stable 70 0 14
✅ nuxt-stable 70 0 14
✅ sveltekit-stable 70 0 14
✅ vite-stable 70 0 14
✅ 📦 Local Production
App Passed Failed Skipped
✅ astro-stable 70 0 14
✅ express-stable 70 0 14
✅ fastify-stable 70 0 14
✅ hono-stable 70 0 14
✅ nextjs-turbopack-canary 59 0 25
✅ nextjs-turbopack-stable 76 0 8
✅ nextjs-webpack-canary 59 0 25
✅ nextjs-webpack-stable 76 0 8
✅ nitro-stable 70 0 14
✅ nuxt-stable 70 0 14
✅ sveltekit-stable 70 0 14
✅ vite-stable 70 0 14
✅ 🐘 Local Postgres
App Passed Failed Skipped
✅ astro-stable 70 0 14
✅ express-stable 70 0 14
✅ fastify-stable 70 0 14
✅ hono-stable 70 0 14
✅ nextjs-turbopack-canary 59 0 25
✅ nextjs-turbopack-stable 76 0 8
✅ nextjs-webpack-canary 59 0 25
✅ nextjs-webpack-stable 76 0 8
✅ nitro-stable 70 0 14
✅ nuxt-stable 70 0 14
✅ sveltekit-stable 70 0 14
✅ vite-stable 70 0 14
✅ 🪟 Windows
App Passed Failed Skipped
✅ nextjs-turbopack 76 0 8
❌ 🌍 Community Worlds
App Passed Failed Skipped
✅ mongodb-dev 5 0 0
❌ mongodb 56 3 8
✅ redis-dev 5 0 0
❌ redis 57 2 8
✅ turso-dev 5 0 0
❌ turso 4 55 8
✅ 📋 Other
App Passed Failed Skipped
✅ e2e-local-dev-nest-stable 70 0 14
✅ e2e-local-postgres-nest-stable 70 0 14
✅ e2e-local-prod-nest-stable 70 0 14

📋 View full workflow run

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 31, 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 🥇 Nitro 0.036s (-18.0% 🟢) 1.005s (~) 0.969s 10 1.00x
💻 Local Express 0.042s (-6.4% 🟢) 1.005s (~) 0.963s 10 1.16x
💻 Local Next.js (Turbopack) 0.047s (-9.2% 🟢) 1.005s (~) 0.958s 10 1.30x
🌐 Redis Next.js (Turbopack) 0.057s (+29.2% 🔺) 1.005s (~) 0.948s 10 1.57x
🐘 Postgres Next.js (Turbopack) 0.061s (-8.7% 🟢) 1.012s (~) 0.950s 10 1.68x
🐘 Postgres Express 0.065s (-6.7% 🟢) 1.012s (~) 0.947s 10 1.77x
🐘 Postgres Nitro 0.065s (-6.3% 🟢) 1.014s (~) 0.948s 10 1.80x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 0.519s (-1.0%) 2.154s (-21.6% 🟢) 1.635s 10 1.00x
▲ Vercel Express 0.660s (+31.0% 🔺) 2.569s (-4.6%) 1.909s 10 1.27x
▲ Vercel Next.js (Turbopack) 1.095s (+84.6% 🔺) 3.054s (+23.9% 🔺) 1.958s 10 2.11x

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

workflow with 1 step

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 1.098s (-3.0%) 2.005s (~) 0.907s 10 1.00x
💻 Local Next.js (Turbopack) 1.113s (-0.5%) 2.005s (~) 0.892s 10 1.01x
🌐 Redis Next.js (Turbopack) 1.132s (+1.7%) 2.007s (~) 0.875s 10 1.03x
💻 Local Express 1.134s (+0.6%) 2.006s (~) 0.872s 10 1.03x
🐘 Postgres Nitro 1.146s (~) 2.009s (~) 0.863s 10 1.04x
🐘 Postgres Express 1.146s (~) 2.012s (~) 0.865s 10 1.04x
🐘 Postgres Next.js (Turbopack) 1.148s (~) 2.011s (~) 0.863s 10 1.05x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 2.124s (-6.2% 🟢) 3.531s (-10.8% 🟢) 1.406s 10 1.00x
▲ Vercel Nitro 2.147s (+3.1%) 3.494s (-10.3% 🟢) 1.348s 10 1.01x
▲ Vercel Next.js (Turbopack) 2.283s (+9.1% 🔺) 4.040s (+3.5%) 1.757s 10 1.07x

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

workflow with 10 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 10.638s (-2.3%) 11.023s (~) 0.385s 3 1.00x
💻 Local Next.js (Turbopack) 10.784s (~) 11.023s (~) 0.238s 3 1.01x
🐘 Postgres Next.js (Turbopack) 10.834s (-0.9%) 11.021s (~) 0.187s 3 1.02x
🐘 Postgres Nitro 10.858s (-0.6%) 11.024s (~) 0.166s 3 1.02x
🐘 Postgres Express 10.887s (-0.6%) 11.022s (~) 0.135s 3 1.02x
🌐 Redis Next.js (Turbopack) 10.917s (+3.1%) 11.024s (~) 0.108s 3 1.03x
💻 Local Express 10.917s (~) 11.023s (~) 0.105s 3 1.03x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 17.122s (-25.7% 🟢) 18.578s (-26.9% 🟢) 1.456s 2 1.00x
▲ Vercel Express 17.461s (~) 19.578s (~) 2.117s 2 1.02x
▲ Vercel Next.js (Turbopack) 17.839s (+4.4%) 19.927s (+4.1%) 2.088s 2 1.04x

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

workflow with 25 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 14.206s (-5.2% 🟢) 15.030s (~) 0.824s 4 1.00x
🐘 Postgres Next.js (Turbopack) 14.417s (~) 15.023s (~) 0.606s 4 1.01x
🌐 Redis Next.js (Turbopack) 14.499s (+4.4%) 15.031s (+5.6% 🔺) 0.532s 4 1.02x
🐘 Postgres Nitro 14.547s (~) 15.028s (~) 0.481s 4 1.02x
💻 Local Next.js (Turbopack) 14.563s (-0.6%) 15.030s (~) 0.467s 4 1.03x
🐘 Postgres Express 14.623s (~) 15.026s (~) 0.403s 4 1.03x
💻 Local Express 14.975s (~) 15.030s (~) 0.055s 4 1.05x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 32.403s (-7.4% 🟢) 33.993s (-7.7% 🟢) 1.590s 2 1.00x
▲ Vercel Next.js (Turbopack) 35.288s (+6.4% 🔺) 37.200s (+6.1% 🔺) 1.911s 2 1.09x
▲ Vercel Nitro 36.723s (+14.2% 🔺) 38.155s (+11.8% 🔺) 1.432s 2 1.13x

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

workflow with 50 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 13.577s (+7.6% 🔺) 14.027s (+7.7% 🔺) 0.450s 7 1.00x
🐘 Postgres Next.js (Turbopack) 13.653s (-2.1%) 14.025s (-1.0%) 0.372s 7 1.01x
🐘 Postgres Nitro 13.739s (-2.6%) 14.023s (-4.8%) 0.284s 7 1.01x
🐘 Postgres Express 14.095s (-2.1%) 14.598s (-2.8%) 0.503s 7 1.04x
💻 Local Nitro 14.819s (-10.5% 🟢) 15.027s (-11.8% 🟢) 0.208s 6 1.09x
💻 Local Next.js (Turbopack) 15.579s (-4.2%) 16.030s (-5.9% 🟢) 0.451s 6 1.15x
💻 Local Express 16.670s (~) 17.030s (~) 0.361s 6 1.23x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 58.520s (+1.7%) 60.352s (+0.6%) 1.832s 2 1.00x
▲ Vercel Next.js (Turbopack) 60.990s (+1.9%) 62.841s (+2.1%) 1.851s 2 1.04x
▲ Vercel Express 65.819s (+17.2% 🔺) 67.112s (+15.4% 🔺) 1.293s 2 1.12x

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

Promise.all with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 1.226s (-0.8%) 2.010s (~) 0.784s 15 1.00x
🐘 Postgres Nitro 1.271s (+0.7%) 2.010s (~) 0.739s 15 1.04x
🐘 Postgres Express 1.280s (+0.5%) 2.012s (~) 0.732s 15 1.04x
🌐 Redis Next.js (Turbopack) 1.329s (+8.6% 🔺) 2.007s (~) 0.678s 15 1.08x
💻 Local Nitro 1.467s (-4.3%) 2.005s (~) 0.538s 15 1.20x
💻 Local Express 1.502s (-2.5%) 2.006s (~) 0.505s 15 1.23x
💻 Local Next.js (Turbopack) 1.519s (-3.4%) 2.006s (~) 0.487s 15 1.24x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.325s (-4.6%) 3.382s (-16.2% 🟢) 1.057s 9 1.00x
▲ Vercel Express 2.425s (-6.4% 🟢) 4.304s (-4.0%) 1.879s 7 1.04x
▲ Vercel Next.js (Turbopack) 2.723s (+4.7%) 4.120s (+1.4%) 1.397s 8 1.17x

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

Promise.all with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 2.335s (-0.6%) 3.011s (~) 0.675s 10 1.00x
🐘 Postgres Express 2.342s (-1.1%) 3.011s (~) 0.669s 10 1.00x
🐘 Postgres Next.js (Turbopack) 2.403s (-1.6%) 3.011s (~) 0.608s 10 1.03x
🌐 Redis Next.js (Turbopack) 2.604s (+8.2% 🔺) 3.009s (~) 0.405s 10 1.11x
💻 Local Nitro 2.676s (-9.7% 🟢) 3.007s (-12.9% 🟢) 0.331s 10 1.15x
💻 Local Next.js (Turbopack) 2.762s (-1.5%) 3.007s (-12.9% 🟢) 0.245s 10 1.18x
💻 Local Express 3.058s (+4.5%) 3.885s (+29.1% 🔺) 0.827s 8 1.31x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.987s (+5.0%) 4.140s (-3.0%) 1.153s 8 1.00x
▲ Vercel Next.js (Turbopack) 3.089s (-15.0% 🟢) 4.501s (-11.7% 🟢) 1.412s 7 1.03x
▲ Vercel Express 3.163s (+20.3% 🔺) 4.469s (+2.4%) 1.307s 7 1.06x

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

Promise.all with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 3.463s (~) 4.011s (~) 0.548s 8 1.00x
🐘 Postgres Express 3.521s (+0.9%) 4.013s (~) 0.492s 8 1.02x
🐘 Postgres Next.js (Turbopack) 3.642s (~) 4.012s (~) 0.370s 8 1.05x
🌐 Redis Next.js (Turbopack) 4.232s (+8.3% 🔺) 4.726s (+14.3% 🔺) 0.494s 7 1.22x
💻 Local Next.js (Turbopack) 6.866s (-12.2% 🟢) 7.418s (-12.9% 🟢) 0.552s 5 1.98x
💻 Local Nitro 7.499s (-6.7% 🟢) 8.015s (-6.0% 🟢) 0.516s 4 2.17x
💻 Local Express 8.332s (+2.1%) 9.020s (~) 0.688s 4 2.41x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 3.129s (+3.0%) 4.385s (-13.3% 🟢) 1.255s 7 1.00x
▲ Vercel Express 3.176s (-13.2% 🟢) 5.020s (-11.7% 🟢) 1.844s 6 1.01x
▲ Vercel Next.js (Turbopack) 3.450s (-9.8% 🟢) 5.047s (-13.8% 🟢) 1.598s 6 1.10x

🔍 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 🥇 Next.js (Turbopack) 1.218s (-1.3%) 2.010s (~) 0.792s 15 1.00x
🐘 Postgres Nitro 1.261s (-0.6%) 2.009s (~) 0.748s 15 1.04x
🐘 Postgres Express 1.272s (+1.3%) 2.011s (~) 0.739s 15 1.04x
🌐 Redis Next.js (Turbopack) 1.327s (+7.7% 🔺) 2.007s (~) 0.680s 15 1.09x
💻 Local Next.js (Turbopack) 1.448s (-3.8%) 2.006s (~) 0.558s 15 1.19x
💻 Local Nitro 1.456s (-7.6% 🟢) 2.006s (~) 0.549s 15 1.20x
💻 Local Express 1.560s (~) 2.006s (~) 0.446s 15 1.28x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.238s (-8.6% 🟢) 3.504s (-12.3% 🟢) 1.266s 9 1.00x
▲ Vercel Express 2.305s (-0.7%) 3.858s (-3.7%) 1.553s 8 1.03x
▲ Vercel Next.js (Turbopack) 2.315s (-2.2%) 3.954s (-0.9%) 1.639s 8 1.03x

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

Promise.race with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 2.333s (-1.0%) 3.011s (~) 0.677s 10 1.00x
🐘 Postgres Next.js (Turbopack) 2.380s (-0.7%) 3.009s (~) 0.629s 10 1.02x
🐘 Postgres Express 2.399s (+3.5%) 3.010s (~) 0.611s 10 1.03x
🌐 Redis Next.js (Turbopack) 2.615s (+8.3% 🔺) 3.008s (~) 0.393s 10 1.12x
💻 Local Next.js (Turbopack) 2.723s (-10.2% 🟢) 3.108s (-10.0% 🟢) 0.384s 10 1.17x
💻 Local Nitro 2.869s (-5.9% 🟢) 3.107s (-20.0% 🟢) 0.238s 10 1.23x
💻 Local Express 3.305s (+5.9% 🔺) 4.136s (+6.4% 🔺) 0.831s 8 1.42x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 2.667s (-14.3% 🟢) 4.290s (-8.3% 🟢) 1.623s 8 1.00x
▲ Vercel Nitro 2.744s (-5.2% 🟢) 3.843s (-10.9% 🟢) 1.098s 8 1.03x
▲ Vercel Express 2.776s (-5.1% 🟢) 4.295s (-5.5% 🟢) 1.519s 7 1.04x

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

Promise.race with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 3.456s (-1.2%) 4.011s (~) 0.555s 8 1.00x
🐘 Postgres Express 3.494s (~) 4.013s (~) 0.518s 8 1.01x
🐘 Postgres Next.js (Turbopack) 3.648s (-1.1%) 4.013s (~) 0.365s 8 1.06x
🌐 Redis Next.js (Turbopack) 4.162s (+6.3% 🔺) 4.584s (+3.3%) 0.421s 7 1.20x
💻 Local Next.js (Turbopack) 7.310s (-11.4% 🟢) 8.015s (-5.9% 🟢) 0.705s 4 2.12x
💻 Local Nitro 7.540s (-12.5% 🟢) 8.016s (-11.1% 🟢) 0.476s 4 2.18x
💻 Local Express 8.631s (-2.5%) 9.025s (-2.7%) 0.394s 4 2.50x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 2.778s (-5.8% 🟢) 4.155s (-6.8% 🟢) 1.378s 8 1.00x
▲ Vercel Nitro 2.917s (-12.5% 🟢) 4.465s (-7.0% 🟢) 1.548s 7 1.05x
▲ Vercel Next.js (Turbopack) 3.490s (-8.2% 🟢) 5.019s (-11.8% 🟢) 1.529s 7 1.26x

🔍 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
💻 Local 🥇 Nitro 0.680s (-31.2% 🟢) 1.004s (-15.0% 🟢) 0.324s 60 1.00x
🌐 Redis Next.js (Turbopack) 0.749s (+30.7% 🔺) 1.005s (-1.6%) 0.256s 60 1.10x
🐘 Postgres Next.js (Turbopack) 0.762s (-5.5% 🟢) 1.007s (~) 0.245s 60 1.12x
🐘 Postgres Nitro 0.814s (-3.7%) 1.006s (~) 0.192s 60 1.20x
🐘 Postgres Express 0.854s (+1.5%) 1.007s (~) 0.153s 60 1.25x
💻 Local Next.js (Turbopack) 0.869s (+1.8%) 1.039s (+1.7%) 0.170s 58 1.28x
💻 Local Express 0.981s (~) 1.181s (~) 0.200s 51 1.44x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 9.954s (-3.0%) 11.297s (-7.7% 🟢) 1.344s 6 1.00x
▲ Vercel Express 10.409s (+3.7%) 12.065s (-0.6%) 1.656s 5 1.05x
▲ Vercel Next.js (Turbopack) 11.077s (-2.9%) 12.908s (-3.8%) 1.832s 5 1.11x

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

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 1.772s (+36.8% 🔺) 2.029s (+1.1%) 0.257s 45 1.00x
🐘 Postgres Next.js (Turbopack) 1.847s (-6.2% 🟢) 2.031s (-10.1% 🟢) 0.184s 45 1.04x
🐘 Postgres Nitro 1.904s (-6.2% 🟢) 2.031s (-25.2% 🟢) 0.127s 45 1.07x
🐘 Postgres Express 2.058s (+1.8%) 2.913s (+10.4% 🔺) 0.855s 31 1.16x
💻 Local Nitro 2.219s (-26.5% 🟢) 3.007s (-16.7% 🟢) 0.789s 30 1.25x
💻 Local Next.js (Turbopack) 2.628s (-3.5%) 3.008s (-1.1%) 0.379s 30 1.48x
💻 Local Express 3.008s (-0.8%) 3.415s (-4.8%) 0.407s 27 1.70x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 33.689s (-0.8%) 35.600s (~) 1.911s 3 1.00x
▲ Vercel Nitro 36.154s (+5.4% 🔺) 37.281s (+2.7%) 1.128s 3 1.07x
▲ Vercel Next.js (Turbopack) 37.195s (+8.4% 🔺) 39.062s (+8.4% 🔺) 1.867s 3 1.10x

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

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 3.583s (+36.8% 🔺) 4.076s (+33.3% 🔺) 0.492s 30 1.00x
🐘 Postgres Next.js (Turbopack) 3.741s (-4.8%) 4.010s (-5.7% 🟢) 0.269s 30 1.04x
🐘 Postgres Nitro 3.902s (-4.9%) 4.110s (-14.6% 🟢) 0.208s 30 1.09x
🐘 Postgres Express 4.129s (~) 4.931s (+1.6%) 0.802s 25 1.15x
💻 Local Nitro 7.271s (-20.6% 🟢) 7.952s (-18.8% 🟢) 0.681s 16 2.03x
💻 Local Next.js (Turbopack) 8.435s (-3.3%) 9.016s (~) 0.582s 14 2.35x
💻 Local Express 9.262s (+0.7%) 10.019s (~) 0.757s 12 2.58x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 93.007s (+6.0% 🔺) 94.615s (+5.5% 🔺) 1.608s 2 1.00x
▲ Vercel Express 98.928s (+14.4% 🔺) 100.640s (+13.7% 🔺) 1.711s 2 1.06x
▲ Vercel Next.js (Turbopack) 101.404s (+10.1% 🔺) 103.150s (+9.9% 🔺) 1.745s 2 1.09x

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

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 0.238s (-9.5% 🟢) 1.008s (~) 0.770s 60 1.00x
🐘 Postgres Nitro 0.279s (-5.5% 🟢) 1.007s (~) 0.728s 60 1.17x
🐘 Postgres Express 0.292s (-1.4%) 1.008s (~) 0.717s 60 1.23x
🌐 Redis Next.js (Turbopack) 0.296s (+11.2% 🔺) 1.005s (~) 0.708s 60 1.24x
💻 Local Nitro 0.578s (~) 1.004s (~) 0.426s 60 2.43x
💻 Local Next.js (Turbopack) 0.592s (+1.8%) 1.004s (~) 0.413s 60 2.49x
💻 Local Express 0.609s (~) 1.005s (-1.6%) 0.396s 60 2.56x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 1.846s (+5.9% 🔺) 3.693s (+11.4% 🔺) 1.847s 17 1.00x
▲ Vercel Nitro 1.899s (+12.5% 🔺) 3.549s (+4.5%) 1.649s 17 1.03x
▲ Vercel Next.js (Turbopack) 1.996s (~) 3.693s (+0.9%) 1.697s 17 1.08x

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

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 0.483s (-3.3%) 1.007s (~) 0.524s 90 1.00x
🐘 Postgres Nitro 0.483s (-6.3% 🟢) 1.007s (~) 0.524s 90 1.00x
🐘 Postgres Express 0.522s (~) 1.008s (~) 0.486s 90 1.08x
🌐 Redis Next.js (Turbopack) 1.241s (+15.0% 🔺) 2.006s (+7.7% 🔺) 0.766s 45 2.57x
💻 Local Nitro 2.466s (-3.4%) 3.007s (~) 0.541s 30 5.10x
💻 Local Next.js (Turbopack) 2.547s (-3.7%) 3.009s (~) 0.462s 30 5.27x
💻 Local Express 2.566s (+1.3%) 3.009s (~) 0.443s 30 5.31x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 3.295s (-18.9% 🟢) 4.587s (-18.8% 🟢) 1.291s 20 1.00x
▲ Vercel Express 3.419s (+8.9% 🔺) 4.789s (-1.9%) 1.370s 19 1.04x
▲ Vercel Next.js (Turbopack) 3.788s (-93.2% 🟢) 5.397s (-90.7% 🟢) 1.609s 17 1.15x

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

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 0.764s (~) 1.007s (~) 0.243s 120 1.00x
🐘 Postgres Nitro 0.768s (-4.2%) 1.007s (~) 0.239s 120 1.01x
🐘 Postgres Express 0.810s (-1.5%) 1.009s (~) 0.200s 119 1.06x
🌐 Redis Next.js (Turbopack) 2.820s (+8.7% 🔺) 3.136s (+4.3%) 0.316s 39 3.69x
💻 Local Next.js (Turbopack) 10.318s (-5.4% 🟢) 10.937s (-4.7%) 0.619s 11 13.50x
💻 Local Nitro 10.629s (-4.9%) 11.023s (-6.2% 🟢) 0.393s 11 13.91x
💻 Local Express 11.183s (-0.7%) 11.938s (+0.8%) 0.756s 11 14.64x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 8.594s (+10.5% 🔺) 10.010s (+7.4% 🔺) 1.416s 13 1.00x
▲ Vercel Next.js (Turbopack) 8.771s (+11.1% 🔺) 10.529s (+9.4% 🔺) 1.758s 12 1.02x
▲ Vercel Nitro 8.960s (+8.4% 🔺) 10.670s (+5.5% 🔺) 1.710s 12 1.04x

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

Stream Benchmarks (includes TTFB metrics)
workflow with stream

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 0.142s (-30.1% 🟢) 1.003s (~) 0.010s (-11.0% 🟢) 1.016s (~) 0.874s 10 1.00x
💻 Local Next.js (Turbopack) 0.167s (-4.7%) 1.002s (~) 0.010s (-12.8% 🟢) 1.016s (~) 0.849s 10 1.18x
🌐 Redis Next.js (Turbopack) 0.187s (+32.7% 🔺) 1.000s (~) 0.002s (+15.4% 🔺) 1.008s (~) 0.821s 10 1.31x
🐘 Postgres Next.js (Turbopack) 0.187s (-5.9% 🟢) 1.002s (~) 0.001s (+8.3% 🔺) 1.012s (~) 0.825s 10 1.32x
🐘 Postgres Nitro 0.203s (-4.3%) 0.993s (-0.5%) 0.001s (+8.3% 🔺) 1.011s (~) 0.808s 10 1.43x
💻 Local Express 0.206s (-0.8%) 1.004s (~) 0.012s (-3.3%) 1.018s (~) 0.812s 10 1.45x
🐘 Postgres Express 0.225s (+1.8%) 0.997s (+0.6%) 0.002s (+14.3% 🔺) 1.014s (~) 0.790s 10 1.58x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 1.599s (-3.6%) 2.970s (+3.5%) 0.304s (-45.8% 🟢) 3.763s (-8.3% 🟢) 2.164s 10 1.00x
▲ Vercel Next.js (Turbopack) 1.719s (+3.2%) 3.140s (+10.3% 🔺) 0.382s (-38.5% 🟢) 4.192s (+0.7%) 2.473s 10 1.07x
▲ Vercel Express 1.720s (-0.9%) 2.926s (-6.0% 🟢) 0.482s (-24.5% 🟢) 3.994s (-12.5% 🟢) 2.274s 10 1.08x

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

stream pipeline with 5 transform steps (1MB)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 0.510s (+34.3% 🔺) 1.000s (~) 0.003s (+4.9%) 1.011s (~) 0.501s 60 1.00x
💻 Local Nitro 0.579s (-20.5% 🟢) 1.010s (~) 0.009s (+6.4% 🔺) 1.022s (~) 0.444s 59 1.13x
🐘 Postgres Nitro 0.596s (-4.4%) 1.002s (~) 0.004s (-5.7% 🟢) 1.022s (~) 0.426s 59 1.17x
🐘 Postgres Next.js (Turbopack) 0.600s (-2.0%) 1.008s (~) 0.004s (+5.3% 🔺) 1.022s (~) 0.422s 59 1.18x
🐘 Postgres Express 0.622s (~) 1.004s (~) 0.004s (+3.0%) 1.024s (~) 0.402s 59 1.22x
💻 Local Next.js (Turbopack) 0.684s (+1.3%) 1.009s (-1.6%) 0.009s (-2.6%) 1.023s (-1.6%) 0.339s 59 1.34x
💻 Local Express 0.731s (~) 1.010s (~) 0.009s (+6.4% 🔺) 1.023s (~) 0.292s 59 1.43x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 4.493s (+6.7% 🔺) 5.491s (~) 0.347s (-29.6% 🟢) 6.410s (-3.3%) 1.917s 10 1.00x
▲ Vercel Nitro 4.764s (+15.3% 🔺) 5.568s (+4.4%) 0.230s (-2.6%) 6.421s (+2.3%) 1.657s 10 1.06x
▲ Vercel Next.js (Turbopack) 4.907s (-19.4% 🟢) 6.270s (-18.2% 🟢) 0.475s (+107.8% 🔺) 7.407s (-14.1% 🟢) 2.501s 9 1.09x

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

10 parallel streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 0.921s (-5.9% 🟢) 1.053s (-19.3% 🟢) 0.000s (-100.0% 🟢) 1.061s (-19.2% 🟢) 0.140s 57 1.00x
🐘 Postgres Nitro 0.921s (-8.0% 🟢) 1.062s (-21.6% 🟢) 0.000s (-41.1% 🟢) 1.082s (-21.5% 🟢) 0.161s 56 1.00x
🌐 Redis Next.js (Turbopack) 0.935s (+11.3% 🔺) 1.072s (+7.2% 🔺) 0.000s (+60.7% 🔺) 1.076s (+7.2% 🔺) 0.141s 56 1.02x
🐘 Postgres Express 0.987s (+2.0%) 1.302s (+6.4% 🔺) 0.000s (+113.3% 🔺) 1.337s (+6.9% 🔺) 0.350s 45 1.07x
💻 Local Nitro 1.170s (-6.2% 🟢) 2.017s (~) 0.000s (-63.6% 🟢) 2.020s (~) 0.850s 30 1.27x
💻 Local Express 1.249s (+0.5%) 2.021s (~) 0.000s (+100.0% 🔺) 2.023s (~) 0.774s 30 1.36x
💻 Local Next.js (Turbopack) 1.300s (+0.5%) 2.021s (~) 0.000s (-12.5% 🟢) 2.024s (~) 0.725s 30 1.41x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 3.164s (-10.6% 🟢) 4.203s (-6.7% 🟢) 0.000s (-75.4% 🟢) 4.849s (-6.8% 🟢) 1.685s 13 1.00x
▲ Vercel Nitro 3.426s (+2.0%) 4.356s (+1.5%) 0.001s (+Infinity% 🔺) 4.840s (-3.3%) 1.414s 13 1.08x
▲ Vercel Next.js (Turbopack) 3.529s (+11.8% 🔺) 4.732s (+10.0% 🔺) 0.000s (-86.5% 🟢) 5.368s (+8.6% 🔺) 1.838s 12 1.12x

🔍 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.700s (-5.2% 🟢) 2.100s (-1.9%) 0.000s (-100.0% 🟢) 2.113s (-1.9%) 0.413s 29 1.00x
🌐 Redis Next.js (Turbopack) 1.726s (+11.3% 🔺) 2.034s (~) 0.000s (+100.0% 🔺) 2.040s (~) 0.314s 30 1.02x
🐘 Postgres Express 1.770s (~) 2.100s (-0.9%) 0.000s (+Infinity% 🔺) 2.115s (-2.4%) 0.345s 29 1.04x
🐘 Postgres Next.js (Turbopack) 1.883s (-1.7%) 2.146s (~) 0.000s (-50.0% 🟢) 2.173s (+0.9%) 0.290s 28 1.11x
💻 Local Nitro 3.453s (-4.4%) 3.969s (-3.2%) 0.001s (+66.7% 🔺) 3.974s (-3.2%) 0.521s 16 2.03x
💻 Local Express 3.579s (+3.3%) 4.034s (~) 0.001s (+20.0% 🔺) 4.038s (~) 0.459s 15 2.11x
💻 Local Next.js (Turbopack) 3.702s (~) 4.165s (~) 0.001s (-11.1% 🟢) 4.170s (~) 0.468s 15 2.18x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 3.877s (-12.0% 🟢) 5.056s (-8.1% 🟢) 0.000s (+Infinity% 🔺) 5.634s (-8.6% 🟢) 1.757s 11 1.00x
▲ Vercel Next.js (Turbopack) 4.490s (-6.0% 🟢) 5.698s (-3.4%) 0.000s (-50.0% 🟢) 6.411s (-3.0%) 1.921s 10 1.16x
▲ Vercel Nitro 4.535s (-1.3%) 5.420s (-3.6%) 0.000s (~) 6.028s (-4.3%) 1.494s 10 1.17x

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

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World 🥇 Fastest Framework Wins
💻 Local Nitro 16/21
🐘 Postgres Next.js (Turbopack) 14/21
▲ Vercel Nitro 11/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework 🥇 Fastest World Wins
Express 🐘 Postgres 16/21
Next.js (Turbopack) 🐘 Postgres 9/21
Nitro 🐘 Postgres 12/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

.from(Schema.events)
.where(eq(Schema.events.runId, effectiveRunId))
.orderBy(Schema.events.eventId);
allEvents = eventRows.map((e) => EventSchema.parse(compact(e)));
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.

Suggested change
allEvents = eventRows.map((e) => EventSchema.parse(compact(e)));
allEvents = eventRows.map((e) => {
e.eventData ||= e.eventDataJson;
const parsed = EventSchema.parse(compact(e));
return stripEventDataRefs(parsed, resolveData);
});

Preloaded events in run_started path are missing the eventData ||= eventDataJson legacy fallback and stripEventDataRefs call, causing legacy events to lose their data during workflow replay.

Fix on Vercel

Copy link
Copy Markdown
Member

@TooTallNate TooTallNate left a comment

Choose a reason for hiding this comment

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

Nice optimization — combining the run fetch, event fetch, and run_started creation into a single round-trip is a clean TTFB win. The overall approach is sound, but there's a correctness bug in the postgres preloaded events path that needs fixing before merge, plus a couple of non-blocking concerns.

Summary of findings:

  • Blocking: Missing eventData ||= eventDataJson legacy fallback in postgres preloaded events (see inline comment)
  • Blocking: Early return for already-running runs in postgres re-fetches the run but doesn't return preloaded events, creating an asymmetry with the local world (see inline comment)
  • Non-blocking: The (result as any).eventData delete in postgres is redundant after the spread logic already excludes it — minor style nit
  • Non-blocking: world-vercel doesn't surface the events field yet, but since it's optional and the runtime falls back gracefully, this is fine for now

.from(Schema.events)
.where(eq(Schema.events.runId, effectiveRunId))
.orderBy(Schema.events.eventId);
allEvents = eventRows.map((e) => EventSchema.parse(compact(e)));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Blocking: Missing eventData ||= eventDataJson legacy fallback.

Every other read path in this file (events.get, events.list, events.listByCorrelationId) applies v.eventData ||= v.eventDataJson before parsing. This preloaded events path skips it, which means legacy events that only have data in the payload (jsonb) column — and not in payload_cbor — will silently have eventData: undefined during replay, breaking those workflows.

Suggested fix:

allEvents = eventRows.map((e) => {
  e.eventData ||= e.eventDataJson;
  return EventSchema.parse(compact(e));
});

(The Vercel bot comment also suggests applying stripEventDataRefs here. Whether that's needed depends on whether the runtime expects resolved or raw refs — getAllWorkflowRunEvents uses the default resolveData: 'all' so applying it would be consistent, though the runtime would work either way since it resolves everything.)

.limit(1);
if (fullRun) {
return { run: deserializeRunError(compact(fullRun)) };
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Blocking: When the run is already running, this returns { run } with no events field. The runtime will then fall back to getAllWorkflowRunEvents(), which is fine functionally — but it means re-invocations of an already-running workflow get no TTFB benefit from the preloaded events optimization.

More importantly, this early return happens before the event insertion, so it short-circuits the rest of the create method. That means it also skips the requestId idempotency check. If two concurrent invocations race, the first one transitions pending→running and continues, but the second hits this branch and also continues with a valid { run } result. Is that the intended behavior? Normally a duplicate run_started would be guarded by the DB unique constraint on eventId, but here we skip insertion entirely.

If this is intentional (i.e., it's safe for multiple invocations to proceed since replay is deterministic), a comment explaining that would help. If not, this should at minimum return the preloaded events for consistency, or throw an EntityConflictError to signal the duplicate.

// If already running, return the run directly without
// creating a duplicate event.
if (currentRun.status === 'running') {
return { run: currentRun };
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Non-blocking (same concern as postgres): This early return for already-running also returns { run: currentRun } with no event and no events. The runtime will fall back to getAllWorkflowRunEvents() which is fine, but worth a comment explaining why skipping the preloaded events is acceptable here (presumably because this path is a rare race condition).

specVersion: SPEC_VERSION_CURRENT,
},
{ requestId }
);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Non-blocking observation: Now that runs.get is removed and the runtime always calls events.create(run_started), the behavior changes for runs that are already running. Previously, runs.get would succeed and the if (status === 'pending') guard would skip the run_started creation. Now, every invocation attempts run_started regardless of status.

This works because the world implementations have an early-return for already-running runs — but it's a semantic change worth noting in the PR description. The contract is now: events.create('run_started') must be idempotent for running status (return the run without error), not just for pending → running transitions.

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.

2 participants