Skip to content

[world-vercel] Switch event endpoints to v4 wire format#2055

Draft
VaguelySerious wants to merge 3 commits into
mainfrom
peter/v4
Draft

[world-vercel] Switch event endpoints to v4 wire format#2055
VaguelySerious wants to merge 3 commits into
mainfrom
peter/v4

Conversation

@VaguelySerious
Copy link
Copy Markdown
Member

@VaguelySerious VaguelySerious commented May 21, 2026

Draft. Switches the world-vercel adapter's event endpoints from v2/v3 to v4. Companion to workflow-server PR #439; both land together.

What changes

The adapter's createWorkflowRunEvent / getEvent / getWorkflowRunEvents keep their public signatures and the EventResult / Event / PaginatedResponse<Event> shapes the workflow runtime consumes. What changes is what's on the wire under those calls:

  • Event metadata rides in x-wf-* HTTP request/response headers instead of inside the body.
  • User payloads stream end-to-end as opaque bytes. The SDK CBOR-encodes at the eventData[field] boundary on write and CBOR-decodes on read; the server treats the bytes as opaque and streams them straight to S3.
  • POST event response carries the materialized EventResult as a CBOR body. The runtime reads e.g. result.run.startedAt immediately after POST without a second round-trip.
  • LIST events response is now the v4 binary-frame stream (application/vnd.workflow.v4-frames). One frame per event with CBOR metadata + raw payload bytes inline. The per-event /refs round-trip used by the v3 client is gone.

What goes away

  • packages/world-vercel/src/refs.ts — deleted. The /refs hydration path is no longer used.
  • hydrateEventRefs / collectPendingRefs / eventDataRefFieldMap and the wire schemas (EventResultResolveWireSchema, EventResultLazyWireSchema, EventWithRefsSchema) — deleted from events.ts.
  • The lazy-refs branching in createWorkflowRunEvent — the server still respects remoteRefBehavior (passed via header for eventsNeedingResolve types) and bakes the resolution decision into its CBOR response, so the SDK has nothing to do.

Net diff: +297 / -601 lines on this PR.

What stays

  • v1Compat path in createWorkflowRunEvent — still uses /v1 endpoints for legacy SDK migrations that haven't moved to event sourcing. v4 doesn't cover these.
  • validateUlidTimestamp on run_created, the HookNotFoundError translation on hook 404s, and the stripEventDataRefs path for resolveData='none'.
  • events-v4.ts is now an internal helper module — not re-exported from the package's public API.

Not yet covered (by design)

  • storage.events.listByCorrelationId throws a clear error explaining the v4 server has no by-correlation-id list endpoint. The runtime mostly used this for hook lookups, which can use storage.hooks.getByToken instead. If real callers need it, a follow-up server PR can add /api/v4/events?correlationId=.

Test plan

  • pnpm --filter @workflow/world-vercel build clean
  • pnpm --filter @workflow/world-vercel test — 79/79 pass
  • pnpm build (full workspace) — 27/27 packages build
  • WORKFLOW_SERVER_URL_OVERRIDE on this branch points at the workflow-server PR Fix command injection vulnerability in CI workflow via untrusted fork PR #439 preview deployment for e2e tests.
  • E2E green against the v4-enabled workflow-server preview.

🤖 Generated with Claude Code

Mirrors the v4 server-side handlers landing in workflow-server. The
v4 wire format moves event metadata into x-wf-* request/response
headers and treats payloads as opaque user-data bytes (streamed
end-to-end). The SDK passes Uint8Array bytes through unchanged at
this layer; higher-level world-vercel adapter glue handles CBOR.

Adds:
  - packages/world-vercel/src/frames.ts: encoder + async-iterable
    decoder for the length-prefixed binary frame format used by the
    v4 list-events response.
  - packages/world-vercel/src/events-v4.ts: three new functions:
    * createWorkflowRunEventV4 — POST with x-wf-* headers + payload
      bytes, returns event/run ids and timestamp from response
      headers.
    * getEventV4 — GET single event, returns metadata + body bytes.
    * getWorkflowRunEventsV4 — GET list, parses frame stream, returns
      events + pagination cursor.
  - V4_HEADERS exported as the canonical name map; mirrors the
    server-side constant.

V4 client characteristics:
  - Required runId in URL for run_created too (no /runs/null/events
    shortcut; the runId is part of the S3 key the server allocates).
    Higher-level callers generate the ULID client-side.
  - Payload bytes flow through without CBOR encode/decode on this
    layer. Callers CBOR-encode for parity with v3 if they want.
  - Pagination cursor surfaces in the LIST response — eliminates the
    per-large-payload /refs round-trip used by v2/v3.

Tests (10 new in src/frames.test.ts, no new e2e):
  - Canonical wire layout round-trip.
  - Multi-frame round-trip with pagination cursor.
  - Decoder survives 1-byte chunk delivery (matching spike B's chunk-
    boundary robustness requirement).
  - 64 KB body split across many small chunks.
  - Bodies containing 0xff padding don't mis-frame.
  - Back-to-back frames in a single chunk.
  - Truncated stream raises.
  - Meta CBOR types (numbers, booleans, arrays) preserved.

The world-vercel adapter still defaults to the v3 path; v4 is exposed
for direct callers and a follow-up PR will switch the adapter over
once the matching server-side PR is on staging.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 21, 2026

🦋 Changeset detected

Latest commit: 963d632

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

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

@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 Ready Ready Preview, Comment May 21, 2026 3:15pm
example-nextjs-workflow-webpack Ready Ready Preview, Comment May 21, 2026 3:15pm
example-workflow Ready Ready Preview, Comment May 21, 2026 3:15pm
workbench-astro-workflow Ready Ready Preview, Comment May 21, 2026 3:15pm
workbench-express-workflow Ready Ready Preview, Comment May 21, 2026 3:15pm
workbench-fastify-workflow Ready Ready Preview, Comment May 21, 2026 3:15pm
workbench-hono-workflow Ready Ready Preview, Comment May 21, 2026 3:15pm
workbench-nitro-workflow Ready Ready Preview, Comment May 21, 2026 3:15pm
workbench-nuxt-workflow Ready Ready Preview, Comment May 21, 2026 3:15pm
workbench-sveltekit-workflow Ready Ready Preview, Comment May 21, 2026 3:15pm
workbench-tanstack-start-workflow Ready Ready Preview, Comment May 21, 2026 3:15pm
workbench-vite-workflow Ready Ready Preview, Comment May 21, 2026 3:15pm
workflow-docs Ready Ready Preview, Comment, Open in v0 May 21, 2026 3:15pm
workflow-swc-playground Ready Ready Preview, Comment May 21, 2026 3:15pm
workflow-tarballs Ready Ready Preview, Comment May 21, 2026 3:15pm
workflow-web Ready Ready Preview, Comment May 21, 2026 3:15pm

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 21, 2026

🧪 E2E Test Results

Some tests failed

Summary

Passed Failed Skipped Total
✅ 💻 Local Development 1587 0 219 1806
❌ 📦 Local Production 1476 1 200 1677
✅ 🐘 Local Postgres 1587 0 219 1806
✅ 🪟 Windows 129 0 0 129
✅ 📋 Other 624 0 150 774
Total 5403 1 788 6192

❌ Failed Tests

📦 Local Production (1 failed)

nextjs-webpack-stable-lazy-discovery-enabled (1 failed):

  • AbortController abortFetchInFlightWorkflow: aborting cancels an in-flight fetch

Details by Category

✅ 💻 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-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 128 1 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

📋 View full workflow run


Some E2E test jobs failed:

  • Vercel Prod: cancelled
  • Local Dev: success
  • Local Prod: failure
  • Local Postgres: success
  • Windows: success

Check the workflow run for details.

@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 🥇 Nitro 0.025s (-43.2% 🟢) 1.004s (~) 0.980s 10 1.00x
💻 Local Express 0.030s (-31.6% 🟢) 1.005s (~) 0.975s 10 1.24x
💻 Local Next.js (Turbopack) 0.040s 1.005s 0.965s 10 1.64x
🐘 Postgres Nitro 0.049s (-49.1% 🟢) 1.011s (-3.1%) 0.962s 10 1.98x
🐘 Postgres Express 0.049s (-15.5% 🟢) 1.013s (~) 0.964s 10 2.00x
🐘 Postgres Next.js (Turbopack) 0.056s 1.011s 0.955s 10 2.30x
workflow with 1 step

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 1.060s (-6.3% 🟢) 2.006s (~) 0.945s 10 1.00x
💻 Local Express 1.075s (-4.5%) 2.006s (~) 0.932s 10 1.01x
🐘 Postgres Nitro 1.081s (-5.2% 🟢) 2.010s (~) 0.929s 10 1.02x
💻 Local Next.js (Turbopack) 1.087s 2.006s 0.918s 10 1.03x
🐘 Postgres Express 1.088s (-5.1% 🟢) 2.010s (~) 0.922s 10 1.03x
🐘 Postgres Next.js (Turbopack) 1.116s 2.009s 0.893s 10 1.05x
workflow with 10 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 10.327s (-5.7% 🟢) 11.020s (~) 0.694s 3 1.00x
🐘 Postgres Express 10.412s (-5.0% 🟢) 11.015s (~) 0.602s 3 1.01x
💻 Local Express 10.415s (-4.6%) 11.021s (~) 0.606s 3 1.01x
🐘 Postgres Nitro 10.424s (-4.1%) 11.013s (~) 0.589s 3 1.01x
💻 Local Next.js (Turbopack) 10.505s 11.020s 0.515s 3 1.02x
🐘 Postgres Next.js (Turbopack) 10.704s 11.021s 0.317s 3 1.04x
workflow with 25 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 13.290s (-11.8% 🟢) 14.026s (-12.5% 🟢) 0.736s 5 1.00x
🐘 Postgres Express 13.463s (-7.7% 🟢) 14.020s (-6.7% 🟢) 0.556s 5 1.01x
💻 Local Express 13.478s (-10.0% 🟢) 14.025s (-6.7% 🟢) 0.547s 5 1.01x
🐘 Postgres Nitro 13.520s (-7.4% 🟢) 14.020s (-6.7% 🟢) 0.499s 5 1.02x
💻 Local Next.js (Turbopack) 13.685s 14.025s 0.340s 5 1.03x
🐘 Postgres Next.js (Turbopack) 14.117s 15.015s 0.897s 4 1.06x
workflow with 50 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 11.679s (-30.4% 🟢) 12.021s (-29.4% 🟢) 0.342s 8 1.00x
💻 Local Express 11.904s (-28.3% 🟢) 12.021s (-29.4% 🟢) 0.117s 8 1.02x
🐘 Postgres Express 11.995s (-14.4% 🟢) 12.394s (-15.1% 🟢) 0.399s 8 1.03x
🐘 Postgres Nitro 12.248s (-12.3% 🟢) 13.017s (-9.0% 🟢) 0.769s 7 1.05x
💻 Local Next.js (Turbopack) 12.464s 13.022s 0.558s 7 1.07x
🐘 Postgres Next.js (Turbopack) 13.069s 14.017s 0.948s 7 1.12x
Promise.all with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.143s (-9.3% 🟢) 2.007s (~) 0.864s 15 1.00x
💻 Local Nitro 1.145s (-29.8% 🟢) 2.005s (-3.3%) 0.860s 15 1.00x
💻 Local Express 1.147s (-23.0% 🟢) 2.005s (~) 0.858s 15 1.00x
🐘 Postgres Nitro 1.148s (-9.9% 🟢) 2.008s (~) 0.860s 15 1.00x
🐘 Postgres Next.js (Turbopack) 1.211s 2.007s 0.797s 15 1.06x
💻 Local Next.js (Turbopack) 1.230s 2.005s 0.775s 15 1.08x
Promise.all with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.199s (-49.2% 🟢) 2.007s (-33.3% 🟢) 0.809s 15 1.00x
🐘 Postgres Nitro 1.199s (-49.0% 🟢) 2.007s (-33.3% 🟢) 0.808s 15 1.00x
🐘 Postgres Next.js (Turbopack) 1.341s 2.007s 0.667s 15 1.12x
💻 Local Nitro 1.573s (-50.0% 🟢) 2.006s (-48.4% 🟢) 0.433s 15 1.31x
💻 Local Express 1.593s (-46.1% 🟢) 2.005s (-41.9% 🟢) 0.412s 15 1.33x
💻 Local Next.js (Turbopack) 1.686s 2.005s 0.319s 15 1.41x
Promise.all with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.277s (-63.3% 🟢) 2.007s (-49.9% 🟢) 0.730s 15 1.00x
🐘 Postgres Express 1.318s (-62.2% 🟢) 2.008s (-49.9% 🟢) 0.690s 15 1.03x
🐘 Postgres Next.js (Turbopack) 1.593s 2.007s 0.414s 15 1.25x
💻 Local Nitro 3.926s (-53.0% 🟢) 4.581s (-49.2% 🟢) 0.655s 7 3.07x
💻 Local Express 4.439s (-46.8% 🟢) 5.012s (-44.5% 🟢) 0.573s 6 3.48x
💻 Local Next.js (Turbopack) 4.483s 5.011s 0.528s 7 3.51x
Promise.race with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.145s (-8.9% 🟢) 2.008s (~) 0.863s 15 1.00x
🐘 Postgres Express 1.145s (-8.9% 🟢) 2.007s (~) 0.862s 15 1.00x
💻 Local Nitro 1.193s (-36.1% 🟢) 2.005s (-14.3% 🟢) 0.812s 15 1.04x
🐘 Postgres Next.js (Turbopack) 1.227s 2.007s 0.780s 15 1.07x
💻 Local Next.js (Turbopack) 1.259s 2.005s 0.746s 15 1.10x
💻 Local Express 1.425s (-24.8% 🟢) 2.005s (-15.2% 🟢) 0.581s 15 1.24x
Promise.race with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.193s (-49.0% 🟢) 2.009s (-33.3% 🟢) 0.815s 15 1.00x
🐘 Postgres Express 1.205s (-48.5% 🟢) 2.008s (-33.3% 🟢) 0.804s 15 1.01x
🐘 Postgres Next.js (Turbopack) 1.335s 2.007s 0.672s 15 1.12x
💻 Local Express 1.610s (-48.6% 🟢) 2.005s (-46.7% 🟢) 0.396s 15 1.35x
💻 Local Nitro 1.682s (-45.1% 🟢) 2.005s (-48.4% 🟢) 0.323s 15 1.41x
💻 Local Next.js (Turbopack) 1.954s 2.220s 0.266s 14 1.64x
Promise.race with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.295s (-62.8% 🟢) 2.009s (-49.9% 🟢) 0.714s 15 1.00x
🐘 Postgres Express 1.332s (-61.9% 🟢) 2.008s (-49.9% 🟢) 0.676s 15 1.03x
🐘 Postgres Next.js (Turbopack) 1.668s 2.075s 0.407s 15 1.29x
💻 Local Nitro 4.399s (-51.9% 🟢) 5.012s (-50.0% 🟢) 0.613s 6 3.40x
💻 Local Express 4.856s (-44.8% 🟢) 5.513s (-40.5% 🟢) 0.657s 6 3.75x
💻 Local Next.js (Turbopack) 5.261s 6.015s 0.755s 5 4.06x
workflow with 10 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.430s (-48.8% 🟢) 1.007s (-1.6%) 0.577s 60 1.00x
🐘 Postgres Nitro 0.449s (-45.3% 🟢) 1.006s (~) 0.557s 60 1.04x
💻 Local Nitro 0.452s (-53.9% 🟢) 1.020s (-6.7% 🟢) 0.569s 59 1.05x
💻 Local Express 0.458s (-53.5% 🟢) 1.003s (-6.7% 🟢) 0.545s 60 1.07x
💻 Local Next.js (Turbopack) 0.555s 1.004s 0.450s 60 1.29x
🐘 Postgres Next.js (Turbopack) 0.673s 1.006s 0.334s 60 1.57x
workflow with 25 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.095s (-43.2% 🟢) 1.807s (-14.0% 🟢) 0.711s 50 1.00x
🐘 Postgres Express 1.122s (-43.2% 🟢) 1.903s (-15.7% 🟢) 0.781s 48 1.02x
💻 Local Express 1.136s (-62.3% 🟢) 2.005s (-44.1% 🟢) 0.869s 45 1.04x
💻 Local Nitro 1.181s (-61.1% 🟢) 1.805s (-52.0% 🟢) 0.624s 50 1.08x
💻 Local Next.js (Turbopack) 1.410s 2.005s 0.594s 45 1.29x
🐘 Postgres Next.js (Turbopack) 1.598s 2.007s 0.409s 45 1.46x
workflow with 50 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 2.091s (-49.0% 🟢) 2.674s (-41.9% 🟢) 0.582s 45 1.00x
🐘 Postgres Express 2.158s (-45.9% 🟢) 2.822s (-35.4% 🟢) 0.665s 43 1.03x
💻 Local Express 2.542s (-72.4% 🟢) 3.007s (-70.0% 🟢) 0.465s 40 1.22x
💻 Local Nitro 2.677s (-71.2% 🟢) 3.135s (-68.7% 🟢) 0.459s 39 1.28x
💻 Local Next.js (Turbopack) 3.103s 3.852s 0.750s 32 1.48x
🐘 Postgres Next.js (Turbopack) 3.152s 4.008s 0.856s 30 1.51x
workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.165s (-41.9% 🟢) 1.005s (~) 0.841s 60 1.00x
🐘 Postgres Express 0.187s (-33.9% 🟢) 1.006s (~) 0.820s 60 1.13x
🐘 Postgres Next.js (Turbopack) 0.232s 1.006s 0.774s 60 1.41x
💻 Local Nitro 0.368s (-39.1% 🟢) 1.005s (-1.6%) 0.637s 60 2.24x
💻 Local Express 0.381s (-31.9% 🟢) 1.003s (~) 0.622s 60 2.32x
💻 Local Next.js (Turbopack) 0.529s 1.021s 0.492s 59 3.22x
workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.284s (-42.9% 🟢) 1.006s (~) 0.722s 90 1.00x
🐘 Postgres Express 0.316s (-38.0% 🟢) 1.006s (~) 0.690s 90 1.11x
🐘 Postgres Next.js (Turbopack) 0.430s 1.006s 0.576s 90 1.52x
💻 Local Nitro 1.926s (-24.1% 🟢) 2.402s (-20.2% 🟢) 0.476s 38 6.79x
💻 Local Express 2.003s (-20.3% 🟢) 2.579s (-14.3% 🟢) 0.575s 35 7.06x
💻 Local Next.js (Turbopack) 2.228s 2.821s 0.593s 32 7.86x
workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.571s (-27.8% 🟢) 1.005s (~) 0.435s 120 1.00x
🐘 Postgres Express 0.642s (-21.6% 🟢) 1.006s (-1.1%) 0.365s 120 1.12x
🐘 Postgres Next.js (Turbopack) 0.899s 1.107s 0.209s 109 1.57x
💻 Local Nitro 8.577s (-23.4% 🟢) 9.096s (-22.0% 🟢) 0.519s 14 15.02x
💻 Local Express 9.359s (-16.4% 🟢) 9.794s (-18.0% 🟢) 0.435s 13 16.39x
💻 Local Next.js (Turbopack) 9.754s 10.524s 0.770s 12 17.09x
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.112s (+420.4% 🔺) 2.004s (+99.5% 🔺) 0.008s (-32.8% 🟢) 2.015s (+97.8% 🔺) 0.903s 10 1.00x
💻 Local Express 1.121s (+463.0% 🔺) 2.005s (+99.6% 🔺) 0.009s (-24.8% 🟢) 2.016s (+98.0% 🔺) 0.895s 10 1.01x
🐘 Postgres Nitro 1.136s (+454.0% 🔺) 2.002s (+100.3% 🔺) 0.001s (-20.0% 🟢) 2.010s (+98.8% 🔺) 0.875s 10 1.02x
🐘 Postgres Express 1.140s (+455.6% 🔺) 2.000s (+100.3% 🔺) 0.001s (-31.3% 🟢) 2.011s (+98.8% 🔺) 0.871s 10 1.02x
💻 Local Next.js (Turbopack) 1.152s 2.003s 0.009s 2.016s 0.864s 10 1.04x
🐘 Postgres Next.js (Turbopack) 1.189s 2.000s 0.002s 2.011s 0.822s 10 1.07x
stream pipeline with 5 transform steps (1MB)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Express 1.502s (+98.3% 🔺) 2.010s (+95.4% 🔺) 0.009s (+0.6%) 2.021s (+94.4% 🔺) 0.520s 30 1.00x
🐘 Postgres Nitro 1.534s (+145.8% 🔺) 2.007s (+99.3% 🔺) 0.003s (-17.8% 🟢) 2.024s (+97.9% 🔺) 0.489s 30 1.02x
🐘 Postgres Express 1.536s (+143.7% 🔺) 1.999s (+98.7% 🔺) 0.004s (+4.4%) 2.025s (+98.0% 🔺) 0.490s 30 1.02x
💻 Local Next.js (Turbopack) 1.551s 2.008s 0.011s 2.022s 0.471s 30 1.03x
🐘 Postgres Next.js (Turbopack) 1.684s 2.010s 0.004s 2.025s 0.341s 30 1.12x
💻 Local Nitro 1.842s (+119.7% 🔺) 2.005s (+98.1% 🔺) 0.007s (-27.3% 🟢) 2.403s (+115.3% 🔺) 0.560s 26 1.23x
10 parallel streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.643s (-33.6% 🟢) 1.052s (-15.7% 🟢) 0.000s (-57.9% 🟢) 1.061s (-15.7% 🟢) 0.418s 57 1.00x
🐘 Postgres Express 0.645s (-32.9% 🟢) 1.012s (-20.8% 🟢) 0.000s (-22.0% 🟢) 1.030s (-21.1% 🟢) 0.385s 59 1.00x
🐘 Postgres Next.js (Turbopack) 0.770s 1.035s 0.000s 1.042s 0.272s 58 1.20x
💻 Local Nitro 1.167s (-4.6%) 1.920s (-5.0%) 0.000s (+337.5% 🔺) 1.922s (-4.9%) 0.755s 32 1.82x
💻 Local Express 1.334s (+8.9% 🔺) 2.013s (~) 0.000s (-20.0% 🟢) 2.015s (~) 0.682s 30 2.07x
💻 Local Next.js (Turbopack) 1.395s 2.012s 0.000s 2.015s 0.621s 30 2.17x
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.312s (-26.8% 🟢) 2.001s (-6.6% 🟢) 0.000s (-100.0% 🟢) 2.009s (-7.6% 🟢) 0.698s 30 1.00x
🐘 Postgres Express 1.390s (-21.6% 🟢) 2.031s (-6.7% 🟢) 0.000s (NaN%) 2.043s (-7.1% 🟢) 0.653s 30 1.06x
🐘 Postgres Next.js (Turbopack) 1.482s 2.105s 0.000s 2.112s 0.630s 29 1.13x
💻 Local Nitro 2.511s (-25.9% 🟢) 3.019s (-25.1% 🟢) 0.000s (-15.6% 🟢) 3.027s (-25.0% 🟢) 0.516s 20 1.91x
💻 Local Next.js (Turbopack) 2.739s 3.358s 0.001s 3.362s 0.624s 18 2.09x
💻 Local Express 3.047s (-12.1% 🟢) 3.560s (-11.8% 🟢) 0.001s (+17.6% 🔺) 3.563s (-11.7% 🟢) 0.516s 17 2.32x

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World 🥇 Fastest Framework Wins
💻 Local Nitro 17/21
🐘 Postgres Nitro 15/21
Fastest World by Framework

Winner determined by most benchmark wins

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

Sets WORKFLOW_SERVER_URL_OVERRIDE in
packages/world-vercel/src/utils.ts to
https://workflow-server-git-peter-v4.vercel.sh so that e2e tests
running off this SDK branch exercise the v4-enabled workflow-server
preview instead of production.

The override is the inline mechanism documented at the constant —
when set, it wins over both the default
(https://vercel-workflow.com) and the VERCEL_WORKFLOW_SERVER_URL
env var. The same pattern is used in v4 testing on the workflow-
server side: CI rewrites this string on PR branches. Reset to ''
before merging to main.

Companion to vercel/workflow-server#439.

Updates four tests in utils.test.ts that previously assumed the
override is empty. Each affected assertion gets a comment noting
what the expectation looks like on main; flipping back to the main
behavior is a one-line edit per test when the override is reset.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The adapter's createWorkflowRunEvent / getEvent / getWorkflowRunEvents
now call the v4 endpoints internally instead of v2/v3. Public function
signatures and the EventResult / Event / PaginatedResponse<Event>
shapes returned to the workflow runtime are unchanged — only the wire
protocol switches.

Wire-format changes the SDK now drives:

  - Event metadata rides in x-wf-* request/response headers.
  - User payload is CBOR-encoded by the SDK at the eventData[field]
    boundary and shipped as opaque body bytes; never parsed on the
    server.
  - POST event response carries the materialized EventResult as a
    CBOR body — see workflow-server PR #439's
    "Return materialized entity in POST event response body" commit
    for the matching server-side change. The SDK no longer needs a
    second round-trip after POST to read run/step state.
  - LIST events uses the v4 binary-frame stream
    (application/vnd.workflow.v4-frames). Per-event `/refs` calls
    are gone — payloads come back inline in each frame.

What goes away:

  - packages/world-vercel/src/refs.ts (deleted) — the /refs ref-
    hydration path is no longer needed.
  - hydrateEventRefs / collectPendingRefs / eventDataRefFieldMap and
    the EventResultResolveWireSchema / EventResultLazyWireSchema /
    EventWithRefsSchema wire schemas (deleted).
  - The lazy-refs branching inside createWorkflowRunEvent — the
    server already respects `remoteRefBehavior` (still sent for
    `eventsNeedingResolve` types) and bakes the resolution decision
    into its CBOR response.

What stays:

  - v1Compat path on `createWorkflowRunEvent` (runs.create.v1 /
    runs.cancel.v1) — still uses v1 endpoints for legacy migrations
    that haven't moved to event sourcing.
  - validateUlidTimestamp on run_created, the HookNotFoundError
    translation on hook_disposed/hook_received 404s, and the
    stripEventDataRefs path for resolveData='none'.

Not yet covered:

  - listEventsByCorrelationId throws a clear error — v4 has no
    by-correlation-id list endpoint yet; callers in the hot path
    have been fetching hooks directly anyway. A future server PR
    can add /api/v4/events?correlationId= if needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@VaguelySerious VaguelySerious changed the title [world-vercel] Add v4 event API client [world-vercel] Switch event endpoints to v4 wire format May 21, 2026
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