Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Add support for custom class instance serialization#762

Merged
TooTallNate merged 24 commits intomainfrom
01-09-add_support_for_custom_class_instance_serialization
Jan 19, 2026
Merged

Add support for custom class instance serialization#762
TooTallNate merged 24 commits intomainfrom
01-09-add_support_for_custom_class_instance_serialization

Conversation

@TooTallNate
Copy link
Member

@TooTallNate TooTallNate commented Jan 10, 2026

Added support for custom class instance serialization across workflow/step boundaries.

What changed?

  • Introduced a new @workflow/serde package with WORKFLOW_SERIALIZE and WORKFLOW_DESERIALIZE symbols
  • Enhanced the serialization system to handle custom class instances using these symbols
  • Updated the SWC plugin to detect classes with serialization methods and register them
  • Added class registry mechanism that works in both step and workflow contexts
  • Implemented comprehensive tests for various serialization scenarios

How to test?

The PR includes a new e2e test customSerializationWorkflow that demonstrates the feature:

import { WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE } from '@workflow/serde';

// Define a class with custom serialization
class Point {
  constructor(public x: number, public y: number) {}

  static [WORKFLOW_SERIALIZE](instance: Point) {
    return { x: instance.x, y: instance.y };
  }

  static [WORKFLOW_DESERIALIZE](data: { x: number; y: number }) {
    return new Point(data.x, data.y);
  }
}

// Use in workflow and steps
export async function customSerializationWorkflow(x: number, y: number) {
  'use workflow';
  const point = new Point(x, y);
  const scaled = await transformPoint(point, 2);
  // ...
}

Run the e2e test to verify that class instances are properly serialized and deserialized.

Why make this change?

Previously, user-defined class instances couldn't be passed between workflows and steps without losing their prototype chain and methods. This change allows developers to define custom serialization/deserialization logic for their classes, enabling proper reconstruction of instances with their full functionality intact when crossing workflow/step boundaries.

@changeset-bot
Copy link

changeset-bot bot commented Jan 10, 2026

🦋 Changeset detected

Latest commit: fdbfb5e

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

This PR includes changesets to release 15 packages
Name Type
@workflow/swc-plugin Patch
@workflow/core Patch
@workflow/astro Patch
@workflow/builders Patch
@workflow/cli Patch
@workflow/next Patch
@workflow/nitro Patch
@workflow/rollup Patch
@workflow/sveltekit Patch
@workflow/docs-typecheck Patch
@workflow/web-shared Patch
workflow Patch
@workflow/world-testing Patch
@workflow/nuxt Patch
@workflow/ai Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link
Contributor

vercel bot commented Jan 10, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
example-nextjs-workflow-turbopack Ready Ready Preview, Comment Jan 19, 2026 11:21pm
example-nextjs-workflow-webpack Ready Ready Preview, Comment Jan 19, 2026 11:21pm
example-workflow Ready Ready Preview, Comment Jan 19, 2026 11:21pm
workbench-astro-workflow Ready Ready Preview, Comment Jan 19, 2026 11:21pm
workbench-express-workflow Ready Ready Preview, Comment Jan 19, 2026 11:21pm
workbench-fastify-workflow Ready Ready Preview, Comment Jan 19, 2026 11:21pm
workbench-hono-workflow Ready Ready Preview, Comment Jan 19, 2026 11:21pm
workbench-nitro-workflow Ready Ready Preview, Comment Jan 19, 2026 11:21pm
workbench-nuxt-workflow Ready Ready Preview, Comment Jan 19, 2026 11:21pm
workbench-sveltekit-workflow Ready Ready Preview, Comment Jan 19, 2026 11:21pm
workbench-vite-workflow Ready Ready Preview, Comment Jan 19, 2026 11:21pm
workflow-docs Ready Ready Preview, Comment Jan 19, 2026 11:21pm

@github-actions
Copy link
Contributor

github-actions bot commented Jan 10, 2026

🧪 E2E Test Results

Some tests failed

Summary

Passed Failed Skipped Total
✅ ▲ Vercel Production 435 0 38 473
✅ 💻 Local Development 398 0 32 430
✅ 📦 Local Production 398 0 32 430
✅ 🐘 Local Postgres 398 0 32 430
✅ 🪟 Windows 43 0 0 43
❌ 🌍 Community Worlds 162 22 0 184
Total 1834 22 134 1990

❌ Failed Tests

🌍 Community Worlds (22 failed)

mongodb (1 failed):

  • webhookWorkflow

redis (1 failed):

  • webhookWorkflow

starter (19 failed):

  • addTenWorkflow
  • addTenWorkflow
  • 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 catchability FatalError can be caught and detected with FatalError.is()
  • hookCleanupTestWorkflow - hook token reuse after workflow completion
  • stepFunctionPassingWorkflow - step function references can be passed as arguments (without closure vars)
  • stepFunctionWithClosureWorkflow - step function with closure variables passed as argument
  • spawnWorkflowFromStepWorkflow - spawning a child workflow using start() inside a step
  • 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

turso (1 failed):

  • webhookWorkflow

Details by Category

✅ ▲ Vercel Production
App Passed Failed Skipped
✅ astro 39 0 4
✅ example 39 0 4
✅ express 39 0 4
✅ fastify 39 0 4
✅ hono 39 0 4
✅ nextjs-turbopack 42 0 1
✅ nextjs-webpack 42 0 1
✅ nitro 39 0 4
✅ nuxt 39 0 4
✅ sveltekit 39 0 4
✅ vite 39 0 4
✅ 💻 Local Development
App Passed Failed Skipped
✅ astro-stable 39 0 4
✅ express-stable 39 0 4
✅ fastify-stable 39 0 4
✅ hono-stable 39 0 4
✅ nextjs-turbopack-stable 43 0 0
✅ nextjs-webpack-stable 43 0 0
✅ nitro-stable 39 0 4
✅ nuxt-stable 39 0 4
✅ sveltekit-stable 39 0 4
✅ vite-stable 39 0 4
✅ 📦 Local Production
App Passed Failed Skipped
✅ astro-stable 39 0 4
✅ express-stable 39 0 4
✅ fastify-stable 39 0 4
✅ hono-stable 39 0 4
✅ nextjs-turbopack-stable 43 0 0
✅ nextjs-webpack-stable 43 0 0
✅ nitro-stable 39 0 4
✅ nuxt-stable 39 0 4
✅ sveltekit-stable 39 0 4
✅ vite-stable 39 0 4
✅ 🐘 Local Postgres
App Passed Failed Skipped
✅ astro-stable 39 0 4
✅ express-stable 39 0 4
✅ fastify-stable 39 0 4
✅ hono-stable 39 0 4
✅ nextjs-turbopack-stable 43 0 0
✅ nextjs-webpack-stable 43 0 0
✅ nitro-stable 39 0 4
✅ nuxt-stable 39 0 4
✅ sveltekit-stable 39 0 4
✅ vite-stable 39 0 4
✅ 🪟 Windows
App Passed Failed Skipped
✅ nextjs-turbopack 43 0 0
❌ 🌍 Community Worlds
App Passed Failed Skipped
✅ mongodb-dev 3 0 0
❌ mongodb 42 1 0
✅ redis-dev 3 0 0
❌ redis 42 1 0
✅ starter-dev 3 0 0
❌ starter 24 19 0
✅ turso-dev 3 0 0
❌ turso 42 1 0

📋 View full workflow run

Copy link
Member Author

TooTallNate commented Jan 10, 2026

Copy link
Contributor

@vercel vercel bot left a comment

Choose a reason for hiding this comment

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

Additional Suggestion:

Global class serialization registry lacks unregister/cleanup mechanism, causing potential test isolation issues and silent overwriting of class registrations

Fix on Vercel

- Remove tests for `this` serialization in step functions since the SWC
  plugin forbids `this` usage in steps (ChainableService and
  thisSerializationWorkflow tests disabled with comment)
- Use Symbol.for() directly in 99_e2e.ts instead of importing from
  'workflow' to avoid pulling in server-side dependencies (node:async_hooks)
  when the file is bundled for the client

Both Next.js turbopack and webpack builds now pass successfully.
Use Symbol.for() directly in 99_e2e.ts instead of importing from 'workflow'
to avoid pulling in server-side dependencies (node:async_hooks) when the
file is bundled for the client in the workbench Next.js apps.
This is an internal function only used by SWC-generated code, which imports
it from workflow/internal/class-serialization instead.
The SWC plugin specifically looks for the `Symbol.for('workflow-serialize')`
pattern in computed property names. Using a variable reference like
`[WORKFLOW_SERIALIZE]` doesn't work because the plugin does AST pattern
matching, not runtime evaluation.
… symbols

The SWC plugin now recognizes custom serialization classes using any of these patterns:

1. Direct Symbol.for() in class definition:
   static [Symbol.for('workflow-serialize')](instance) { ... }

2. Imported symbols from 'workflow' package:
   import { WORKFLOW_SERIALIZE } from 'workflow';
   static [WORKFLOW_SERIALIZE](instance) { ... }

3. Local const defined with Symbol.for():
   const MY_SYM = Symbol.for('workflow-serialize');
   static [MY_SYM](instance) { ... }

4. Re-exported symbols from other modules

Added test fixtures for imported and local const patterns.
Sort classes_needing_serialization before iterating to ensure
deterministic output ordering in test fixtures.
The serializer needs the classId property on the class to serialize
instances (e.g., step return values). Previously this was only set
in workflow mode by the SWC plugin, but step mode also needs it for
serializing return values.
…d workflow modes

- Move class registry to globalThis for isomorphic access in both contexts
- Add WORKFLOW_CLASS_REGISTRY symbol to symbols.ts
- Refactor class-serialization.ts to use globalThis-based registry that is
  lazily initialized (works in both step mode and VM workflow mode)
- Update SWC plugin workflow mode to import and call registerSerializationClass()
  instead of manual classId assignment and globalThis[].set() calls
- Add fallback in serialization.ts reviver to check VM's globalThis registry
- Update test fixtures for the new import/call pattern

This simplifies the code by using the same registerSerializationClass() function
in both step and workflow modes, rather than generating different code for each.
@TooTallNate
Copy link
Member Author

Rebased, cleaned up a bit, removed docs for now (moved to a separate PR, which we can merge when we feel comfortable to publicly document). Going to merge this so we can continue to :itg:

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