Commit Graph

936 Commits

Author SHA1 Message Date
Peter Wielander 56f22219b3 [core] Handle 429 and 500 errors from worlds in runtime (#966) 2026-02-09 14:43:12 -08:00
Vercel Release Bot d25630a27c Version Packages (beta) (#971) 2026-02-09 13:47:40 -08:00
Peter Wielander aa448c29b4 [world-vercel] Allow specifying backend URL via env variable (#985) 2026-02-09 13:38:38 -08:00
Nathan Rajlich d9e98590fa Generate runId client-side in start() and simplify streaming types (#954)
## Summary

- Generates `runId` client-side in `start()` using ULID (`wrun_${ulid()}`), instead of deferring to the server
- Passes client-generated `runId` to `events.create()` and verifies the server accepted it
- Simplifies `runId` types from `string | Promise<string>` to `string` throughout the serialization layer
- Removes the `withResolvers<string>()` / deferred runId pattern that was needed when `runId` was server-generated
- Updates `Streamer` interface and `WorkflowServerWritableStream` to use plain `string` runId
- Updates `Storage.events.create()` first overload to accept `string | null` instead of just `null`

This is a prerequisite for E2E encryption, where we need the `runId` before serialization so it can be used as part of the encryption context.

## Test plan

All existing tests pass. Updated `start.test.ts` to expect client-generated runId, removed `writable-stream.test.ts` promise runId test (no longer applicable).
2026-02-08 00:27:40 -08:00
Nathan Rajlich fcfaf8bbaa Support step function serialization in client mode (#924) 2026-02-07 15:34:29 -08:00
JJ Kasper 2d1d69f4ca Fix getImportPath package handling (#972)
* Fix getImportPath package handling

* Add root entrypoint comments and coverage for package imports
2026-02-07 14:02:07 -08:00
Nathan Rajlich ef23b0be77 Fix step functions nested multiple levels deep in an object (#923)
Fixed a bug where step functions nested multiple levels deep in an object weren't being properly transformed.

### What changed?

- Enhanced the SWC plugin to recursively process nested objects to find step functions
- Added support for step functions defined inside deeply nested object properties, including function call arguments
- Implemented proper path handling for nested step IDs, using compound paths (e.g., `vade/tools/VercelRequest/execute`)
- Fixed variable name generation by replacing slashes with `$` to create valid JavaScript identifiers
- Updated documentation with examples of nested object property step functions
- Added test cases for deeply nested step functions and agent tool steps

### How to test?

Test with code that has step functions nested multiple levels deep in objects:

```javascript
export const config = createConfig({
  level1: {
    level2: {
      level3: {
        myStep: async (input) => {
          "use step";
          return input * 2;
        },
      },
    },
  },
});
```

Or with agent tools:

```javascript
export const vade = agent({
  tools: {
    VercelRequest: {
      execute: async (input, ctx) => {
        "use step";
        return 1 + 1;
      },
    },
  },
});
```

### Why make this change?

This fix ensures that step functions can be properly identified and transformed regardless of how deeply they are nested in object structures. This is particularly important for complex configurations like agent tools where step functions might be several levels deep in the object hierarchy.
2026-02-07 11:14:20 -08:00
Nathan Rajlich 5b5b36a03b [e2e] Remove /api/hook and /api/test-health-check endpoints, call APIs directly
- Remove debug logging from bench.bench.ts
- Remove awaitReturnValue() wrapper, use run.returnValue directly in benchmarks
- Add changeset for Nitro builder manifest fix
- Refactor hookWorkflow tests to call getHookByToken()/resumeHook() directly
  (pass hook object to resumeHook to avoid duplicate lookups)
- Refactor queue-based health check test to call healthCheck() directly
- Assert specific error message for invalid hook token test
- Remove /api/hook and /api/test-health-check endpoints from all workbench apps
  (nextjs-turbopack, nextjs-webpack, vite, hono, express, fastify, sveltekit,
  astro, nuxt, nitro-v2, nitro-v3, nest, example)
2026-02-07 09:00:19 -08:00
Nathan Rajlich 86f62f2779 Refactor e2e tests to no longer use "trigger" endpoint (#958)
## Summary

Refactors the E2E tests to call `start()` from `workflow/api` directly instead of going through the `/api/trigger` HTTP endpoint in each workbench app. This removes a layer of indirection — the tests now use the same API that users would use to start workflows programmatically.

### Before

```ts
const run = await triggerWorkflow('addTenWorkflow', [123]);
const returnValue = await getWorkflowReturnValue(run.runId);
```

- `triggerWorkflow()` sent an HTTP POST to `/api/trigger` on the workbench app
- The workbench app looked up the workflow function, called `start()`, and returned the run ID
- `getWorkflowReturnValue()` polled `GET /api/trigger?runId=...` until the workflow completed

### After

```ts
const run = await start(await e2e('addTenWorkflow'), [123]);
const returnValue = await run.returnValue;
```

- `e2e()` / `getWorkflowMetadata()` fetches the manifest from `/.well-known/workflow/v1/manifest.json` to look up the correct `workflowId`
- `start()` is called directly from the test process via the configured World
- `run.returnValue` polls for completion via the World (no HTTP polling endpoint needed)

### Changes

**`packages/core/e2e/e2e.test.ts`**
- Removed `triggerWorkflow()` and `getWorkflowReturnValue()` helpers
- Added `fetchManifest()` to fetch and cache the workflow manifest from the deployment
- Added `getWorkflowMetadata(file, fn)` to look up `{ workflowId }` from the manifest
- Added `e2e(fn)` shorthand for the common case of `workflows/99_e2e.ts`
- All tests call `start()` and `run.returnValue` directly
- Error tests use `.catch()` to inspect `WorkflowRunFailedError`
- Output stream tests use `run.getReadable()` directly (skipped on local world where cross-process streaming isn't supported)
- `beforeAll` configures the local World with the correct data directory and base URL
- Pages Router tests use `startWorkflowViaHttp()` to specifically validate the HTTP trigger path

**Workbench apps (hono, express, fastify, nest)**
- Removed `/api/trigger` route handlers
- Kept `/api/hook`, `/api/test-direct-step-call`, `/api/test-health-check` endpoints
- Re-added `_workflows.js` side-effect import for hono/express/fastify to maintain Nitro's HMR dependency graph

**Deleted trigger-only route files** from: nextjs-turbopack, nextjs-webpack, vite, sveltekit, astro, nuxt, nitro-v2, nitro-v3, example

**`.github/workflows/tests.yml`**
- Added `WORKFLOW_PUBLIC_MANIFEST: '1'` to all E2E test jobs

### Dependencies

Stacked on #963 which adds `WORKFLOW_PUBLIC_MANIFEST` support to all framework builders.
2026-02-06 16:25:47 -08:00
Peter Wielander f7fd88ea96 [core] Tidy health check latency calculation (#951) 2026-02-06 15:54:53 -08:00
Vercel Release Bot 835e2fbd3c Version Packages (beta) (#921)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-06 14:20:08 -08:00
Nathan Rajlich a733449e48 Fix release PR bad lockfile (#968) 2026-02-06 14:08:43 -08:00
Karthik Kalyan f090de1eb4 Refactor web-shared to separate UI components from server concerns (#927)
* Refactor web-shared to separate UI components from server concerns

- Move UI components into src/components/ subdirectory
- Move server actions and API client to @workflow/web
- Add world-actions/ module with shared helpers (recreateRun, cancelRun, etc.)
- Add runtime subpath exports to @workflow/core
- Components now accept callbacks as props for data fetching

* add support for streams

* update lock file

* Migrate helpers from web-shared to core

* Migrate helpers from web-shared to core

* Migrate helpers from web-shared to core

* Migrate helpers from web-shared to core

* Fix claude's comments

* Fix server actions bug for web

* fix copilot bugs
2026-02-06 13:16:48 -08:00
Nathan Rajlich 661724c01e Expose workflow manifest via HTTP when WORKFLOW_PUBLIC_MANIFEST=1 (#963)
## Summary

When `WORKFLOW_PUBLIC_MANIFEST=1` is set, each framework builder exposes the workflow manifest at `/.well-known/workflow/v1/manifest.json` via the most appropriate mechanism for the framework:

- **Next.js**: Copies manifest to `public/.well-known/workflow/v1/manifest.json` (served as a static file)
- **SvelteKit**: Copies manifest to `static/.well-known/workflow/v1/manifest.json` (served as a static file)
- **Vercel Build Output API** (example workbench): Copies manifest to `.vercel/output/static/.well-known/workflow/v1/manifest.json` (served as a static file)
- **Nitro** (vite, hono, express, fastify, nuxt, nitro-v2, nitro-v3): Registers a virtual handler that reads and serves the manifest JSON
- **Astro**: Generates a `manifest.json.js` page route that returns the manifest JSON
- **NestJS**: Adds a `@Get('manifest.json')` endpoint on the `WorkflowController` (gated by the env var at runtime)

### Other changes

- `BaseBuilder.createManifest()` now returns the manifest JSON string so framework builders can use it
- Added `shouldExposePublicManifest` getter to `BaseBuilder`
- Fixed Astro `LocalBuilder` which was missing the `createManifest()` call that all other framework builders have
- Removed unused `public/` directory and static file copy from the example workbench build script
- Added `WORKFLOW_PUBLIC_MANIFEST` to `turbo.json` build task env so Vercel builds can access it
- Added generated manifest paths to `.gitignore`
2026-02-06 12:22:37 -08:00
Pranay Prakash 088de0ae42 Various Performance Improvements (#932)
* Parallelize async operations in step handler for performance

- Parallelize getPort(), getSpanKind(), and world.steps.get() calls
- Start step_started event creation while hydrating arguments (CPU work)
- Parallelize step_completed event with trace serialization

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix race condition: await step_started before hydration

Reverts Optimization 1 to fix a race condition where hydrateStepArguments()
could throw before stepStartedPromise was awaited, causing stale step.attempt
in the catch handler and potentially allowing extra retries.

Optimizations 0 and 2 are preserved as they don't have this issue.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Skip initial world.steps.get() in step handler for performance

Eliminate the world.steps.get() HTTP call by calling step_started first
and relying on server-side validation. This saves 50-80ms per step
execution by removing one HTTP round-trip.

The server (workflow-server) now validates:
- Step not in terminal state (returns 409)
- retryAfter timestamp reached (returns 425 with Retry-After header)
- Workflow still active (returns 410 if completed)

Changes:
- Remove world.steps.get() from initial Promise.all
- Call step_started first to get step entity and validate state
- Handle 409 (terminal state) by re-queueing workflow
- Handle 425 (retryAfter not reached) by returning timeout
- Handle 410 (workflow gone) as no-op

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add retryAfter validation to local and postgres worlds

Add server-side retryAfter validation to match workflow-server behavior:
- Check retryAfter timestamp before allowing step_started
- Return HTTP 425 with retryAfter timestamp in response meta
- Clear retryAfter field when step starts successfully

This ensures consistent behavior across all world implementations
and allows the step-handler optimization to work correctly.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix step terminal state HTTP status code to 409 (Conflict)

Aligns local and postgres worlds with workflow-server, which returns
409 via InvalidOperationStateError for step in terminal state.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix world-vercel telemetry to use parent application's tracer

The world-vercel package was creating spans under a separate
'workflow-world-vercel' service name, causing HTTP spans for
workflow-server API calls (step_started, step_completed) to be
filtered out when viewing traces for the main application service.

Now uses the same 'workflow' tracer name as @workflow/core to
ensure all spans are reported under the parent application's service.

* Update /demo command to include OTEL tracing with Jaeger

- Start Jaeger container for local trace visualization
- Configure OTEL exporter environment variables for dev server
- Open Jaeger UI automatically
- Add documentation about available trace attributes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add OTEL instrumentation to world-local and improve trace consistency

- Add telemetry.ts and instrumentObject.ts to world-local for tracing parity
  with world-vercel (world.runs, world.steps, world.events, world.hooks spans)
- Change workflow span name from uppercase "WORKFLOW" to lowercase "workflow"
  for consistency with step spans and OTEL naming conventions
- Add step.execute child span to trace actual user step function execution
  separately from step handler infrastructure

These changes enable local development to have the same observability as
production deployments, making performance analysis and debugging easier.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Use uppercase WORKFLOW and STEP span names for consistency with HTTP spans

HTTP spans use uppercase methods like "GET /path" and "POST /path".
Following the same convention, workflow and step spans now use:
- WORKFLOW <workflow-name>
- STEP <step-name>

Child spans (workflow.run, workflow.loadEvents, step.execute, world.events.create)
remain lowercase as they represent internal operations, not top-level entries.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add W3C trace context headers to step queue messages

Include traceparent and tracestate headers when queueing step
execution messages. This enables automatic trace propagation
by Vercel's infrastructure, potentially linking step invocation
spans to the parent workflow trace.

The trace carrier is now serialized once and included in both:
- Payload: for manual context restoration in step handler
- Headers: for automatic HTTP-based trace propagation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Improve OTEL tracing with service attribution and enhanced instrumentation

- Add peer.service attributes for workflow-server and VQS for Datadog service maps
- Rename queueMessage span to queue.publish for consistency
- Add step.hydrate, step.dehydrate, and workflow.replay spans
- Include event type in world.events.create span names (e.g., "world.events.create step_started")
- Add span.recordException() for errors with category classification (fatal/retryable/transient)
- Add span events for milestones: retry.scheduled, step.skipped, step.delayed
- Add HTTP semantic conventions with peer.service for world-vercel HTTP calls
- Add baggage propagation for workflow context (run_id, workflow_name)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Update changesets to reflect all PR changes

- step-handler-parallelization.md: Add race condition fix and 409 status code fix
- world-vercel-telemetry-tracer.md: Add peer.service and event type in span names
- otel-tracing-improvements.md: New changeset for comprehensive OTEL improvements

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Use span name for rpc.method to show event type in Datadog resource

Datadog derives the resource name from rpc.method attribute. Updated to
use the full span name (which includes event type) instead of just the
method name, so Datadog shows "world.events.create step_started" instead
of "world.events.create workflow-server".

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Use lazy ref resolution for events where client discards response data

Skip expensive S3 ref resolution (~200-460ms) for event types where
the client doesn't use the response entity data (step_created,
step_completed, step_failed, run_completed, etc). Only resolve refs
for run_created, run_started, and step_started where the client
reads the response.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Update changeset to include lazy ref resolution optimization

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Address PR review comments: module scope const, comment cleanup, status code audit

- Move eventsNeedingResolve Set to module scope (PR review #2)
- Rewrite changelog-style OPTIMIZATION comments as current-state docs (PR review #4)
- Fix run terminal state errors: 410 → 409 in world-local and world-postgres to
  match workflow-server's InvalidOperationStateError (409) (PR review #3)
- Fix remaining step terminal state 410 → 409 in world-postgres fallback paths

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 11:52:03 -08:00
Karthik Kalyan fc077108ef Remove dirs option (#961) 2026-02-05 22:56:03 -08:00
Nathan Rajlich efb33b2b5e Pass class as this context to custom serializer/deserializer methods (#947)
* fix: pass class as this context to custom serializer/deserializer methods

When calling WORKFLOW_SERIALIZE and WORKFLOW_DESERIALIZE static methods,
use .call(ctor, value) and .call(cls, data) respectively to ensure the
class is passed as 'this' context. This is necessary for serializers that
access static properties or methods via 'this'.

Adds test case to verify this behavior.

* fix: address PR review feedback

- Fix test to actually use `this` instead of `Counter` directly, which
  properly validates the fix
- Standardize variable naming: rename `ctor` to `cls` in the Instance
  reducer for consistency with the reviver

* Add changeset

* fix: use this in test serde functions with biome-ignore

The test now properly uses `this.serializedCount` and `this.deserializedCount`
to validate the fix. Added biome-ignore comments to prevent the linter from
auto-fixing `this` back to the class name.
2026-02-05 11:09:53 -08:00
Pranay Prakash 0ce46b91d9 Add x-workflow-run-id header to queue messages (#922)
* Add x-workflow-run-id header to queue messages

Pass the workflow run ID as a custom header when enqueueing messages, enabling better observability and correlation with VQS server logs.

- Add headers field to QueueOptions interface in @workflow/world
- Pass headers through to VQS client in @workflow/world-vercel
- Include x-workflow-run-id header in all queueMessage calls

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Upgrade @vercel/queue to 0.0.0-alpha.36

This version includes the headers support needed for passing x-workflow-run-id to VQS.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Pass custom headers through world-local queue implementation

Custom headers are now spread first in the fetch headers so that system
headers (x-vqs-*, content-type) take precedence and cannot be overridden.

Also adds TODO comment in world-postgres noting headers are not propagated.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 20:34:12 -08:00
Pranay Prakash fed805a15f Bump Next.js and React in workbenches (#944)
* Bump Next.js and React in workbenches

- Next.js: 16.0.10 → 16.1.6 in nextjs-turbopack, nextjs-webpack, swc-playground workbenches
- Next.js: 16.0.10 → 16.1.6 in @workflow/next devDependencies (for type compatibility)
- React/React-DOM: ^19.0.0/19.2.1 → 19.2.4

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add changeset

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 18:07:14 -08:00
Nathan Rajlich 35a9f0cb03 Add subpath export resolution for package IDs (#901)
Fixed a bug in module specifier resolution and added support for package subpath exports in workflow IDs.

### What changed?

- Fixed a caching bug in the module specifier resolution system that could cause incorrect IDs
- Added support for subpath exports in package IDs (e.g., `workflow/internal/builtins@4.0.0`)
- Improved module resolution by passing absolute file paths to the SWC transform
- Enhanced manifest merging to properly combine results from both workflow and step bundles
- Updated builders to return and merge manifests from both workflow and step bundles

### How to test?

1. Build a project that uses subpath exports in packages
2. Verify that workflow IDs correctly include the subpath (e.g., `workflow/internal/builtins@4.0.0`)
3. Test with a project that has multiple builds to ensure module specifier caching works correctly

### Why make this change?

This change addresses an issue where the module specifier cache could return incorrect results, leading to inconsistent workflow IDs. It also adds support for packages with multiple entry points through subpath exports, ensuring that steps with the same name in different subpaths don't collide. This improves the reliability of cross-bundle references and makes the system more robust when working with complex package structures.
2026-02-04 16:49:16 -08:00
Pranay Prakash e0061b861d Consolidate console logging to structured logger utility (#935)
* Consolidate console logging to structured logger utility

Replace all console.log, console.warn, and console.error calls in
packages/core/src with the structured logger utility from logger.ts.
This provides consistent, filterable logging with OpenTelemetry
integration.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add changeset

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Improve logging: fix levels and ensure visibility

- Logger now outputs error/warn to console (always visible, not just when DEBUG enabled)
- Downgrade warn → info for expected states (OTel unavailable, workflow/step already completed, idempotency cases)
- Downgrade error → warn for transient failures (step invoked erroneously, error with retry pending)
- Remove verbose debug logs from hot paths (events-consumer)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 16:16:46 -08:00
Pranay Prakash 79e988fa85 Add improved OpenTelemetry instrumentation for workflow observability (#933)
* Add improved OpenTelemetry instrumentation for workflow observability

- Add tracing to HTTP requests in world-vercel with method, endpoint, and status attributes
- Add tracing to storage operations (runs, steps, events, hooks)
- Add tracing to event loading with event count and pages loaded attributes
- Add queue timing breakdown attributes (deserialize, execution, serialize times)
- Add new semantic conventions for world/storage, events, serialization, and queue breakdown

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Split changeset into separate entries for core and world-vercel

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Update OTEL instrumentation to use standard semantic conventions

- Replace custom world.http.* attributes with standard OTEL HTTP conventions
  (http.request.method, http.response.status_code, url.full, server.address, server.port)
- Replace custom queue.* attributes with standard OTEL messaging conventions
  (messaging.system, messaging.destination.name, messaging.message.id, messaging.operation.type)
- Add error.type attribute for HTTP error spans
- Add span.recordException() for proper error recording
- Namespace workflow-specific attributes under workflow.* prefix
- Fix span naming: use "HTTP {method}" instead of "WORLD.http {method} {endpoint}"

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Address PR review comments

- Remove unused semantic conventions (SerializeFormat, SerializeBytes, DeserializeFormat, DeserializeBytes, WorldParseBytes)
- Add documentation explaining intentional duplication in world-vercel/telemetry.ts
- Add documentation explaining why WorkflowSuspension isn't handled in world-vercel
- Add comment about deserialize timing measurement limitation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Use lowercase span names per OTEL semantic conventions

- world.runs, world.steps, world.events, world.hooks (storage)
- world.parse, world.validate (HTTP parsing)
- http {method} (HTTP client spans)
- workflow.run, workflow.start, workflow.loadEvents
- step {name}, hook.resume

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 16:12:30 -08:00
Nathan Rajlich 73bf7be925 Change compiler ID generation logic to use Node.js import specifier (#899)
## Summary

This PR changes how the SWC compiler generates IDs for workflows, steps, and classes. Instead of using raw file paths, IDs are now based on **Node.js module specifiers** when the file belongs to a package (either in `node_modules` or a workspace package).

## Motivation

Previously, IDs were generated using file paths like `step//src/jobs/order.ts//fetchData`. This caused several issues:

1. **Package exports conditions**: When a package uses conditional exports (e.g., `"workflow"` vs `"default"` conditions in `package.json`), the same import specifier can resolve to different files. Using file paths meant IDs could differ based on which export condition was used.
2. **Cross-bundle consistency**: Classes serialized in one bundle couldn't be deserialized in another if the file paths differed.
3. **Version tracking**: No way to include package versions in IDs for cache invalidation.

## Changes

### New ID Format

IDs now use the format `{type}//{modulePath}//{identifier}` where `modulePath` is either:

- A **module specifier** like `point@0.0.1` or `@myorg/shared@1.2.3` for package files
- A **relative path** prefixed with `./` like `./src/jobs/order` for local app files

Examples:

- `step//workflow@4.0.1-beta.50//fetch` (SDK step)
- `step//./workflows/order//processOrder` (local step)
- `class//point@0.0.1//Point` (package class)
- `class//./src/models/User//User` (local class)

### New Module Specifier Resolution

Added `packages/builders/src/module-specifier.ts` which:

- Detects if a file is in `node_modules` or a workspace package
- Finds the nearest `package.json` and extracts name/version
- Returns the module specifier for the SWC plugin to use

### SWC Plugin Changes

- Added `moduleSpecifier` option to plugin config
- Updated `naming.rs` to support both module specifiers and relative paths
- Added `get_module_path()` helper that uses specifier when available, falls back to `./filename` format

### Special Cases

- **Builtin functions** (`__builtin_*`): Continue to use just the function name as the ID for stable, version-independent lookup from the workflow VM runtime.

## Testing

- Updated all 125+ SWC plugin test fixtures to use new ID format
- Added tests for module specifier resolution
- Added tests for Windows path normalization in naming

## Breaking Changes

This is technically a breaking change for any persisted workflow runs that reference the old ID format. However, since IDs are internal implementation details and not user-facing, this should not affect end users.

## Files Changed

- `packages/builders/src/module-specifier.ts` - **NEW**: Module specifier resolution logic
- `packages/builders/src/apply-swc-transform.ts` - Pass module specifier to SWC plugin
- `packages/builders/src/base-builder.ts` - Use `getImportPath` for virtual entry imports
- `packages/swc-plugin-workflow/transform/src/lib.rs` - Accept and use module specifier
- `packages/swc-plugin-workflow/transform/src/naming.rs` - New ID formatting with module paths
- `packages/swc-plugin-workflow/spec.md` - Updated documentation
- `packages/core/e2e/e2e.test.ts` - Updated test assertions for new ID format
2026-02-04 14:23:02 -08:00
JJ Kasper 8ab1ba24d6 Add Next.js cache invalidation on swc transform change (#926)
* Add Next.js cache invalidation on swc transform change

* fix exports

* update distDir

* Use build-hash instead of package version

* refactor to util
2026-02-04 09:56:37 -08:00
Nathan Rajlich 2453b29426 Make wf build --manifest-file include steps / classes metadata (#931) 2026-02-04 09:22:11 -08:00
Pranay Prakash 6208616d06 Add sequential step benchmarks and fix broken benchmark infrastructure (#845)
* Add 50, 100, 500 concurrent step benchmarks

Enable Promise.all and Promise.race benchmarks for 50, 100, and 500
concurrent steps (previously 100+ were skipped).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add 50, 100, 500 sequential step benchmarks

Extends the sequential step benchmarks to test workflows with 50, 100,
and 500 sequential steps in addition to the existing 10 step test.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add full/quick benchmark suite toggle for CI

- Add BENCHMARK_FULL_SUITE env var to control which benchmarks run
- Quick suite (default for PRs): 10, 25, 50 step benchmarks
- Full suite (main branch, manual dispatch): adds 100, 500 step benchmarks
- Add workflow_dispatch input to manually trigger full suite from GitHub UI
- Skip 100+ sequential and concurrent step benchmarks by default

This keeps PR benchmarks fast while allowing full stress testing on demand.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add 25 sequential steps benchmark

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix benchmark API calls to use binary format

PR #853 changed the workflow trigger API to expect binary data
(application/octet-stream) instead of JSON. The e2e tests were updated
but the benchmark file was missed, causing all benchmarks to fail
silently since Jan 28.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Only run full benchmark suite on manual dispatch

Remove automatic full suite on main branch pushes - only run full suite
when manually triggered with full_suite=true.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 18:02:36 -08:00
Nathan Rajlich c54ba21c19 Add optional writeToStreamMulti function to the World interface (#867) 2026-02-03 17:53:36 -08:00
Nathan Rajlich c1d7c8dbb4 Add support for "use step" functions in class instance methods (#777)
Added support for `"use step"` directive in class instance methods, allowing instance methods to be used as workflow steps.

### What changed?

- Modified the SWC plugin to recognize and transform instance methods with the "use step" directive
- Added registration logic for instance method steps using `ClassName.prototype.methodName`
- Implemented proper serialization of class instances to preserve the `this` context across workflow/step boundaries
- Added comprehensive end-to-end tests for instance method steps
- Updated error handling to allow "use step" in instance methods while still preventing "use workflow" in instance methods

### How to test?

The PR includes a new end-to-end test `instanceMethodStepWorkflow` that demonstrates the functionality:

1. Run the e2e tests to verify the new instance method step functionality
2. The test creates a `Counter` class with instance methods marked as steps
3. It verifies that the instance methods can be called as steps with proper serialization of the `this` context
4. It also verifies that multiple instances of the same class can be used independently

### Why make this change?

This change enables a more natural object-oriented programming model when working with workflows. Previously, only static methods, standalone functions, and object methods could be marked as steps. Now, developers can create classes with instance methods that are steps, allowing for better encapsulation and more intuitive code organization. This is particularly useful for complex workflows that need to maintain state across multiple step invocations.
2026-02-03 00:11:38 -08:00
Nathan Rajlich b5296a7a32 Add discovered serializable classes in all context modes (#874)
This PR ensures that all classes with custom serialization are automatically included in all bundle contexts (step, workflow, client) to ensure proper serialization/deserialization when crossing execution boundaries:

- Classes defined in any context can now be properly serialized when passing data between:
  - Client → Workflow (when starting workflows)
  - Workflow → Step (when calling steps)
  - Step → Workflow (when returning step results)
  - Workflow → Client (when returning workflow results)

- The build system now automatically discovers all files containing serializable classes and includes them in each bundle, regardless of where the class is originally defined.

- No manual configuration is required - cross-registration happens automatically during the build process.
2026-02-02 23:26:29 -08:00
Nathan Rajlich 38e8d5571d Ensure class serialization / deserialization only happens in the proper global context (#873)
Fixed class serialization/deserialization to properly respect VM context boundaries, ensuring workflow code only accesses classes registered in its own global context.

### What changed?

- Modified `getSerializationClass()` to no longer fall back to checking `globalThis` when a class isn't found in the provided global context
- Updated the serialization tests to properly simulate how classes are registered and accessed in both host and VM contexts
- Added VM-specific class registration in tests to better match production behavior where workflow code runs in isolation

### How to test?

- Run the updated serialization tests which now properly simulate the VM boundary
- Verify that class serialization/deserialization works correctly when classes are registered in their respective contexts
- Confirm that classes registered in one context aren't accessible from another context

### Why make this change?

This change ensures proper isolation between the host environment and workflow VM contexts, matching production serverless behavior where workflow code runs in isolation. Previously, the code could leak class registrations between contexts, potentially causing unexpected behavior or security issues. This fix maintains proper boundaries between execution contexts.
2026-02-02 23:24:27 -08:00
Nathan Rajlich b9c782d75f Fix class ID generation when class is bound to a variable (#872)
Fixed class ID generation for class expressions bound to variables in the SWC plugin.

### What changed?

- Added support for correctly handling class expressions with binding names that differ from internal class names
- Updated the SWC plugin to use the variable name (binding name) for registration instead of the internal class name
- Added test cases for class expressions with different binding names and anonymous class expressions
- Updated documentation to explain how class expressions with binding names are handled

### How to test?

Test with code patterns like:
```javascript
var Bash = class _Bash {
  // Class implementation with serialization methods
};
```

Verify that the generated code correctly uses the binding name "Bash" for registration rather than the internal name "_Bash".

### Why make this change?

When a class expression is assigned to a variable, the internal class name is only accessible inside the class body, not at module level. Using the binding name ensures that the registration call references a symbol that's actually in scope at module level, preventing runtime errors when the code attempts to register the class for serialization.
2026-02-02 23:22:28 -08:00
Nathan Rajlich 8114792600 Add discovery for custom classes with workflow serialization (#859)
Added automatic discovery for custom classes with workflow serialization, allowing serialization classes to be defined in separate files without requiring explicit directives.

### What changed?

- Added detection for files containing custom class serialization patterns:
    - Files importing from `@workflow/serde`
    - Files using `Symbol.for('workflow-serialize')` or `Symbol.for('workflow-deserialize')`
- Created shared utilities in `transform-utils.ts` for consistent pattern detection across all build tools
- Updated Next.js, Nitro, Rollup, and Vite plugins to use the new detection patterns
- Added exclusion logic to prevent re-processing of generated workflow files
- Added tests to verify the pattern detection works correctly
- Updated documentation in the SWC plugin spec to explain the new discovery mechanism

### How to test?

1. Create a class with custom serialization in a separate file:

```js
// models/point.js
export class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  static [Symbol.for('workflow-serialize')](instance) {
    return { x: instance.x, y: instance.y };
  }

  static [Symbol.for('workflow-deserialize')](data) {
    return new Point(data.x, data.y);
  }
}
```

1. Import and use this class in a workflow or step file:

```js
'use workflow';
import { Point } from '../models/point';

export function myWorkflow() {
  const point = new Point(10, 20);
  return point;
}
```

1. Verify the class is properly serialized when passed between client and server

### Why make this change?

Previously, files containing custom serialization classes needed to include a `'use step'` directive to be discovered and transformed, even if they weren't actual step functions. This was unintuitive and could lead to confusion.

This change allows for a more natural code organization pattern where model classes with serialization can be defined in their own files without requiring directives. The build system will automatically discover and transform these files to ensure the serialization works correctly when the classes are used in workflows or steps.
2026-02-02 23:19:00 -08:00
Vercel Release Bot f6deb699af Version Packages (beta) (#915) 2026-02-02 16:53:50 -08:00
Peter Wielander e4e32812f8 Fix circular dependency in workflow package runtime (#916) 2026-02-02 11:59:45 -08:00
Karthik Kalyan f40532a872 fix(builders): Inline Next.js pseudo‑packages and strip server-only/client-only requires in workflow bundles (#911)
* Fix bundling behavior for dynamic imports and server-only, client-only imports

* Generate changeset

* Filter tsconfig to only pull in code from within the current working directory(workflow dir)

* Rewrite server-only and client-only

* add additional pseudo directives

* upmerge

* fix failing unit test
2026-02-02 11:07:53 -08:00
Vercel Release Bot 3dde14d529 Version Packages (beta) (#834) 2026-02-02 10:33:56 -08:00
Peter Wielander 90cd97beca [relaese] Fix workflow/ai peer dependendency causing a major bump in changeset (#913) 2026-02-02 10:00:00 -08:00
Peter Wielander 3730eb83ad Fix issues in prior changesets (#910) 2026-01-30 18:32:38 -08:00
Peter Wielander 6ae65f0ed0 [core] Follow up fixes for v1 compat (#907) 2026-01-30 17:55:19 -08:00
Peter Wielander a2b688d062 [core] Add runtime and world backwards compatibility for specVersion v1 (#894) 2026-01-30 13:23:08 -08:00
Nathan Rajlich 13d4ceef74 Use proper pluarlization in the builder log line (#898) 2026-01-30 11:58:55 -08:00
Peter Wielander 60a9b7661a [world-local] Increase default concurrency (#903) 2026-01-30 11:57:47 -08:00
Peter Wielander 36a0b41cb3 [web-shared] Fix o11y binary response parsing (#892) 2026-01-29 16:10:37 -08:00
Karthik Kalyan 0aa50e5ca4 Add 'use client' directives (#891)
* add use client

* add changeset
2026-01-29 15:45:11 -08:00
Peter Wielander b16cec4fdf [world] Fix zod compat for specVersion v1 payloads (#890) 2026-01-29 15:00:57 -08:00
Nathan Rajlich 1f684df6b7 Add a format identifier prefix for serialized data (#884) 2026-01-29 09:21:57 -08:00
Nathan Rajlich 1060f9d04a Change user input/output to be binary data at the World interface (#853) 2026-01-28 10:36:28 -08:00
Alan Cohen 4ad3fcd0a3 Pass runtime option to Vercel Build Output API functions (#800)
Signed-off-by: Alan Cohen <alan.mit@gmail.com>
2026-01-28 10:15:12 -08:00
Karthik Kalyan 409972e3b4 [perf]: add configurable dirs option to withWorkflow (#843)
* perf(builders): optimize workflow discovery with hybrid SWC approach

Apply SWC transforms only to files containing workflow/step directives
instead of all files. Uses regex for fast filtering, then SWC where needed.

- esbuild follows imports to build dependency graph
- regex detects 'use workflow'/'use step' directives
- SWC applied only to files with directives (<1% typically)
- other files use esbuild's built-in TS handling

* Remove blank line

* feat(next): add configurable `dirs` option to withWorkflow

Allow users to specify which directories to scan for workflow directives,
helping reduce memory usage and build times in large Next.js applications.

- Add `dirs` option that overrides the default directories
- Document all configuration options in API reference
- Add troubleshooting guide for OOM errors during build

* fix(docs): add missing imports in withWorkflow code example

Add missing import statements and nextConfig declaration to the
troubleshooting code example to fix docs typecheck.

* docs: move OOM troubleshooting to getting-started/next

Move the OOM troubleshooting section from the API reference to the
existing troubleshooting section in docs/getting-started/next.mdx
for better discoverability.

* move dirs inside workflow config
2026-01-27 21:51:39 -08:00
Peter Wielander 0b343192a3 [docs] Document NestJS setup + Vercel e2e tests (#850) 2026-01-27 19:12:50 -08:00