Skip to content

Add AES-256-GCM encryption primitives and HKDF key derivation#956

Merged
TooTallNate merged 12 commits intomainfrom
nate/vercel-encryption
Feb 20, 2026
Merged

Add AES-256-GCM encryption primitives and HKDF key derivation#956
TooTallNate merged 12 commits intomainfrom
nate/vercel-encryption

Conversation

@TooTallNate
Copy link
Copy Markdown
Member

@TooTallNate TooTallNate commented Feb 6, 2026

Summary

Adds the encryption foundation for end-to-end encryption of workflow user data:

  • @workflow/core: Browser-compatible encrypt()/decrypt() using Web Crypto API (AES-256-GCM), importKey() for callers to convert raw bytes to CryptoKey once per run
  • @workflow/world: Overloaded getEncryptionKeyForRun interface — accepts WorkflowRun entity when available, or (runId, context) for start() when the run doesn't exist yet
  • @workflow/world-vercel: deriveRunKey() (HKDF-SHA256), fetchRunKey() (API call with projectId + runId), getEncryptionKeyForRun implementation reading context?.deploymentId
  • All runtime callers updated to call importKey() after key resolution and pass WorkflowRun entity where available
  • handleSuspension refactored to accept WorkflowRun parameter
  • resume-hook.ts uses getHookByTokenWithKey to fetch full WorkflowRun alongside hook + key
  • Separate changesets for @workflow/core, @workflow/world, @workflow/world-vercel

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Feb 6, 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, Open in v0 Feb 20, 2026 10:26pm
example-nextjs-workflow-webpack Ready Ready Preview, Comment, Open in v0 Feb 20, 2026 10:26pm
example-workflow Ready Ready Preview, Comment, Open in v0 Feb 20, 2026 10:26pm
workbench-astro-workflow Ready Ready Preview, Comment, Open in v0 Feb 20, 2026 10:26pm
workbench-express-workflow Ready Ready Preview, Comment, Open in v0 Feb 20, 2026 10:26pm
workbench-fastify-workflow Ready Ready Preview, Comment, Open in v0 Feb 20, 2026 10:26pm
workbench-hono-workflow Ready Ready Preview, Comment, Open in v0 Feb 20, 2026 10:26pm
workbench-nitro-workflow Ready Ready Preview, Comment, Open in v0 Feb 20, 2026 10:26pm
workbench-nuxt-workflow Ready Ready Preview, Comment, Open in v0 Feb 20, 2026 10:26pm
workbench-sveltekit-workflow Ready Ready Preview, Comment, Open in v0 Feb 20, 2026 10:26pm
workbench-vite-workflow Ready Ready Preview, Comment, Open in v0 Feb 20, 2026 10:26pm
workflow-docs Ready Ready Preview, Comment, Open in v0 Feb 20, 2026 10:26pm
workflow-nest Ready Ready Preview, Comment, Open in v0 Feb 20, 2026 10:26pm
workflow-swc-playground Ready Ready Preview, Comment, Open in v0 Feb 20, 2026 10:26pm

Copilot AI review requested due to automatic review settings February 6, 2026 02:30
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Feb 6, 2026

🦋 Changeset detected

Latest commit: c28baee

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

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

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 Feb 6, 2026

🧪 E2E Test Results

Some tests failed

Summary

Passed Failed Skipped Total
✅ ▲ Vercel Production 523 0 49 572
✅ 💻 Local Development 556 0 68 624
✅ 📦 Local Production 556 0 68 624
✅ 🐘 Local Postgres 556 0 68 624
✅ 🪟 Windows 49 0 3 52
❌ 🌍 Community Worlds 111 45 9 165
✅ 📋 Other 135 0 21 156
Total 2486 45 286 2817

❌ Failed Tests

🌍 Community Worlds (45 failed)

turso (45 failed):

  • addTenWorkflow
  • addTenWorkflow
  • should work with react rendering in step
  • promiseAllWorkflow
  • promiseRaceWorkflow
  • promiseAnyWorkflow
  • hookWorkflow
  • webhookWorkflow
  • sleepingWorkflow
  • parallelSleepWorkflow
  • nullByteWorkflow
  • workflowAndStepMetadataWorkflow
  • fetchWorkflow
  • promiseRaceStressTestWorkflow
  • 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 retry behavior workflow completes despite transient 5xx on step_completed
  • error handling catchability FatalError can be caught and detected with FatalError.is()
  • hookCleanupTestWorkflow - hook token reuse after workflow completion
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously
  • stepFunctionPassingWorkflow - step function references can be passed as arguments (without closure vars)
  • stepFunctionWithClosureWorkflow - step function with closure variables passed as argument
  • closureVariableWorkflow - nested step functions with closure variables
  • spawnWorkflowFromStepWorkflow - spawning a child workflow using start() inside a step
  • health check (queue-based) - workflow and step endpoints respond to health check messages
  • pathsAliasWorkflow - TypeScript path aliases resolve correctly
  • Calculator.calculate - static workflow method using static step methods from another class
  • AllInOneService.processNumber - static workflow method using sibling static step methods
  • ChainableService.processWithThis - static step methods using this to reference the class
  • thisSerializationWorkflow - step function invoked with .call() and .apply()
  • customSerializationWorkflow - custom class serialization with WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE
  • instanceMethodStepWorkflow - instance methods with "use step" directive
  • crossContextSerdeWorkflow - classes defined in step code are deserializable in workflow context
  • stepFunctionAsStartArgWorkflow - step function reference passed as start() argument
  • cancelRun - cancelling a running workflow
  • cancelRun via CLI - cancelling a running workflow
  • pages router addTenWorkflow via pages router
  • pages router promiseAllWorkflow via pages router
  • pages router sleepingWorkflow via pages router

Details by Category

✅ ▲ Vercel Production
App Passed Failed Skipped
✅ astro 47 0 5
✅ example 47 0 5
✅ express 47 0 5
✅ fastify 47 0 5
✅ hono 47 0 5
✅ nextjs-turbopack 50 0 2
✅ nextjs-webpack 50 0 2
✅ nitro 47 0 5
✅ nuxt 47 0 5
✅ sveltekit 47 0 5
✅ vite 47 0 5
✅ 💻 Local Development
App Passed Failed Skipped
✅ astro-stable 45 0 7
✅ express-stable 45 0 7
✅ fastify-stable 45 0 7
✅ hono-stable 45 0 7
✅ nextjs-turbopack-canary 49 0 3
✅ nextjs-turbopack-stable 49 0 3
✅ nextjs-webpack-canary 49 0 3
✅ nextjs-webpack-stable 49 0 3
✅ nitro-stable 45 0 7
✅ nuxt-stable 45 0 7
✅ sveltekit-stable 45 0 7
✅ vite-stable 45 0 7
✅ 📦 Local Production
App Passed Failed Skipped
✅ astro-stable 45 0 7
✅ express-stable 45 0 7
✅ fastify-stable 45 0 7
✅ hono-stable 45 0 7
✅ nextjs-turbopack-canary 49 0 3
✅ nextjs-turbopack-stable 49 0 3
✅ nextjs-webpack-canary 49 0 3
✅ nextjs-webpack-stable 49 0 3
✅ nitro-stable 45 0 7
✅ nuxt-stable 45 0 7
✅ sveltekit-stable 45 0 7
✅ vite-stable 45 0 7
✅ 🐘 Local Postgres
App Passed Failed Skipped
✅ astro-stable 45 0 7
✅ express-stable 45 0 7
✅ fastify-stable 45 0 7
✅ hono-stable 45 0 7
✅ nextjs-turbopack-canary 49 0 3
✅ nextjs-turbopack-stable 49 0 3
✅ nextjs-webpack-canary 49 0 3
✅ nextjs-webpack-stable 49 0 3
✅ nitro-stable 45 0 7
✅ nuxt-stable 45 0 7
✅ sveltekit-stable 45 0 7
✅ vite-stable 45 0 7
✅ 🪟 Windows
App Passed Failed Skipped
✅ nextjs-turbopack 49 0 3
❌ 🌍 Community Worlds
App Passed Failed Skipped
✅ mongodb-dev 3 0 0
✅ mongodb 49 0 3
✅ redis-dev 3 0 0
✅ redis 49 0 3
✅ turso-dev 3 0 0
❌ turso 4 45 3
✅ 📋 Other
App Passed Failed Skipped
✅ e2e-local-dev-nest-stable 45 0 7
✅ e2e-local-postgres-nest-stable 45 0 7
✅ e2e-local-prod-nest-stable 45 0 7

📋 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

This PR adds AES-256-GCM encryption with HKDF-SHA256 key derivation to the @workflow/world-vercel package. The implementation provides per-run encryption isolation by deriving unique keys from a deployment key, project ID, and run ID. It integrates seamlessly with the async serialization infrastructure added in PR #955 and uses the client-generated run IDs from PR #954.

Changes:

  • Implements createEncryptor() and createEncryptorFromEnv() functions with full Encryptor interface support
  • Adds AES-256-GCM encryption with random nonces and 128-bit authentication tags
  • Uses HKDF-SHA256 for per-run key derivation with projectId and runId as context
  • Wires encryption into createVercelWorld() via environment variables (VERCEL_DEPLOYMENT_KEY, VERCEL_PROJECT_ID)
  • Includes 18 comprehensive tests covering round-trip, format validation, isolation, and tamper detection

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 12 comments.

File Description
packages/world-vercel/src/encryption.ts Core encryption implementation with key derivation, encrypt/decrypt, and key material access
packages/world-vercel/src/encryption.test.ts Comprehensive test suite with 18 tests covering functionality and security properties
packages/world-vercel/src/index.ts Integration into World creation and public API exports
Comments suppressed due to low confidence (1)

packages/world-vercel/src/index.ts:12

  • The VercelEncryptionConfig interface is exported from encryption.ts but not re-exported from index.ts. Users who want to use createEncryptor() directly would need to import from the internal encryption module, which is not a typical pattern.

Consider adding to index.ts:

export type { VercelEncryptionConfig } from './encryption.js';

This follows the pattern already established with APIConfig and makes the public API more discoverable.

export { createEncryptor, createEncryptorFromEnv } from './encryption.js';
export { createQueue } from './queue.js';
export { createStorage } from './storage.js';
export { createStreamer } from './streamer.js';
export type { APIConfig } from './utils.js';

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

Comment thread packages/world-vercel/src/encryption.ts Outdated
Comment thread packages/world-vercel/src/encryption.test.ts Outdated
Comment thread packages/world-vercel/src/encryption.ts Outdated
Comment thread packages/world-vercel/src/encryption.ts Outdated
Comment thread packages/world-vercel/src/encryption.ts Outdated
Comment thread packages/world-vercel/src/encryption.test.ts Outdated
Comment thread packages/world-vercel/src/encryption.ts Outdated
Comment thread packages/world-vercel/src/encryption.ts Outdated
Comment thread packages/world-vercel/src/encryption.ts Outdated
Comment thread packages/world-vercel/src/encryption.ts Outdated
Copy link
Copy Markdown
Contributor

@pranaygp pranaygp left a comment

Choose a reason for hiding this comment

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

Review: PR #956 - Add Vercel encryption implementation (AES-256-GCM with HKDF)

Summary: Implements AES-256-GCM encryption with HKDF-SHA256 per-run key derivation in @workflow/world-vercel. This is the core crypto PR and the most security-sensitive in the stack.

Crypto Design Review

Algorithm choices are sound:

  • AES-256-GCM: authenticated encryption, industry standard, hardware-accelerated
  • HKDF-SHA256 for key derivation: correct use of KDF for deriving per-run keys from a deployment key
  • 12-byte (96-bit) nonce: standard for GCM
  • 128-bit auth tag: standard for GCM
  • Random nonce per encryption: correct, avoids nonce reuse

Key derivation design:

  • HKDF(key=deploymentKey, salt=zeros, info="${projectId}|${runId}")
  • Zero salt is acceptable here since the deployment key is already random key material (HKDF specification allows this)
  • Using projectId|runId as info provides good domain separation between runs and projects

Concerns

  1. Zero salt vs random salt: While zero salt is acceptable per RFC 5869 when the input key material is already uniformly random, using a random salt stored alongside the ciphertext would provide additional protection against certain attacks (e.g., if the deployment key has less entropy than expected). This is a minor concern given that VERCEL_DEPLOYMENT_KEY should be high-entropy, but worth considering for defense in depth.

  2. Key derivation per encryption call: The deriveKey function is called on every encrypt() and decrypt() call. For workflows with many steps, this means the same HKDF derivation is repeated for each serialization. Consider caching the derived key per (projectId, runId) pair:

    // Cache derived keys to avoid re-deriving per encrypt/decrypt call
    const keyCache = new Map<string, CryptoKey>();

    The cache key would be ${projectId}|${runId}. This is a performance optimization, not a correctness issue.

  3. getKeyMaterial returns raw key bytes: The getKeyMaterial() method returns the raw deploymentKey bytes. This is documented as being for external decryption (o11y tooling), but it means any code with access to the encryptor can extract the master key. Consider:

    • Adding a warning comment about this being sensitive
    • Possibly scoping the returned key material to the derived per-run key rather than the deployment key
  4. createEncryptorFromEnv validation: When VERCEL_DEPLOYMENT_KEY is set but decodes to fewer than 32 bytes, createEncryptor will throw. However, createEncryptorFromEnv doesn't catch this, meaning the world creation will fail hard. Consider catching and logging a warning instead, falling back to no encryption, so a misconfigured key doesn't crash the entire application.

  5. Format prefix encr is 4 bytes of overhead per encrypted payload. This is fine for data payloads but for stream chunks (which may be small), it adds overhead per chunk. Not a major concern but worth noting.

Test Coverage

The 18 tests are comprehensive:

  • Round-trip tests (basic, empty, large, all byte values)
  • Format validation (prefix, structure, nonce randomness)
  • Per-run key isolation
  • Tamper detection
  • Cross-project/cross-key isolation
  • Environment variable handling (missing vars, valid vars)

One test case that could be added: concurrent encryption -- verifying that multiple concurrent encrypt() calls with the same encryptor produce correct results (nonce uniqueness under concurrency).

Minor

  • The Buffer.from(deploymentKeyBase64, 'base64') in createEncryptorFromEnv will silently ignore invalid base64 characters. Consider validating the decoded length matches expectations, or at minimum let createEncryptor's 32-byte check catch it.

Overall, the crypto implementation is solid and follows best practices. The concerns above are mostly optimizations and hardening suggestions rather than security issues.

Comment thread packages/world-vercel/src/encryption.ts
Comment thread packages/world-vercel/src/encryption.ts Outdated
Comment thread packages/world-vercel/src/encryption.ts Outdated
Copy link
Copy Markdown
Contributor

@pranaygp pranaygp left a comment

Choose a reason for hiding this comment

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

Overall the crypto design is solid — AES-256-GCM with HKDF-SHA256 per-run key derivation is the right approach, the separation between browser-compatible core encryption and Node.js-specific key management is clean, and the World interface integration is well thought out.

A few concerns below, mostly around performance during replay and a potentially unreachable code path.

Comment thread packages/core/src/encryption.ts
Comment thread packages/world-vercel/src/encryption.ts
Comment thread packages/world-vercel/src/encryption.ts
Comment thread packages/world-vercel/src/index.ts Outdated
Comment thread packages/world-vercel/src/index.ts Outdated
Comment thread packages/world-vercel/src/encryption.test.ts
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 18, 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.026s (-1.2%) 1.004s (~) 0.979s 10 1.00x
💻 Local Express 0.032s (-1.8%) 1.005s (~) 0.973s 10 1.25x
💻 Local Next.js (Turbopack) 0.040s (-9.9% 🟢) 1.005s (~) 0.965s 10 1.56x
🌐 Redis Next.js (Turbopack) 0.046s (-2.1%) 1.005s (~) 0.958s 10 1.81x
🌐 MongoDB Next.js (Turbopack) 0.078s (-15.4% 🟢) 1.007s (~) 0.929s 10 3.04x
🐘 Postgres Express 0.127s (-0.8%) 1.010s (~) 0.883s 10 4.94x
🐘 Postgres Nitro 0.377s (+317.5% 🔺) 1.009s (~) 0.632s 10 14.67x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 0.612s (+25.2% 🔺) 1.929s (-4.1%) 1.318s 10 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Next.js (Turbopack)

workflow with 1 step

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 1.074s (~) 2.005s (~) 0.931s 10 1.00x
🌐 Redis Next.js (Turbopack) 1.103s (~) 2.006s (~) 0.903s 10 1.03x
💻 Local Next.js (Turbopack) 1.105s (+0.7%) 2.005s (~) 0.900s 10 1.03x
💻 Local Express 1.105s (-0.5%) 2.006s (~) 0.900s 10 1.03x
🌐 MongoDB Next.js (Turbopack) 1.315s (~) 2.007s (~) 0.692s 10 1.22x
🐘 Postgres Nitro 2.403s (-2.6%) 3.014s (~) 0.611s 10 2.24x
🐘 Postgres Express 2.404s (~) 3.013s (~) 0.609s 10 2.24x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 2.087s (+4.6%) 3.098s (-5.3% 🟢) 1.011s 10 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 10.543s (~) 11.021s (~) 0.478s 3 1.00x
🌐 Redis Next.js (Turbopack) 10.698s (~) 11.021s (~) 0.323s 3 1.01x
💻 Local Next.js (Turbopack) 10.749s (~) 11.023s (~) 0.274s 3 1.02x
💻 Local Express 10.837s (~) 11.022s (~) 0.186s 3 1.03x
🌐 MongoDB Next.js (Turbopack) 12.209s (-0.7%) 13.020s (~) 0.810s 3 1.16x
🐘 Postgres Nitro 20.252s (~) 21.062s (~) 0.810s 2 1.92x
🐘 Postgres Express 20.368s (+13.9% 🔺) 21.056s (+13.5% 🔺) 0.688s 2 1.93x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 15.943s (-1.2%) 16.835s (-2.1%) 0.892s 2 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 26.759s (~) 27.047s (~) 0.288s 3 1.00x
🌐 Redis Next.js (Turbopack) 26.847s (~) 27.050s (~) 0.203s 3 1.00x
💻 Local Next.js (Turbopack) 27.221s (~) 28.053s (~) 0.832s 3 1.02x
💻 Local Express 27.502s (~) 28.051s (~) 0.549s 3 1.03x
🌐 MongoDB Next.js (Turbopack) 30.465s (~) 31.038s (~) 0.573s 2 1.14x
🐘 Postgres Express 50.386s (+31.9% 🔺) 51.129s (+30.8% 🔺) 0.743s 2 1.88x
🐘 Postgres Nitro 50.532s (~) 51.128s (~) 0.597s 2 1.89x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 42.878s (+10.0% 🔺) 43.689s (+7.4% 🔺) 0.811s 2 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 54.206s (~) 55.095s (~) 0.888s 2 1.00x
💻 Local Nitro 55.559s (~) 56.099s (~) 0.539s 2 1.02x
💻 Local Next.js (Turbopack) 56.587s (~) 57.114s (~) 0.527s 2 1.04x
💻 Local Express 57.264s (~) 58.103s (~) 0.838s 2 1.06x
🌐 MongoDB Next.js (Turbopack) 61.036s (~) 61.072s (~) 0.035s 2 1.13x
🐘 Postgres Express 78.183s (+3.8%) 78.675s (+3.3%) 0.492s 2 1.44x
🐘 Postgres Nitro 100.149s (~) 100.224s (~) 0.075s 1 1.85x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 81.685s (-1.1%) 82.625s (-1.8%) 0.940s 2 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Next.js (Turbopack)

Promise.all with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 1.245s (+1.5%) 2.006s (~) 0.761s 15 1.00x
💻 Local Nitro 1.352s (~) 2.005s (~) 0.653s 15 1.09x
💻 Local Next.js (Turbopack) 1.413s (+0.6%) 2.005s (~) 0.592s 15 1.13x
💻 Local Express 1.415s (+1.0%) 2.006s (~) 0.590s 15 1.14x
🐘 Postgres Express 1.927s (+3.7%) 2.515s (+25.0% 🔺) 0.588s 12 1.55x
🌐 MongoDB Next.js (Turbopack) 2.118s (-1.8%) 3.008s (~) 0.890s 10 1.70x
🐘 Postgres Nitro 2.253s (+1.4%) 3.013s (~) 0.760s 10 1.81x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 2.246s (-7.6% 🟢) 3.144s (-11.0% 🟢) 0.898s 10 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 2.280s (+1.8%) 3.006s (~) 0.727s 10 1.00x
💻 Local Next.js (Turbopack) 2.431s (-2.2%) 2.918s (-3.0%) 0.487s 11 1.07x
🌐 Redis Next.js (Turbopack) 2.466s (-1.2%) 3.007s (~) 0.542s 10 1.08x
💻 Local Express 2.648s (+7.7% 🔺) 3.008s (~) 0.359s 10 1.16x
🌐 MongoDB Next.js (Turbopack) 4.696s (-0.6%) 5.176s (~) 0.479s 6 2.06x
🐘 Postgres Express 8.660s (-13.7% 🟢) 9.537s (-8.0% 🟢) 0.877s 4 3.80x
🐘 Postgres Nitro 9.498s (+33.6% 🔺) 10.057s (+33.5% 🔺) 0.559s 3 4.17x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 2.689s (-36.9% 🟢) 3.374s (-38.7% 🟢) 0.685s 9 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 4.042s (-0.8%) 4.725s (-3.0%) 0.682s 7 1.00x
💻 Local Nitro 6.167s (+2.8%) 7.016s (+9.4% 🔺) 0.850s 5 1.53x
💻 Local Next.js (Turbopack) 6.445s (-5.7% 🟢) 7.016s (-5.4% 🟢) 0.571s 5 1.59x
💻 Local Express 7.857s (+16.2% 🔺) 8.272s (+17.9% 🔺) 0.414s 4 1.94x
🌐 MongoDB Next.js (Turbopack) 9.925s (+1.2%) 10.348s (~) 0.423s 3 2.46x
🐘 Postgres Nitro 46.235s (-8.3% 🟢) 47.132s (-7.8% 🟢) 0.897s 1 11.44x
🐘 Postgres Express 47.330s (-4.8%) 48.095s (-4.1%) 0.765s 1 11.71x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 3.494s (-20.5% 🟢) 4.448s (-19.5% 🟢) 0.953s 7 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 1.263s (+2.0%) 2.006s (~) 0.742s 15 1.00x
💻 Local Nitro 1.374s (~) 2.004s (~) 0.631s 15 1.09x
💻 Local Next.js (Turbopack) 1.432s (~) 2.005s (~) 0.573s 15 1.13x
💻 Local Express 1.449s (+1.5%) 2.006s (~) 0.557s 15 1.15x
🐘 Postgres Express 1.837s (-9.5% 🟢) 2.011s (-16.1% 🟢) 0.174s 15 1.45x
🐘 Postgres Nitro 2.095s (-5.5% 🟢) 2.679s (+3.2%) 0.585s 12 1.66x
🌐 MongoDB Next.js (Turbopack) 2.127s (-0.6%) 3.008s (~) 0.881s 10 1.68x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 2.212s (+0.9%) 3.124s (-13.6% 🟢) 0.912s 10 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 2.353s (~) 3.007s (~) 0.654s 10 1.00x
🌐 Redis Next.js (Turbopack) 2.488s (~) 3.008s (~) 0.520s 10 1.06x
💻 Local Next.js (Turbopack) 2.606s (-3.3%) 3.007s (~) 0.401s 10 1.11x
💻 Local Express 2.754s (+6.7% 🔺) 3.008s (~) 0.255s 10 1.17x
🌐 MongoDB Next.js (Turbopack) 4.742s (~) 5.177s (~) 0.434s 6 2.02x
🐘 Postgres Express 9.968s (-16.0% 🟢) 10.373s (-13.8% 🟢) 0.405s 3 4.24x
🐘 Postgres Nitro 10.288s (-7.0% 🟢) 11.029s (-5.7% 🟢) 0.742s 3 4.37x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 2.275s (-11.9% 🟢) 3.197s (-12.8% 🟢) 0.922s 10 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 4.020s (-0.6%) 4.725s (~) 0.705s 7 1.00x
💻 Local Next.js (Turbopack) 6.608s (-5.0%) 7.216s (-4.0%) 0.608s 5 1.64x
💻 Local Nitro 6.888s (~) 7.015s (-2.8%) 0.127s 5 1.71x
💻 Local Express 8.103s (+12.5% 🔺) 8.775s (+9.5% 🔺) 0.672s 4 2.02x
🌐 MongoDB Next.js (Turbopack) 10.056s (+1.1%) 10.348s (-3.1%) 0.292s 3 2.50x
🐘 Postgres Nitro 46.096s (-8.6% 🟢) 47.125s (-7.8% 🟢) 1.029s 1 11.47x
🐘 Postgres Express 48.860s (-1.0%) 49.106s (-2.0%) 0.246s 1 12.16x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 3.233s (-4.2%) 4.059s (-22.3% 🟢) 0.826s 8 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Next.js (Turbopack)

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.114s (+3.2%) 1.002s (~) 0.009s (-3.2%) 1.014s (~) 0.899s 10 1.00x
🌐 Redis Next.js (Turbopack) 0.145s (-2.8%) 1.000s (~) 0.001s (~) 1.007s (~) 0.862s 10 1.27x
💻 Local Next.js (Turbopack) 0.151s (+6.9% 🔺) 1.001s (~) 0.010s (-9.4% 🟢) 1.015s (~) 0.865s 10 1.32x
💻 Local Express 0.171s (-6.4% 🟢) 1.003s (~) 0.011s (+10.4% 🔺) 1.016s (~) 0.845s 10 1.50x
🌐 MongoDB Next.js (Turbopack) 0.510s (+2.5%) 0.935s (-1.6%) 0.001s (-6.7% 🟢) 1.008s (~) 0.498s 10 4.46x
🐘 Postgres Express 1.317s (-9.1% 🟢) 1.800s (+8.6% 🔺) 0.001s (-15.4% 🟢) 2.012s (~) 0.695s 10 11.51x
🐘 Postgres Nitro 2.275s (+75.8% 🔺) 2.768s (+58.7% 🔺) 0.001s (-23.1% 🟢) 3.014s (+49.7% 🔺) 0.739s 10 19.89x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - - -

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 1.481s (-26.7% 🟢) 2.272s (-18.6% 🟢) 0.121s (-6.9% 🟢) 2.773s (-21.9% 🟢) 1.292s 10 1.00x
▲ Vercel Express ⚠️ missing - - - - -
▲ Vercel Nitro ⚠️ missing - - - - -

🔍 Observability: Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World 🥇 Fastest Framework Wins
💻 Local Nitro 11/12
🐘 Postgres Express 8/12
▲ Vercel Next.js (Turbopack) 12/12
Fastest World by Framework

Winner determined by most benchmark wins

Framework 🥇 Fastest World Wins
Express 💻 Local 12/12
Next.js (Turbopack) 🌐 Redis 7/12
Nitro 💻 Local 12/12
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

if (encryptionKeyOverride) {
encryptionKey = encryptionKeyOverride;
} else {
const rawKey = await world.getEncryptionKeyForRun?.(workflowRun);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

For cross-deployment hook resumptions / fetching of the encryption key, we should consider enriching the "source" of the request for the key (i.e. resumeHook in this case) for the audit log to include the context.

Comment thread packages/world-vercel/src/index.ts Outdated
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.

4 participants