Skip to content

[world-vercel] Add /run-id sub-export with tagged ULID encode/decode#1978

Open
TooTallNate wants to merge 5 commits into
mainfrom
world-vercel-tagged-run-id
Open

[world-vercel] Add /run-id sub-export with tagged ULID encode/decode#1978
TooTallNate wants to merge 5 commits into
mainfrom
world-vercel-tagged-run-id

Conversation

@TooTallNate
Copy link
Copy Markdown
Member

@TooTallNate TooTallNate commented May 13, 2026

Summary

Adds a new sub-export @workflow/world-vercel/run-id exposing encode and decode helpers that produce ULID-shaped workflow run IDs carrying:

  • A tag bit (MSB of byte 0) marking the value as a tagged run ID — distinguishes the scheme from a plain ULID and shifts the first char into the range 4..7.
  • A 5-bit version (0–31) — defaults to 1; 0 is reserved as a "no metadata" sentinel.
  • A 6-bit region ID (0–63) — maps to a known Vercel compute region (e.g. iad1, fra1, sfo1).

Tagged values remain valid 26-char Crockford-Base32 ULIDs, so they continue to sort lexicographically and flow through any system that accepts ULIDs.

Bit layout

byte[0]   bit 7        TAG bit (1 = tagged run ID)
byte[14]  bits 0..2    version high 3 bits
byte[15]  bits 6..7    version low 2 bits   } 5-bit version (0..31)
byte[15]  bits 0..5    regionId (0..63)

Randomness: 80 bits → 69 bits preserved (~5.9 × 10²⁰ values per ms).

Region table

Covers the 21 currently-deployed Vercel compute regions plus hel1 and zrh1, which are reserved for future rollout. 0 = unknown sentinel.

IDs are stable and append-only — never reorder or reuse, since they're encoded on the wire in every run ID emitted.

API

import { encode, decode, isTagged, REGION_IDS } from '@workflow/world-vercel/run-id';

// Accepts either a RegionCode or a numeric region ID
const taggedRunId = encode(ulid(), 'iad1');

const { tagged, region, regionId, version, ulid: cleared } = decode(taggedRunId);
// tagged === true, region === 'iad1', regionId === 1, version === 1

decode clears only the tag bit on the returned ulid field — the 11 metadata bits remain in place so the encoded info is recoverable from the "cleared" value too.

Verification

  • pnpm vitest run src/run-id/ → 34/34 pass (includes exact-string assertions for all encoded outputs)
  • pnpm typecheck → clean
  • pnpm build → emits dist/run-id/{index,codec,regions}.{js,d.ts,...}

Files

  • packages/world-vercel/src/run-id/regions.ts — region table + lookupRegion/regionIdFor
  • packages/world-vercel/src/run-id/codec.ts — Crockford-Base32 ↔ 16-byte bit-packing
  • packages/world-vercel/src/run-id/index.ts — public encode/decode/isTagged API
  • packages/world-vercel/src/run-id/codec.test.ts (13 tests) + index.test.ts (21 tests)
  • packages/world-vercel/package.json./run-id added to exports
  • .changeset/tagged-run-id.md — minor bump for @workflow/world-vercel

Encodes a tag bit, 5-bit version, and 6-bit Vercel region ID into a
ULID-shaped string used for workflow run IDs. Tagged values remain
valid 26-char Crockford-Base32 ULIDs so they still sort and round-trip
through any system that accepts ULIDs.
Copilot AI review requested due to automatic review settings May 13, 2026 07:38
@TooTallNate TooTallNate requested a review from a team as a code owner May 13, 2026 07:38
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 13, 2026

🦋 Changeset detected

Latest commit: d077ad1

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 13, 2026

🧪 E2E Test Results

All tests passed

Summary

Passed Failed Skipped Total
✅ ▲ Vercel Production 1093 0 217 1310
✅ 💻 Local Development 1615 0 219 1834
✅ 📦 Local Production 1615 0 219 1834
✅ 🐘 Local Postgres 1615 0 219 1834
✅ 🪟 Windows 131 0 0 131
✅ 📋 Other 741 0 176 917
Total 6810 0 1050 7860

Details by Category

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

📋 View full workflow run


Some E2E test jobs failed:

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

Check the workflow run for details.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 13, 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.029s (-32.5% 🟢) 1.005s (~) 0.976s 10 1.00x
💻 Local Express 0.031s (-29.1% 🟢) 1.006s (~) 0.974s 10 1.08x
🐘 Postgres Nitro 0.048s (-49.9% 🟢) 1.011s (-3.1%) 0.963s 10 1.64x
🐘 Postgres Express 0.050s (-14.5% 🟢) 1.012s (~) 0.963s 10 1.70x
💻 Local Next.js (Turbopack) 0.050s 1.006s 0.956s 10 1.71x
🐘 Postgres Next.js (Turbopack) 0.056s 1.010s 0.954s 10 1.92x
workflow with 1 step

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Express 1.071s (-4.8%) 2.007s (~) 0.936s 10 1.00x
💻 Local Nitro 1.077s (-4.8%) 2.015s (~) 0.937s 10 1.01x
🐘 Postgres Nitro 1.083s (-5.0%) 2.009s (~) 0.926s 10 1.01x
🐘 Postgres Express 1.090s (-4.9%) 2.009s (~) 0.919s 10 1.02x
💻 Local Next.js (Turbopack) 1.106s 2.006s 0.901s 10 1.03x
🐘 Postgres Next.js (Turbopack) 1.123s 2.009s 0.886s 10 1.05x
workflow with 10 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 10.406s (-4.9%) 11.021s (~) 0.615s 3 1.00x
🐘 Postgres Nitro 10.414s (-4.2%) 11.020s (~) 0.605s 3 1.00x
💻 Local Express 10.421s (-4.6%) 11.022s (~) 0.601s 3 1.00x
🐘 Postgres Express 10.479s (-4.4%) 11.018s (~) 0.538s 3 1.01x
💻 Local Next.js (Turbopack) 10.674s 11.022s 0.347s 3 1.03x
🐘 Postgres Next.js (Turbopack) 10.750s 11.019s 0.269s 3 1.03x
workflow with 25 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 13.454s (-10.7% 🟢) 14.027s (-12.5% 🟢) 0.572s 5 1.00x
💻 Local Express 13.481s (-9.9% 🟢) 14.027s (-6.7% 🟢) 0.546s 5 1.00x
🐘 Postgres Nitro 13.487s (-7.6% 🟢) 14.016s (-6.7% 🟢) 0.529s 5 1.00x
🐘 Postgres Express 13.605s (-6.7% 🟢) 14.021s (-6.7% 🟢) 0.416s 5 1.01x
💻 Local Next.js (Turbopack) 14.066s 14.628s 0.562s 5 1.05x
🐘 Postgres Next.js (Turbopack) 14.176s 15.014s 0.838s 4 1.05x
workflow with 50 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Express 11.928s (-28.2% 🟢) 12.147s (-28.7% 🟢) 0.219s 8 1.00x
🐘 Postgres Nitro 11.976s (-14.3% 🟢) 12.389s (-13.4% 🟢) 0.412s 8 1.00x
💻 Local Nitro 11.993s (-28.5% 🟢) 12.522s (-26.5% 🟢) 0.529s 8 1.01x
🐘 Postgres Express 12.270s (-12.4% 🟢) 13.021s (-10.8% 🟢) 0.751s 7 1.03x
💻 Local Next.js (Turbopack) 13.017s 13.452s 0.435s 7 1.09x
🐘 Postgres Next.js (Turbopack) 13.270s 14.013s 0.743s 7 1.11x
Promise.all with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.142s (-10.4% 🟢) 2.008s (~) 0.866s 15 1.00x
🐘 Postgres Express 1.155s (-8.3% 🟢) 2.008s (~) 0.852s 15 1.01x
💻 Local Nitro 1.170s (-28.3% 🟢) 2.006s (-3.3%) 0.836s 15 1.02x
💻 Local Express 1.175s (-21.1% 🟢) 2.005s (~) 0.830s 15 1.03x
🐘 Postgres Next.js (Turbopack) 1.199s 2.007s 0.807s 15 1.05x
💻 Local Next.js (Turbopack) 1.264s 2.006s 0.742s 15 1.11x
Promise.all with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.196s (-49.1% 🟢) 2.007s (-33.3% 🟢) 0.811s 15 1.00x
🐘 Postgres Express 1.201s (-49.1% 🟢) 2.008s (-33.3% 🟢) 0.807s 15 1.00x
🐘 Postgres Next.js (Turbopack) 1.338s 2.008s 0.670s 15 1.12x
💻 Local Nitro 1.677s (-46.7% 🟢) 2.006s (-48.4% 🟢) 0.329s 15 1.40x
💻 Local Express 1.774s (-39.9% 🟢) 2.007s (-41.9% 🟢) 0.232s 15 1.48x
💻 Local Next.js (Turbopack) 1.878s 2.221s 0.343s 14 1.57x
Promise.all with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.307s (-62.5% 🟢) 2.007s (-49.9% 🟢) 0.701s 15 1.00x
🐘 Postgres Express 1.343s (-61.5% 🟢) 2.010s (-49.9% 🟢) 0.666s 15 1.03x
🐘 Postgres Next.js (Turbopack) 1.623s 2.008s 0.386s 15 1.24x
💻 Local Nitro 4.424s (-47.0% 🟢) 5.011s (-44.4% 🟢) 0.588s 7 3.39x
💻 Local Express 5.431s (-34.9% 🟢) 5.846s (-35.2% 🟢) 0.415s 6 4.16x
💻 Local Next.js (Turbopack) 5.528s 6.012s 0.484s 5 4.23x
Promise.race with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.144s (-9.0% 🟢) 2.008s (~) 0.864s 15 1.00x
🐘 Postgres Express 1.171s (-6.8% 🟢) 2.010s (~) 0.839s 15 1.02x
🐘 Postgres Next.js (Turbopack) 1.204s 2.009s 0.805s 15 1.05x
💻 Local Next.js (Turbopack) 1.277s 2.006s 0.730s 15 1.12x
💻 Local Nitro 1.359s (-27.1% 🟢) 2.006s (-14.3% 🟢) 0.646s 15 1.19x
💻 Local Express 1.425s (-24.7% 🟢) 2.006s (-15.1% 🟢) 0.581s 15 1.25x
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.008s (-33.3% 🟢) 0.816s 15 1.00x
🐘 Postgres Express 1.215s (-48.1% 🟢) 2.010s (-33.2% 🟢) 0.795s 15 1.02x
🐘 Postgres Next.js (Turbopack) 1.341s 2.008s 0.667s 15 1.12x
💻 Local Nitro 1.965s (-35.9% 🟢) 2.391s (-38.5% 🟢) 0.426s 13 1.65x
💻 Local Next.js (Turbopack) 2.116s 3.008s 0.892s 10 1.77x
💻 Local Express 2.131s (-32.0% 🟢) 2.592s (-31.1% 🟢) 0.460s 12 1.79x
Promise.race with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.303s (-62.5% 🟢) 2.007s (-49.9% 🟢) 0.704s 15 1.00x
🐘 Postgres Express 1.345s (-61.6% 🟢) 2.009s (-49.9% 🟢) 0.664s 15 1.03x
🐘 Postgres Next.js (Turbopack) 1.604s 2.007s 0.403s 15 1.23x
💻 Local Nitro 4.857s (-46.9% 🟢) 5.179s (-48.3% 🟢) 0.322s 6 3.73x
💻 Local Express 5.411s (-38.5% 🟢) 6.014s (-35.1% 🟢) 0.602s 5 4.15x
💻 Local Next.js (Turbopack) 5.645s 6.213s 0.569s 5 4.33x
workflow with 10 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.423s (-48.4% 🟢) 1.006s (~) 0.582s 60 1.00x
💻 Local Nitro 0.508s (-48.2% 🟢) 1.057s (-3.4%) 0.549s 57 1.20x
💻 Local Express 0.516s (-47.6% 🟢) 1.021s (-5.1% 🟢) 0.505s 59 1.22x
🐘 Postgres Express 0.529s (-37.0% 🟢) 1.024s (~) 0.496s 59 1.25x
🐘 Postgres Next.js (Turbopack) 0.658s 1.006s 0.348s 60 1.55x
💻 Local Next.js (Turbopack) 0.705s 1.004s 0.300s 60 1.66x
workflow with 25 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.057s (-45.1% 🟢) 1.576s (-25.0% 🟢) 0.518s 58 1.00x
💻 Local Nitro 1.178s (-61.2% 🟢) 2.006s (-46.6% 🟢) 0.828s 45 1.11x
🐘 Postgres Express 1.186s (-40.0% 🟢) 2.030s (-10.1% 🟢) 0.844s 45 1.12x
💻 Local Express 1.199s (-60.2% 🟢) 2.006s (-44.1% 🟢) 0.807s 45 1.13x
🐘 Postgres Next.js (Turbopack) 1.602s 2.008s 0.406s 45 1.52x
💻 Local Next.js (Turbopack) 1.796s 2.028s 0.231s 45 1.70x
workflow with 50 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 2.064s (-49.7% 🟢) 2.562s (-44.4% 🟢) 0.498s 47 1.00x
🐘 Postgres Express 2.349s (-41.1% 🟢) 3.059s (-30.0% 🟢) 0.710s 40 1.14x
💻 Local Nitro 2.655s (-71.4% 🟢) 3.032s (-69.7% 🟢) 0.377s 40 1.29x
💻 Local Express 2.688s (-70.8% 🟢) 3.007s (-70.0% 🟢) 0.320s 40 1.30x
🐘 Postgres Next.js (Turbopack) 3.119s 4.009s 0.891s 30 1.51x
💻 Local Next.js (Turbopack) 3.744s 4.041s 0.297s 30 1.81x
workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.171s (-39.6% 🟢) 1.006s (~) 0.835s 60 1.00x
🐘 Postgres Express 0.190s (-32.7% 🟢) 1.007s (~) 0.817s 60 1.11x
🐘 Postgres Next.js (Turbopack) 0.231s 1.006s 0.774s 60 1.35x
💻 Local Express 0.393s (-29.8% 🟢) 1.004s (~) 0.611s 60 2.30x
💻 Local Nitro 0.402s (-33.5% 🟢) 1.004s (-1.7%) 0.602s 60 2.35x
💻 Local Next.js (Turbopack) 0.505s 1.004s 0.499s 60 2.95x
workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.290s (-43.2% 🟢) 1.006s (~) 0.717s 90 1.00x
🐘 Postgres Nitro 0.331s (-33.3% 🟢) 1.018s (+1.1%) 0.687s 89 1.14x
🐘 Postgres Next.js (Turbopack) 0.431s 1.006s 0.575s 90 1.49x
💻 Local Nitro 2.044s (-19.4% 🟢) 2.536s (-15.7% 🟢) 0.492s 36 7.06x
💻 Local Express 2.142s (-14.8% 🟢) 2.797s (-7.1% 🟢) 0.654s 33 7.39x
💻 Local Next.js (Turbopack) 2.219s 2.853s 0.634s 32 7.66x
workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.597s (-27.0% 🟢) 1.006s (-1.1%) 0.409s 120 1.00x
🐘 Postgres Nitro 0.610s (-22.8% 🟢) 1.006s (~) 0.395s 120 1.02x
🐘 Postgres Next.js (Turbopack) 0.897s 1.128s 0.231s 107 1.50x
💻 Local Nitro 9.653s (-13.7% 🟢) 10.194s (-12.6% 🟢) 0.541s 12 16.16x
💻 Local Express 10.202s (-8.8% 🟢) 10.777s (-9.7% 🟢) 0.575s 12 17.08x
💻 Local Next.js (Turbopack) 10.775s 11.481s 0.706s 11 18.04x
Stream Benchmarks (includes TTFB metrics)
workflow with stream

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.137s (+454.7% 🔺) 1.996s (+99.7% 🔺) 0.001s (-33.3% 🟢) 2.010s (+98.8% 🔺) 0.873s 10 1.00x
💻 Local Express 1.139s (+472.1% 🔺) 2.005s (+99.6% 🔺) 0.012s (+0.8%) 2.020s (+98.4% 🔺) 0.881s 10 1.00x
🐘 Postgres Express 1.149s (+460.3% 🔺) 2.002s (+100.5% 🔺) 0.001s (-25.0% 🟢) 2.011s (+98.8% 🔺) 0.862s 10 1.01x
💻 Local Nitro 1.153s (+439.4% 🔺) 2.004s (+99.5% 🔺) 0.011s (-16.0% 🟢) 2.017s (+98.0% 🔺) 0.865s 10 1.01x
💻 Local Next.js (Turbopack) 1.183s 2.003s 0.012s 2.019s 0.836s 10 1.04x
🐘 Postgres Next.js (Turbopack) 1.196s 2.002s 0.001s 2.010s 0.814s 10 1.05x
stream pipeline with 5 transform steps (1MB)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.502s (+140.6% 🔺) 2.007s (+99.4% 🔺) 0.004s (-7.3% 🟢) 2.025s (+98.1% 🔺) 0.524s 30 1.00x
💻 Local Nitro 1.510s (+80.0% 🔺) 2.010s (+98.7% 🔺) 0.010s (+8.5% 🔺) 2.022s (+81.2% 🔺) 0.513s 30 1.01x
💻 Local Express 1.529s (+102.0% 🔺) 2.013s (+95.6% 🔺) 0.011s (+11.5% 🔺) 2.025s (+94.7% 🔺) 0.496s 30 1.02x
🐘 Postgres Express 1.542s (+144.8% 🔺) 2.006s (+99.3% 🔺) 0.004s (-7.8% 🟢) 2.023s (+97.8% 🔺) 0.481s 30 1.03x
🐘 Postgres Next.js (Turbopack) 1.685s 2.011s 0.004s 2.025s 0.341s 30 1.12x
💻 Local Next.js (Turbopack) 1.841s 2.010s 0.010s 2.202s 0.362s 28 1.23x
10 parallel streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.659s (-31.5% 🟢) 1.017s (-20.4% 🟢) 0.000s (-22.0% 🟢) 1.024s (-21.6% 🟢) 0.366s 59 1.00x
🐘 Postgres Nitro 0.662s (-31.7% 🟢) 1.030s (-17.5% 🟢) 0.000s (-100.0% 🟢) 1.043s (-17.1% 🟢) 0.381s 58 1.01x
🐘 Postgres Next.js (Turbopack) 0.779s 1.072s 0.000s 1.079s 0.300s 56 1.18x
💻 Local Nitro 1.340s (+9.6% 🔺) 2.014s (~) 0.000s (+100.0% 🔺) 2.016s (~) 0.676s 30 2.03x
💻 Local Express 1.346s (+9.9% 🔺) 2.015s (~) 0.000s (-70.0% 🟢) 2.017s (~) 0.670s 30 2.04x
💻 Local Next.js (Turbopack) 1.377s 2.014s 0.000s 2.017s 0.640s 30 2.09x
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.262s (-29.6% 🟢) 2.031s (-5.1% 🟢) 0.000s (-100.0% 🟢) 2.043s (-6.0% 🟢) 0.782s 30 1.00x
🐘 Postgres Express 1.458s (-17.7% 🟢) 2.071s (-4.9%) 0.000s (NaN%) 2.080s (-5.4% 🟢) 0.621s 29 1.16x
🐘 Postgres Next.js (Turbopack) 1.586s 2.105s 0.000s 2.145s 0.559s 28 1.26x
💻 Local Next.js (Turbopack) 2.656s 3.291s 0.001s 3.294s 0.638s 19 2.11x
💻 Local Nitro 3.010s (-11.2% 🟢) 3.672s (-8.9% 🟢) 0.001s (+43.4% 🔺) 3.679s (-8.9% 🟢) 0.669s 17 2.39x
💻 Local Express 3.143s (-9.4% 🟢) 3.966s (-1.7%) 0.000s (-68.8% 🟢) 3.968s (-1.7%) 0.825s 16 2.49x

Summary

Fastest Framework by World

Winner determined by most benchmark wins

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

Winner determined by most benchmark wins

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


Some benchmark jobs failed:

  • Local: success
  • Postgres: success
  • Vercel: failure

Check the workflow run for details.

Add exact-string expectations for encoded outputs at known inputs,
covering the default region/version pair, numeric region IDs, version
overrides, boundary values (all-zero, all-max), the dirty-input
overwrite case, and the lexicographic-order checks. Also adds an
explicit byte-array expectation for the canonical ULID-spec example
string and an additional first-char-range coverage test for isTagged.
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 introduces a new @workflow/world-vercel/run-id sub-export that can encode/decode tagged ULID-shaped workflow run IDs, embedding a tag bit, a 5-bit version, and a 6-bit Vercel region ID while preserving ULID sortability.

Changes:

  • Add a region table (REGION_IDS) and helpers for mapping between region codes and 6-bit region IDs.
  • Implement a Crockford-Base32 ↔ 16-byte codec and public encode/decode/isTagged API for tagged ULIDs.
  • Add tests for codec correctness and encode/decode behavior; expose the new subpath export via package.json and add a changeset.

Reviewed changes

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

Show a summary per file
File Description
packages/world-vercel/src/run-id/regions.ts Adds stable region↔ID mapping and lookup helpers used by tagged run IDs.
packages/world-vercel/src/run-id/index.ts Public API for encoding/decoding tagged ULIDs + re-exports/constants.
packages/world-vercel/src/run-id/index.test.ts Tests for encode/decode semantics, validation, and ordering properties.
packages/world-vercel/src/run-id/codec.ts Implements ULID base32 packing/unpacking and tag-bit detection helper.
packages/world-vercel/src/run-id/codec.test.ts Tests for codec round-trips, validation, and tag-bit detection.
packages/world-vercel/package.json Exposes ./run-id as a public package sub-export.
.changeset/tagged-run-id.md Declares a minor release for the new sub-export/API surface.

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

Comment thread packages/world-vercel/src/run-id/codec.ts Outdated
Comment thread packages/world-vercel/src/run-id/regions.ts Outdated
Comment thread packages/world-vercel/src/run-id/index.ts Outdated
Copy link
Copy Markdown
Contributor

@karthikscale3 karthikscale3 left a comment

Choose a reason for hiding this comment

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

Reviewed for blockers/regressions — none found. Change is purely additive (new sub-export, nothing in the repo consumes it yet). Three non-blocking nits below.

* relevant.
*
* Tagged ULIDs remain valid ULIDs (lexicographically sortable, monotonic when
* generated with a monotonic factory), so they can flow through any system
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.

The "monotonic when generated with a monotonic factory" claim is misleading. ulid's monotonicFactory() preserves intra-millisecond order by incrementing the bottom of the 80-bit randomness section — i.e. exactly the bits encode overwrites with the metadata. Two consecutive monotonic ULIDs in the same ms, tagged with the same (region, version), will collide (or worse, flip order if the carry only reaches into the bottom 11 bits but not above).

Suggest softening to something like: "Tagged ULIDs preserve lexicographic order across millisecond boundaries; intra-millisecond monotonicity is not preserved because the bottom 11 randomness bits are overwritten by the metadata."

Not a blocker — but a foot-gun for the eventual consumer if they wrap monotonicFactory() with encode.

/** Encoded format version (0..31). */
version: number;
/** Encoded region ID (0..63). 0 represents "unknown". */
regionId: number;
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.

When tagged === false, regionId and version are populated from whatever bits happen to sit in those positions of an arbitrary ULID — i.e. random garbage. The docstring on tagged above says callers should "generally ignore them," but it's an easy footgun to read decoded.region without first checking decoded.tagged.

Consider returning regionId: null, version: null, region: null when tagged === false so the type system forces the check. Not a correctness issue — just ergonomics.

*/
export function regionIdFor(code: RegionCode): RegionId {
const id = REGION_IDS[code];
if (id === undefined) {
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.

This branch is unreachable: RegionCode = Exclude<RegionKey, 'unknown'>, and every non-'unknown' key of REGION_IDS maps to a defined number. At runtime, callers passing a stray string from outside TS will get undefined here and hit this throw, so it's not dead defensively — but the docstring says "Throws if the code is not recognized," which TS already prevents.

Fine to leave as a runtime backstop; just flagging that c8 will likely show this as uncovered.

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.

3 participants