Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/abort-signal-replay-ordering.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@workflow/core': patch
---

Fix a race where an `AbortController` aborted from a step was not reflected in a `controller.signal` passed to a subsequent step. The step now commits the abort's durable hook event before completing, and the workflow's suspension waits for the abort to land before serializing downstream step arguments.
4 changes: 4 additions & 0 deletions .changeset/add-platformatic-world.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
---

Add Platformatic to `worlds-manifest.json` as a community world, and add a generic `docker` service type to the community-world CI (both the E2E and benchmark reusable workflows) so worlds can declare arbitrary Docker containers in their manifest `services` array. Platformatic's E2E job is gated by the existing `if: false` on `e2e-community` until community worlds ship CBOR queue transport support.
5 changes: 0 additions & 5 deletions .changeset/cancel-v4-frame-stream.md

This file was deleted.

6 changes: 6 additions & 0 deletions .changeset/fast-workflow-discovery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@workflow/builders': patch
'@workflow/next': patch
---

Optimize eager workflow discovery and improve default eager build compatibility.
6 changes: 6 additions & 0 deletions .changeset/perf-memoize-step-hydration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@workflow/core': patch
'workflow': patch
---

Memoize hydrated step return values across inline replay iterations, turning the per-invocation step-result decrypt+parse cost from O(N²) to O(N) for sequential workflows. Only primitive results are cached, so deterministic replay is preserved.
7 changes: 7 additions & 0 deletions .changeset/pre.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
},
"changesets": [
"abort-e2e-flakes",
"abort-signal-replay-ordering",
"ack-after-step-dispatch",
"afraid-bananas-peel",
"allow-sync-step-functions",
Expand Down Expand Up @@ -108,6 +109,7 @@
"event-log-race-repro-infra",
"events-exact-id-search",
"experimental-attributes-docs",
"fast-workflow-discovery",
"fatal-retryable-error-serialization",
"features-encryption-metadata",
"few-cups-share",
Expand Down Expand Up @@ -201,6 +203,7 @@
"parallel-inline-optimistic-start",
"pending-trace-viewer-gray-indicator",
"perf-cached-workflow-script",
"perf-memoize-step-hydration",
"pr-comment-stale-banner-via-path",
"precise-trace-viewer-durations",
"preserve-imports-used-by-hoisted-steps",
Expand All @@ -210,6 +213,7 @@
"proud-friends-decide",
"queue-namespace-primitive",
"queued-for-uses-first-step-started",
"quiet-lamps-parse",
"quiet-trace-viewer-duration",
"quieter-stream-metadata",
"rare-badgers-judge",
Expand All @@ -218,6 +222,7 @@
"reject-empty-hook-token",
"relative-time-card",
"remove-client-mode",
"remove-next-lazy-discovery",
"remove-private-subpath",
"remove-sdk-serde-exclusion",
"remove-step-file-copy",
Expand Down Expand Up @@ -282,6 +287,7 @@
"trace-viewer-load-more",
"trace-viewer-loading-skeleton",
"trace-viewer-polish",
"turbo-mode-first-invocation",
"turbo-next-workbench-outputs",
"turbo-next-workbench-vercel-output",
"update-queue-client-version",
Expand All @@ -298,6 +304,7 @@
"vast-oranges-fail",
"vercel-world-custom-dispatcher",
"versioning-docs",
"vitest-bundle-local-step-deps",
"warn-external-workflow-packages",
"web-shared-error-family-revivers",
"web-vercel-preset",
Expand Down
5 changes: 5 additions & 0 deletions .changeset/prewarm-next-swc-cache.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@workflow/next': patch
---

Prewarm the Workflow SWC plugin cache before Next.js starts parallel loader workers.
8 changes: 8 additions & 0 deletions .changeset/remove-next-lazy-discovery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@workflow/next': minor
'@workflow/builders': patch
---

Remove the Next.js lazy discovery/deferred builder path and the `workflows.lazyDiscovery` option.

Fall back to direct generated-file overwrites on Windows when atomic rename is blocked by Next.js dev server file handles.
5 changes: 5 additions & 0 deletions .changeset/stream-read-v3-reconnect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@workflow/world-vercel': patch
---

Use v3 endpoint for stream reads, which supports automatic transparent reconnects.
6 changes: 6 additions & 0 deletions .changeset/turbo-mode-first-invocation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'workflow': minor
'@workflow/core': minor
---

Add turbo mode (on by default, disable with `WORKFLOW_TURBO=0`): on the first delivery of a run's first invocation the runtime backgrounds `run_started`, skips the initial event-log load, and forces optimistic inline start so the run reaches its first steps with no preceding network round-trips. It is safe there because the first delivery has no concurrent handler to race; turbo mode deactivates once a hook or sleep is encountered.
7 changes: 7 additions & 0 deletions .changeset/turbo-skip-run-started-preload.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@workflow/core': patch
'@workflow/world': patch
'@workflow/world-vercel': patch
---

Turbo mode now tells world-vercel to skip the run_started event-log preload it never reads, reducing request time.
4 changes: 1 addition & 3 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
# Default owners for the entire repository
* @vercel/workflow

packages/next/src @ijjk @vercel/workflow
* @ijjk @vercel/workflow
74 changes: 72 additions & 2 deletions .github/workflows/benchmark-community-world.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ on:
description: 'NPM package name for the world'
required: true
type: string
package-version:
description: 'Optional npm version/dist-tag to pin the world package to (empty = latest)'
required: false
type: string
default: ''
app-name:
description: 'App to test (default: nextjs-turbopack)'
required: false
Expand All @@ -30,10 +35,15 @@ on:
type: string
default: '{}'
service-type:
description: 'Service container to run (none, mongodb, redis)'
description: 'Service container to run (none, mongodb, redis, docker)'
required: false
type: string
default: 'none'
services:
description: 'JSON array of service definitions from the manifest (used when service-type is docker)'
required: false
type: string
default: '[]'
full-suite:
description: 'Run full benchmark suite including long-running tests'
required: false
Expand Down Expand Up @@ -81,6 +91,58 @@ jobs:
sleep 2
done

- name: Start Docker services
if: ${{ inputs.service-type == 'docker' }}
# Services start serially in manifest order. Each service's health check
# must pass before the next one starts, so any service that depends on
# another (e.g. a workflow service that needs postgres) must be listed
# AFTER its dependency in the manifest's `services` array.
#
# `healthCheck.cmd` is executed via `sh -c` below, which means the
# manifest is a shell-execution surface. Safe here because
# `worlds-manifest.json` is in-repo and PR-reviewed.
run: |
set -euo pipefail
SERVICES='${{ inputs.services }}'
count=$(echo "$SERVICES" | jq '. | length')
for idx in $(seq 0 $((count - 1))); do
svc=$(echo "$SERVICES" | jq -c ".[$idx]")
name=$(echo "$svc" | jq -r '.name')
image=$(echo "$svc" | jq -r '.image')
health_cmd=$(echo "$svc" | jq -r '.healthCheck.cmd // empty')
retries=$(echo "$svc" | jq -r '.healthCheck.retries // 10')

# Build docker run as an array so env values with spaces/quotes
# survive intact (no shell re-interpretation via eval).
args=(docker run -d --name "$name" --network host)
env_lines=$(echo "$svc" | jq -r '.env // {} | to_entries[] | "\(.key)=\(.value)"')
if [ -n "$env_lines" ]; then
while IFS= read -r line; do
args+=(-e "$line")
done <<< "$env_lines"
fi
args+=("$image")

echo "Starting $name ($image)..."
"${args[@]}"

# Health check runs on the host (--network host shares the network).
if [ -n "$health_cmd" ]; then
echo "Waiting for $name to be ready..."
for i in $(seq 1 "$retries"); do
if sh -c "$health_cmd" &>/dev/null; then
echo "$name is ready"
break
fi
if [ "$i" -eq "$retries" ]; then
echo "ERROR: $name health check did not pass after $retries retries"
exit 1
fi
sleep 2
done
fi
done

- name: Setup environment
uses: ./.github/actions/setup-workflow-dev
with:
Expand Down Expand Up @@ -109,7 +171,10 @@ jobs:
env:
APP_NAME: ${{ inputs.app-name }}
WORLD_PACKAGE: ${{ inputs.world-package }}
run: pnpm --filter "$APP_NAME" add "$WORLD_PACKAGE"
WORLD_PACKAGE_VERSION: ${{ inputs.package-version }}
run: |
SPEC="${WORLD_PACKAGE}${WORLD_PACKAGE_VERSION:+@$WORLD_PACKAGE_VERSION}"
pnpm --filter "$APP_NAME" add "$SPEC"

# Per-world setup. Hardcoded (not taken from the matrix) so a malicious
# fork PR cannot smuggle arbitrary shell through matrix.world.setup-command.
Expand Down Expand Up @@ -175,3 +240,8 @@ jobs:
run: |
docker stop mongodb 2>/dev/null || true
docker stop redis 2>/dev/null || true
if [ '${{ inputs.service-type }}' = 'docker' ]; then
echo '${{ inputs.services }}' | jq -r '.[].name' 2>/dev/null | while read -r name; do
docker stop "$name" 2>/dev/null || true
done
fi
2 changes: 2 additions & 0 deletions .github/workflows/benchmarks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -513,8 +513,10 @@ jobs:
world-id: ${{ matrix.world.id }}
world-name: ${{ matrix.world.name }}
world-package: ${{ matrix.world.package }}
package-version: ${{ matrix.world.version }}
service-type: ${{ matrix.world.service-type }}
env-vars: ${{ matrix.world.env-vars }}
services: ${{ matrix.world.services }}
# Run full suite only when manually triggered with full_suite=true
full-suite: ${{ (github.event_name == 'workflow_dispatch' && inputs.full_suite) || contains(github.event.pull_request.labels.*.name, 'stress-test') }}
secrets: inherit
Expand Down
74 changes: 72 additions & 2 deletions .github/workflows/e2e-community-world.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ on:
description: 'NPM package name for the world'
required: true
type: string
package-version:
description: 'Optional npm version/dist-tag to pin the world package to (empty = latest)'
required: false
type: string
default: ''
app-name:
description: 'App to test (default: nextjs-turbopack)'
required: false
Expand All @@ -34,10 +39,15 @@ on:
type: string
default: '{}'
service-type:
description: 'Service container to run (none, mongodb, redis)'
description: 'Service container to run (none, mongodb, redis, docker)'
required: false
type: string
default: 'none'
services:
description: 'JSON array of service definitions from the manifest (used when service-type is docker)'
required: false
type: string
default: '[]'

jobs:
e2e:
Expand Down Expand Up @@ -83,6 +93,58 @@ jobs:
sleep 2
done

- name: Start Docker services
if: ${{ inputs.service-type == 'docker' }}
# Services start serially in manifest order. Each service's health check
# must pass before the next one starts, so any service that depends on
# another (e.g. a workflow service that needs postgres) must be listed
# AFTER its dependency in the manifest's `services` array.
#
# `healthCheck.cmd` is executed via `sh -c` below, which means the
# manifest is a shell-execution surface. Safe here because
# `worlds-manifest.json` is in-repo and PR-reviewed.
run: |
set -euo pipefail
SERVICES='${{ inputs.services }}'
count=$(echo "$SERVICES" | jq '. | length')
for idx in $(seq 0 $((count - 1))); do
svc=$(echo "$SERVICES" | jq -c ".[$idx]")
name=$(echo "$svc" | jq -r '.name')
image=$(echo "$svc" | jq -r '.image')
health_cmd=$(echo "$svc" | jq -r '.healthCheck.cmd // empty')
retries=$(echo "$svc" | jq -r '.healthCheck.retries // 10')

# Build docker run as an array so env values with spaces/quotes
# survive intact (no shell re-interpretation via eval).
args=(docker run -d --name "$name" --network host)
env_lines=$(echo "$svc" | jq -r '.env // {} | to_entries[] | "\(.key)=\(.value)"')
if [ -n "$env_lines" ]; then
while IFS= read -r line; do
args+=(-e "$line")
done <<< "$env_lines"
fi
args+=("$image")

echo "Starting $name ($image)..."
"${args[@]}"

# Health check runs on the host (--network host shares the network).
if [ -n "$health_cmd" ]; then
echo "Waiting for $name to be ready..."
for i in $(seq 1 "$retries"); do
if sh -c "$health_cmd" &>/dev/null; then
echo "$name is ready"
break
fi
if [ "$i" -eq "$retries" ]; then
echo "ERROR: $name health check did not pass after $retries retries"
exit 1
fi
sleep 2
done
fi
done

- name: Setup environment
uses: ./.github/actions/setup-workflow-dev
with:
Expand Down Expand Up @@ -111,7 +173,10 @@ jobs:
env:
APP_NAME: ${{ inputs.app-name }}
WORLD_PACKAGE: ${{ inputs.world-package }}
run: pnpm --filter "$APP_NAME" add "$WORLD_PACKAGE"
WORLD_PACKAGE_VERSION: ${{ inputs.package-version }}
run: |
SPEC="${WORLD_PACKAGE}${WORLD_PACKAGE_VERSION:+@$WORLD_PACKAGE_VERSION}"
pnpm --filter "$APP_NAME" add "$SPEC"

# Per-world setup. Hardcoded (not taken from the matrix) so a malicious
# fork PR cannot smuggle arbitrary shell through matrix.world.setup-command.
Expand Down Expand Up @@ -175,3 +240,8 @@ jobs:
run: |
docker stop mongodb 2>/dev/null || true
docker stop redis 2>/dev/null || true
if [ '${{ inputs.service-type }}' = 'docker' ]; then
echo '${{ inputs.services }}' | jq -r '.[].name' 2>/dev/null | while read -r name; do
docker stop "$name" 2>/dev/null || true
done
fi
1 change: 0 additions & 1 deletion .github/workflows/event-log-race-repro.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ jobs:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
WORKFLOW_PUBLIC_MANIFEST: '1'
WORKFLOW_NEXT_LAZY_DISCOVERY: '0'

steps:
- name: Checkout Repo
Expand Down
Loading
Loading