Extract browser-safe serialization format and move hydration client-side#1015
Extract browser-safe serialization format and move hydration client-side#1015TooTallNate merged 7 commits intomainfrom
Conversation
🦋 Changeset detectedLatest commit: 7889a91 The changes in this PR will be included in the next version bump. This PR includes changesets to release 15 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 (42 failed)mongodb (1 failed):
turso (41 failed):
Details by Category✅ ▲ Vercel Production
✅ 💻 Local Development
✅ 📦 Local Production
✅ 🐘 Local Postgres
✅ 🪟 Windows
❌ 🌍 Community Worlds
✅ 📋 Other
|
This stack of pull requests is managed by Graphite. Learn more about stacking. |
f9343d5 to
42dad79
Compare
The WorkflowTraceViewer defined DetailPanel as an inline function component, causing React to remount EntityDetailPanel on every parent re-render. The customPanelComponent stored in TraceViewerContext was also only set during initialization and never updated. Fix by: - Replace inline DetailPanel component with a JSX element (no remount) - Add setCustomPanelComponent reducer action and useEffect to update the context when the panel element changes - Remove debug hydration logging Also fix web revivers for non-structurally-cloneable types: - URLSearchParams, Headers, URL are converted to plain equivalents since the trace viewer's web worker uses postMessage which requires structured cloneability
- Fix reviver spread order in CLI and web-shared hydration: spread observabilityRevivers first so environment-specific overrides for Instance/Class take precedence - Remove @workflow/core/observability module entirely (no consumers) - Delete observability.test.ts, port relevant tests to serialization-format.test.ts (isStreamRef, isStreamId, extractStreamIds, truncateId, ClassInstanceRef JSON roundtrip) - Optimize event hydration: find matching event before hydrating instead of hydrating all events - Add comprehensive unit tests for serialization-format.ts (36 tests)
Stream chunks were serialized without the 'devl' format prefix, unlike step inputs/outputs. This meant stream data couldn't go through the same client-side hydration pipeline. Core changes: - getSerializeStream: wrap each chunk with encodeWithFormatPrefix - getDeserializeStream: handle both format-prefixed (v2) and legacy newline-delimited (v1) chunks Web changes: - Stream route: CBOR-encode and length-prefix each chunk so the client can distinguish chunk boundaries - useStreamReader: decode length-prefixed CBOR frames, then hydrate each chunk using hydrateData with web revivers
ca37040 to
7653e6b
Compare
6878be5 to
515d669
Compare
The global parameter was never passed by any callsite — all three usages call with no arguments. Use the global constructors directly.
515d669 to
7889a91
Compare
📊 Benchmark Results
workflow with no steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) | Express | Nitro workflow with 1 step💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Next.js (Turbopack) | Express workflow with 10 sequential steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) | Express | Nitro 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 | Next.js (Turbopack) | Express Promise.all with 10 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Nitro | Next.js (Turbopack) Promise.all with 25 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Express | Next.js (Turbopack) Promise.all with 50 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Express | Next.js (Turbopack) Promise.race with 10 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) | Express | Nitro Promise.race with 25 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Nitro | Next.js (Turbopack) Promise.race with 50 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Next.js (Turbopack) | Express Stream Benchmarks (includes TTFB metrics)workflow with stream💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Express | Next.js (Turbopack) SummaryFastest Framework by WorldWinner determined by most benchmark wins
Fastest World by FrameworkWinner determined by most benchmark wins
Column Definitions
Worlds:
|
Merge activity
|

Summary
Split the serialization/deserialization logic into environment-specific layers so data hydration can happen client-side in the browser. This is a prerequisite for e2e encryption where decryption keys are only available in the browser.
Architecture
Layer 1:
@workflow/core/serialization-format(new, browser-safe)devl, futureencr, etc.)hydrateData()dispatch — handles Uint8Array (v2 binary), legacy arrays (v1), and plain valueshydrateResourceIO(resource, revivers)resource-type dispatcher (step/hook/event/workflow field mapping)ClassInstanceRef(plain data class, nonode:utildependency)StreamRef, type guards (isStreamRef,isStreamId,isClassInstanceRef), utility functions (extractStreamIds,truncateId)observabilityReviversfor stream/class/step display overridesLayer 2: Environment-specific revivers
@workflow/web-shared(lib/hydration.ts) — browser-safe revivers usingatob()for base64, realURLSearchParams/Headers/URLinstances,ClassInstanceReffor UI rendering@workflow/cli(lib/inspect/hydration.ts) — Node.js revivers usingBuffer.from()for base64,CLIClassInstanceRefwithutil.inspect.customfor CLI outputEach module exports a pre-bound
hydrateResourceIO(resource)that uses its environment's revivers.Removed:
@workflow/core/observabilityobservability.tsandobservability.test.tsentirely (no remaining consumers)"./observability"export from@workflow/core/package.jsonworkflowpackage'sinternal/observability.tsre-exportserialization-format.ts(shared types/utilities) and the environment-specific hydration modulesWeb package changes
Uint8Array)hydrateResourceIOfrom@workflow/web-sharedafter receiving CBOR-decoded datanode:*stubs needed since@workflow/core/serialization-formatis browser-safePackages affected
@workflow/core— newserialization-formatexport (with tests), removedobservabilityexport@workflow/web-shared— newlib/hydration.tswith browser-safe revivers@workflow/cli— newlib/inspect/hydration.tswith Node.js revivers, updatedoutput.tsimport@workflow/web— client-side hydration, removed server-side hydrationworkflow— removedinternal/observability.tsre-export