Conversation
🦋 Changeset detectedLatest commit: 6d07e58 The changes in this PR will be included in the next version bump. This PR includes changesets to release 14 packages
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 |
🧪 E2E Test Results❌ Some tests failed Summary
❌ Failed Tests🌍 Community Worlds (46 failed)mongodb (1 failed):
turso (45 failed):
Details by Category✅ ▲ Vercel Production
✅ 💻 Local Development
✅ 📦 Local Production
✅ 🐘 Local Postgres
✅ 🪟 Windows
❌ 🌍 Community Worlds
✅ 📋 Other
|
📊 Benchmark Results
workflow with no steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Next.js (Turbopack) | Express workflow with 1 step💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Express | Next.js (Turbopack) workflow with 10 sequential steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Nitro | Next.js (Turbopack) workflow with 25 sequential steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Express | Next.js (Turbopack) workflow with 50 sequential steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Express | Next.js (Turbopack) Promise.all with 10 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Express | Next.js (Turbopack) Promise.all with 25 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Next.js (Turbopack) | Express Promise.all with 50 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) | Nitro | Express Promise.race with 10 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Next.js (Turbopack) | Nitro Promise.race with 25 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) | Nitro | Express Promise.race with 50 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Nitro | Next.js (Turbopack) Stream Benchmarks (includes TTFB metrics)workflow with stream💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Nitro | Next.js (Turbopack) SummaryFastest Framework by WorldWinner determined by most benchmark wins
Fastest World by FrameworkWinner determined by most benchmark wins
Column Definitions
Worlds:
|
This stack of pull requests is managed by Graphite. Learn more about stacking. |
pranaygp
left a comment
There was a problem hiding this comment.
Review: PR #978 - Make serialization functions async
Summary: Clean mechanical refactor that makes all 8 dehydrate/hydrate functions async. This is well-structured as a standalone PR since it's a pure no-op refactor -- the function bodies remain synchronous, only signatures and call sites are updated.
Strengths:
- Good separation of concerns -- this refactor is isolated from the actual encryption logic
- All callers are properly updated with
await/Promise.all - The changeset correctly lists all affected packages
- The
Promise.racetest assertion relaxation is reasonable given async microtask timing changes
Questions/Concerns:
-
Performance consideration: In
step.tsandhook.ts, the callers that were previously synchronous callbacks now use.then()/.catch()patterns. This is the right approach since you can't useawaitin the resolve callback of a Promise constructor, but worth noting that this adds microtask overhead on every step completion during replay. For workflows with hundreds of steps, this could add up. Not a blocker, just something to be aware of. -
Changeset scope: The changeset includes
@workflow/world-testingbut I don't see material changes to that package's public API in this diff -- it's just updating test utilities. Consider whether this really warrants a published changeset entry for that package, or if it's just an internal change.
Overall this is a clean, well-scoped PR. Looks good to land.
There was a problem hiding this comment.
Pull request overview
Refactors the core serialization “dehydrate/hydrate” API surface to be async, returning Promise<...>, and updates runtime/CLI/web/test call sites to await (or use Promise.all / .then() where needed). This enables future async-only transforms (e.g., encrypt/decrypt) without another broad signature change.
Changes:
- Make 8 core serialization functions in
packages/core/src/serialization.tsasync and update their return types toPromise<...>. - Update callers across runtime, observability, CLI, and web server actions to
awaithydration/dehydration (including batching withPromise.all). - Update tests for async serialization/hydration and relax a
Promise.raceordering assertion.
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/core/src/serialization.ts | Converts core (de)serialization APIs to async/Promise return types. |
| packages/core/src/workflow.ts | Awaits workflow argument hydration and return value dehydration. |
| packages/core/src/step.ts | Adjusts step result hydration to async .then/.catch resolution. |
| packages/core/src/workflow/hook.ts | Adjusts hook payload hydration to async .then/.catch resolution. |
| packages/core/src/runtime/start.ts | Awaits workflow argument dehydration before emitting run_created. |
| packages/core/src/runtime/run.ts | Awaits workflow return value hydration for completed runs. |
| packages/core/src/runtime/runs.ts | Awaits workflow argument hydration when recreating runs. |
| packages/core/src/runtime/step-handler.ts | Awaits step argument hydration and step return value dehydration. |
| packages/core/src/runtime/suspension-handler.ts | Awaits dehydration when building hook/step events; uses Promise.all for hook events. |
| packages/core/src/runtime/resume-hook.ts | Awaits hook metadata hydration and payload dehydration. |
| packages/core/src/observability.ts | Makes hydrateResourceIO and helpers async; awaits internal hydration paths. |
| packages/cli/src/lib/inspect/output.ts | Awaits hydrateResourceIO and batches hydration with Promise.all. |
| packages/web/src/server/workflow-server-actions.ts | Makes hydration helper async and batches list hydration via Promise.all. |
| workbench/nextjs-webpack/pages/api/trigger-pages.ts | Awaits workflow argument hydration from request body. |
| workbench/nextjs-turbopack/pages/api/trigger-pages.ts | Awaits workflow argument hydration from request body. |
| packages/core/src/serialization.test.ts | Updates serialization tests to async/await and promise rejection assertions. |
| packages/core/src/workflow.test.ts | Updates workflow tests for async serde and relaxes Promise.race ordering assertion. |
| packages/core/src/step.test.ts | Updates step tests for async dehydration. |
| packages/core/src/workflow/hook.test.ts | Updates hook tests for async dehydration. |
| packages/core/src/observability.test.ts | Updates observability tests for async hydration/dehydration. |
| .changeset/async-serde.md | Publishes a patch changeset documenting async serialization refactor. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
814d64c to
702c96d
Compare
pranaygp
left a comment
There was a problem hiding this comment.
Overall this is a clean, well-scoped mechanical refactor. The previous review feedback has been properly addressed (macrotask timing in step.ts, numeric sort comparator, runId in test calls, changeset simplification). Two observations below.
| .then((payload) => { | ||
| next.resolve(payload); | ||
| }) | ||
| .catch((error) => { |
There was a problem hiding this comment.
Unlike step.ts where the Copilot reviewer flagged the macrotask-to-microtask timing change and setTimeout was preserved inside .then()/.catch(), this hook hydration path didn't have setTimeout originally (it was a synchronous next.resolve(payload)), so the .then() pattern is technically adding a microtask delay that wasn't there before.
In practice this should be fine since hook payloads are resolved asynchronously anyway, but wanted to flag the asymmetry: step.ts preserves timing with setTimeout inside .then(), while hook.ts does not. If you want full consistency you could add setTimeout here too, but I don't think it's necessary.
packages/core/src/workflow.test.ts
Outdated
| const raceResults = await hydrateWorkflowReturnValue( | ||
| result as any, | ||
| ops, | ||
| 'wrun_test' |
There was a problem hiding this comment.
The relaxation from expect(...).toEqual([4, 3, 2, 1, 0]) to a set-equality check is the most important behavioral change in this PR. This confirms that the async refactor does change observable Promise resolution ordering during replay (previously deterministic reverse-event-order, now non-deterministic due to microtask scheduling).
Worth confirming this is acceptable for production workflows that use Promise.race — if user code depends on the specific winner of a race during replay, does the non-deterministic ordering matter? I believe it's fine since Promise.race semantics only guarantee the first resolved value, not the order of all resolutions, but this is the one spot where "pure mechanical refactor, no functional changes" isn't quite accurate.
There was a problem hiding this comment.
Wow nice catch. Reverted that change.

Summary
@workflow/coreasync (returningPromise<...>)awaitthe resultsPart 1 of the end-to-end encryption PR stack.