* Decode typed array stream chunks
* Render decoded stream bytes with raw view
* Render decoded bytes in data inspector
* Use generic byte inspector for streams
* review feedback: narrow stream-display exports, fix tab a11y, add collapseRefs tests
- Remove unused formatStreamChunkForDisplay/sanitizeStreamChunkForDisplay
exports; keep only the formatArrayBufferViewForDisplay path actually used
by DataInspector.
- Replace broken role=tablist/role=tab on the Decoded/Bytes switcher
with aria-pressed toggle-button semantics.
- Export collapseRefs/isBytesDisplay and add regression tests covering
typed-array detection (top-level, nested in object/array/Map/Set,
DataView exclusion).
* Replace eval with JSON.parse in serialization revive helper (#1848)
* Replace eval with JSON.parse in serialization revive helper
devalue.stringify() always produces valid JSON — special values
(undefined, NaN, Infinity, -0) are encoded as negative integer
sentinels. JSON.parse yields the same flattened array form that
unflatten() expects, without the eval anti-pattern (VULN-918).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Drop redundant workflow package from changeset
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* Add e2e test for UTF-8 parseable stream chunks
Emits Uint8Array chunks containing multi-byte UTF-8 (Latin Extended,
CJK, emoji, RTL Arabic) plus a UTF-8 encoded JSON document, and
asserts each chunk round-trips through TextDecoder({ fatal: true }).
Exercises the same decode path the web inspector relies on for
typed-array stream values.
Made-with: Cursor
---------
Co-authored-by: Pranay Prakash <pranay.gp@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Nitro users can declare packages as externals via
`externals: { external: ['fsevents'] }` in `nitro.config.ts`. Previously
the workflow SDK's step/workflow/webhook bundles were built in isolation
and ignored that config, forcing users to either patch the Nitro adapter
or ship a custom builder (as in the 250MB bundle-size workaround gist).
Forward string entries from `nitro.options.externals.external` to the
builder's esbuild `external` option. Only string entries are forwarded
— RegExp and function matchers aren't supported by esbuild's
`external` API, so those are skipped.
* Replace eval with JSON.parse in serialization revive helper
devalue.stringify() always produces valid JSON — special values
(undefined, NaN, Infinity, -0) are encoded as negative integer
sentinels. JSON.parse yields the same flattened array form that
unflatten() expects, without the eval anti-pattern (VULN-918).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Drop redundant workflow package from changeset
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* test: enable step source-map assertions for vite local dev
Vite ^7.3.2 (bumped in #1827) preserves step bundle source maps in
dev mode, so stack traces now contain original file paths. Update
hasStepSourceMaps() so vite returns true in local dev and stays false
only in local prod, fixing the consistently failing 'basic step error'
and 'cross-file step error' e2e tests.
* chore: drop body from empty changeset
* address review: drop redundant vite local-prod guard
The default `!DEV_TEST_CONFIG` fall-through already returns false for
vite local prod, so the vite-specific guard is dead code. Just remove
the vite block entirely now that vite local dev matches the default
'has source maps' behavior.
* CI script improvements
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* address codex feedback: harden remaining workflows
- add allowlist regex in prepare-workbench-path to block path traversal
- move matrix/input values to env vars across e2e-vercel-prod,
benchmarks (local/postgres/vercel), and the reusable community-world
workflows
- validate app-name/world-id/world-package inputs in the reusable
community-world workflows
- pipe getCommunityWorldsMatrix script output through jq -c to prevent
\$GITHUB_OUTPUT injection
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* feat(world-vercel): support WORKFLOW_VERCEL_PROTECTION_BYPASS env var
Allows sending a Vercel Deployment Protection bypass secret via the
`x-vercel-protection-bypass` header on all outbound requests made by
the Vercel world, enabling use against protected deployments (e.g.
previews, or workflow-server once protection is enabled).
* feat(world-vercel): support VERCEL_WORKFLOW_SERVER_URL env var
Replace hard-coded WORKFLOW_SERVER_URL_OVERRIDE constant with a function
that reads from the VERCEL_WORKFLOW_SERVER_URL env var. Allows configuring
the workflow-server URL per-deployment (e.g. workbench Preview envs
pointing to a branch deployment) without editing source.
* fix(world-vercel): preserve inline WORKFLOW_SERVER_URL_OVERRIDE const
Keep the inline const as an empty-string literal so external CI rewrite
tooling continues to work unmodified; the env var is a fallback when the
inline value is empty.
* refactor(world-vercel): rename to VERCEL_WORKFLOW_SERVER_PROTECTION_BYPASS
Align env var naming with VERCEL_WORKFLOW_SERVER_URL.
* ci: expose workflow-server protection bypass env vars to e2e-vercel-prod
Set VERCEL_WORKFLOW_SERVER_URL and VERCEL_WORKFLOW_SERVER_PROTECTION_BYPASS
on PR runs so e2e tests hit the protected workflow-server preview; leave
unset on main so production runs use the public default URL.
* refactor(world-vercel): address PR review comments
- Consolidate bypass header logic in getHeaders() to reuse
getProtectionBypassHeader() instead of duplicating env lookup.
- Use consistent 'Authorization' casing in direct fetch() calls.
- Add unit tests for getProtectionBypassHeader, getHttpUrl, and getHeaders
covering env var toggling and proxy/override combinations.
* feat: allow start() to be called directly inside workflow functions
Add 'use step' to start() so it can be called directly from workflow
code. The SWC compiler strips the function body in workflow mode and
replaces it with a step proxy. When called from a workflow:
1. The workflow function reference is serialized via WorkflowFunction
reducer (serializes { workflowId })
2. start() executes in the step context with full Node.js access
3. The returned Run is serialized via WORKFLOW_SERIALIZE and deserialized
back in the workflow VM
4. Run getters (.status, .returnValue, etc.) are 'use step' getters
that each execute as separate steps
Also re-exports start from @workflow/core/runtime/start in api-workflow.ts
instead of using a throwing stub, adds e2e tests for startFromWorkflow
(with hook communication) and fibonacciWorkflow (recursive composition).
* fix(next): don't copy package step files in deferred builder to avoid duplicate classes
Files belonging to packages (detected by walking up to find a
package.json with a name field) are imported via relative path
instead of being copied to __workflow_step_files__/. Copying creates
a second module instance which breaks JS native private field (#)
brand checks when the runtime creates instances from one copy and
the step handler accesses fields from the other.
* fix(next): only skip copying package files that are serde classes, not all package step files
Regular package step files (like fetch) must still be copied to ensure
the SWC loader registers them. Only serde class files from packages are
excluded from copying since those define classes with JS native private
fields (#) that break when duplicated.
* fix(next): generate thin wrappers for package serde step files instead of full copies
For package files that define serde classes (like Run), generate a thin
wrapper that imports the original class and registers steps/classes from
the manifest. This avoids duplicating the class definition (which breaks
JS native private field brand checks) while still registering all step
functions and the class in the serialization registry.
Regular package step files (like fetch) are still copied as before.
* fix(next): use forceStepModeFiles to transform package serde files in step mode
Instead of copying package serde+step files (which creates duplicate
classes with #private brand check issues) or generating fragile wrappers,
add the original file paths to a shared forceStepModeFiles set. The
loader checks this set and transforms those files in step mode directly,
so the SWC plugin generates proper step registrations on the original
class — no duplication, no reimplemented registration logic.
* fix(next): use step mode for all files with step/serde patterns, not just copies
The loader now selects step mode for any file that has 'use step'
directives or serde patterns, regardless of whether it's a deferred
step copy. Step mode is a superset of client mode — the only addition
is step registry IIFEs, which are harmless for non-step consumers.
This means package serde+step files (like Run) no longer need to be
copied to get step registrations. They're imported directly in the
step route and the loader transforms the original file in step mode.
One class instance, no duplication, no wrapper generation.
---------
Co-authored-by: Nathan Rajlich <n@n8.io>
* refactor(swc-plugin): remove client transform mode, merge into step mode
Remove the `client` transform mode from the SWC compiler plugin. The
`client` and `step` modes were nearly identical — both preserved step
function bodies, replaced workflow bodies with throw stubs, and emitted
the same JSON manifest. Step mode now absorbs all client-mode behaviors:
- Dead code elimination (previously only workflow + client)
- Hoisted variable references for object property steps
- All integrations use mode: 'step' instead of 'client'
BREAKING CHANGE: The `client` value for the SWC plugin `mode` option is
no longer accepted. Use `step` instead.
* fix(nitro): force-inline workflow packages in dev mode for serde classId registration
In dev mode, Nitro's Rollup externalizes npm packages like @workflow/core,
so the SWC transform plugin never processes files like run.js. This means
serde classes (e.g. Run) never get the classId registration IIFE, causing
serialization failures when step functions return Run instances.
Uses a Rollup resolveId hook to force workflow SDK packages to be bundled
(non-external) while leaving all other dependencies external. This is more
targeted than noExternals=true which bundles everything and causes TDZ
errors from circular imports in packages like vue-bundle-renderer/h3.
The Nitro module now also ignores .nitro/workflow/** in watchOptions so
writing generated workflow bundles does not retrigger Nitro's own dev
bundle rebuild loop.
Also wraps dev:reload workflow rebuilds and makes LocalBuilder.build()
atomic (writes to temp files, renames on success) to avoid partial output
state during HMR.
For Nuxt, also configures Vite's ssr.noExternal to bundle workflow
packages in the SSR context.
* fix(nitro,nuxt): address review feedback on dev-mode classId fix
- nitro builders: use crypto.randomUUID() for temp file suffix instead of
Date.now() to avoid collisions under rapid/concurrent build() calls,
and serialize concurrent build() calls through an internal queue so
two overlapping dev rebuilds cannot clobber each other's temp outputs.
- nitro index: use fileURLToPath() to convert file:// URLs to filesystem
paths, which correctly handles Windows paths (file:///C:/... -> C:\...)
and percent-decoding, instead of relying on new URL(...).pathname.
- nuxt module: normalize vite.ssr.noExternal to an array (preserving any
existing string/RegExp/array entry) before appending workflow package
matchers, so the force-bundle behavior is not a no-op when noExternal
is already set to a non-array value.
* fix(swc-plugin): preserve step function names in stack traces after bundler minification
Add Object.defineProperty(fn, 'name', { value, configurable: true }) to the
IIFE step registration so the original function name survives bundler name
mangling (e.g. Turbopack renaming errorStepFn to eD). V8 uses the .name
property for stack traces, so this ensures 'at errorStepFn (...)' appears
instead of 'at eD (...)'.
* Apply suggestion from @Copilot
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Nathan Rajlich <n@n8.io>
---------
Signed-off-by: Nathan Rajlich <n@n8.io>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Enable TypeScript inlineSources in the shared package tsconfig so published @workflow packages ship self-contained JavaScript sourcemaps. This fixes Vite and Vitest warnings in downstream repos when sourcemaps point at ../src/*.ts paths that are not published.
* [docs] Rename workflowdevkit references to workflowsdk
* [docs] Rename useworkflow.dev to workflow-sdk.dev
* [chore] Add changeset for domain rename
* [docs] Revert sitemap rewrite to useworkflow.dev (crawled-sitemap not yet available for new domain)
* feat: add clickable Run reference rendering in observability UI
When a serialized Run object appears in step input/output data, it is
now rendered as a clickable purple badge showing the runId. Clicking
navigates to the target run's detail page.
Changes:
- serialization-format.ts: Add RunRef type, isRunRef(), serializedRunToRunRef(),
and 'Run' entry in observabilityRevivers
- data-inspector.tsx: Add RunRefInline component (purple badge), RunClickContext,
collapseRefs() to make refs non-expandable in ObjectInspector
- attribute-panel.tsx: Thread onRunClick prop, wrap in RunClickContext.Provider
- entity-detail-panel.tsx: Thread onRunClick prop
- run-trace-view.tsx: Thread onRunClick prop
- workflow-trace-view.tsx: Thread onRunClick prop, reset selected span on run change
- trace-span-construction.ts: Show step name for builtin steps instead of empty string
- hydration.ts: Re-export RunRef types
- run-detail-view.tsx: Add handleRunRefClick that navigates to /run/{targetRunId}
* fix: guard collapseRefs against class instances and memoize result
Only recurse into plain objects (prototype is Object.prototype or null)
to avoid stripping class instances like Date, Error, Map, etc. that
have their own rendering in NodeRenderer. Also memoize the collapsed
result to avoid recomputing on every render.
* fix: detect Run instances in Instance reviver instead of fake Run serde key
The Run class goes through the standard Instance serialization pipeline
(WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE), not a dedicated 'Run' key.
Move the RunRef detection into serializedInstanceToRef() which checks
if the className is 'Run' and the data contains a runId string, then
returns a RunRef instead of a generic ClassInstanceRef.
* fix: use serializedInstanceToRef in web and CLI Instance revivers
Both the web and CLI hydration layers override the observabilityRevivers
Instance handler with their own implementation (for react-inspector
named constructors and CLI inspect.custom respectively), bypassing the
RunRef detection in serializedInstanceToRef. Fix by calling
serializedInstanceToRef first and returning a RunRef when detected.
@workflow/web-shared is a devDependency of @workflow/web because it is
bundled into web's build output. This means changesets does not bump
web's version when only web-shared has changes, causing version drift.
Adding both packages to a fixed group ensures they always receive the
same version bump. See https://github.com/changesets/changesets/issues/944
* Make encrypted markers clickable to trigger decryption and detect encryption at run level before span selection
* Make encrypted markers clickable to trigger decryption and detect encryption at run level before span selection
* Make encrypted markers clickable to trigger decryption and detect encryption at run level before span selection
* Make encrypted markers clickable to trigger decryption and detect encryption at run level before span selection
* Make encrypted markers clickable to trigger decryption and detect encryption at run level before span selection
* move swc-playground-wasm into workbench/swc-playground
Move the Rust/WASM source from packages/swc-playground-wasm into
workbench/swc-playground/wasm/ so it is no longer built as part of
the packages/* turbo filter in CI. The WASM build now runs as part
of the playground's own prebuild/dev scripts, avoiding Rust toolchain
failures in unrelated CI jobs.
* fix: ensure default rustup toolchain in wasm build
The Vercel build environment has rustup installed but no default
toolchain configured. Add a check for this and install stable
if needed. Also check for VERCEL env var in addition to CI.
* fix: ensure cargo bin dir is in PATH on Vercel
After cargo install wasm-pack, the binary lives in $CARGO_HOME/bin
which may not be in PATH on the Vercel build environment. Always
add the cargo bin directory to PATH after ensuring the toolchain.
Move `workflow/runtime` resolution from `createStepsBundle` into
`discoverEntries` so callers pass the original `inputFiles` array
reference. This restores the WeakMap cache hit when
`createWorkflowsBundle` and `createStepsBundle` are called with
the same inputFiles, avoiding a redundant second esbuild discovery
pass per build that was causing E2E Local Dev Tests to flake on CI.
* fix(next): resolve next/package.json from working directory first
In npm workspaces monorepos, `@workflow/next` can be hoisted to the root
`node_modules/` while `next` stays in a workspace's local `node_modules/`.
The eager `require('next/package.json')` on the fallback path resolved
relative to `@workflow/next`'s own location, failing before the correct
working-directory-relative resolution could run.
Restructure `resolveNextVersion()` to try the working directory path first,
fall back to the package-relative path second, and wrap both in separate
try/catch blocks so neither can crash the process.
Closes#1680
* fix(next): capture resolution errors and include workingDir in error message
Address review feedback: capture caught errors from both resolution
attempts and attach them via `cause` on the final thrown error. Include
the working directory in the error message to aid debugging in monorepo
and CI environments.
* feat(core): add serialization support for workflow function references
Add a WorkflowFunction reducer/reviver pair so that workflow function
references (functions with a .workflowId property, as produced by the
SWC compiler) can cross the step serialization boundary.
Reducer: detects functions with a .workflowId string property, serializes
as { workflowId }. Only matches functions (not plain objects) to prevent
infinite recursion since the reduced form is a plain object.
Reviver: deserializes as a plain { workflowId } metadata object, which
start() already accepts via its WorkflowFunction | WorkflowMetadata
parameter type.
Also adds an observability reviver that renders as '<workflow:workflowId>'.
* fix: address review feedback - fix error assertion, add o11y reviver test
* fix(world-vercel): include runId in streams.get() request URL
streams.get() was ignoring the runId parameter and hardcoding undefined,
causing requests to hit /api/v2/stream/:streamId instead of the correct
/api/v2/runs/:runId/stream/:streamId. Also removes the fallback path in
getStreamUrl() that allowed omitting runId.
* test(world-vercel): add regression test for streams.get() runId in URL
The correlationId was generated as hc_${generateId()}, but
generateHealthCheckRunId() already adds wrun_hc_, producing
wrun_hc_hc_... Similarly, getHealthCheckStreamName() adds
__health_check__, making the hc_ prefix in correlationId redundant.
* feat: serialize Run via custom class serialization with "use step"
Single Run class implementation:
- Add WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE to core Run class
- Mark all runtime-dependent methods/getters with "use step" so SWC
strips their bodies from the workflow bundle
- Remove duplicate Run stub from api-workflow.ts; re-export core Run
- SWC auto-registers the class (no manual registerSerializationClass)
- Add e2e test for Run serialization across workflow/step boundaries
- Add unit tests for serde roundtrip
* chore: bump changeset to minor (new feature)
* fix: include resilientStart in Run serde payload and add readable getter comment
* .
* fix: remove eager getWorld() from constructor, rely on lazy getter
* fix(builders): improve step bundle discovery and externalization for SDK serde classes
- Broaden importParents tracking to all imports (not just file extensions)
so parentHasChild() works through bare specifier imports
- Include workflow/runtime in discovery inputs so SDK serde classes like
Run are always discovered
- Bundle node_modules deps instead of externalizing with broken relative
paths in CJS shims
* fix(builders): address review feedback
- Preserve inputFiles array reference for discoverEntries() WeakMap
caching when no extra entries are needed
- Add test verifying importParents/parentHasChild works through bare
specifier imports (entry -> bare-pkg -> serde file)
* feat(swc): dead code eliminate unreferenced private class members in workflow mode
After stripping 'use step' methods from a class body in workflow mode,
eliminate private members (both JS native #field/#method and TypeScript
private field/private method) that are no longer referenced by any
remaining public member.
The algorithm is iterative: references are seeded from public members,
then expanded through surviving private members until a fixed point,
enabling cascading elimination (e.g. a private field only referenced by
a private method that is itself unreferenced).
* test(swc): add fixture for JS native private member DCE (#field, #method)
* fix(swc): address review feedback on private member DCE
- Namespace JS native private names with # prefix to avoid collisions
with TS private members of the same name
- Track TS private member accesses on non-this receivers (e.g. a.x in
static methods) by maintaining a set of known TS-private names
- Use visit_children_with for full traversal including computed member
expressions
- Extract retain logic into shared retain_referenced_private_members()
helper used by both visit_mut_class_decl and visit_mut_class_expr
* fix(next): resolve bare specifiers in copied step files for lazy discovery
When the deferred builder copies step files to __workflow_step_files__/,
bare specifiers that are transitive SDK deps can't resolve from the app
directory. Use enhanced-resolve with ESM conditions (preferring 'import'
over 'require') to resolve from the original source location, only when
the specifier can't be resolved from the app directory.
Also add enhanced-resolve to the pnpm catalog and use catalog: in both
@workflow/builders and @workflow/next.
* fix(next): address review feedback on lazy discovery resolver
- Cache ESM/CJS resolvers as class fields instead of re-creating per call
- Remove redundant try/catch (resolveBareCopiedStepSpecifier already
returns undefined on failure)
- Use shared NODE_RESOLVE_OPTIONS / NODE_ESM_RESOLVE_OPTIONS matching
the configuration in swc-esbuild-plugin.ts for consistent resolution
* Followup fixes for sync step function support
Address review feedback from #1633 (merged):
- Remove dangling incomplete comment from validate_async_function removal
- Restore export validation for file-level 'use step' files: allow
sync/async function exports, reject non-function exports (constants,
classes, re-exports) which can pull Node-only code into bundles
- Fix InvalidExport error message: 'Only functions can be exported'
for step files vs 'Only async functions can be exported' for workflow
- Update spec.md error table and supported function forms to document
sync step support
- Add sync-step-class test fixture (sync static methods, sync function
expressions with var/let)
- Add sync-workflow error test (sync workflow still errors)
* Tighten step file export validation, fix spec completeness
- Reject uninitialized var exports (export let x) in step files
- Reject local named exports (export { value }) in step files since
we cannot statically verify the binding is a function
- Add test coverage for both new rejection cases
- Add sync variants for let/var arrow functions in spec.md
* Add test fixtures for remaining export validation edge cases
- Add re-export with specifiers to invalid-exports (export { x } from)
- Add error fixture for default class export in step files
- Add fixture for sync default function export (should pass)
- Total: 220 tests (165 fixture + 33 error + 22 unit)
* Remove isWorkflowSdkFile serde exclusion
The SWC detect mode's AST-level manifest (hasManifestEntries) already
correctly filters files without serde class definitions. The broad
isWorkflowSdkFile path exclusion is redundant and was preventing
class definitions in SDK packages from being discovered.
Removed from: builders, next, rollup
* Address review feedback: major semver bump, fix doc comment, add tests
- Bump changeset to major (removing exported APIs is breaking)
- Fix misleading doc comment referencing 'detect mode' in shouldTransformFile
- Add unit tests for shouldTransformFile covering all code paths
* Add AST-level serde filtering to Next.js deferred builder
The isWorkflowSdkFile path-based exclusion was removed from all paths,
but the Next.js deferred builder (collectTransitiveSerdeFiles) was still
using regexp-only detection, causing SDK internal files to be bundled
into the workflow sandbox and triggering stack overflows.
Fix: add applySwcTransform('detect', ...) verification at the end of
collectTransitiveSerdeFiles(). Regex-matched serde candidates are now
verified via the SWC plugin's AST-level manifest to confirm they
actually define serde classes before inclusion in the bundle.
* Add features.encryption to WorkflowMetadata
Expose a `features` object on `getWorkflowMetadata()` so library
authors can detect at runtime whether encryption is enabled for the
current workflow run, allowing them to conditionally handle sensitive
data serialization.
* Improve docs example: show workflow-level encryption check
Step inputs are serialized before the step body runs, so the encryption
check should happen in the workflow function before passing data to
steps.
* Fix docs example: show encryption check on step return value
Step return values are serialized to the event log after the step body
runs, so this is where the check is actually useful - controlling what
data gets persisted as output.
* Fix docs typecheck: declare external function in code sample
* minor
Lift the async function restriction from "use step" in both the SWC
compiler plugin and the TypeScript language service plugin. This enables
using "use step" as a mechanism to strip Node.js-dependent code from
the workflow VM bundle without requiring the function to be async.
The async restriction is preserved for "use workflow" functions.
- SWC plugin: removed async guards from all step function code paths,
updated should_transform_function, updated InvalidExport validation
- TypeScript plugin: removed error 9002 for sync step functions
- Added sync-step fixture test, cleaned up error test fixtures
- Updated spec.md error documentation