Skip to content

Add first-class serialization for built-in Error subclasses#1511

Merged
TooTallNate merged 3 commits into
mainfrom
nrajlich/error-subclass-serialization
May 1, 2026
Merged

Add first-class serialization for built-in Error subclasses#1511
TooTallNate merged 3 commits into
mainfrom
nrajlich/error-subclass-serialization

Conversation

@TooTallNate
Copy link
Copy Markdown
Member

Summary

  • Replace the single generic Error reducer/reviver in the devalue serialization pipeline with specific handlers for each built-in JavaScript Error subclass: TypeError, RangeError, SyntaxError, URIError, ReferenceError, EvalError, and AggregateError
  • Preserve the cause property on all Error types (including nested cause chains)
  • Add Error to the Serializable type union
  • Add 20 new tests covering round-trips for all subclasses, cause chains, AggregateError.errors, cross-VM boundaries, and serialization key verification

Context

This is PR 1 of 5 in a series to improve error handling in Workflow DevKit. The full series:

  1. This PR — First-class serde for built-in Error subclasses in the devalue pipeline
  2. DOMException serde support
  3. WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE for FatalError and RetryableError
  4. Serialize thrown step errors through the devalue pipeline (event log format change)
  5. Serialize thrown workflow errors through the devalue pipeline

What changed

Previously, all Error instances were serialized to { name, message, stack } and deserialized back as a plain Error — losing the original class identity. Now:

  • new TypeError("bad arg") round-trips as a TypeError
  • new AggregateError([...], "msg") preserves its errors array
  • new Error("x", { cause: new TypeError("y") }) preserves the full cause chain
  • Unrecognized Error subclasses (without WORKFLOW_SERIALIZE) still fall through to the base Error reducer, preserving name — same as before
  • Instance/Class reducers remain first, so custom error subclasses with WORKFLOW_SERIALIZE still take priority

Cross-VM safety is maintained: types.isNativeError() gates all Error reducers, and value.constructor?.name distinguishes subclasses (works across VM boundaries where instanceof fails).

Test plan

  • 20 new tests in built-in Error subclass serialization describe block
  • All 513 existing core package tests pass with no regressions
  • Core package builds cleanly with tsc

@TooTallNate TooTallNate requested a review from a team as a code owner March 24, 2026 21:32
Copilot AI review requested due to automatic review settings March 24, 2026 21:32
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 24, 2026

🦋 Changeset detected

Latest commit: 4a02f24

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

This PR includes changesets to release 17 packages
Name Type
@workflow/core Minor
@workflow/builders Patch
@workflow/cli Patch
@workflow/next Patch
@workflow/nitro Patch
@workflow/vitest Patch
@workflow/web-shared Patch
@workflow/web Patch
workflow Minor
@workflow/world-testing Patch
@workflow/astro Patch
@workflow/nest Patch
@workflow/rollup Patch
@workflow/sveltekit Patch
@workflow/vite Patch
@workflow/nuxt Patch
@workflow/ai Major

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 Mar 24, 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 1, 2026 6:09pm
example-nextjs-workflow-webpack Ready Ready Preview, Comment May 1, 2026 6:09pm
example-workflow Ready Ready Preview, Comment May 1, 2026 6:09pm
workbench-astro-workflow Ready Ready Preview, Comment May 1, 2026 6:09pm
workbench-express-workflow Ready Ready Preview, Comment May 1, 2026 6:09pm
workbench-fastify-workflow Ready Ready Preview, Comment May 1, 2026 6:09pm
workbench-hono-workflow Ready Ready Preview, Comment May 1, 2026 6:09pm
workbench-nitro-workflow Ready Ready Preview, Comment May 1, 2026 6:09pm
workbench-nuxt-workflow Ready Ready Preview, Comment May 1, 2026 6:09pm
workbench-sveltekit-workflow Ready Ready Preview, Comment May 1, 2026 6:09pm
workbench-vite-workflow Ready Ready Preview, Comment May 1, 2026 6:09pm
workflow-docs Ready Ready Preview, Comment, Open in v0 May 1, 2026 6:09pm
workflow-swc-playground Ready Ready Preview, Comment May 1, 2026 6:09pm
workflow-web Ready Ready Preview, Comment May 1, 2026 6:09pm

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 24, 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.043s (-1.2%) 1.006s (~) 0.963s 10 1.00x
💻 Local Next.js (Turbopack) 0.049s 1.005s 0.956s 10 1.16x
🐘 Postgres Next.js (Turbopack) 0.055s 1.009s 0.954s 10 1.30x
🐘 Postgres Express 0.062s (+6.7% 🔺) 1.011s (~) 0.949s 10 1.45x
🐘 Postgres Nitro 0.062s (-34.6% 🟢) 1.011s (-3.1%) 0.948s 10 1.46x
💻 Local Express ⚠️ missing - - - -
workflow with 1 step

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Next.js (Turbopack) 1.126s 2.006s 0.880s 10 1.00x
💻 Local Nitro 1.131s (~) 2.006s (~) 0.876s 10 1.00x
🐘 Postgres Express 1.140s (-0.6%) 2.008s (~) 0.868s 10 1.01x
🐘 Postgres Next.js (Turbopack) 1.142s 2.011s 0.869s 10 1.01x
🐘 Postgres Nitro 1.151s (+1.0%) 2.009s (~) 0.858s 10 1.02x
💻 Local Express ⚠️ missing - - - -
workflow with 10 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Next.js (Turbopack) 10.785s 11.025s 0.239s 3 1.00x
🐘 Postgres Next.js (Turbopack) 10.856s 11.017s 0.161s 3 1.01x
🐘 Postgres Express 10.863s (-0.9%) 11.020s (~) 0.157s 3 1.01x
🐘 Postgres Nitro 10.919s (~) 11.028s (~) 0.109s 3 1.01x
💻 Local Nitro 10.954s (~) 11.023s (~) 0.069s 3 1.02x
💻 Local Express ⚠️ missing - - - -
workflow with 25 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 14.513s (~) 15.028s (~) 0.514s 4 1.00x
🐘 Postgres Nitro 14.527s (~) 15.025s (~) 0.498s 4 1.00x
🐘 Postgres Next.js (Turbopack) 14.559s 15.024s 0.465s 4 1.00x
💻 Local Next.js (Turbopack) 14.636s 15.029s 0.393s 4 1.01x
💻 Local Nitro 15.016s (~) 15.281s (-4.7%) 0.265s 4 1.03x
💻 Local Express ⚠️ missing - - - -
workflow with 50 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 13.824s 14.164s 0.341s 7 1.00x
🐘 Postgres Express 13.925s (-0.6%) 14.308s (-2.0%) 0.382s 7 1.01x
🐘 Postgres Nitro 13.998s (~) 14.307s (~) 0.309s 7 1.01x
💻 Local Next.js (Turbopack) 16.294s 16.864s 0.570s 6 1.18x
💻 Local Nitro 16.928s (+0.9%) 17.199s (+1.0%) 0.271s 6 1.22x
💻 Local Express ⚠️ missing - - - -
Promise.all with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 1.230s 2.010s 0.780s 15 1.00x
🐘 Postgres Express 1.253s (-0.6%) 2.011s (~) 0.758s 15 1.02x
🐘 Postgres Nitro 1.259s (-1.2%) 2.010s (~) 0.750s 15 1.02x
💻 Local Next.js (Turbopack) 1.510s 2.006s 0.496s 15 1.23x
💻 Local Nitro 1.515s (-7.2% 🟢) 2.006s (-3.3%) 0.492s 15 1.23x
💻 Local Express ⚠️ missing - - - -
Promise.all with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 2.335s (-1.1%) 3.011s (~) 0.675s 10 1.00x
🐘 Postgres Nitro 2.339s (-0.5%) 3.009s (~) 0.670s 10 1.00x
🐘 Postgres Next.js (Turbopack) 2.399s 3.009s 0.611s 10 1.03x
💻 Local Next.js (Turbopack) 2.773s 3.008s 0.235s 10 1.19x
💻 Local Nitro 2.909s (-7.5% 🟢) 3.453s (-11.1% 🟢) 0.545s 9 1.25x
💻 Local Express ⚠️ missing - - - -
Promise.all with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 3.460s (-0.7%) 4.010s (~) 0.549s 8 1.00x
🐘 Postgres Nitro 3.486s (~) 4.011s (~) 0.525s 8 1.01x
🐘 Postgres Next.js (Turbopack) 3.692s 4.010s 0.318s 8 1.07x
💻 Local Next.js (Turbopack) 7.713s 8.272s 0.560s 4 2.23x
💻 Local Nitro 8.540s (+2.3%) 9.026s (~) 0.485s 4 2.47x
💻 Local Express ⚠️ missing - - - -
Promise.race with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 1.221s 2.008s 0.787s 15 1.00x
🐘 Postgres Nitro 1.264s (+0.6%) 2.008s (~) 0.744s 15 1.03x
🐘 Postgres Express 1.264s (+0.6%) 2.008s (~) 0.743s 15 1.04x
💻 Local Next.js (Turbopack) 1.538s 2.007s 0.469s 15 1.26x
💻 Local Nitro 1.562s (-16.3% 🟢) 2.007s (-14.3% 🟢) 0.445s 15 1.28x
💻 Local Express ⚠️ missing - - - -
Promise.race with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 2.338s (~) 3.011s (~) 0.673s 10 1.00x
🐘 Postgres Nitro 2.341s (~) 3.011s (~) 0.670s 10 1.00x
🐘 Postgres Next.js (Turbopack) 2.412s 3.009s 0.598s 10 1.03x
💻 Local Next.js (Turbopack) 2.924s 3.453s 0.529s 9 1.25x
💻 Local Nitro 3.196s (+4.3%) 4.010s (+3.2%) 0.814s 8 1.37x
💻 Local Express ⚠️ missing - - - -
Promise.race with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 3.478s (-0.6%) 4.012s (~) 0.534s 8 1.00x
🐘 Postgres Nitro 3.483s (~) 4.009s (~) 0.526s 8 1.00x
🐘 Postgres Next.js (Turbopack) 3.643s 4.012s 0.368s 8 1.05x
💻 Local Next.js (Turbopack) 8.382s 8.775s 0.394s 4 2.41x
💻 Local Nitro 9.241s (+1.1%) 9.777s (-2.5%) 0.536s 4 2.66x
💻 Local Express ⚠️ missing - - - -
workflow with 10 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 0.750s 1.006s 0.256s 60 1.00x
🐘 Postgres Express 0.814s (-3.0%) 1.041s (+1.8%) 0.227s 58 1.08x
🐘 Postgres Nitro 0.818s (~) 1.023s (+1.7%) 0.205s 59 1.09x
💻 Local Next.js (Turbopack) 0.844s 1.004s 0.160s 60 1.13x
💻 Local Nitro 1.030s (+5.1% 🔺) 1.942s (+77.5% 🔺) 0.911s 31 1.37x
💻 Local Express ⚠️ missing - - - -
workflow with 25 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 1.865s 2.053s 0.187s 44 1.00x
🐘 Postgres Express 1.922s (-2.8%) 2.124s (-5.9% 🟢) 0.202s 43 1.03x
🐘 Postgres Nitro 1.926s (~) 2.101s (~) 0.175s 43 1.03x
💻 Local Next.js (Turbopack) 2.709s 3.008s 0.299s 30 1.45x
💻 Local Nitro 3.118s (+2.7%) 4.010s (+6.7% 🔺) 0.893s 23 1.67x
💻 Local Express ⚠️ missing - - - -
workflow with 50 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 3.810s 4.009s 0.200s 30 1.00x
🐘 Postgres Nitro 3.919s (-4.5%) 4.143s (-10.0% 🟢) 0.224s 30 1.03x
🐘 Postgres Express 4.015s (+0.6%) 4.332s (-0.9%) 0.317s 28 1.05x
💻 Local Next.js (Turbopack) 8.709s 9.018s 0.308s 14 2.29x
💻 Local Nitro 9.380s (+0.9%) 10.019s (~) 0.639s 12 2.46x
💻 Local Express ⚠️ missing - - - -
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.254s 1.007s 0.753s 60 1.00x
🐘 Postgres Express 0.278s (-1.7%) 1.007s (~) 0.729s 60 1.09x
🐘 Postgres Nitro 0.281s (-0.7%) 1.007s (~) 0.726s 60 1.11x
💻 Local Next.js (Turbopack) 0.551s 1.005s 0.453s 60 2.17x
💻 Local Nitro 0.612s (+1.2%) 1.005s (-1.6%) 0.393s 60 2.41x
💻 Local Express ⚠️ missing - - - -
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.479s 1.006s 0.527s 90 1.00x
🐘 Postgres Nitro 0.491s (-1.1%) 1.006s (~) 0.516s 90 1.02x
🐘 Postgres Express 0.494s (-3.0%) 1.007s (~) 0.512s 90 1.03x
💻 Local Nitro 2.489s (-1.9%) 3.010s (~) 0.521s 30 5.19x
💻 Local Next.js (Turbopack) 2.657s 3.009s 0.352s 30 5.54x
💻 Local Express ⚠️ missing - - - -
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.763s 1.006s 0.243s 120 1.00x
🐘 Postgres Express 0.785s (-4.2%) 1.008s (-0.9%) 0.223s 120 1.03x
🐘 Postgres Nitro 0.795s (+0.6%) 1.007s (~) 0.212s 120 1.04x
💻 Local Next.js (Turbopack) 10.715s 11.207s 0.492s 11 14.04x
💻 Local Nitro 11.016s (-1.6%) 11.575s (-0.8%) 0.558s 11 14.44x
💻 Local Express ⚠️ missing - - - -
Stream Benchmarks (includes TTFB metrics)
workflow with stream

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Next.js (Turbopack) 0.170s 1.003s 0.011s 1.018s 0.847s 10 1.00x
🐘 Postgres Nitro 0.195s (-5.0% 🟢) 0.998s (~) 0.002s (+20.0% 🔺) 1.010s (~) 0.815s 10 1.14x
🐘 Postgres Next.js (Turbopack) 0.204s 1.002s 0.002s 1.012s 0.808s 10 1.20x
💻 Local Nitro 0.217s (+1.6%) 1.004s (~) 0.013s (~) 1.019s (~) 0.802s 10 1.27x
🐘 Postgres Express 0.219s (+6.7% 🔺) 0.994s (~) 0.001s (-12.5% 🟢) 1.010s (~) 0.791s 10 1.28x
💻 Local Express ⚠️ missing - - - - -
stream pipeline with 5 transform steps (1MB)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 0.602s 1.009s 0.004s 1.023s 0.421s 59 1.00x
🐘 Postgres Express 0.609s (-3.4%) 1.006s (~) 0.014s (+261.9% 🔺) 1.032s (+0.9%) 0.424s 59 1.01x
🐘 Postgres Nitro 0.612s (-2.0%) 1.005s (~) 0.004s (-7.0% 🟢) 1.022s (~) 0.410s 59 1.02x
💻 Local Nitro 0.844s (+0.6%) 1.011s (~) 0.009s (~) 1.115s (~) 0.271s 54 1.40x
💻 Local Next.js (Turbopack) 0.847s 1.011s 0.010s 1.210s 0.363s 54 1.41x
💻 Local Express ⚠️ missing - - - - -
10 parallel streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 0.930s 1.177s 0.000s 1.184s 0.254s 51 1.00x
🐘 Postgres Nitro 0.970s (~) 1.172s (-6.0% 🟢) 0.000s (-100.0% 🟢) 1.184s (-5.8% 🟢) 0.214s 51 1.04x
🐘 Postgres Express 0.978s (+1.7%) 1.192s (-6.7% 🟢) 0.000s (-54.0% 🟢) 1.219s (-6.7% 🟢) 0.242s 50 1.05x
💻 Local Nitro 1.223s (~) 2.020s (~) 0.000s (+300.0% 🔺) 2.022s (~) 0.799s 30 1.31x
💻 Local Next.js (Turbopack) 1.255s 2.019s 0.000s 2.022s 0.768s 30 1.35x
💻 Local Express ⚠️ missing - - - - -
fan-out fan-in 10 streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.755s (-0.9%) 2.139s (-1.8%) 0.000s (+Infinity% 🔺) 2.169s (-1.4%) 0.414s 28 1.00x
🐘 Postgres Next.js (Turbopack) 1.783s 2.072s 0.000s 2.092s 0.310s 29 1.02x
🐘 Postgres Nitro 1.809s (+1.0%) 2.101s (-1.9%) 0.000s (+93.1% 🔺) 2.113s (-2.8%) 0.304s 29 1.03x
💻 Local Nitro 3.462s (+2.2%) 4.033s (~) 0.001s (+25.0% 🔺) 4.036s (~) 0.574s 15 1.97x
💻 Local Next.js (Turbopack) 3.573s 4.099s 0.000s 4.102s 0.530s 15 2.04x
💻 Local Express ⚠️ missing - - - - -

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World 🥇 Fastest Framework Wins
💻 Local Next.js (Turbopack) 16/21
🐘 Postgres Next.js (Turbopack) 13/21
Fastest World by Framework

Winner determined by most benchmark wins

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


Some benchmark jobs failed:

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

Check the workflow run for details.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 24, 2026

🧪 E2E Test Results

All tests passed

Summary

Passed Failed Skipped Total
✅ ▲ Vercel Production 1000 0 67 1067
✅ 💻 Local Development 1078 0 86 1164
✅ 📦 Local Production 1078 0 86 1164
✅ 🐘 Local Postgres 1078 0 86 1164
✅ 🪟 Windows 97 0 0 97
✅ 📋 Other 273 0 18 291
Total 4604 0 343 4947

Details by Category

✅ ▲ Vercel Production
App Passed Failed Skipped
✅ astro 90 0 7
✅ example 90 0 7
✅ express 90 0 7
✅ fastify 90 0 7
✅ hono 90 0 7
✅ nextjs-turbopack 95 0 2
✅ nextjs-webpack 95 0 2
✅ nitro 90 0 7
✅ nuxt 90 0 7
✅ sveltekit 90 0 7
✅ vite 90 0 7
✅ 💻 Local Development
App Passed Failed Skipped
✅ astro-stable 91 0 6
✅ express-stable 91 0 6
✅ fastify-stable 91 0 6
✅ hono-stable 91 0 6
✅ nextjs-turbopack-canary 78 0 19
✅ nextjs-turbopack-stable 97 0 0
✅ nextjs-webpack-canary 78 0 19
✅ nextjs-webpack-stable 97 0 0
✅ nitro-stable 91 0 6
✅ nuxt-stable 91 0 6
✅ sveltekit-stable 91 0 6
✅ vite-stable 91 0 6
✅ 📦 Local Production
App Passed Failed Skipped
✅ astro-stable 91 0 6
✅ express-stable 91 0 6
✅ fastify-stable 91 0 6
✅ hono-stable 91 0 6
✅ nextjs-turbopack-canary 78 0 19
✅ nextjs-turbopack-stable 97 0 0
✅ nextjs-webpack-canary 78 0 19
✅ nextjs-webpack-stable 97 0 0
✅ nitro-stable 91 0 6
✅ nuxt-stable 91 0 6
✅ sveltekit-stable 91 0 6
✅ vite-stable 91 0 6
✅ 🐘 Local Postgres
App Passed Failed Skipped
✅ astro-stable 91 0 6
✅ express-stable 91 0 6
✅ fastify-stable 91 0 6
✅ hono-stable 91 0 6
✅ nextjs-turbopack-canary 78 0 19
✅ nextjs-turbopack-stable 97 0 0
✅ nextjs-webpack-canary 78 0 19
✅ nextjs-webpack-stable 97 0 0
✅ nitro-stable 91 0 6
✅ nuxt-stable 91 0 6
✅ sveltekit-stable 91 0 6
✅ vite-stable 91 0 6
✅ 🪟 Windows
App Passed Failed Skipped
✅ nextjs-turbopack 97 0 0
✅ 📋 Other
App Passed Failed Skipped
✅ e2e-local-dev-nest-stable 91 0 6
✅ e2e-local-postgres-nest-stable 91 0 6
✅ e2e-local-prod-nest-stable 91 0 6

📋 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 improves @workflow/core’s devalue-based serialization so built-in JavaScript Error subclasses round-trip with their original class identity (and preserves cause chains), which is foundational for the broader error-handling improvements planned in the PR series.

Changes:

  • Add dedicated reducers/revivers for built-in Error subclasses (TypeError, RangeError, SyntaxError, URIError, ReferenceError, EvalError, AggregateError) plus cause preservation.
  • Add Error to the Serializable union and expand SerializableSpecial typing for error payloads.
  • Add a new test suite covering subclass round-trips, cause chains, AggregateError.errors, cross-VM behavior, and serialization key selection.

Reviewed changes

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

File Description
packages/core/src/serialization.ts Introduces subclass-aware Error reducers/revivers and adds cause handling.
packages/core/src/serialization.test.ts Adds tests for built-in Error subclass round-tripping, causes, AggregateError, and cross-VM behavior.
packages/core/src/schemas.ts Extends Serializable to include Error.
.changeset/error-subclass-serialization.md Declares a patch release for the new error subclass serialization behavior.

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

Comment thread packages/core/src/serialization.ts Outdated
Comment thread packages/core/src/serialization.ts Outdated
Comment thread packages/core/src/serialization.test.ts Outdated
Comment thread packages/core/src/serialization.ts Outdated
@TooTallNate
Copy link
Copy Markdown
Member Author

Addressed the review feedback in bfd0c99:

cause serialization/deserialization (comments on lines 826 and 1182): Fixed. Reducers now only include cause when 'cause' in value is true, and revivers only pass the cause option to the constructor when the serialized data has the key. This preserves the distinction between no-cause and cause-is-undefined.

undefined cause test (comment on line 3447): Fixed. Renamed the test and added assertion expect('cause' in hydrated).toBe(false) to verify the hydrated error does not gain an own cause property.

name preservation on subclasses (comment on line 794): Intentionally not changing this. Mutating .name on a built-in Error subclass (e.g., const e = new TypeError('x'); e.name = 'Custom') is an unusual edge case. The subclass reducers prioritize reconstructing the correct prototype chain (instanceof TypeError), which is the more common and useful property to preserve. If someone needs custom names on errors, they should use a custom Error subclass (which goes through the base Error reducer and preserves name, or through the Instance reducer with WORKFLOW_SERIALIZE).

Replace the generic Error reducer/reviver with specific handlers for
TypeError, RangeError, SyntaxError, URIError, ReferenceError, EvalError,
and AggregateError. Preserve the cause property on all Error types,
only serializing it when present to maintain absence vs presence
semantics.
Extract repetitive reducer/reviver logic into makeErrorSubclassReducer
and makeErrorSubclassReviver helpers. Reduces ~120 lines of duplicated
code to ~50 while preserving identical behavior. AggregateError remains
as a wrapping extension since it needs to preserve the errors array.
Adding new serialization support for built-in Error subclasses is a
feature, not a bug fix.
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