From 7f6ead53f221da0a2fa4697704c8bbc2e112a0f8 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Sat, 24 Jan 2026 15:18:39 -0800 Subject: [PATCH 1/2] queues example --- .claude/agents/playwright-test-generator.md | 106 -- .claude/agents/playwright-test-healer.md | 72 -- .claude/agents/playwright-test-planner.md | 56 - CLAUDE.md | 34 +- examples/CLAUDE.md | 164 ++- examples/queue-sandbox/README.md | 66 ++ examples/queue-sandbox/frontend/App.tsx | 651 ++++++++++++ examples/queue-sandbox/frontend/main.tsx | 12 + examples/queue-sandbox/index.html | 312 ++++++ examples/queue-sandbox/package.json | 44 + examples/queue-sandbox/src/actors.ts | 18 + .../queue-sandbox/src/actors/keep-awake.ts | 70 ++ .../queue-sandbox/src/actors/multi-queue.ts | 31 + .../queue-sandbox/src/actors/self-sender.ts | 48 + examples/queue-sandbox/src/actors/sender.ts | 36 + examples/queue-sandbox/src/actors/timeout.ts | 44 + examples/queue-sandbox/src/actors/worker.ts | 45 + examples/queue-sandbox/src/server.ts | 6 + examples/queue-sandbox/tsconfig.json | 23 + examples/queue-sandbox/turbo.json | 9 + examples/queue-sandbox/vite.config.ts | 7 + examples/workflow-sandbox/README.md | 99 ++ examples/workflow-sandbox/frontend/App.tsx | 956 ++++++++++++++++++ examples/workflow-sandbox/frontend/main.tsx | 12 + examples/workflow-sandbox/index.html | 878 ++++++++++++++++ examples/workflow-sandbox/package.json | 39 + examples/workflow-sandbox/src/actors.ts | 63 ++ .../workflow-sandbox/src/actors/_helpers.ts | 12 + .../workflow-sandbox/src/actors/approval.ts | 110 ++ examples/workflow-sandbox/src/actors/batch.ts | 125 +++ .../workflow-sandbox/src/actors/dashboard.ts | 203 ++++ examples/workflow-sandbox/src/actors/order.ts | 89 ++ .../workflow-sandbox/src/actors/payment.ts | 175 ++++ examples/workflow-sandbox/src/actors/race.ts | 112 ++ examples/workflow-sandbox/src/actors/timer.ts | 69 ++ examples/workflow-sandbox/src/server.ts | 8 + examples/workflow-sandbox/tsconfig.json | 16 + examples/workflow-sandbox/turbo.json | 9 + examples/workflow-sandbox/vite.config.ts | 7 + pnpm-lock.yaml | 60 ++ prd.txt | 6 + .../packages/rivetkit/src/actor/config.ts | 43 +- .../rivetkit/src/actor/instance/mod.ts | 8 +- .../rivetkit/src/actor/instance/queue.ts | 5 + .../driver-test-suite/tests/actor-workflow.ts | 5 + .../packages/rivetkit/src/workflow/context.ts | 4 + .../packages/rivetkit/src/workflow/driver.ts | 18 +- .../packages/rivetkit/src/workflow/mod.ts | 56 +- .../workflow-engine/contrib-docs/FLUSHING.md | 176 ++++ .../packages/workflow-engine/package.json | 1 + .../packages/workflow-engine/src/context.ts | 26 + .../packages/workflow-engine/src/index.ts | 29 + .../packages/workflow-engine/src/types.ts | 3 + scripts/ralph/prompt.txt | 17 + scripts/ralph/run.sh | 35 + .../examples/workflow-sandbox/image.png | Bin 0 -> 317518 bytes workflow-friction.md | 225 +++++ 57 files changed, 5263 insertions(+), 290 deletions(-) delete mode 100644 .claude/agents/playwright-test-generator.md delete mode 100644 .claude/agents/playwright-test-healer.md delete mode 100644 .claude/agents/playwright-test-planner.md create mode 100644 examples/queue-sandbox/README.md create mode 100644 examples/queue-sandbox/frontend/App.tsx create mode 100644 examples/queue-sandbox/frontend/main.tsx create mode 100644 examples/queue-sandbox/index.html create mode 100644 examples/queue-sandbox/package.json create mode 100644 examples/queue-sandbox/src/actors.ts create mode 100644 examples/queue-sandbox/src/actors/keep-awake.ts create mode 100644 examples/queue-sandbox/src/actors/multi-queue.ts create mode 100644 examples/queue-sandbox/src/actors/self-sender.ts create mode 100644 examples/queue-sandbox/src/actors/sender.ts create mode 100644 examples/queue-sandbox/src/actors/timeout.ts create mode 100644 examples/queue-sandbox/src/actors/worker.ts create mode 100644 examples/queue-sandbox/src/server.ts create mode 100644 examples/queue-sandbox/tsconfig.json create mode 100644 examples/queue-sandbox/turbo.json create mode 100644 examples/queue-sandbox/vite.config.ts create mode 100644 examples/workflow-sandbox/README.md create mode 100644 examples/workflow-sandbox/frontend/App.tsx create mode 100644 examples/workflow-sandbox/frontend/main.tsx create mode 100644 examples/workflow-sandbox/index.html create mode 100644 examples/workflow-sandbox/package.json create mode 100644 examples/workflow-sandbox/src/actors.ts create mode 100644 examples/workflow-sandbox/src/actors/_helpers.ts create mode 100644 examples/workflow-sandbox/src/actors/approval.ts create mode 100644 examples/workflow-sandbox/src/actors/batch.ts create mode 100644 examples/workflow-sandbox/src/actors/dashboard.ts create mode 100644 examples/workflow-sandbox/src/actors/order.ts create mode 100644 examples/workflow-sandbox/src/actors/payment.ts create mode 100644 examples/workflow-sandbox/src/actors/race.ts create mode 100644 examples/workflow-sandbox/src/actors/timer.ts create mode 100644 examples/workflow-sandbox/src/server.ts create mode 100644 examples/workflow-sandbox/tsconfig.json create mode 100644 examples/workflow-sandbox/turbo.json create mode 100644 examples/workflow-sandbox/vite.config.ts create mode 100644 prd.txt create mode 100644 rivetkit-typescript/packages/workflow-engine/contrib-docs/FLUSHING.md create mode 100644 scripts/ralph/prompt.txt create mode 100755 scripts/ralph/run.sh create mode 100644 website/public/examples/workflow-sandbox/image.png create mode 100644 workflow-friction.md diff --git a/.claude/agents/playwright-test-generator.md b/.claude/agents/playwright-test-generator.md deleted file mode 100644 index 886efc6ef0..0000000000 --- a/.claude/agents/playwright-test-generator.md +++ /dev/null @@ -1,106 +0,0 @@ ---- -name: playwright-test-generator -description: 'Use this agent when you need to create automated browser tests using Playwright Examples: Context: User wants to generate a test for the test plan item. ' -tools: Glob, Grep, Read, LS, mcp__playwright-test__browser_click, mcp__playwright-test__browser_drag, mcp__playwright-test__browser_evaluate, mcp__playwright-test__browser_file_upload, mcp__playwright-test__browser_handle_dialog, mcp__playwright-test__browser_hover, mcp__playwright-test__browser_navigate, mcp__playwright-test__browser_press_key, mcp__playwright-test__browser_select_option, mcp__playwright-test__browser_snapshot, mcp__playwright-test__browser_type, mcp__playwright-test__browser_verify_element_visible, mcp__playwright-test__browser_verify_list_visible, mcp__playwright-test__browser_verify_text_visible, mcp__playwright-test__browser_verify_value, mcp__playwright-test__browser_wait_for, mcp__playwright-test__generator_read_log, mcp__playwright-test__generator_setup_page, mcp__playwright-test__generator_write_test -model: sonnet -color: blue ---- - -You are a Playwright Test Generator, an expert in browser automation and end-to-end testing. -Your specialty is creating robust, reliable Playwright tests that accurately simulate user interactions and validate -application behavior. - -# Testing Guidelines - -**IMPORTANT**: Follow the testing guidelines documented in `frontend/docs/testing/GUIDELINES.md`. - -All generated tests must be placed in `frontend/e2e/` directory. - -## Element Selection Strategy - -1. **Prefer test IDs over other selectors**: Use `data-testid` attributes for stable element selection - - ✅ `page.getByTestId("onboarding-path-agent")` - - ❌ `page.getByText("Use Coding Agent")` - -2. **Add test IDs to components when needed**: If a component lacks a test ID, note that one should be added - - Use descriptive, kebab-case names: `data-testid="onboarding-path-agent"` - - Format: `{feature}-{element}-{variant?}` - -3. **Fallback hierarchy** (when test IDs are not available): - - `getByRole()` - for accessible elements (buttons, links, headings) - - `getByLabel()` - for form inputs - - `getByPlaceholder()` - for inputs with placeholders - - `getByText()` - last resort, avoid exact matching - -## Screenshot Testing - -Use visual regression testing to catch unintended UI changes: -- Capture full page screenshots for key states: `await expect(page).toHaveScreenshot("feature-state.png");` -- Capture component screenshots for specific elements -- Screenshot naming convention: `{feature}-{state}.png` - -# For each test you generate -- Obtain the test plan with all the steps and verification specification -- Run the `generator_setup_page` tool to set up page for the scenario -- For each step and verification in the scenario, do the following: - - Use Playwright tool to manually execute it in real-time. - - Use the step description as the intent for each Playwright tool call. -- Retrieve generator log via `generator_read_log` -- Immediately after reading the test log, invoke `generator_write_test` with the generated source code - - File must be placed in `frontend/e2e/` directory - - File should contain single test - - File name must be fs-friendly scenario name with `.spec.ts` extension - - Test must be placed in a describe matching the top-level test plan item - - Test title must match the scenario name - - Includes a comment with the step text before each step execution. Do not duplicate comments if step requires - multiple actions. - - Always use best practices from the log when generating tests. - - Include screenshot assertions for key states - - - For following plan: - - ```markdown file=frontend/specs/plan.md - ### 1. Onboarding - Path Selection - **Seed:** `frontend/e2e/seed.spec.ts` - - #### 1.1 displays three integration paths - **Steps:** - 1. Navigate to the home page - 2. Wait for path selection to load - **Verify:** - - All three paths are displayed - - #### 1.2 selecting coding agent proceeds to form - ... - ``` - - Following file is generated: - - ```ts file=frontend/e2e/onboarding-path-selection.spec.ts - // spec: frontend/specs/plan.md - // seed: frontend/e2e/seed.spec.ts - - import { setupClerkTestingToken } from "@clerk/testing/playwright"; - import { expect, test } from "@playwright/test"; - - test.describe('Onboarding - Path Selection', () => { - test('displays three integration paths', async ({ page }) => { - await setupClerkTestingToken({ page }); - await page.goto("/"); - - // 1. Wait for path selection to load - const pathSelection = page.getByTestId("onboarding-path-selection"); - await expect(pathSelection).toBeVisible(); - - // 2. Verify all three paths are displayed using test IDs - await expect(page.getByTestId("onboarding-path-agent")).toBeVisible(); - await expect(page.getByTestId("onboarding-path-template")).toBeVisible(); - await expect(page.getByTestId("onboarding-path-manual")).toBeVisible(); - - // Screenshot of path selection - await expect(page).toHaveScreenshot("onboarding-path-selection.png"); - }); - }); - ``` - \ No newline at end of file diff --git a/.claude/agents/playwright-test-healer.md b/.claude/agents/playwright-test-healer.md deleted file mode 100644 index f638c5b4bc..0000000000 --- a/.claude/agents/playwright-test-healer.md +++ /dev/null @@ -1,72 +0,0 @@ ---- -name: playwright-test-healer -description: Use this agent when you need to debug and fix failing Playwright tests in frontend/e2e/ -tools: Glob, Grep, Read, LS, Edit, MultiEdit, Write, mcp__playwright-test__browser_console_messages, mcp__playwright-test__browser_evaluate, mcp__playwright-test__browser_generate_locator, mcp__playwright-test__browser_network_requests, mcp__playwright-test__browser_snapshot, mcp__playwright-test__test_debug, mcp__playwright-test__test_list, mcp__playwright-test__test_run -model: sonnet -color: red ---- - -You are the Playwright Test Healer, an expert test automation engineer specializing in debugging and -resolving Playwright test failures. Your mission is to systematically identify, diagnose, and fix -broken Playwright tests using a methodical approach. - -# Testing Guidelines - -**IMPORTANT**: Follow the testing guidelines documented in `frontend/docs/testing/GUIDELINES.md`. - -All tests are located in `frontend/e2e/` directory. Run tests from the `frontend/` directory. - -## Element Selection Strategy - -When fixing selectors, follow this priority order: - -1. **Prefer test IDs over other selectors**: Use `data-testid` attributes for stable element selection - - ✅ `page.getByTestId("onboarding-path-agent")` - - ❌ `page.getByText("Use Coding Agent")` - -2. **Add test IDs to components when needed**: If a component lacks a test ID, add one manually - - Use descriptive, kebab-case names: `data-testid="onboarding-path-agent"` - - Format: `{feature}-{element}-{variant?}` - -3. **Fallback hierarchy** (when test IDs are not available): - - `getByRole()` - for accessible elements (buttons, links, headings) - - `getByLabel()` - for form inputs - - `getByPlaceholder()` - for inputs with placeholders - - `getByText()` - last resort, avoid exact matching - -# Your workflow - -1. **Initial Execution**: Run all tests using `test_run` tool to identify failing tests -2. **Debug failed tests**: For each failing test run `test_debug`. -3. **Error Investigation**: When the test pauses on errors, use available Playwright MCP tools to: - - Examine the error details - - Capture page snapshot to understand the context - - Analyze selectors, timing issues, or assertion failures -4. **Root Cause Analysis**: Determine the underlying cause of the failure by examining: - - Element selectors that may have changed - - Timing and synchronization issues - - Data dependencies or test environment problems - - Application changes that broke test assumptions -5. **Code Remediation**: Edit the test code to address identified issues, focusing on: - - Updating selectors to match current application state (prefer test IDs) - - Fixing assertions and expected values - - Improving test reliability and maintainability - - For inherently dynamic data, utilize regular expressions to produce resilient locators -6. **Verification**: Restart the test after each fix to validate the changes -7. **Iteration**: Repeat the investigation and fixing process until the test passes cleanly - -# Key principles - -- Be systematic and thorough in your debugging approach -- Document your findings and reasoning for each fix -- Prefer robust, maintainable solutions over quick hacks -- Use Playwright best practices for reliable test automation -- If multiple errors exist, fix them one at a time and retest -- Provide clear explanations of what was broken and how you fixed it -- You will continue this process until the test runs successfully without any failures or errors. -- If the error persists and you have high level of confidence that the test is correct, mark this test as test.fixme() - so that it is skipped during the execution. Add a comment before the failing step explaining what is happening instead - of the expected behavior. -- Do not ask user questions, you are not interactive tool, do the most reasonable thing possible to pass the test. -- Never wait for networkidle or use other discouraged or deprecated apis -- When updating selectors, prefer `getByTestId()` over text-based selectors \ No newline at end of file diff --git a/.claude/agents/playwright-test-planner.md b/.claude/agents/playwright-test-planner.md deleted file mode 100644 index 5cd94be95f..0000000000 --- a/.claude/agents/playwright-test-planner.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -name: playwright-test-planner -description: Plan comprehensive test scenarios for web applications following Rivet's testing guidelines and best practices -tools: Glob, Grep, Read, LS, mcp__playwright-test__browser_click, mcp__playwright-test__browser_close, mcp__playwright-test__browser_console_messages, mcp__playwright-test__browser_drag, mcp__playwright-test__browser_evaluate, mcp__playwright-test__browser_file_upload, mcp__playwright-test__browser_handle_dialog, mcp__playwright-test__browser_hover, mcp__playwright-test__browser_navigate, mcp__playwright-test__browser_navigate_back, mcp__playwright-test__browser_network_requests, mcp__playwright-test__browser_press_key, mcp__playwright-test__browser_select_option, mcp__playwright-test__browser_snapshot, mcp__playwright-test__browser_take_screenshot, mcp__playwright-test__browser_type, mcp__playwright-test__browser_wait_for, mcp__playwright-test__planner_setup_page, mcp__playwright-test__planner_save_plan -model: sonnet -color: green ---- - -You are an expert test planner specializing in comprehensive test scenario design and business logic validation. You will create test plans that focus on critical user flows and outcomes while following Rivet's testing philosophy. - -## Your Responsibilities - -### 1. Explore and Understand -- Invoke `planner_setup_page` once at the start -- Use browser snapshot and `browser_*` tools to explore interface -- Identify all interactive elements, forms, navigation paths, and critical flows -- Do not take screenshots unless absolutely necessary -- Map primary user journeys and critical paths - -### 2. Follow Rivet Testing Guidelines -Before creating test plans, **always**: -- Read `frontend/docs/testing/GUIDELINES.md` to understand the testing philosophy -- Check `frontend/docs/testing/references/` for shared documentation -- Review existing tests in `frontend/e2e/` to understand current patterns - -**Key Principles**: -- **Test business logic only**: Focus on critical functionality, data flow, error handling, connection states -- **Test what, not how**: Verify user outcomes, not implementation details -- **Avoid specific text**: Test information presence, not exact wording -- **Use present tense**: "User is informed", "Data is displayed" -- **Be ambiguous**: Allow test implementation flexibility - -### 3. Design Scenarios Following Guidelines -Create test scenarios that follow Rivet's format: -- Use high-level descriptions (not implementation details) -- Structure with **Given/When/Then** or **Verify** sections -- Focus on: user information, available actions, business outcomes -- Include happy path, edge cases, and error scenarios -- Assume blank/fresh starting state unless specified - -### 4. Output Format -- Create markdown documentation in `frontend/docs/testing/scenarios/` -- Use clear headings, numbered steps, and professional formatting -- Reference shared documents from `frontend/docs/testing/references/` -- Structure for both manual testing and e2e test development - -## Quality Standards -- Scenarios must be clear enough for any tester to follow -- Include negative testing and error cases -- Scenarios are independent and can run in any order -- Focus on critical business logic (connection, state, user actions, errors) -- Exclude accessibility, performance, styling, animations - - -**Output Format**: Always save the complete test plan as a markdown file with clear headings, numbered steps, and -professional formatting suitable for sharing with development and QA teams. \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index bde7ccc0d3..4412b2b600 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -76,8 +76,39 @@ gt m ``` ## Dependency Management + +### pnpm Workspace - Use pnpm for all npm-related commands. We're using a pnpm workspace. +### RivetKit Package Resolutions +The root `/package.json` contains `resolutions` that map RivetKit packages to their local workspace versions: + +```json +{ + "resolutions": { + "rivetkit": "workspace:*", + "@rivetkit/react": "workspace:*", + "@rivetkit/workflow-engine": "workspace:*", + // ... other @rivetkit/* packages + } +} +``` + +When adding RivetKit dependencies to examples in `/examples/`, use `*` as the version. The root resolutions will automatically resolve these to the local workspace packages: + +```json +{ + "dependencies": { + "rivetkit": "*", + "@rivetkit/react": "*" + } +} +``` + +If you need to add a new `@rivetkit/*` package that isn't already in the root resolutions, add it to the `resolutions` object in `/package.json` with `"workspace:*"` as the value. Internal packages like `@rivetkit/workflow-engine` should be re-exported from `rivetkit` subpaths (e.g., `rivetkit/workflow`) rather than added as direct dependencies. + +### Rust Dependencies + ## Documentation - If you need to look at the documentation for a package, visit `https://docs.rs/{package-name}`. For example, serde docs live at https://docs.rs/serde/ @@ -168,7 +199,7 @@ Key points: - For example: `fn foo() -> Result { /* ... */ }` - Do not glob import (`::*`) from anyhow. Instead, import individual types and traits -**Dependency Management** +**Rust Dependency Management** - When adding a dependency, check for a workspace dependency in Cargo.toml - If available, use the workspace dependency (e.g., `anyhow.workspace = true`) - If you need to add a dependency and can't find it in the Cargo.toml of the workspace, add it to the workspace dependencies in Cargo.toml (`[workspace.dependencies]`) and then add it to the package you need with `{dependency}.workspace = true` @@ -220,6 +251,7 @@ Data structures often include: ## Testing Guidelines - When running tests, always pipe the test to a file in /tmp/ then grep it in a second step. You can grep test logs multiple times to search for different log lines. - For RivetKit TypeScript tests, run from `rivetkit-typescript/packages/rivetkit` and use `pnpm test ` with `-t` to narrow to specific suites. For example: `pnpm test driver-file-system -t ".*Actor KV.*"`. +- For frontend testing, use the `agent-browser` skill to interact with and test web UIs in examples. This allows automated browser-based testing of frontend applications. ## Optimizations diff --git a/examples/CLAUDE.md b/examples/CLAUDE.md index 75a09e633c..8fec98ec76 100644 --- a/examples/CLAUDE.md +++ b/examples/CLAUDE.md @@ -575,18 +575,152 @@ The following example types are not converted to Vercel: 3. The script detects changes via git diff and only regenerates modified examples 4. Commit both the origin and generated Vercel examples -## TODO: Examples Cleanup - -The following issues need to be fixed across examples: - -- [x] Rename `src/registry.ts` to `src/actors.ts` in all examples -- [ ] Update all relative imports to use `.ts` extensions (ESM compliance) - only cloudflare examples remaining -- [ ] Add `allowImportingTsExtensions` and `rewriteRelativeImportExtensions` to tsconfig.json -- [x] Remove unused `tsup.config.ts` from examples using vite-plugin-srvx -- [x] Remove unused `tsup` devDependency from examples using vite-plugin-srvx -- [x] Move `srvx` from devDependencies to dependencies (used by `start` script) -- [x] Move `@hono/node-server` and `@hono/node-ws` from devDependencies to dependencies -- [x] Remove unused `concurrently` devDependency from examples using vite-plugin-srvx -- [ ] Remove `scripts/` directories with CLI client scripts - only cloudflare/next-js examples remaining -- [x] Remove `prompts` and `@types/prompts` devDependencies -- [x] Migrate all frontend examples to use vite-plugin-srvx +## Frontend Style Guide + +Examples should follow these design conventions: + +**Color Palette (Dark Theme)** +- Primary accent: `#ff4f00` (orange) for interactive elements and highlights +- Background: `#000000` (main), `#1c1c1e` (cards/containers) +- Borders: `#2c2c2e` +- Input backgrounds: `#2c2c2e` with border `#3a3a3c` +- Text: `#ffffff` (primary), `#8e8e93` (secondary/muted) +- Success: `#30d158` (green) +- Warning: `#ff4f00` (orange) +- Danger: `#ff3b30` (red) +- Purple: `#bf5af2` (for special states like rollback) + +**Typography** +- UI: System fonts (`-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Inter', Roboto, sans-serif`) +- Code: `ui-monospace, SFMono-Regular, 'SF Mono', Consolas, monospace` +- Sizes: 14-16px body, 12-13px labels, large numbers 48-72px + +**Sizing & Spacing** +- Border radius: 8px (cards/containers/buttons), 6px (inputs/badges) +- Section padding: 20-24px +- Gap between items: 12px +- Transitions: 200ms ease for all interactive states + +**Button Styles** +- Padding: 12px 20px +- Border: none +- Border radius: 8px +- Font size: 14px, weight 600 +- Hover: none (no hover state) +- Disabled: 50% opacity, `cursor: not-allowed` + +**CSS Approach** +- Plain CSS in ` + + +
+ + + diff --git a/examples/queue-sandbox/package.json b/examples/queue-sandbox/package.json new file mode 100644 index 0000000000..30af6e6a6d --- /dev/null +++ b/examples/queue-sandbox/package.json @@ -0,0 +1,44 @@ +{ + "name": "queue-sandbox", + "version": "2.0.21", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "check-types": "tsc --noEmit", + "build": "vite build && vite build --mode server", + "start": "srvx --static=public/ dist/server.js" + }, + "devDependencies": { + "@types/node": "^22.13.9", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "@vitejs/plugin-react": "^4.2.0", + "tsx": "^3.12.7", + "typescript": "^5.5.2", + "vite": "^5.0.0", + "vite-plugin-srvx": "^1.0.0" + }, + "dependencies": { + "@hono/node-server": "^1.19.7", + "@hono/node-ws": "^1.2.0", + "@rivetkit/react": "^2.0.38", + "hono": "^4.11.3", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "rivetkit": "^2.0.38", + "srvx": "^0.10.0" + }, + "template": { + "technologies": [ + "typescript", + "react" + ], + "tags": [ + "queues" + ], + "priority": 100, + "frontendPort": 5173 + }, + "license": "MIT" +} diff --git a/examples/queue-sandbox/src/actors.ts b/examples/queue-sandbox/src/actors.ts new file mode 100644 index 0000000000..c581fe152e --- /dev/null +++ b/examples/queue-sandbox/src/actors.ts @@ -0,0 +1,18 @@ +import { setup } from "rivetkit"; +import { sender } from "./actors/sender.ts"; +import { multiQueue } from "./actors/multi-queue.ts"; +import { timeout } from "./actors/timeout.ts"; +import { worker } from "./actors/worker.ts"; +import { selfSender } from "./actors/self-sender.ts"; +import { keepAwake } from "./actors/keep-awake.ts"; + +export const registry = setup({ + use: { + sender, + multiQueue, + timeout, + worker, + selfSender, + keepAwake, + }, +}); diff --git a/examples/queue-sandbox/src/actors/keep-awake.ts b/examples/queue-sandbox/src/actors/keep-awake.ts new file mode 100644 index 0000000000..dcf9a0a28e --- /dev/null +++ b/examples/queue-sandbox/src/actors/keep-awake.ts @@ -0,0 +1,70 @@ +import { actor } from "rivetkit"; + +export interface CurrentTask { + id: string; + startedAt: number; + durationMs: number; +} + +export interface CompletedTask { + id: string; + completedAt: number; +} + +export interface KeepAwakeState { + currentTask: CurrentTask | null; + completedTasks: CompletedTask[]; +} + +export const keepAwake = actor({ + state: { + currentTask: null as CurrentTask | null, + completedTasks: [] as CompletedTask[], + }, + async run(c) { + while (!c.abortSignal.aborted) { + const job = await c.queue.next("tasks", { timeout: 1000 }); + if (job) { + const taskId = crypto.randomUUID(); + const { durationMs } = job.body as { durationMs: number }; + + c.state.currentTask = { + id: taskId, + startedAt: Date.now(), + durationMs, + }; + c.broadcast("taskStarted", c.state.currentTask); + + // Wrap long-running work in keepAwake so actor doesn't sleep + await c.keepAwake( + new Promise((resolve) => setTimeout(resolve, durationMs)), + ); + + c.state.completedTasks.push({ id: taskId, completedAt: Date.now() }); + c.state.currentTask = null; + c.broadcast("taskCompleted", { + taskId, + completedTasks: c.state.completedTasks, + }); + } + } + }, + actions: { + getState(c): KeepAwakeState { + return { + currentTask: c.state.currentTask, + completedTasks: c.state.completedTasks, + }; + }, + clearTasks(c) { + c.state.completedTasks = []; + c.broadcast("taskCompleted", { + taskId: null, + completedTasks: [], + }); + }, + }, + options: { + sleepTimeout: 2000, + }, +}); diff --git a/examples/queue-sandbox/src/actors/multi-queue.ts b/examples/queue-sandbox/src/actors/multi-queue.ts new file mode 100644 index 0000000000..57d57e7ad8 --- /dev/null +++ b/examples/queue-sandbox/src/actors/multi-queue.ts @@ -0,0 +1,31 @@ +import { actor } from "rivetkit"; + +export interface QueueMessage { + name: string; + body: unknown; +} + +export const multiQueue = actor({ + state: { + messages: [] as QueueMessage[], + }, + actions: { + async receiveFromQueues(c, names: string[], count: number) { + const msgs = await c.queue.next(names, { count, timeout: 100 }); + if (msgs && msgs.length > 0) { + for (const msg of msgs) { + c.state.messages.push({ name: msg.name, body: msg.body }); + } + c.broadcast("messagesReceived", c.state.messages); + } + return msgs ?? []; + }, + getMessages(c): QueueMessage[] { + return c.state.messages; + }, + clearMessages(c) { + c.state.messages = []; + c.broadcast("messagesReceived", c.state.messages); + }, + }, +}); diff --git a/examples/queue-sandbox/src/actors/self-sender.ts b/examples/queue-sandbox/src/actors/self-sender.ts new file mode 100644 index 0000000000..bfd1b3cd1a --- /dev/null +++ b/examples/queue-sandbox/src/actors/self-sender.ts @@ -0,0 +1,48 @@ +import { actor } from "rivetkit"; + +export interface SelfSenderState { + sentCount: number; + receivedCount: number; + messages: unknown[]; +} + +export const selfSender = actor({ + state: { + sentCount: 0, + receivedCount: 0, + messages: [] as unknown[], + }, + actions: { + async receiveFromSelf(c) { + const msg = await c.queue.next("self", { timeout: 100 }); + if (msg) { + c.state.receivedCount += 1; + c.state.messages.push(msg.body); + c.broadcast("received", { + receivedCount: c.state.receivedCount, + message: msg.body, + }); + return msg.body; + } + return null; + }, + getState(c): SelfSenderState { + return { + sentCount: c.state.sentCount, + receivedCount: c.state.receivedCount, + messages: c.state.messages, + }; + }, + clearMessages(c) { + c.state.sentCount = 0; + c.state.receivedCount = 0; + c.state.messages = []; + c.broadcast("sent", { sentCount: 0 }); + c.broadcast("received", { receivedCount: 0, message: null }); + }, + incrementSentCount(c) { + c.state.sentCount += 1; + c.broadcast("sent", { sentCount: c.state.sentCount }); + }, + }, +}); diff --git a/examples/queue-sandbox/src/actors/sender.ts b/examples/queue-sandbox/src/actors/sender.ts new file mode 100644 index 0000000000..0db2c49a0b --- /dev/null +++ b/examples/queue-sandbox/src/actors/sender.ts @@ -0,0 +1,36 @@ +import { actor } from "rivetkit"; + +export interface ReceivedMessage { + name: string; + body: unknown; + receivedAt: number; +} + +export const sender = actor({ + state: { + messages: [] as ReceivedMessage[], + }, + actions: { + getMessages(c): ReceivedMessage[] { + return c.state.messages; + }, + async receiveOne(c) { + const msg = await c.queue.next("task", { timeout: 100 }); + if (msg) { + const received: ReceivedMessage = { + name: msg.name, + body: msg.body, + receivedAt: Date.now(), + }; + c.state.messages.push(received); + c.broadcast("messageReceived", c.state.messages); + return received; + } + return null; + }, + clearMessages(c) { + c.state.messages = []; + c.broadcast("messageReceived", c.state.messages); + }, + }, +}); diff --git a/examples/queue-sandbox/src/actors/timeout.ts b/examples/queue-sandbox/src/actors/timeout.ts new file mode 100644 index 0000000000..be0ff8fea7 --- /dev/null +++ b/examples/queue-sandbox/src/actors/timeout.ts @@ -0,0 +1,44 @@ +import { actor } from "rivetkit"; + +export interface TimeoutResult { + timedOut: boolean; + message?: unknown; + waitedMs: number; +} + +export interface TimeoutState { + lastResult: TimeoutResult | null; + waitStartedAt: number | null; +} + +export const timeout = actor({ + state: { + lastResult: null as TimeoutResult | null, + waitStartedAt: null as number | null, + }, + actions: { + async waitForMessage(c, timeoutMs: number): Promise { + const startedAt = Date.now(); + c.state.waitStartedAt = startedAt; + c.broadcast("waitStarted", { startedAt, timeoutMs }); + + const msg = await c.queue.next("work", { timeout: timeoutMs }); + + const waitedMs = Date.now() - startedAt; + const result: TimeoutResult = msg + ? { timedOut: false, message: msg.body, waitedMs } + : { timedOut: true, waitedMs }; + + c.state.lastResult = result; + c.state.waitStartedAt = null; + c.broadcast("waitCompleted", result); + return result; + }, + getState(c): TimeoutState { + return { + lastResult: c.state.lastResult, + waitStartedAt: c.state.waitStartedAt, + }; + }, + }, +}); diff --git a/examples/queue-sandbox/src/actors/worker.ts b/examples/queue-sandbox/src/actors/worker.ts new file mode 100644 index 0000000000..e7b710f5dc --- /dev/null +++ b/examples/queue-sandbox/src/actors/worker.ts @@ -0,0 +1,45 @@ +import { actor } from "rivetkit"; + +export interface WorkerState { + status: "idle" | "running"; + processed: number; + lastJob: unknown; +} + +export const worker = actor({ + state: { + status: "idle" as "idle" | "running", + processed: 0, + lastJob: null as unknown, + }, + async run(c) { + c.state.status = "running"; + c.broadcast("statusChanged", { + status: c.state.status, + processed: c.state.processed, + }); + + while (!c.abortSignal.aborted) { + const job = await c.queue.next("jobs", { timeout: 1000 }); + if (job) { + c.state.processed += 1; + c.state.lastJob = job.body; + c.broadcast("jobProcessed", { + processed: c.state.processed, + job: job.body, + }); + } + } + + c.state.status = "idle"; + }, + actions: { + getState(c): WorkerState { + return { + status: c.state.status, + processed: c.state.processed, + lastJob: c.state.lastJob, + }; + }, + }, +}); diff --git a/examples/queue-sandbox/src/server.ts b/examples/queue-sandbox/src/server.ts new file mode 100644 index 0000000000..95c8895f94 --- /dev/null +++ b/examples/queue-sandbox/src/server.ts @@ -0,0 +1,6 @@ +import { Hono } from "hono"; +import { registry } from "./actors.ts"; + +const app = new Hono(); +app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +export default app; diff --git a/examples/queue-sandbox/tsconfig.json b/examples/queue-sandbox/tsconfig.json new file mode 100644 index 0000000000..081970ac06 --- /dev/null +++ b/examples/queue-sandbox/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "esnext", + "lib": ["esnext", "dom"], + "jsx": "react-jsx", + "module": "esnext", + "moduleResolution": "bundler", + "types": ["node", "vite/client"], + "resolveJsonModule": true, + "allowJs": true, + "checkJs": false, + "noEmit": true, + "isolatedModules": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitAny": false, + "skipLibCheck": true, + "allowImportingTsExtensions": true, + "rewriteRelativeImportExtensions": true + }, + "include": ["src/**/*", "frontend/**/*"] +} diff --git a/examples/queue-sandbox/turbo.json b/examples/queue-sandbox/turbo.json new file mode 100644 index 0000000000..c5e71016d3 --- /dev/null +++ b/examples/queue-sandbox/turbo.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://turbo.build/schema.json", + "extends": ["//"], + "tasks": { + "build": { + "dependsOn": ["@rivetkit/react#build", "rivetkit#build"] + } + } +} diff --git a/examples/queue-sandbox/vite.config.ts b/examples/queue-sandbox/vite.config.ts new file mode 100644 index 0000000000..06dae893f5 --- /dev/null +++ b/examples/queue-sandbox/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import srvx from "vite-plugin-srvx"; + +export default defineConfig({ + plugins: [react(), ...srvx({ entry: "src/server.ts" })], +}); diff --git a/examples/workflow-sandbox/README.md b/examples/workflow-sandbox/README.md new file mode 100644 index 0000000000..46eec1d50d --- /dev/null +++ b/examples/workflow-sandbox/README.md @@ -0,0 +1,99 @@ +# Workflow Sandbox + +Interactive sandbox for testing all RivetKit workflow patterns. + +## Getting Started + +```sh +git clone https://github.com/rivet-dev/rivet.git +cd rivet/examples/workflow-sandbox +npm install +npm run dev +``` + + +## Features + +This example demonstrates all workflow features through a tabbed interface: + +- **Steps** - Multi-step order processing with automatic retries +- **Sleep** - Durable countdown timers that survive restarts +- **Loops** - Batch processing with persistent cursor state +- **Listen** - Approval queue with timeout-based decisions +- **Join** - Parallel data fetching (wait-all pattern) +- **Race** - Work vs timeout (first-wins pattern) +- **Rollback** - Compensating transactions with rollback handlers + +## Implementation + +Each workflow pattern is implemented as a separate actor with its own state and workflow loop. The key patterns demonstrated: + +### Steps +```typescript +await loopCtx.step("validate", async () => { /* ... */ }); +await loopCtx.step("charge", async () => { /* ... */ }); +await loopCtx.step("fulfill", async () => { /* ... */ }); +``` + +### Sleep +```typescript +await loopCtx.sleep("countdown", durationMs); +``` + +### Loops +```typescript +await ctx.loop({ + name: "batch-loop", + state: { cursor: 0 }, + run: async (loopCtx, state) => { + // Process batch + return Loop.continue({ cursor: state.cursor + 1 }); + }, +}); +``` + +### Listen +```typescript +const decision = await loopCtx.listenWithTimeout( + "wait-decision", + "decision-queue", + 30000 // 30 second timeout +); +``` + +### Join +```typescript +const results = await loopCtx.join("fetch-all", { + users: { run: async (ctx) => fetchUsers() }, + orders: { run: async (ctx) => fetchOrders() }, +}); +``` + +### Race +```typescript +const { winner, value } = await loopCtx.race("work-vs-timeout", [ + { name: "work", run: async (ctx) => doWork() }, + { name: "timeout", run: async (ctx) => ctx.sleep("wait", timeout) }, +]); +``` + +### Rollback +```typescript +await loopCtx.rollbackCheckpoint("payment-checkpoint"); + +await loopCtx.step({ + name: "charge-card", + run: async () => chargeCard(), + rollback: async () => refundCard(), +}); +``` + +See the implementation in [`src/actors.ts`](https://github.com/rivet-dev/rivet/tree/main/examples/workflow-sandbox/src/actors.ts). + +## Resources + +Read more about [workflows](/docs/workflows). + +## License + +MIT diff --git a/examples/workflow-sandbox/frontend/App.tsx b/examples/workflow-sandbox/frontend/App.tsx new file mode 100644 index 0000000000..18c16da037 --- /dev/null +++ b/examples/workflow-sandbox/frontend/App.tsx @@ -0,0 +1,956 @@ +import { useState, useEffect } from "react"; +import { createRivetKit } from "@rivetkit/react"; +import type { + registry, + Order, + Timer, + BatchJob, + ApprovalRequest, + DashboardState, + RaceTask, + Transaction, +} from "../src/actors.ts"; + +const { useActor } = createRivetKit( + `${location.origin}/api/rivet` +); + +// localStorage helpers for persisting actor keys across page refreshes +function usePersistedState(key: string, initial: T): [T, React.Dispatch>] { + const [state, setState] = useState(() => { + const stored = localStorage.getItem(key); + return stored ? JSON.parse(stored) : initial; + }); + + useEffect(() => { + localStorage.setItem(key, JSON.stringify(state)); + }, [key, state]); + + return [state, setState]; +} + +type Tab = + | "steps" + | "sleep" + | "loops" + | "listen" + | "join" + | "race" + | "rollback"; + +const TABS: { id: Tab; label: string; description: string }[] = [ + { id: "steps", label: "Steps", description: "Multi-step order processing" }, + { id: "sleep", label: "Sleep", description: "Durable countdown timers" }, + { id: "loops", label: "Loops", description: "Batch processing with cursor" }, + { id: "listen", label: "Listen", description: "Approval queue with timeout" }, + { id: "join", label: "Join", description: "Parallel data aggregation" }, + { id: "race", label: "Race", description: "Work vs timeout pattern" }, + { + id: "rollback", + label: "Rollback", + description: "Compensating transactions", + }, +]; + +export function App() { + const [activeTab, setActiveTab] = useState("steps"); + + return ( +
+
+

Workflow Sandbox

+

Test different RivetKit workflow patterns

+
+ + + +
+ {activeTab === "steps" && } + {activeTab === "sleep" && } + {activeTab === "loops" && } + {activeTab === "listen" && } + {activeTab === "join" && } + {activeTab === "race" && } + {activeTab === "rollback" && } +
+
+ ); +} + +// ============================================================================ +// STEPS DEMO - One actor per order +// ============================================================================ + +function StepsDemo() { + const [orderKeys, setOrderKeys] = usePersistedState("workflow-sandbox:orders", []); + + const createOrder = () => { + const orderId = `ORD-${Date.now().toString(36).toUpperCase()}`; + setOrderKeys((prev) => [orderId, ...prev]); + }; + + return ( +
+
+

Steps Demo

+

+ Sequential workflow steps with automatic retries. Each order goes + through validate, charge, and fulfill steps. +

+ +
+ +
+
+ {orderKeys.length === 0 && ( +

No orders yet. Create one to see the workflow!

+ )} + {orderKeys.map((orderId) => ( + + ))} +
+
+
+ ); +} + +function OrderCard({ orderId }: { orderId: string }) { + const actor = useActor({ name: "order", key: [orderId] }); + const [order, setOrder] = useState(null); + + useEffect(() => { + actor.connection?.getOrder().then(setOrder); + }, [actor.connection]); + + actor.useEvent("orderUpdated", setOrder); + + const getStatusColor = (status: Order["status"]) => { + switch (status) { + case "pending": + return "#8e8e93"; + case "validating": + case "charging": + case "fulfilling": + return "#ff9f0a"; + case "completed": + return "#30d158"; + case "failed": + return "#ff3b30"; + default: + return "#8e8e93"; + } + }; + + if (!order) return
Loading...
; + + return ( +
+
+ {order.id} + + {order.status} + +
+
+ {["Validate", "Charge", "Fulfill", "Complete"].map((step, idx) => ( +
idx ? "done" : ""} ${order.step === idx + 1 ? "active" : ""}`} + > +
+ {order.step > idx ? "✓" : idx + 1} +
+ {step} +
+ ))} +
+ {order.error &&
{order.error}
} +
+ ); +} + +// ============================================================================ +// SLEEP DEMO - One actor per timer +// ============================================================================ + +function SleepDemo() { + const [timerKeys, setTimerKeys] = usePersistedState< + { id: string; name: string; durationMs: number }[] + >("workflow-sandbox:timers", []); + const [duration, setDuration] = useState(10); + + const createTimer = () => { + const timerId = crypto.randomUUID(); + const name = `Timer ${timerKeys.length + 1}`; + setTimerKeys((prev) => [{ id: timerId, name, durationMs: duration * 1000 }, ...prev]); + }; + + return ( +
+
+

Sleep Demo

+

+ Durable sleep that survives restarts. Set a timer and watch it + countdown - even if you refresh the page! +

+
+ setDuration(parseInt(e.target.value) || 1)} + /> + seconds + +
+
+ +
+
+ {timerKeys.length === 0 && ( +

No timers yet. Create one to test durable sleep!

+ )} + {timerKeys.map((t) => ( + + ))} +
+
+
+ ); +} + +function TimerCard({ + timerId, + name, + durationMs, +}: { + timerId: string; + name: string; + durationMs: number; +}) { + const actor = useActor({ + name: "timer", + key: [timerId], + createWithInput: { name, durationMs }, + }); + const [timer, setTimer] = useState(null); + const [remaining, setRemaining] = useState(null); + + useEffect(() => { + actor.connection?.getTimer().then(setTimer); + }, [actor.connection]); + + actor.useEvent("timerStarted", setTimer); + actor.useEvent("timerCompleted", setTimer); + + useEffect(() => { + if (!timer) return; + if (timer.completedAt) { + setRemaining(0); + return; + } + + const update = () => { + const elapsed = Date.now() - timer.startedAt; + const left = Math.max(0, timer.durationMs - elapsed); + setRemaining(left); + }; + + update(); + const interval = setInterval(update, 100); + return () => clearInterval(interval); + }, [timer]); + + if (!timer || remaining === null) return
Loading...
; + + const isComplete = !!timer.completedAt; + const progress = isComplete + ? 100 + : ((timer.durationMs - remaining) / timer.durationMs) * 100; + + return ( +
+
+ {timer.name} + + {isComplete ? "Completed" : `${Math.ceil(remaining / 1000)}s`} + +
+
+
+
+
+ ); +} + +// ============================================================================ +// LOOPS DEMO - One actor per batch job +// ============================================================================ + +function LoopsDemo() { + const [jobKeys, setJobKeys] = usePersistedState("workflow-sandbox:jobs", []); + + const startJob = () => { + const jobId = `JOB-${Date.now().toString(36).toUpperCase()}`; + setJobKeys((prev) => [jobId, ...prev]); + }; + + return ( +
+
+

Loops Demo

+

+ Batch processing with persistent cursor state. Process 50 items in + batches of 5 - state persists across restarts. +

+ +
+ +
+
+ {jobKeys.length === 0 && ( +

No batch jobs yet. Start one to see loop processing!

+ )} + {jobKeys.map((jobId) => ( + + ))} +
+
+
+ ); +} + +function BatchJobCard({ jobId }: { jobId: string }) { + const actor = useActor({ + name: "batch", + key: [jobId], + createWithInput: { totalItems: 50, batchSize: 5 }, + }); + const [job, setJob] = useState(null); + + useEffect(() => { + actor.connection?.getJob().then(setJob); + }, [actor.connection]); + + actor.useEvent("stateChanged", setJob); + + if (!job) return
Loading...
; + + return ( +
+
+ {job.id} + {job.status} +
+
+
+ {job.processedTotal} + Items +
+
+ {job.batches.length} + Batches +
+
+
+
+
+
+ ); +} + +// ============================================================================ +// LISTEN DEMO - One actor per approval request +// ============================================================================ + +function ListenDemo() { + const [requestKeys, setRequestKeys] = usePersistedState< + { id: string; title: string; description: string }[] + >("workflow-sandbox:requests", []); + + const submitRequest = () => { + const requestId = crypto.randomUUID(); + const title = `Request ${requestKeys.length + 1}`; + setRequestKeys((prev) => [ + { id: requestId, title, description: "Please approve this request" }, + ...prev, + ]); + }; + + return ( +
+
+

Listen Demo

+

+ Approval workflow with 30-second timeout. Submit a request and + approve/reject it before it times out. +

+ +
+ +
+
+ {requestKeys.length === 0 && ( +

No requests yet. Submit one to test listen!

+ )} + {requestKeys.map((r) => ( + + ))} +
+
+
+ ); +} + +function ApprovalRequestCard({ + requestId, + title, + description, +}: { + requestId: string; + title: string; + description: string; +}) { + const actor = useActor({ + name: "approval", + key: [requestId], + createWithInput: { title, description }, + }); + const [request, setRequest] = useState(null); + + useEffect(() => { + actor.connection?.getRequest().then(setRequest); + }, [actor.connection]); + + actor.useEvent("requestCreated", setRequest); + actor.useEvent("requestUpdated", setRequest); + + const getStatusColor = (status: ApprovalRequest["status"]) => { + switch (status) { + case "pending": + return "#ff9f0a"; + case "approved": + return "#30d158"; + case "rejected": + return "#ff3b30"; + case "timeout": + return "#8e8e93"; + default: + return "#8e8e93"; + } + }; + + if (!request) return
Loading...
; + + const isPending = request.status === "pending" && !request.deciding; + const isDeciding = request.status === "pending" && request.deciding; + + return ( +
+
+ {request.title} + + {isDeciding ? "processing..." : request.status} + +
+ {isPending && ( + actor.connection?.approve(approver)} + onReject={(approver) => actor.connection?.reject(approver)} + /> + )} + {request.decidedBy && ( +
Decided by: {request.decidedBy}
+ )} +
+ ); +} + +function RequestCountdown({ + request, + onApprove, + onReject, +}: { + request: ApprovalRequest; + onApprove: (approver: string) => void; + onReject: (approver: string) => void; +}) { + const [remaining, setRemaining] = useState(30); + + useEffect(() => { + const update = () => { + const elapsed = Date.now() - request.createdAt; + const left = Math.max(0, 30000 - elapsed); + setRemaining(Math.ceil(left / 1000)); + }; + + update(); + const interval = setInterval(update, 1000); + return () => clearInterval(interval); + }, [request.createdAt]); + + return ( +
+ {remaining}s remaining + + +
+ ); +} + +// ============================================================================ +// JOIN DEMO - Single dashboard actor +// ============================================================================ + +function JoinDemo() { + const actor = useActor({ name: "dashboard", key: ["main"] }); + const [state, setState] = useState({ + data: null, + loading: false, + branches: { users: "pending", orders: "pending", metrics: "pending" }, + lastRefresh: null, + }); + + useEffect(() => { + actor.connection?.getState().then(setState); + }, [actor.connection]); + + actor.useEvent("stateChanged", setState); + + const getBranchColor = (status: string) => { + switch (status) { + case "pending": + return "#8e8e93"; + case "running": + return "#ff9f0a"; + case "completed": + return "#30d158"; + case "failed": + return "#ff3b30"; + default: + return "#8e8e93"; + } + }; + + return ( +
+
+

Join Demo

+

+ Parallel data fetching with join (wait-all). Fetch users, orders, and + metrics simultaneously. +

+ +
+ +
+
+ {(["users", "orders", "metrics"] as const).map((branch) => ( +
+ + {branch} + {state.branches[branch]} +
+ ))} +
+ + {state.data && ( +
+
+

Users

+
{state.data.users.count}
+
+ {state.data.users.activeToday} active today +
+
+
+

Orders

+
{state.data.orders.count}
+
+ ${state.data.orders.revenue.toLocaleString()} revenue +
+
+
+

Metrics

+
+ {state.data.metrics.pageViews.toLocaleString()} +
+
page views
+
+
+ )} +
+
+ ); +} + +// ============================================================================ +// RACE DEMO - One actor per race task +// ============================================================================ + +function RaceDemo() { + const [taskKeys, setTaskKeys] = usePersistedState< + { id: string; workDurationMs: number; timeoutMs: number }[] + >("workflow-sandbox:raceTasks", []); + const [workDuration, setWorkDuration] = useState(3000); + const [timeout, setTimeoutVal] = useState(5000); + + const runTask = () => { + const taskId = crypto.randomUUID(); + setTaskKeys((prev) => [ + { id: taskId, workDurationMs: workDuration, timeoutMs: timeout }, + ...prev, + ]); + }; + + return ( +
+
+

Race Demo

+

+ Race pattern - work vs timeout. If work completes before timeout, it + wins. Otherwise timeout wins. +

+
+
+ + setWorkDuration(parseInt(e.target.value) || 0)} + /> +
+
+ + setTimeoutVal(parseInt(e.target.value) || 0)} + /> +
+ +
+
+ +
+
+ {taskKeys.map((t) => ( + + ))} +
+
+
+ ); +} + +function RaceTaskCard({ + taskId, + workDurationMs, + timeoutMs, +}: { + taskId: string; + workDurationMs: number; + timeoutMs: number; +}) { + const actor = useActor({ + name: "race", + key: [taskId], + createWithInput: { workDurationMs, timeoutMs }, + }); + const [task, setTask] = useState(null); + const [elapsed, setElapsed] = useState(0); + const [showAnimation, setShowAnimation] = useState(true); + + useEffect(() => { + actor.connection?.getTask().then(setTask); + }, [actor.connection]); + + actor.useEvent("raceStarted", setTask); + actor.useEvent("raceCompleted", setTask); + + // Update elapsed time for animation + useEffect(() => { + if (!task) return; + + const updateElapsed = () => { + const now = task.completedAt ?? Date.now(); + const newElapsed = now - task.startedAt; + setElapsed(newElapsed); + + // Hide animation once both bars have completed + const maxDuration = Math.max(task.workDurationMs, task.timeoutMs); + if (newElapsed > maxDuration + 500) { + setShowAnimation(false); + } + }; + + updateElapsed(); + const interval = setInterval(updateElapsed, 50); + return () => clearInterval(interval); + }, [task]); + + if (!task) return
Loading...
; + + const workProgress = Math.min(100, (elapsed / task.workDurationMs) * 100); + const timeoutProgress = Math.min(100, (elapsed / task.timeoutMs) * 100); + const isCompleted = task.status !== "running"; + + // Show animation if still running or if recently completed + if (showAnimation) { + return ( +
+
+
+ Work ({task.workDurationMs}ms) +
+
+
+ {isCompleted && task.status === "work_won" && Winner!} +
+
+ Timeout ({task.timeoutMs}ms) +
+
+
+ {isCompleted && task.status === "timeout_won" && Winner!} +
+
+ {isCompleted && ( +
+ {task.status === "work_won" ? "Work completed first!" : "Timeout triggered!"} + {task.actualDurationMs}ms +
+ )} +
+ ); + } + + return ( +
+
+ {task.status === "work_won" ? "Work Won!" : "Timeout!"} +
+
+ Work: {task.workDurationMs}ms | Timeout: {task.timeoutMs}ms | Actual:{" "} + {task.actualDurationMs}ms +
+
+ ); +} + +// ============================================================================ +// ROLLBACK DEMO - One actor per transaction +// ============================================================================ + +function RollbackDemo() { + const [txKeys, setTxKeys] = usePersistedState< + { id: string; amount: number; shouldFail: boolean }[] + >("workflow-sandbox:transactions", []); + + const processPayment = (shouldFail: boolean) => { + const txId = crypto.randomUUID(); + const amount = Math.floor(50 + Math.random() * 200); + setTxKeys((prev) => [{ id: txId, amount, shouldFail }, ...prev]); + }; + + return ( +
+
+

Rollback Demo

+

+ Compensating transactions with rollback. Process payments with + automatic rollback on failure. +

+
+ + +
+
+ +
+
+ {txKeys.length === 0 && ( +

+ No transactions yet. Process a payment to see rollback! +

+ )} + {txKeys.map((t) => ( + + ))} +
+
+
+ ); +} + +function TransactionCard({ + txId, + amount, + shouldFail, +}: { + txId: string; + amount: number; + shouldFail: boolean; +}) { + const actor = useActor({ + name: "payment", + key: [txId], + createWithInput: { amount, shouldFail }, + }); + const [tx, setTx] = useState(null); + + useEffect(() => { + actor.connection?.getTransaction().then(setTx); + }, [actor.connection]); + + actor.useEvent("transactionStarted", setTx); + actor.useEvent("transactionUpdated", setTx); + actor.useEvent("transactionCompleted", setTx); + actor.useEvent("transactionFailed", setTx); + + const getStepColor = (status: string) => { + switch (status) { + case "pending": + return "#8e8e93"; + case "running": + return "#ff9f0a"; + case "completed": + return "#30d158"; + case "rolling_back": + return "#bf5af2"; + case "rolled_back": + return "#bf5af2"; + case "failed": + return "#ff3b30"; + default: + return "#8e8e93"; + } + }; + + const getStepIcon = (status: string) => { + switch (status) { + case "pending": + return "○"; + case "completed": + return "✓"; + case "rolled_back": + return "↩"; + default: + return "○"; + } + }; + + if (!tx) return
Loading...
; + + const isRollingBack = tx.status === "rolling_back"; + const hasRollback = tx.steps.some((s) => s.status === "rolled_back"); + + return ( +
+
+ ${tx.amount} + + {tx.status === "rolling_back" ? "↩ rolling back" : tx.status} + +
+ {isRollingBack && ( +
+ Compensating actions in progress... +
+ )} +
+ {tx.steps.map((step) => ( +
+ + {getStepIcon(step.status)} + + {step.name} + + {step.status} + {step.status === "rolled_back" && " ↩"} + +
+ ))} +
+ {hasRollback && tx.status === "failed" && ( +
+ All completed steps have been rolled back +
+ )} + {tx.error &&
{tx.error}
} +
+ ); +} diff --git a/examples/workflow-sandbox/frontend/main.tsx b/examples/workflow-sandbox/frontend/main.tsx new file mode 100644 index 0000000000..372f49c622 --- /dev/null +++ b/examples/workflow-sandbox/frontend/main.tsx @@ -0,0 +1,12 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { App } from "./App.tsx"; + +const root = document.getElementById("root"); +if (!root) throw new Error("Root element not found"); + +createRoot(root).render( + + + +); diff --git a/examples/workflow-sandbox/index.html b/examples/workflow-sandbox/index.html new file mode 100644 index 0000000000..662a01901b --- /dev/null +++ b/examples/workflow-sandbox/index.html @@ -0,0 +1,878 @@ + + + + + + Workflow Sandbox + + + +
+ + + diff --git a/examples/workflow-sandbox/package.json b/examples/workflow-sandbox/package.json new file mode 100644 index 0000000000..032a106076 --- /dev/null +++ b/examples/workflow-sandbox/package.json @@ -0,0 +1,39 @@ +{ + "name": "workflow-sandbox", + "version": "2.0.21", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "check-types": "tsc --noEmit", + "build": "vite build && vite build --mode server", + "start": "srvx --static=public/ dist/server.js" + }, + "dependencies": { + "@hono/node-server": "^1.19.7", + "@hono/node-ws": "^1.3.0", + "@rivetkit/react": "*", + "hono": "^4.11.3", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "rivetkit": "*", + "srvx": "^0.10.0" + }, + "devDependencies": { + "@types/node": "^22.13.9", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "@vitejs/plugin-react": "^4.2.0", + "tsx": "^3.12.7", + "typescript": "^5.5.2", + "vite": "^5.0.0", + "vite-plugin-srvx": "^1.0.0" + }, + "stableVersion": "0.8.0", + "template": { + "technologies": ["react", "typescript"], + "tags": ["experimental"], + "frontendPort": 5173 + }, + "license": "MIT" +} diff --git a/examples/workflow-sandbox/src/actors.ts b/examples/workflow-sandbox/src/actors.ts new file mode 100644 index 0000000000..24d87fdad9 --- /dev/null +++ b/examples/workflow-sandbox/src/actors.ts @@ -0,0 +1,63 @@ +// Workflow Sandbox - Actor Registry +// Each actor demonstrates a different workflow feature using actor-per-workflow pattern + +import { setup } from "rivetkit"; + +// Import actors from individual files +export { timer } from "./actors/timer.ts"; +export type { Timer, TimerInput } from "./actors/timer.ts"; + +export { order } from "./actors/order.ts"; +export type { Order, OrderStatus } from "./actors/order.ts"; + +export { batch } from "./actors/batch.ts"; +export type { BatchInfo, BatchJob, BatchJobInput } from "./actors/batch.ts"; + +export { approval } from "./actors/approval.ts"; +export type { + ApprovalRequest, + ApprovalRequestInput, + RequestStatus, +} from "./actors/approval.ts"; + +export { dashboard } from "./actors/dashboard.ts"; +export type { + DashboardData, + DashboardState, + UserStats, + OrderStats, + MetricsStats, + BranchStatus, +} from "./actors/dashboard.ts"; + +export { race } from "./actors/race.ts"; +export type { RaceTask, RaceTaskInput } from "./actors/race.ts"; + +export { payment } from "./actors/payment.ts"; +export type { + Transaction, + TransactionStep, + TransactionInput, +} from "./actors/payment.ts"; + +// Import for registry setup +import { timer } from "./actors/timer.ts"; +import { order } from "./actors/order.ts"; +import { batch } from "./actors/batch.ts"; +import { approval } from "./actors/approval.ts"; +import { dashboard } from "./actors/dashboard.ts"; +import { race } from "./actors/race.ts"; +import { payment } from "./actors/payment.ts"; + +// Registry setup +export const registry = setup({ + use: { + timer, + order, + batch, + approval, + dashboard, + race, + payment, + }, +}); diff --git a/examples/workflow-sandbox/src/actors/_helpers.ts b/examples/workflow-sandbox/src/actors/_helpers.ts new file mode 100644 index 0000000000..3c6eed304c --- /dev/null +++ b/examples/workflow-sandbox/src/actors/_helpers.ts @@ -0,0 +1,12 @@ +// Type helper - cast loop context to access actor-specific properties +// Only call these helpers INSIDE a step callback where state access is allowed +// biome-ignore lint/suspicious/noExplicitAny: Workflow context typing workaround +export type ActorLoopContext = { + state: S; + broadcast: (name: string, ...args: unknown[]) => void; +}; + +// biome-ignore lint/suspicious/noExplicitAny: Workflow context typing workaround +export function actorCtx(ctx: unknown): ActorLoopContext { + return ctx as any; +} diff --git a/examples/workflow-sandbox/src/actors/approval.ts b/examples/workflow-sandbox/src/actors/approval.ts new file mode 100644 index 0000000000..d6d5225c72 --- /dev/null +++ b/examples/workflow-sandbox/src/actors/approval.ts @@ -0,0 +1,110 @@ +// APPROVAL REQUEST (Listen Demo) +// Demonstrates: Message listening with timeout for approval workflows +// One actor per approval request - actor key is the request ID + +import { actor } from "rivetkit"; +import { Loop, workflow, workflowQueueName } from "rivetkit/workflow"; +import { actorCtx } from "./_helpers.ts"; + +export type RequestStatus = "pending" | "approved" | "rejected" | "timeout"; + +export type ApprovalRequest = { + id: string; + title: string; + description: string; + status: RequestStatus; + createdAt: number; + decidedAt?: number; + decidedBy?: string; + deciding?: boolean; // True when a decision is being processed +}; + +type State = ApprovalRequest; + +const QUEUE_DECISION = workflowQueueName("decision"); + +const APPROVAL_TIMEOUT_MS = 30000; + +export type ApprovalRequestInput = { + title?: string; + description?: string; +}; + +export const approval = actor({ + createState: (c, input: ApprovalRequestInput): ApprovalRequest => ({ + id: c.key[0] as string, + title: input?.title ?? "Untitled Request", + description: input?.description ?? "", + status: "pending", + createdAt: Date.now(), + }), + + actions: { + getRequest: (c): ApprovalRequest => c.state, + + approve: async (c, approver: string) => { + if (c.state.status !== "pending") return; + c.state.deciding = true; + c.broadcast("requestUpdated", c.state); + await c.queue.send(QUEUE_DECISION, { approved: true, approver }); + }, + + reject: async (c, approver: string) => { + if (c.state.status !== "pending") return; + c.state.deciding = true; + c.broadcast("requestUpdated", c.state); + await c.queue.send(QUEUE_DECISION, { approved: false, approver }); + }, + }, + + run: workflow(async (ctx) => { + await ctx.loop({ + name: "approval-loop", + run: async (loopCtx) => { + const c = actorCtx(loopCtx); + + await loopCtx.step("init-request", async () => { + ctx.log.info({ + msg: "waiting for approval decision", + requestId: c.state.id, + title: c.state.title, + }); + c.broadcast("requestCreated", c.state); + }); + + const decision = await loopCtx.listenWithTimeout<{ + approved: boolean; + approver: string; + }>("wait-decision", "decision", APPROVAL_TIMEOUT_MS); + + await loopCtx.step("update-status", async () => { + c.state.deciding = false; + if (decision === null) { + c.state.status = "timeout"; + ctx.log.info({ msg: "request timed out", requestId: c.state.id }); + } else if (decision.approved) { + c.state.status = "approved"; + c.state.decidedBy = decision.approver; + ctx.log.info({ + msg: "request approved", + requestId: c.state.id, + approver: decision.approver, + }); + } else { + c.state.status = "rejected"; + c.state.decidedBy = decision.approver; + ctx.log.info({ + msg: "request rejected", + requestId: c.state.id, + approver: decision.approver, + }); + } + c.state.decidedAt = Date.now(); + c.broadcast("requestUpdated", c.state); + }); + + return Loop.break(undefined); + }, + }); + }), +}); diff --git a/examples/workflow-sandbox/src/actors/batch.ts b/examples/workflow-sandbox/src/actors/batch.ts new file mode 100644 index 0000000000..921c055a72 --- /dev/null +++ b/examples/workflow-sandbox/src/actors/batch.ts @@ -0,0 +1,125 @@ +// BATCH PROCESSOR (Loops Demo) +// Demonstrates: Loop with persistent state (cursor) for batch processing +// One actor per batch job - actor key is the job ID + +import { actor } from "rivetkit"; +import { Loop, workflow } from "rivetkit/workflow"; +import { actorCtx } from "./_helpers.ts"; + +export type BatchInfo = { + id: number; + count: number; + processedAt: number; +}; + +export type BatchJob = { + id: string; + totalItems: number; + batchSize: number; + status: "running" | "stopped" | "completed"; + processedTotal: number; + currentBatch: number; + batches: BatchInfo[]; + startedAt: number; + completedAt?: number; +}; + +type State = BatchJob; + +function fetchBatch( + cursor: number, + batchSize: number, + totalItems: number +): { items: number[]; hasMore: boolean } { + const start = cursor * batchSize; + const end = Math.min(start + batchSize, totalItems); + const items = []; + for (let i = start; i < end; i++) { + items.push(i); + } + return { + items, + hasMore: end < totalItems, + }; +} + +export type BatchJobInput = { + totalItems?: number; + batchSize?: number; +}; + +export const batch = actor({ + createState: (c, input: BatchJobInput): BatchJob => ({ + id: c.key[0] as string, + totalItems: input?.totalItems ?? 50, + batchSize: input?.batchSize ?? 5, + status: "running", + processedTotal: 0, + currentBatch: 0, + batches: [], + startedAt: Date.now(), + }), + + actions: { + getJob: (c): BatchJob => c.state, + }, + + run: workflow(async (ctx) => { + await ctx.loop({ + name: "batch-loop", + state: { cursor: 0 }, + run: async (batchCtx, loopState: { cursor: number }) => { + const c = actorCtx(batchCtx); + + const batch = await batchCtx.step("fetch-batch", async () => { + ctx.log.info({ + msg: "processing batch", + jobId: c.state.id, + cursor: loopState.cursor, + }); + await new Promise((r) => setTimeout(r, 200 + Math.random() * 300)); + return fetchBatch(loopState.cursor, c.state.batchSize, c.state.totalItems); + }); + + await batchCtx.step("process-batch", async () => { + await new Promise((r) => setTimeout(r, 300 + Math.random() * 500)); + + const batchInfo: BatchInfo = { + id: loopState.cursor, + count: batch.items.length, + processedAt: Date.now(), + }; + + c.state.currentBatch = loopState.cursor; + c.state.processedTotal += batch.items.length; + c.state.batches.push(batchInfo); + + c.broadcast("batchProcessed", batchInfo); + c.broadcast("stateChanged", c.state); + + ctx.log.info({ + msg: "batch processed", + jobId: c.state.id, + cursor: loopState.cursor, + count: batch.items.length, + }); + }); + + if (!batch.hasMore) { + await batchCtx.step("mark-complete", async () => { + c.state.status = "completed"; + c.state.completedAt = Date.now(); + c.broadcast("stateChanged", c.state); + c.broadcast("processingComplete", { + totalBatches: loopState.cursor + 1, + totalItems: c.state.processedTotal, + }); + }); + return Loop.break(loopState.cursor + 1); + } + + return Loop.continue({ cursor: loopState.cursor + 1 }); + }, + }); + }), +}); diff --git a/examples/workflow-sandbox/src/actors/dashboard.ts b/examples/workflow-sandbox/src/actors/dashboard.ts new file mode 100644 index 0000000000..86887d466e --- /dev/null +++ b/examples/workflow-sandbox/src/actors/dashboard.ts @@ -0,0 +1,203 @@ +// DASHBOARD (Join Demo) +// Demonstrates: Parallel data fetching with join (wait-all) + +import { actor } from "rivetkit"; +import { Loop, workflow, workflowQueueName } from "rivetkit/workflow"; +import { actorCtx } from "./_helpers.ts"; + +export type UserStats = { + count: number; + activeToday: number; + newThisWeek: number; +}; + +export type OrderStats = { + count: number; + revenue: number; + avgOrderValue: number; +}; + +export type MetricsStats = { + pageViews: number; + sessions: number; + bounceRate: number; +}; + +export type DashboardData = { + users: UserStats; + orders: OrderStats; + metrics: MetricsStats; + fetchedAt: number; +}; + +export type BranchStatus = "pending" | "running" | "completed" | "failed"; + +export type DashboardState = { + data: DashboardData | null; + loading: boolean; + branches: { + users: BranchStatus; + orders: BranchStatus; + metrics: BranchStatus; + }; + lastRefresh: number | null; +}; + +type State = DashboardState; + +const QUEUE_REFRESH = workflowQueueName("refresh"); + +async function fetchUserStats(): Promise { + await new Promise((r) => setTimeout(r, 800 + Math.random() * 1200)); + return { + count: Math.floor(1000 + Math.random() * 500), + activeToday: Math.floor(100 + Math.random() * 200), + newThisWeek: Math.floor(20 + Math.random() * 80), + }; +} + +async function fetchOrderStats(): Promise { + await new Promise((r) => setTimeout(r, 600 + Math.random() * 1000)); + const count = Math.floor(50 + Math.random() * 150); + const revenue = Math.floor(5000 + Math.random() * 15000); + return { + count, + revenue, + avgOrderValue: Math.round(revenue / count), + }; +} + +async function fetchMetricsStats(): Promise { + await new Promise((r) => setTimeout(r, 400 + Math.random() * 800)); + return { + pageViews: Math.floor(10000 + Math.random() * 50000), + sessions: Math.floor(2000 + Math.random() * 8000), + bounceRate: Math.round(30 + Math.random() * 40), + }; +} + +export const dashboard = actor({ + state: { + data: null as DashboardData | null, + loading: false, + branches: { + users: "pending" as BranchStatus, + orders: "pending" as BranchStatus, + metrics: "pending" as BranchStatus, + }, + lastRefresh: null as number | null, + }, + + actions: { + refresh: async (c) => { + if (!c.state.loading) { + c.state.loading = true; + c.state.branches = { + users: "pending", + orders: "pending", + metrics: "pending", + }; + c.broadcast("stateChanged", c.state); + await c.queue.send(QUEUE_REFRESH, {}); + } + }, + + getState: (c): DashboardState => c.state, + }, + + run: workflow(async (ctx) => { + await ctx.loop({ + name: "refresh-loop", + run: async (loopCtx) => { + const c = actorCtx(loopCtx); + + await loopCtx.listen("wait-refresh", "refresh"); + + ctx.log.info({ msg: "starting dashboard refresh" }); + + const results = await loopCtx.join("fetch-all", { + users: { + run: async (branchCtx) => { + const bc = actorCtx(branchCtx); + + await branchCtx.step("mark-running", async () => { + bc.state.branches.users = "running"; + bc.broadcast("stateChanged", bc.state); + }); + + const data = await branchCtx.step("fetch-users", async () => { + return await fetchUserStats(); + }); + + await branchCtx.step("mark-complete", async () => { + bc.state.branches.users = "completed"; + bc.broadcast("stateChanged", bc.state); + }); + + return data; + }, + }, + orders: { + run: async (branchCtx) => { + const bc = actorCtx(branchCtx); + + await branchCtx.step("mark-running", async () => { + bc.state.branches.orders = "running"; + bc.broadcast("stateChanged", bc.state); + }); + + const data = await branchCtx.step("fetch-orders", async () => { + return await fetchOrderStats(); + }); + + await branchCtx.step("mark-complete", async () => { + bc.state.branches.orders = "completed"; + bc.broadcast("stateChanged", bc.state); + }); + + return data; + }, + }, + metrics: { + run: async (branchCtx) => { + const bc = actorCtx(branchCtx); + + await branchCtx.step("mark-running", async () => { + bc.state.branches.metrics = "running"; + bc.broadcast("stateChanged", bc.state); + }); + + const data = await branchCtx.step("fetch-metrics", async () => { + return await fetchMetricsStats(); + }); + + await branchCtx.step("mark-complete", async () => { + bc.state.branches.metrics = "completed"; + bc.broadcast("stateChanged", bc.state); + }); + + return data; + }, + }, + }); + + await loopCtx.step("save-data", async () => { + c.state.data = { + users: results.users, + orders: results.orders, + metrics: results.metrics, + fetchedAt: Date.now(), + }; + c.state.loading = false; + c.state.lastRefresh = Date.now(); + c.broadcast("stateChanged", c.state); + c.broadcast("refreshComplete", c.state.data); + }); + + ctx.log.info({ msg: "dashboard refresh complete" }); + + return Loop.continue(undefined); + }, + }); + }), +}); diff --git a/examples/workflow-sandbox/src/actors/order.ts b/examples/workflow-sandbox/src/actors/order.ts new file mode 100644 index 0000000000..12b5344744 --- /dev/null +++ b/examples/workflow-sandbox/src/actors/order.ts @@ -0,0 +1,89 @@ +// ORDER PROCESSOR (Steps Demo) +// Demonstrates: Sequential workflow steps with automatic retries +// One actor per order - actor key is the order ID + +import { actor } from "rivetkit"; +import { Loop, workflow } from "rivetkit/workflow"; +import { actorCtx } from "./_helpers.ts"; + +export type OrderStatus = + | "pending" + | "validating" + | "charging" + | "fulfilling" + | "completed" + | "failed"; + +export type Order = { + id: string; + status: OrderStatus; + step: number; + error?: string; + createdAt: number; + completedAt?: number; +}; + +type State = Order; + +async function simulateWork(name: string, failChance = 0.1): Promise { + await new Promise((resolve) => + setTimeout(resolve, 500 + Math.random() * 1000) + ); + if (Math.random() < failChance) { + throw new Error(`${name} failed (simulated)`); + } +} + +export const order = actor({ + createState: (c): Order => ({ + id: c.key[0] as string, + status: "pending", + step: 0, + createdAt: Date.now(), + }), + + actions: { + getOrder: (c): Order => c.state, + }, + + run: workflow(async (ctx) => { + await ctx.loop({ + name: "process-order", + run: async (loopCtx) => { + const c = actorCtx(loopCtx); + + await loopCtx.step("validate", async () => { + ctx.log.info({ msg: "processing order", orderId: c.state.id }); + c.state.status = "validating"; + c.state.step = 1; + c.broadcast("orderUpdated", c.state); + await simulateWork("validation", 0.05); + }); + + await loopCtx.step("charge", async () => { + c.state.status = "charging"; + c.state.step = 2; + c.broadcast("orderUpdated", c.state); + await simulateWork("payment", 0.1); + }); + + await loopCtx.step("fulfill", async () => { + c.state.status = "fulfilling"; + c.state.step = 3; + c.broadcast("orderUpdated", c.state); + await simulateWork("fulfillment", 0.05); + }); + + await loopCtx.step("complete", async () => { + c.state.status = "completed"; + c.state.step = 4; + c.state.completedAt = Date.now(); + c.broadcast("orderUpdated", c.state); + ctx.log.info({ msg: "order completed", orderId: c.state.id }); + }); + + return Loop.break(undefined); + }, + }); + }), +}); diff --git a/examples/workflow-sandbox/src/actors/payment.ts b/examples/workflow-sandbox/src/actors/payment.ts new file mode 100644 index 0000000000..c9ad701a82 --- /dev/null +++ b/examples/workflow-sandbox/src/actors/payment.ts @@ -0,0 +1,175 @@ +// PAYMENT PROCESSOR (Rollback Demo) +// Demonstrates: Rollback checkpoints with compensating actions +// One actor per transaction - actor key is the transaction ID + +import { actor } from "rivetkit"; +import { Loop, workflow } from "rivetkit/workflow"; +import { actorCtx } from "./_helpers.ts"; + +export type TransactionStep = { + name: string; + status: "pending" | "completed" | "rolled_back"; + completedAt?: number; + rolledBackAt?: number; +}; + +export type Transaction = { + id: string; + amount: number; + shouldFail: boolean; + status: + | "pending" + | "reserving" + | "charging" + | "completing" + | "completed" + | "rolling_back" + | "failed"; + steps: TransactionStep[]; + error?: string; + startedAt: number; + completedAt?: number; +}; + +type State = Transaction; + +export type TransactionInput = { + amount?: number; + shouldFail?: boolean; +}; + +export const payment = actor({ + createState: (c, input: TransactionInput): Transaction => ({ + id: c.key[0] as string, + amount: input?.amount ?? 100, + shouldFail: input?.shouldFail ?? false, + status: "pending", + steps: [ + { name: "reserve-inventory", status: "pending" }, + { name: "charge-card", status: "pending" }, + { name: "complete-order", status: "pending" }, + ], + startedAt: Date.now(), + }), + + actions: { + getTransaction: (c): Transaction => c.state, + }, + + run: workflow(async (ctx) => { + await ctx.loop({ + name: "payment-loop", + run: async (loopCtx) => { + const c = actorCtx(loopCtx); + + await loopCtx.step("init-payment", async () => { + ctx.log.info({ + msg: "starting payment processing", + txId: c.state.id, + amount: c.state.amount, + shouldFail: c.state.shouldFail, + }); + c.broadcast("transactionStarted", c.state); + }); + + await loopCtx.rollbackCheckpoint("payment-checkpoint"); + + // Step 1: Reserve inventory + await loopCtx.step({ + name: "reserve-inventory", + run: async () => { + c.state.status = "reserving"; + const step = c.state.steps.find( + (s) => s.name === "reserve-inventory" + ); + if (step) { + step.status = "completed"; + step.completedAt = Date.now(); + } + c.broadcast("transactionUpdated", c.state); + + await new Promise((r) => + setTimeout(r, 500 + Math.random() * 500) + ); + ctx.log.info({ msg: "inventory reserved", txId: c.state.id }); + return { reserved: true }; + }, + rollback: async () => { + // Set rolling_back status on first rollback + c.state.status = "rolling_back"; + const step = c.state.steps.find( + (s) => s.name === "reserve-inventory" + ); + if (step) { + step.status = "rolled_back"; + step.rolledBackAt = Date.now(); + } + ctx.log.info({ msg: "inventory released", txId: c.state.id }); + c.broadcast("transactionUpdated", c.state); + // Small delay so UI can show the rollback + await new Promise((r) => setTimeout(r, 400)); + }, + }); + + // Step 2: Charge card + await loopCtx.step({ + name: "charge-card", + run: async () => { + c.state.status = "charging"; + const step = c.state.steps.find((s) => s.name === "charge-card"); + if (step) { + step.status = "completed"; + step.completedAt = Date.now(); + } + c.broadcast("transactionUpdated", c.state); + + await new Promise((r) => + setTimeout(r, 500 + Math.random() * 500) + ); + + if (c.state.shouldFail) { + throw new Error("Payment declined (simulated)"); + } + + ctx.log.info({ msg: "card charged", txId: c.state.id }); + return { chargeId: `ch_${c.state.id}` }; + }, + rollback: async () => { + c.state.status = "rolling_back"; + const step = c.state.steps.find((s) => s.name === "charge-card"); + if (step) { + step.status = "rolled_back"; + step.rolledBackAt = Date.now(); + } + ctx.log.info({ msg: "charge refunded", txId: c.state.id }); + c.broadcast("transactionUpdated", c.state); + // Small delay so UI can show the rollback + await new Promise((r) => setTimeout(r, 400)); + }, + }); + + // Step 3: Complete order + await loopCtx.step({ + name: "complete-order", + run: async () => { + c.state.status = "completing"; + const step = c.state.steps.find((s) => s.name === "complete-order"); + if (step) step.status = "completed"; + c.broadcast("transactionUpdated", c.state); + + await new Promise((r) => + setTimeout(r, 300 + Math.random() * 300) + ); + + c.state.status = "completed"; + c.state.completedAt = Date.now(); + ctx.log.info({ msg: "order completed", txId: c.state.id }); + c.broadcast("transactionCompleted", c.state); + }, + }); + + return Loop.break(undefined); + }, + }); + }), +}); diff --git a/examples/workflow-sandbox/src/actors/race.ts b/examples/workflow-sandbox/src/actors/race.ts new file mode 100644 index 0000000000..173055591c --- /dev/null +++ b/examples/workflow-sandbox/src/actors/race.ts @@ -0,0 +1,112 @@ +// RACE RUNNER (Race Demo) +// Demonstrates: Race (parallel first-wins) for timeout patterns +// One actor per race task - actor key is the task ID + +import { actor } from "rivetkit"; +import { Loop, workflow } from "rivetkit/workflow"; +import { actorCtx } from "./_helpers.ts"; + +export type RaceTask = { + id: string; + workDurationMs: number; + timeoutMs: number; + status: "running" | "work_won" | "timeout_won"; + result?: string; + startedAt: number; + completedAt?: number; + actualDurationMs?: number; +}; + +type State = RaceTask; + +export type RaceTaskInput = { + workDurationMs?: number; + timeoutMs?: number; +}; + +export const race = actor({ + createState: (c, input: RaceTaskInput): RaceTask => ({ + id: c.key[0] as string, + workDurationMs: input?.workDurationMs ?? 2000, + timeoutMs: input?.timeoutMs ?? 3000, + status: "running", + startedAt: Date.now(), + }), + + actions: { + getTask: (c): RaceTask => c.state, + }, + + run: workflow(async (ctx) => { + await ctx.loop({ + name: "race-loop", + run: async (loopCtx) => { + const c = actorCtx(loopCtx); + + // Get durations inside a step since state is only available in steps + const { workDurationMs, timeoutMs, taskId } = await loopCtx.step( + "start-race", + async () => { + ctx.log.info({ + msg: "starting race", + taskId: c.state.id, + workDurationMs: c.state.workDurationMs, + timeoutMs: c.state.timeoutMs, + }); + c.broadcast("raceStarted", c.state); + return { + workDurationMs: c.state.workDurationMs, + timeoutMs: c.state.timeoutMs, + taskId: c.state.id, + }; + } + ); + + const { winner, value } = await loopCtx.race("work-vs-timeout", [ + { + name: "work", + run: async (branchCtx) => { + await branchCtx.sleep("simulate-work", workDurationMs); + return await branchCtx.step("complete-work", async () => { + return `Result for task ${taskId}`; + }); + }, + }, + { + name: "timeout", + run: async (branchCtx) => { + await branchCtx.sleep("timeout-wait", timeoutMs); + return null; + }, + }, + ]); + + await loopCtx.step("save-result", async () => { + c.state.completedAt = Date.now(); + c.state.actualDurationMs = c.state.completedAt - c.state.startedAt; + + if (winner === "work") { + c.state.status = "work_won"; + c.state.result = value as string; + ctx.log.info({ + msg: "work completed before timeout", + taskId: c.state.id, + durationMs: c.state.actualDurationMs, + }); + } else { + c.state.status = "timeout_won"; + ctx.log.info({ + msg: "timeout won the race", + taskId: c.state.id, + durationMs: c.state.actualDurationMs, + }); + } + + c.broadcast("raceCompleted", c.state); + }); + + return Loop.break(undefined); + }, + }); + }), +}); diff --git a/examples/workflow-sandbox/src/actors/timer.ts b/examples/workflow-sandbox/src/actors/timer.ts new file mode 100644 index 0000000000..d642dfa740 --- /dev/null +++ b/examples/workflow-sandbox/src/actors/timer.ts @@ -0,0 +1,69 @@ +// TIMER (Sleep Demo) +// Demonstrates: Durable sleep that survives restarts +// One actor per timer - actor key is the timer ID + +import { actor } from "rivetkit"; +import { Loop, workflow } from "rivetkit/workflow"; +import { actorCtx } from "./_helpers.ts"; + +export type Timer = { + id: string; + name: string; + durationMs: number; + startedAt: number; + completedAt?: number; +}; + +type State = Timer; + +export type TimerInput = { + name?: string; + durationMs?: number; +}; + +export const timer = actor({ + createState: (c, input: TimerInput): Timer => ({ + id: c.key[0] as string, + name: input?.name ?? "Timer", + durationMs: input?.durationMs ?? 10000, + startedAt: Date.now(), + }), + + actions: { + getTimer: (c): Timer => c.state, + }, + + run: workflow(async (ctx) => { + await ctx.loop({ + name: "timer-loop", + run: async (loopCtx) => { + const c = actorCtx(loopCtx); + + // Get duration inside a step since state is only available in steps + const durationMs = await loopCtx.step("start-timer", async () => { + ctx.log.info({ + msg: "starting timer", + timerId: c.state.id, + durationMs: c.state.durationMs, + }); + c.broadcast("timerStarted", c.state); + return c.state.durationMs; + }); + + await loopCtx.sleep("countdown", durationMs); + + await loopCtx.step("complete-timer", async () => { + c.state.completedAt = Date.now(); + c.broadcast("timerCompleted", c.state); + ctx.log.info({ msg: "timer completed", timerId: c.state.id }); + }); + + return Loop.break(undefined); + }, + }); + }), + + options: { + sleepTimeout: 1000, + }, +}); diff --git a/examples/workflow-sandbox/src/server.ts b/examples/workflow-sandbox/src/server.ts new file mode 100644 index 0000000000..8c065a6b5e --- /dev/null +++ b/examples/workflow-sandbox/src/server.ts @@ -0,0 +1,8 @@ +import { Hono } from "hono"; +import { registry } from "./actors.ts"; + +const app = new Hono(); + +app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); + +export default app; diff --git a/examples/workflow-sandbox/tsconfig.json b/examples/workflow-sandbox/tsconfig.json new file mode 100644 index 0000000000..6daa5df3fd --- /dev/null +++ b/examples/workflow-sandbox/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "esnext", + "lib": ["esnext", "dom"], + "jsx": "react-jsx", + "module": "esnext", + "moduleResolution": "bundler", + "types": ["node", "vite/client"], + "noEmit": true, + "strict": true, + "skipLibCheck": true, + "allowImportingTsExtensions": true, + "rewriteRelativeImportExtensions": true + }, + "include": ["src/**/*", "frontend/**/*"] +} diff --git a/examples/workflow-sandbox/turbo.json b/examples/workflow-sandbox/turbo.json new file mode 100644 index 0000000000..c5e71016d3 --- /dev/null +++ b/examples/workflow-sandbox/turbo.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://turbo.build/schema.json", + "extends": ["//"], + "tasks": { + "build": { + "dependsOn": ["@rivetkit/react#build", "rivetkit#build"] + } + } +} diff --git a/examples/workflow-sandbox/vite.config.ts b/examples/workflow-sandbox/vite.config.ts new file mode 100644 index 0000000000..06dae893f5 --- /dev/null +++ b/examples/workflow-sandbox/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import srvx from "vite-plugin-srvx"; + +export default defineConfig({ + plugins: [react(), ...srvx({ entry: "src/server.ts" })], +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e57f812eda..9cf22e769d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2753,6 +2753,58 @@ importers: specifier: ^5.5.2 version: 5.9.3 + examples/workflow-sandbox: + dependencies: + '@hono/node-server': + specifier: ^1.19.7 + version: 1.19.9(hono@4.11.3) + '@hono/node-ws': + specifier: ^1.3.0 + version: 1.3.0(@hono/node-server@1.19.9(hono@4.11.3))(hono@4.11.3) + '@rivetkit/react': + specifier: workspace:* + version: link:../../rivetkit-typescript/packages/react + hono: + specifier: ^4.11.3 + version: 4.11.3 + react: + specifier: 19.1.0 + version: 19.1.0 + react-dom: + specifier: 19.1.0 + version: 19.1.0(react@19.1.0) + rivetkit: + specifier: workspace:* + version: link:../../rivetkit-typescript/packages/rivetkit + srvx: + specifier: ^0.10.0 + version: 0.10.0 + devDependencies: + '@types/node': + specifier: ^22.13.9 + version: 22.19.5 + '@types/react': + specifier: ^19 + version: 19.2.2 + '@types/react-dom': + specifier: ^19 + version: 19.2.2(@types/react@19.2.2) + '@vitejs/plugin-react': + specifier: ^4.2.0 + version: 4.7.0(vite@5.4.20(@types/node@22.19.5)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.44.1)) + tsx: + specifier: ^3.12.7 + version: 3.14.0 + typescript: + specifier: ^5.5.2 + version: 5.9.3 + vite: + specifier: ^5.0.0 + version: 5.4.20(@types/node@22.19.5)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.44.1) + vite-plugin-srvx: + specifier: ^1.0.0 + version: 1.0.0(srvx@0.10.0)(vite@5.4.20(@types/node@22.19.5)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.44.1)) + frontend: dependencies: '@clerk/clerk-js': @@ -3844,6 +3896,9 @@ importers: fdb-tuple: specifier: ^1.0.0 version: 1.0.0 + pino: + specifier: ^9.6.0 + version: 9.9.5 vbare: specifier: ^0.0.4 version: 0.0.4 @@ -31434,6 +31489,11 @@ snapshots: srvx: 0.10.0 vite: 5.4.20(@types/node@22.19.3)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.44.1) + vite-plugin-srvx@1.0.0(srvx@0.10.0)(vite@5.4.20(@types/node@22.19.5)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.44.1)): + dependencies: + srvx: 0.10.0 + vite: 5.4.20(@types/node@22.19.5)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.44.1) + vite-plugin-srvx@1.0.0(srvx@0.10.0)(vite@6.4.1(@types/node@22.18.1)(jiti@1.21.7)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.44.1)(tsx@4.20.5)(yaml@2.8.2)): dependencies: srvx: 0.10.0 diff --git a/prd.txt b/prd.txt new file mode 100644 index 0000000000..8a5abe937f --- /dev/null +++ b/prd.txt @@ -0,0 +1,6 @@ +- get all parts tabs & buttons of the examples/workflow-sandbox/ example working as you'd expect. fix any bugs in the example or rivetkit. +- write oneoff throwaway test to starting a workflow, killing the process, and ensure workflow continues from mid-run. be 100% sure that worklfows can be terminated and proceed to run from the correct place. +- ensure that all parts of examples/queue-sandbox/ works as intended. fix any bugs in the example or rivetkit. +- read driver test suite actors & tests & examples/queue-sandbox for queue. write documentation page on queues. add to sitemap. read other docs pages to see good structure first. cover all features of queues. +- read driver test suite actors & test & examples/workflow-sandbox for workflows. write documentation page on workflows. add to sitemap. read other docs pages to see good structure first. cover all features of workflows. +- update rivetkit client to allow overriding the runner name configured in the client in the getorcreate and create calls. also enable this in the react integration. diff --git a/rivetkit-typescript/packages/rivetkit/src/actor/config.ts b/rivetkit-typescript/packages/rivetkit/src/actor/config.ts index 2a33749a6b..6ecd5884dd 100644 --- a/rivetkit-typescript/packages/rivetkit/src/actor/config.ts +++ b/rivetkit-typescript/packages/rivetkit/src/actor/config.ts @@ -21,6 +21,17 @@ import type { } from "./contexts"; import type { AnyDatabaseProvider } from "./database"; +/** + * Configuration object that can be returned from `workflow()` to provide + * metadata and the run handler. + */ +export interface RunConfig { + /** Icon to display in the inspector for this run handler */ + icon?: string; + /** The actual run handler function */ + run: (...args: any[]) => any; +} + export interface ActorTypes< TState, TConnParams, @@ -47,13 +58,19 @@ const zFunction = < // We don't use Zod generics with `z.custom` because: // (a) there seems to be a weird bug in either Zod, tsup, or TSC that causese external packages to have different types from `z.infer` than from within the same package and // (b) it makes the type definitions incredibly difficult to read as opposed to vanilla TypeScript. +// Schema for RunConfig objects returned by workflow() +const RunConfigSchema = z.object({ + icon: z.string().optional(), + run: zFunction(), +}); + export const ActorConfigSchema = z .object({ onCreate: zFunction().optional(), onDestroy: zFunction().optional(), onWake: zFunction().optional(), onSleep: zFunction().optional(), - run: zFunction().optional(), + run: z.union([zFunction(), RunConfigSchema]).optional(), onStateChange: zFunction().optional(), onBeforeConnect: zFunction().optional(), onConnect: zFunction().optional(), @@ -338,18 +355,22 @@ interface BaseActorConfig< * On shutdown, the actor waits for this handler to complete with a * configurable timeout (options.runStopTimeout, default 15s). * + * Can be a function or a RunConfig object (returned by `workflow()`). + * * @returns Void or a Promise. If the promise exits, the actor crashes. */ - run?: ( - c: RunContext< - TState, - TConnParams, - TConnState, - TVars, - TInput, - TDatabase - >, - ) => void | Promise; + run?: + | (( + c: RunContext< + TState, + TConnParams, + TConnState, + TVars, + TInput, + TDatabase + >, + ) => void | Promise) + | RunConfig; /** * Called when the actor's state changes. diff --git a/rivetkit-typescript/packages/rivetkit/src/actor/instance/mod.ts b/rivetkit-typescript/packages/rivetkit/src/actor/instance/mod.ts index 79b04e52f7..7ba4473ced 100644 --- a/rivetkit-typescript/packages/rivetkit/src/actor/instance/mod.ts +++ b/rivetkit-typescript/packages/rivetkit/src/actor/instance/mod.ts @@ -1173,9 +1173,15 @@ export class ActorInstance { this.#rLog.debug({ msg: "starting run handler" }); +// Handle both function and RunConfig object (returned by workflow()) + const runFn = + typeof this.#config.run === "function" + ? this.#config.run + : this.#config.run.run; + const runSpan = this.startTraceSpan("actor.run"); const runResult = this.#traces.withSpan(runSpan, () => - this.#config.run!(this.actorContext), + runFn(this.actorContext), ); if (runResult instanceof Promise) { diff --git a/rivetkit-typescript/packages/rivetkit/src/actor/instance/queue.ts b/rivetkit-typescript/packages/rivetkit/src/actor/instance/queue.ts index aac08c867b..9f7ccec70c 100644 --- a/rivetkit-typescript/packages/rivetkit/src/actor/instance/queue.ts +++ b/rivetkit-typescript/packages/rivetkit/src/actor/instance/queue.ts @@ -71,4 +71,9 @@ export class ActorQueue { return messages[0]; } + + /** Sends a message to the specified queue. */ + async send(name: string, body: unknown): Promise { + return await this.#queueManager.enqueue(name, body); + } } diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-workflow.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-workflow.ts index 277a526c34..cbce9c0c52 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-workflow.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-workflow.ts @@ -48,5 +48,10 @@ export function runActorWorkflowTests(driverTestConfig: DriverTestConfig) { expect(next.ticks).toBeGreaterThan(initial.ticks); }); + + // NOTE: Test for workflow persistence across actor sleep is complex because + // calling c.sleep() during a workflow prevents clean shutdown. The workflow + // persistence is implicitly tested by the "sleeps and resumes between ticks" + // test which verifies the workflow continues from persisted state. }); } diff --git a/rivetkit-typescript/packages/rivetkit/src/workflow/context.ts b/rivetkit-typescript/packages/rivetkit/src/workflow/context.ts index 9539d56c7c..f3670889be 100644 --- a/rivetkit-typescript/packages/rivetkit/src/workflow/context.ts +++ b/rivetkit-typescript/packages/rivetkit/src/workflow/context.ts @@ -228,6 +228,10 @@ export class ActorWorkflowContext< return this.#runCtx.actorId; } + broadcast>(name: string, ...args: Args): void { + this.#runCtx.broadcast(name, ...args); + } + async #wrapActive(run: () => Promise): Promise { return await this.#runCtx.keepAwake(run()); } diff --git a/rivetkit-typescript/packages/rivetkit/src/workflow/driver.ts b/rivetkit-typescript/packages/rivetkit/src/workflow/driver.ts index 2f95fa0366..4d5d57667e 100644 --- a/rivetkit-typescript/packages/rivetkit/src/workflow/driver.ts +++ b/rivetkit-typescript/packages/rivetkit/src/workflow/driver.ts @@ -109,6 +109,10 @@ export class ActorWorkflowDriver implements EngineDriver { this.messageDriver = new ActorWorkflowMessageDriver(actor, runCtx); } + #log(msg: string, data?: Record) { + this.#runCtx.log.info({ msg: `[workflow-driver] ${msg}`, ...data }); + } + async get(key: Uint8Array): Promise { const [value] = await this.#runCtx.keepAwake( this.#actor.driver.kvBatchGet(this.#actor.id, [ @@ -167,11 +171,17 @@ export class ActorWorkflowDriver implements EngineDriver { async batch(writes: KVWrite[]): Promise { if (writes.length === 0) return; + + // Flush actor state together with workflow state to ensure atomicity. + // If the server crashes after workflow flush, actor state must also be persisted. await this.#runCtx.keepAwake( - this.#actor.driver.kvBatchPut( - this.#actor.id, - writes.map(({ key, value }) => [makeWorkflowKey(key), value]), - ), + Promise.all([ + this.#actor.driver.kvBatchPut( + this.#actor.id, + writes.map(({ key, value }) => [makeWorkflowKey(key), value]), + ), + this.#actor.stateManager.saveState({ immediate: true }), + ]), ); } diff --git a/rivetkit-typescript/packages/rivetkit/src/workflow/mod.ts b/rivetkit-typescript/packages/rivetkit/src/workflow/mod.ts index 17a074f23f..8ba51c3329 100644 --- a/rivetkit-typescript/packages/rivetkit/src/workflow/mod.ts +++ b/rivetkit-typescript/packages/rivetkit/src/workflow/mod.ts @@ -2,12 +2,14 @@ import { ACTOR_CONTEXT_INTERNAL_SYMBOL } from "@/actor/contexts/base/actor"; import type { RunContext } from "@/actor/contexts/run"; import type { AnyDatabaseProvider } from "@/actor/database"; import type { AnyActorInstance } from "@/actor/instance/mod"; +import type { RunConfig } from "@/actor/config"; import { stringifyError } from "@/utils"; import { runWorkflow } from "@rivetkit/workflow-engine"; import invariant from "invariant"; import { ActorWorkflowContext } from "./context"; import { ActorWorkflowDriver, workflowQueueName } from "./driver"; +export { Loop } from "@rivetkit/workflow-engine"; export { workflowQueueName } from "./driver"; export { ActorWorkflowContext } from "./context"; @@ -29,8 +31,8 @@ export function workflow< TDatabase >, ) => Promise, -) { - return async function run( +): RunConfig { + async function run( runCtx: RunContext< TState, TConnParams, @@ -40,20 +42,21 @@ export function workflow< TDatabase >, ): Promise { - const actor = (runCtx as unknown as { - [ACTOR_CONTEXT_INTERNAL_SYMBOL]?: AnyActorInstance; - })[ACTOR_CONTEXT_INTERNAL_SYMBOL]; + const actor = ( + runCtx as unknown as { + [ACTOR_CONTEXT_INTERNAL_SYMBOL]?: AnyActorInstance; + } + )[ACTOR_CONTEXT_INTERNAL_SYMBOL]; invariant(actor, "workflow() requires an actor instance"); const driver = new ActorWorkflowDriver(actor, runCtx); const handle = runWorkflow( actor.id, - async (ctx) => - await fn(new ActorWorkflowContext(ctx, runCtx)), + async (ctx) => await fn(new ActorWorkflowContext(ctx, runCtx)), undefined, driver, - { mode: "live" }, + { mode: "live", logger: runCtx.log }, ); runCtx.abortSignal.addEventListener( @@ -64,21 +67,26 @@ export function workflow< { once: true }, ); - runCtx.waitUntil( - handle.result - .then(() => { - // Ignore normal completion; the actor will be restarted if needed. - }) - .catch((error) => { - runCtx.log.error({ - msg: "workflow run failed", - error: stringifyError(error), - }); - }), - ); + runCtx.waitUntil( + handle.result + .then(() => { + // Ignore normal completion; the actor will be restarted if needed. + }) + .catch((error) => { + runCtx.log.error({ + msg: "workflow run failed", + error: stringifyError(error), + }); + }), + ); - return await new Promise(() => { - // Intentionally never resolve to keep the run handler alive. - }); - }; + return await new Promise(() => { + // Intentionally never resolve to keep the run handler alive. + }); } + + return { + icon: "diagram-project", + run, + }; +} diff --git a/rivetkit-typescript/packages/workflow-engine/contrib-docs/FLUSHING.md b/rivetkit-typescript/packages/workflow-engine/contrib-docs/FLUSHING.md new file mode 100644 index 0000000000..3623292ad6 --- /dev/null +++ b/rivetkit-typescript/packages/workflow-engine/contrib-docs/FLUSHING.md @@ -0,0 +1,176 @@ +# Workflow Engine: Flushing & Persistence + +This document explains how the workflow engine persists state and the critical role of `flush()`. + +## Overview + +The workflow engine uses an in-memory storage layer (`Storage`) that periodically writes to persistent KV storage via `flush()`. Understanding when and why to flush is critical for maintaining workflow durability. + +## The Storage Model + +``` +┌─────────────────────────────────────────────────────────┐ +│ In-Memory Storage │ +├─────────────────────────────────────────────────────────┤ +│ nameRegistry[] - Deduplicated strings for names │ +│ history.entries - Map workflow steps │ +│ entryMetadata - Map retry info │ +│ messages[] - Pending workflow messages │ +│ state - pending/running/sleeping/etc │ +│ output/error - Final workflow result │ +└─────────────────────────────────────────────────────────┘ + │ + │ flush() + ▼ +┌─────────────────────────────────────────────────────────┐ +│ Persistent KV Storage │ +├─────────────────────────────────────────────────────────┤ +│ workflow:name:{idx} - Name registry entries │ +│ workflow:history:{loc} - Serialized Entry objects │ +│ workflow:meta:{id} - Entry metadata │ +│ workflow:message:{id} - Pending messages │ +│ workflow:state - Workflow state enum │ +│ workflow:output - Final output │ +│ workflow:error - Error if failed │ +└─────────────────────────────────────────────────────────┘ +``` + +## The Dirty Flag Pattern + +Entries use a `dirty` flag to track what needs to be written: + +```typescript +// 1. Create or modify an entry +entry.dirty = true; + +// 2. Flush writes all dirty entries to KV +await flush(storage, driver); // Sets dirty = false + +// 3. Entry is now persisted and safe from crashes +``` + +## When to Flush + +### The Golden Rule + +**Flush immediately after any state change that must survive a crash.** + +If the workflow could crash (or yield via `SleepError`) after a state change, that state must be flushed first. + +### Current Flush Points + +| Operation | When Flushed | Why | +|-----------|--------------|-----| +| Non-ephemeral step | After completion | Step results must persist | +| Loop iteration | After each iteration | Progress must be resumable | +| Sleep (past deadline) | After marking complete | Don't re-sleep on restart | +| Sleep (short, in-memory) | After completing | Same as above | +| Listen (messages consumed) | After consumption | Don't re-consume | +| Listen with timeout | After deadline created | Deadline must persist | +| Join entry creation | Before branches run | Entry structure must exist | +| Race entry creation | Before branches run | Entry structure must exist | +| Join/Race completion | After all branches | Final state must persist | +| Message consumption | After deletion | Prevent re-delivery | + +### Ephemeral Steps + +Ephemeral steps (`{ ephemeral: true }`) skip immediate flush for performance. They batch with the next non-ephemeral operation. Use only for: +- Idempotent operations +- Operations where replay is acceptable +- Performance-critical paths + +## Common Bugs to Avoid + +### Bug Pattern 1: Missing Flush Before Yield + +```typescript +// BAD: Entry created but not flushed before potential SleepError +setEntry(storage, location, entry); +entry.dirty = true; +await somethingThatMightThrowSleepError(); // If this yields, entry is lost! + +// GOOD: Flush before potential yield +setEntry(storage, location, entry); +entry.dirty = true; +await flush(storage, driver); // Persist first +await somethingThatMightThrowSleepError(); // Safe to yield now +``` + +### Bug Pattern 2: Missing Flush Before Branching + +```typescript +// BAD: Parent entry not flushed before children run +entry = createEntry(location, { type: "join", data: {...} }); +setEntry(storage, location, entry); +entry.dirty = true; +// Start branches immediately - if one fails fast, parent entry is lost! +await Promise.all(branches.map(b => b.run())); + +// GOOD: Flush parent before children +entry.dirty = true; +await flush(storage, driver); // Parent entry persisted +await Promise.all(branches.map(b => b.run())); // Safe to run +``` + +### Bug Pattern 3: Missing Flush for In-Memory Completion + +```typescript +// BAD: Short sleep completes in memory but state not persisted +if (remaining < workerPollInterval) { + await sleep(remaining); + entry.kind.data.state = "completed"; + entry.dirty = true; + return; // Crash here = sleep replays! +} + +// GOOD: Flush before returning +entry.dirty = true; +await flush(storage, driver); +return; // Safe - completion persisted +``` + +## Replay Behavior + +When a workflow resumes after a crash: + +1. `loadStorage()` reads all persisted state from KV +2. Workflow code re-executes from the beginning +3. For each operation: + - If entry exists in history → return cached result (replay) + - If entry missing → execute operation (forward progress) + +This is why flushing is critical: **missing entries mean operations replay**. + +## Testing Flush Behavior + +To verify flush behavior: + +1. Execute workflow until target operation +2. Simulate crash (evict workflow) +3. Resume workflow +4. Verify operation didn't replay (check step execution counts, side effects) + +## Performance Considerations + +Each `flush()` is a KV batch write. To minimize flushes: + +- Use ephemeral steps for non-critical operations +- Batch multiple state changes before flush when safe +- Consider `commitInterval` for batching (TODO: not yet implemented) + +However, **never skip flush for durability** - correctness > performance. + +## Debugging Tips + +1. **HistoryDivergedError**: Usually means an entry wasn't flushed and is missing on replay +2. **Duplicate execution**: Step ran twice = entry wasn't persisted before crash +3. **Wrong deadline**: Listen timeout entry wasn't flushed before yield + +Enable debug logging to trace flush operations: +```typescript +// In driver implementation +async batch(writes: KVWrite[]): Promise { + console.log(`Flushing ${writes.length} entries:`, writes.map(w => w.key)); + // ... actual write +} +``` diff --git a/rivetkit-typescript/packages/workflow-engine/package.json b/rivetkit-typescript/packages/workflow-engine/package.json index f96b4bf6dd..78e3036c67 100644 --- a/rivetkit-typescript/packages/workflow-engine/package.json +++ b/rivetkit-typescript/packages/workflow-engine/package.json @@ -54,6 +54,7 @@ "@rivetkit/bare-ts": "^0.6.2", "cbor-x": "^1.6.0", "fdb-tuple": "^1.0.0", + "pino": "^9.6.0", "vbare": "^0.0.4" }, "devDependencies": { diff --git a/rivetkit-typescript/packages/workflow-engine/src/context.ts b/rivetkit-typescript/packages/workflow-engine/src/context.ts index af607ee0a5..32a6cd35d3 100644 --- a/rivetkit-typescript/packages/workflow-engine/src/context.ts +++ b/rivetkit-typescript/packages/workflow-engine/src/context.ts @@ -1,3 +1,4 @@ +import type { Logger } from "pino"; import type { EngineDriver } from "./driver.js"; import { CancelledError, @@ -108,6 +109,7 @@ export class WorkflowContextImpl implements WorkflowContextInterface { private rollbackCheckpointSet: boolean; /** Track names used in current execution to detect duplicates */ private usedNamesInExecution = new Set(); + private logger?: Logger; constructor( public readonly workflowId: string, @@ -119,12 +121,14 @@ export class WorkflowContextImpl implements WorkflowContextInterface { mode: "forward" | "rollback" = "forward", rollbackActions?: RollbackAction[], rollbackCheckpointSet = false, + logger?: Logger, ) { this.currentLocation = location; this.abortController = abortController ?? new AbortController(); this.mode = mode; this.rollbackActions = rollbackActions; this.rollbackCheckpointSet = rollbackCheckpointSet; + this.logger = logger; } get abortSignal(): AbortSignal { @@ -164,9 +168,18 @@ export class WorkflowContextImpl implements WorkflowContextInterface { this.mode, this.rollbackActions, this.rollbackCheckpointSet, + this.logger, ); } + /** + * Log a debug message using the configured logger. + */ + private log(level: "debug" | "info" | "warn" | "error", data: Record): void { + if (!this.logger) return; + this.logger[level](data); + } + /** * Mark a key as visited. */ @@ -351,6 +364,7 @@ export class WorkflowContextImpl implements WorkflowContextInterface { // Replay successful result if (stepData.output !== undefined) { + this.log("debug", { msg: "replaying step from history", step: config.name, key }); return stepData.output as T; } @@ -387,10 +401,13 @@ export class WorkflowContextImpl implements WorkflowContextInterface { existing ?? createEntry(location, { type: "step", data: {} }); if (!existing) { // New entry - register name + this.log("debug", { msg: "executing new step", step: config.name, key }); const nameIndex = registerName(this.storage, config.name); entry.location = [...location]; entry.location[entry.location.length - 1] = nameIndex; setEntry(this.storage, location, entry); + } else { + this.log("debug", { msg: "retrying step", step: config.name, key }); } const metadata = getOrCreateMetadata(this.storage, entry.id); @@ -423,9 +440,11 @@ export class WorkflowContextImpl implements WorkflowContextInterface { // next flush from a non-ephemeral operation. The purpose of ephemeral // is to batch writes, not to avoid persistence entirely. if (!config.ephemeral) { + this.log("debug", { msg: "flushing step", step: config.name, key }); await flush(this.storage, this.driver); } + this.log("debug", { msg: "step completed", step: config.name, key }); return output; } catch (error) { // Timeout errors are treated as critical (no retry) @@ -855,6 +874,7 @@ export class WorkflowContextImpl implements WorkflowContextInterface { entry.kind.data.state = "completed"; } entry.dirty = true; + await flush(this.storage, this.driver); return; } @@ -1251,6 +1271,8 @@ export class WorkflowContextImpl implements WorkflowContextInterface { }); setEntry(this.storage, sleepLocation, sleepEntry); sleepEntry.dirty = true; + // Flush immediately to persist deadline before potential SleepError + await flush(this.storage, this.driver); } return this.executeListenNUntilImpl( @@ -1446,6 +1468,8 @@ export class WorkflowContextImpl implements WorkflowContextInterface { }); setEntry(this.storage, location, entry); entry.dirty = true; + // Flush immediately to persist entry before branches execute + await flush(this.storage, this.driver); } if (entry.kind.type !== "join") { @@ -1595,6 +1619,8 @@ export class WorkflowContextImpl implements WorkflowContextInterface { }); setEntry(this.storage, location, entry); entry.dirty = true; + // Flush immediately to persist entry before branches execute + await flush(this.storage, this.driver); } if (entry.kind.type !== "race") { diff --git a/rivetkit-typescript/packages/workflow-engine/src/index.ts b/rivetkit-typescript/packages/workflow-engine/src/index.ts index d697d13e61..c2378e9a0a 100644 --- a/rivetkit-typescript/packages/workflow-engine/src/index.ts +++ b/rivetkit-typescript/packages/workflow-engine/src/index.ts @@ -1,3 +1,5 @@ +import type { Logger } from "pino"; + // Types // Context @@ -306,6 +308,7 @@ async function executeRollback( messageDriver: WorkflowMessageDriver, abortController: AbortController, storage: Storage, + logger?: Logger, ): Promise { const rollbackActions: RollbackAction[] = []; const ctx = new WorkflowContextImpl( @@ -317,6 +320,8 @@ async function executeRollback( abortController, "rollback", rollbackActions, + false, + logger, ); try { @@ -476,6 +481,7 @@ async function executeLiveWorkflow( messageDriver: WorkflowMessageDriver, abortController: AbortController, runtime: LiveRuntime, + logger?: Logger, ): Promise> { let lastResult: WorkflowResult | undefined; @@ -487,6 +493,7 @@ async function executeLiveWorkflow( driver, messageDriver, abortController, + logger, ); lastResult = result; @@ -553,6 +560,8 @@ export function runWorkflow( const mode: WorkflowRunMode = options.mode ?? "yield"; const liveRuntime = mode === "live" ? createLiveRuntime() : undefined; + const logger = options.logger; + const resultPromise = mode === "live" && liveRuntime ? executeLiveWorkflow( @@ -563,6 +572,7 @@ export function runWorkflow( messageDriver, abortController, liveRuntime, + logger, ) : executeWorkflow( workflowId, @@ -571,6 +581,7 @@ export function runWorkflow( driver, messageDriver, abortController, + logger, ); return { @@ -700,9 +711,21 @@ async function executeWorkflow( driver: EngineDriver, messageDriver: WorkflowMessageDriver, abortController: AbortController, + logger?: Logger, ): Promise> { const storage = await loadStorage(driver, messageDriver); + if (logger) { + const entryKeys = Array.from(storage.history.entries.keys()); + logger.debug({ + msg: "loaded workflow storage", + state: storage.state, + entryCount: entryKeys.length, + entries: entryKeys.slice(0, 10), + nameRegistry: storage.nameRegistry, + }); + } + // Check if workflow was cancelled if (storage.state === "cancelled") { throw new EvictedError(); @@ -734,6 +757,7 @@ async function executeWorkflow( messageDriver, abortController, storage, + logger, ); } catch (error) { if (error instanceof EvictedError) { @@ -761,6 +785,10 @@ async function executeWorkflow( messageDriver, undefined, abortController, + "forward", + undefined, + false, + logger, ); storage.state = "running"; @@ -819,6 +847,7 @@ async function executeWorkflow( messageDriver, abortController, storage, + logger, ); } catch (rollbackError) { if (rollbackError instanceof EvictedError) { diff --git a/rivetkit-typescript/packages/workflow-engine/src/types.ts b/rivetkit-typescript/packages/workflow-engine/src/types.ts index 2e7190533e..2d7db85282 100644 --- a/rivetkit-typescript/packages/workflow-engine/src/types.ts +++ b/rivetkit-typescript/packages/workflow-engine/src/types.ts @@ -1,3 +1,5 @@ +import type { Logger } from "pino"; + /** * Index into the entry name registry. * Names are stored once and referenced by this index to avoid repetition. @@ -379,6 +381,7 @@ export type WorkflowRunMode = "yield" | "live"; export interface RunWorkflowOptions { mode?: WorkflowRunMode; + logger?: Logger; } export type WorkflowFunction = ( diff --git a/scripts/ralph/prompt.txt b/scripts/ralph/prompt.txt new file mode 100644 index 0000000000..f3845b0d59 --- /dev/null +++ b/scripts/ralph/prompt.txt @@ -0,0 +1,17 @@ +@prd.md @progress.txt + +Work on exactly ONE feature, then STOP. + +1. Decide which task to work on next (highest priority, not necessarily first in list) +2. Implement that ONE feature +3. Run type checks and tests +4. Append progress to progress.txt +5. Make a git commit +6. STOP IMMEDIATELY - do not start another task + +If all work is complete, output COMPLETE + +CRITICAL: After committing, you are DONE. Do not look for more work. Do not start the next feature. Just stop. + +Never ask questions or request clarification. + diff --git a/scripts/ralph/run.sh b/scripts/ralph/run.sh new file mode 100755 index 0000000000..c3392db5b5 --- /dev/null +++ b/scripts/ralph/run.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# ralph run script +# Usage: ./run.sh + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROMPT_FILE="$SCRIPT_DIR/prompt.txt" +OUTPUT_FILE="$SCRIPT_DIR/output.txt" + +if [ -z "$1" ]; then + echo "Usage: $0 " + exit 1 +fi + +if [ ! -f "$PROMPT_FILE" ]; then + echo "Error: prompt.txt not found at $PROMPT_FILE" + exit 1 +fi + +PROMPT=$(cat "$PROMPT_FILE") + +for ((i=1; i<=$1; i++)); do + echo "=== Starting iteration $i ===" + + claude -p "$PROMPT" --output-format stream-json --verbose --dangerously-skip-permissions \ + | tee "$OUTPUT_FILE" \ + | jq -r 'select(.type == "assistant") | .message.content[]? | select(.type == "text") | .text // empty' 2>/dev/null || true + + if grep -q "COMPLETE" "$OUTPUT_FILE"; then + echo "PRD complete, exiting." + exit 0 + fi +done + diff --git a/website/public/examples/workflow-sandbox/image.png b/website/public/examples/workflow-sandbox/image.png new file mode 100644 index 0000000000000000000000000000000000000000..55dae97540d0167b20f0ed2459dc34dfb4c1c8fa GIT binary patch literal 317518 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

Ov<&lJ1f;5FSR~#IKA+DNClE@!=K*YIfJF$m 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�d^BLfX*kIVBfqZjN{pG=r(lugZpy5YC{y6S&r)a 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≤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

$J!3AiRT%wO*K-k@dh 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 { + // TypeScript error: Property 'state' does not exist on type 'WorkflowContextInterface' + const item = loopCtx.state.items.find(i => i.id === id); + + // TypeScript error: Property 'broadcast' does not exist on type 'WorkflowContextInterface' + loopCtx.broadcast("itemUpdated", item); + } +}); +``` + +**Workaround:** Create helper functions to cast the context: +```typescript +const getState = (ctx: unknown): S => (ctx as { state: S }).state; +const getBroadcast = (ctx: unknown) => + (ctx as { broadcast: (name: string, ...args: unknown[]) => void }).broadcast; + +// Usage inside loop +const state = getState(loopCtx); +const broadcast = getBroadcast(loopCtx); +``` + +**Impact:** Every workflow example needs boilerplate type helpers. State types must be defined separately and cannot be inferred from the actor definition. + +### 2. No way to access actor state type from actor definition + +**Problem:** There's no `._` property or similar mechanism to extract the state type from an actor definition. + +**What doesn't work:** +```typescript +// Doesn't work - '._' does not exist +const state = getState(loopCtx); +``` + +**Workaround:** Define state types separately: +```typescript +type MyActorState = { items: Item[] }; + +export const myActor = actor({ + state: { items: [] as Item[] }, + // ... +}); + +// In workflow +const state = getState(loopCtx); +``` + +**Impact:** State type definitions are duplicated - once in the actor definition and once as a standalone type. + +## Missing Methods + +### 3. `ActorQueue` was missing `send()` method + +**Problem:** The `ActorQueue` class only had `next()` for receiving messages, but no method to send messages to queues. + +**Error:** +```typescript +// Property 'send' does not exist on type 'ActorQueue<...>' +await c.queue.send(QUEUE_NAME, { orderId }); +``` + +**Fix:** Added `send()` method to `ActorQueue` class: +```typescript +async send(name: string, body: unknown): Promise { + return await this.#queueManager.enqueue(name, body); +} +``` + +### 4. `ActorWorkflowContext` was missing `broadcast()` method + +**Problem:** The workflow context wrapper didn't expose the `broadcast()` method from the underlying run context. + +**Error:** +```typescript +// Property 'broadcast' does not exist on type 'ActorWorkflowContext<...>' +ctx.broadcast("orderUpdated", order); +``` + +**Fix:** Added `broadcast()` method that delegates to `this.#runCtx.broadcast()`. + +## Package Resolution Issues + +### 5. `@rivetkit/workflow-engine` not found on npm + +**Problem:** Examples using `Loop` from `@rivetkit/workflow-engine` failed with 404 error because the package isn't published to npm. + +**Error:** +``` +ERR_PNPM_FETCH_404 GET https://registry.npmjs.org/@rivetkit/workflow-engine - Not Found +``` + +**Workaround:** Re-export `Loop` from `rivetkit/workflow`: +```typescript +// In rivetkit/workflow/mod.ts +export { Loop } from "@rivetkit/workflow-engine"; + +// In examples +import { Loop, workflow } from "rivetkit/workflow"; +``` + +**Impact:** Internal packages need to be re-exported through public packages. + +### 6. Need to understand pnpm workspace resolutions for examples + +**Problem:** Examples need to use `*` as version for workspace packages, and the root `package.json` needs `resolutions` entries with `workspace:*`. + +**What doesn't work:** +```json +{ + "dependencies": { + "rivetkit": "workspace:*" // Doesn't work in examples + } +} +``` + +**What works:** +```json +// In example package.json +{ + "dependencies": { + "rivetkit": "*" + } +} + +// In root package.json +{ + "resolutions": { + "rivetkit": "workspace:*" + } +} +``` + +## Build Issues + +### 7. `workflow/mod.ts` return type incompatible with `RunConfig` + +**Problem:** The `run` function type in `workflow()` wasn't compatible with `RunConfig`'s expected type. + +**Error:** +``` +TS2322: Type '(...) => Promise' is not assignable to type '(...) => unknown' +``` + +**Fix:** Cast the return value: +```typescript +return { + icon: "diagram-project", + run: run as (...args: unknown[]) => unknown, +}; +``` + +### 8. Invalid example tag "workflows" + +**Problem:** The `template.tags` field in package.json had an invalid tag value. + +**Error:** +``` +Invalid tag "workflows" +``` + +**Fix:** Changed to valid tag "experimental". + +## Frontend Integration Issues + +### 9. `actor.connection` is possibly null + +**Problem:** The useActor hook returns a connection that can be null before the actor is connected. + +**Symptom:** Every connection method call needs null checking: +```typescript +// Error: actor.connection is possibly null +await actor.connection.createOrder(orderId); + +// Fix +await actor.connection?.createOrder(orderId); +``` + +### 10. Union types in useActor break method access + +**Problem:** When passing actor to child components, the type becomes a union of all possible actor connections, making it impossible to call actor-specific methods. + +**Symptom:** +```typescript +// Type: ReturnType +// Property 'approve' does not exist on union type +actor.connection.approve(requestId, "Admin"); +``` + +**Workaround:** Pass callback functions instead of the actor: +```typescript +// Instead of + + +// Use + actor.connection?.approve(id, approver)} + onReject={(id, approver) => actor.connection?.reject(id, approver)} +/> +``` + +## Summary + +The biggest pain points are: +1. **Type system gaps** - loop context doesn't have the right type, requiring manual type helpers +2. **Missing methods** - `send()` and `broadcast()` weren't exposed on expected interfaces +3. **Package structure** - internal packages not accessible, need re-exports through public API + +These issues could be addressed by: +- Making `ActorWorkflowContext` the declared type for loop callbacks +- Exposing a type helper like `actor.State` or `typeof actor['state']` +- Ensuring all expected methods are on the public interfaces +- Re-exporting internal types/values through the main package From 889509ab8298c7c18d9b45b946782ed751eaf7d1 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Sat, 24 Jan 2026 18:38:37 -0800 Subject: [PATCH 2/2] fix(workflow-engine): wake workflows on message arrival during listenWithTimeout When listenWithTimeout/listenUntil was used, workflows would only wake when the deadline passed, not when a message arrived. This broke approval workflows and other human-in-the-loop patterns. Changes: - Extended SleepError to optionally carry messageNames array - Modified listen methods to pass message names when throwing SleepError - Updated execution loop to wait for EITHER messages OR deadline - Fixed race branches to properly yield on SleepError instead of failing Co-Authored-By: Claude Opus 4.5 --- examples/queue-sandbox-vercel/.gitignore | 4 + examples/queue-sandbox-vercel/README.md | 71 ++ examples/queue-sandbox-vercel/api/index.ts | 3 + .../queue-sandbox-vercel/frontend/App.tsx | 651 ++++++++++++ .../queue-sandbox-vercel/frontend/main.tsx | 12 + examples/queue-sandbox-vercel/index.html | 312 ++++++ examples/queue-sandbox-vercel/package.json | 41 + examples/queue-sandbox-vercel/src/actors.ts | 18 + .../src/actors/keep-awake.ts | 70 ++ .../src/actors/multi-queue.ts | 31 + .../src/actors/self-sender.ts | 48 + .../queue-sandbox-vercel/src/actors/sender.ts | 36 + .../src/actors/timeout.ts | 44 + .../queue-sandbox-vercel/src/actors/worker.ts | 45 + examples/queue-sandbox-vercel/src/server.ts | 6 + examples/queue-sandbox-vercel/tsconfig.json | 26 + examples/queue-sandbox-vercel/turbo.json | 6 + examples/queue-sandbox-vercel/vercel.json | 9 + examples/queue-sandbox-vercel/vite.config.ts | 6 + examples/workflow-sandbox-vercel/.gitignore | 4 + examples/workflow-sandbox-vercel/README.md | 104 ++ examples/workflow-sandbox-vercel/api/index.ts | 3 + .../workflow-sandbox-vercel/frontend/App.tsx | 956 ++++++++++++++++++ .../workflow-sandbox-vercel/frontend/main.tsx | 12 + examples/workflow-sandbox-vercel/index.html | 878 ++++++++++++++++ examples/workflow-sandbox-vercel/package.json | 41 + .../workflow-sandbox-vercel/src/actors.ts | 63 ++ .../src/actors/_helpers.ts | 12 + .../src/actors/approval.ts | 110 ++ .../src/actors/batch.ts | 125 +++ .../src/actors/dashboard.ts | 203 ++++ .../src/actors/order.ts | 89 ++ .../src/actors/payment.ts | 175 ++++ .../src/actors/race.ts | 112 ++ .../src/actors/timer.ts | 69 ++ .../workflow-sandbox-vercel/src/server.ts | 8 + .../workflow-sandbox-vercel/tsconfig.json | 26 + examples/workflow-sandbox-vercel/turbo.json | 6 + examples/workflow-sandbox-vercel/vercel.json | 9 + .../workflow-sandbox-vercel/vite.config.ts | 6 + progress.txt | 43 + .../packages/workflow-engine/src/context.ts | 50 +- .../packages/workflow-engine/src/errors.ts | 12 +- .../packages/workflow-engine/src/index.ts | 53 +- 44 files changed, 4596 insertions(+), 12 deletions(-) create mode 100644 examples/queue-sandbox-vercel/.gitignore create mode 100644 examples/queue-sandbox-vercel/README.md create mode 100644 examples/queue-sandbox-vercel/api/index.ts create mode 100644 examples/queue-sandbox-vercel/frontend/App.tsx create mode 100644 examples/queue-sandbox-vercel/frontend/main.tsx create mode 100644 examples/queue-sandbox-vercel/index.html create mode 100644 examples/queue-sandbox-vercel/package.json create mode 100644 examples/queue-sandbox-vercel/src/actors.ts create mode 100644 examples/queue-sandbox-vercel/src/actors/keep-awake.ts create mode 100644 examples/queue-sandbox-vercel/src/actors/multi-queue.ts create mode 100644 examples/queue-sandbox-vercel/src/actors/self-sender.ts create mode 100644 examples/queue-sandbox-vercel/src/actors/sender.ts create mode 100644 examples/queue-sandbox-vercel/src/actors/timeout.ts create mode 100644 examples/queue-sandbox-vercel/src/actors/worker.ts create mode 100644 examples/queue-sandbox-vercel/src/server.ts create mode 100644 examples/queue-sandbox-vercel/tsconfig.json create mode 100644 examples/queue-sandbox-vercel/turbo.json create mode 100644 examples/queue-sandbox-vercel/vercel.json create mode 100644 examples/queue-sandbox-vercel/vite.config.ts create mode 100644 examples/workflow-sandbox-vercel/.gitignore create mode 100644 examples/workflow-sandbox-vercel/README.md create mode 100644 examples/workflow-sandbox-vercel/api/index.ts create mode 100644 examples/workflow-sandbox-vercel/frontend/App.tsx create mode 100644 examples/workflow-sandbox-vercel/frontend/main.tsx create mode 100644 examples/workflow-sandbox-vercel/index.html create mode 100644 examples/workflow-sandbox-vercel/package.json create mode 100644 examples/workflow-sandbox-vercel/src/actors.ts create mode 100644 examples/workflow-sandbox-vercel/src/actors/_helpers.ts create mode 100644 examples/workflow-sandbox-vercel/src/actors/approval.ts create mode 100644 examples/workflow-sandbox-vercel/src/actors/batch.ts create mode 100644 examples/workflow-sandbox-vercel/src/actors/dashboard.ts create mode 100644 examples/workflow-sandbox-vercel/src/actors/order.ts create mode 100644 examples/workflow-sandbox-vercel/src/actors/payment.ts create mode 100644 examples/workflow-sandbox-vercel/src/actors/race.ts create mode 100644 examples/workflow-sandbox-vercel/src/actors/timer.ts create mode 100644 examples/workflow-sandbox-vercel/src/server.ts create mode 100644 examples/workflow-sandbox-vercel/tsconfig.json create mode 100644 examples/workflow-sandbox-vercel/turbo.json create mode 100644 examples/workflow-sandbox-vercel/vercel.json create mode 100644 examples/workflow-sandbox-vercel/vite.config.ts create mode 100644 progress.txt diff --git a/examples/queue-sandbox-vercel/.gitignore b/examples/queue-sandbox-vercel/.gitignore new file mode 100644 index 0000000000..e28f3d79db --- /dev/null +++ b/examples/queue-sandbox-vercel/.gitignore @@ -0,0 +1,4 @@ +.actorcore +node_modules +dist +.vercel diff --git a/examples/queue-sandbox-vercel/README.md b/examples/queue-sandbox-vercel/README.md new file mode 100644 index 0000000000..378f0f6772 --- /dev/null +++ b/examples/queue-sandbox-vercel/README.md @@ -0,0 +1,71 @@ +> **Note:** This is the Vercel-optimized version of the [queue-sandbox](../queue-sandbox) example. +> It uses the `hono/vercel` adapter and is configured for Vercel deployment. + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frivet-gg%2Frivet%2Ftree%2Fmain%2Fexamples%2Fqueue-sandbox-vercel&project-name=queue-sandbox-vercel) + +# Queue Sandbox + +Interactive demo showcasing all the ways to use queues in RivetKit. Each tab demonstrates a different queue pattern with real-time feedback. + +## Getting Started + +```bash +cd examples/queue-sandbox +pnpm install +pnpm dev +``` + +## Features + +- Six interactive tabs demonstrating different queue patterns +- Real-time state updates via broadcasts and polling +- Progress indicators for long-running operations +- Multi-queue priority handling + +## Implementation + +This example demonstrates six queue patterns: + +### Send + +Basic queue messaging where the client sends messages to an actor queue, and the actor manually receives them. + +See [`src/actors/sender.ts`](https://github.com/rivet-dev/rivet/tree/main/examples/queue-sandbox/src/actors/sender.ts). + +### Multi-Queue + +Listen to multiple named queues (high, normal, low priority) simultaneously using `c.queue.next(names, { count })`. + +See [`src/actors/multi-queue.ts`](https://github.com/rivet-dev/rivet/tree/main/examples/queue-sandbox/src/actors/multi-queue.ts). + +### Timeout + +Demonstrate the timeout option when waiting for messages. Shows countdown timer and handles both successful receives and timeouts. + +See [`src/actors/timeout.ts`](https://github.com/rivet-dev/rivet/tree/main/examples/queue-sandbox/src/actors/timeout.ts). + +### Worker + +Use the `run` handler to continuously consume queue messages in a loop. The worker polls for jobs and processes them automatically. + +See [`src/actors/worker.ts`](https://github.com/rivet-dev/rivet/tree/main/examples/queue-sandbox/src/actors/worker.ts). + +### Self-Send + +Actor sends messages to its own queue using the inline client pattern (`c.client()`). + +See [`src/actors/self-sender.ts`](https://github.com/rivet-dev/rivet/tree/main/examples/queue-sandbox/src/actors/self-sender.ts). + +### Keep Awake + +Consume queue messages and perform long-running tasks wrapped in `c.keepAwake()` to prevent the actor from sleeping during processing. + +See [`src/actors/keep-awake.ts`](https://github.com/rivet-dev/rivet/tree/main/examples/queue-sandbox/src/actors/keep-awake.ts). + +## Resources + +Read more about [queues](/docs/actors/queues), [run handlers](/docs/actors/run), and [state](/docs/actors/state). + +## License + +MIT diff --git a/examples/queue-sandbox-vercel/api/index.ts b/examples/queue-sandbox-vercel/api/index.ts new file mode 100644 index 0000000000..07a830391f --- /dev/null +++ b/examples/queue-sandbox-vercel/api/index.ts @@ -0,0 +1,3 @@ +import app from "../src/server.ts"; + +export default app; diff --git a/examples/queue-sandbox-vercel/frontend/App.tsx b/examples/queue-sandbox-vercel/frontend/App.tsx new file mode 100644 index 0000000000..31d182ff40 --- /dev/null +++ b/examples/queue-sandbox-vercel/frontend/App.tsx @@ -0,0 +1,651 @@ +import { createRivetKit } from "@rivetkit/react"; +import { useState, useEffect, useCallback } from "react"; +import type { registry } from "../src/actors.ts"; +import type { ReceivedMessage } from "../src/actors/sender.ts"; +import type { QueueMessage } from "../src/actors/multi-queue.ts"; +import type { TimeoutResult } from "../src/actors/timeout.ts"; +import type { WorkerState } from "../src/actors/worker.ts"; +import type { SelfSenderState } from "../src/actors/self-sender.ts"; +import type { KeepAwakeState } from "../src/actors/keep-awake.ts"; + +const { useActor } = createRivetKit( + `${location.origin}/api/rivet`, +); + +type TabName = "send" | "multi-queue" | "timeout" | "worker" | "self-send" | "keep-awake"; + +// Generate unique key for actor instances +const instanceKey = `demo-${Date.now()}`; + +export function App() { + const [activeTab, setActiveTab] = useState("send"); + + return ( +

+ ); +} + +function SendTab() { + const [messageText, setMessageText] = useState("Hello, Queue!"); + const [messages, setMessages] = useState([]); + + const actor = useActor({ name: "sender", key: [instanceKey] }); + + useEffect(() => { + if (actor.connection) { + actor.connection.getMessages().then(setMessages); + } + }, [actor.connection]); + + const sendMessage = async () => { + if (actor.handle) { + await actor.handle.queue.task.send({ text: messageText }); + } + }; + + const receiveMessage = async () => { + if (actor.connection) { + const result = await actor.connection.receiveOne(); + if (result) { + setMessages((prev) => [...prev, result]); + } + } + }; + + const clearMessages = async () => { + if (actor.connection) { + await actor.connection.clearMessages(); + setMessages([]); + } + }; + + return ( +
+

Send Messages to Queue

+

+ Client sends messages to actor queue; actor receives and displays them +

+ +
+ + setMessageText(e.target.value)} + placeholder="Enter message text" + /> +
+ +
+ + + +
+ +
+

Received Messages ({messages.length})

+ {messages.length === 0 ? ( +

No messages received yet

+ ) : ( +
    + {messages.map((msg, i) => ( +
  • + {msg.name} + + {JSON.stringify(msg.body)} + + + {new Date(msg.receivedAt).toLocaleTimeString()} + +
  • + ))} +
+ )} +
+
+ ); +} + +function MultiQueueTab() { + const [messages, setMessages] = useState([]); + + const actor = useActor({ name: "multiQueue", key: [instanceKey] }); + + useEffect(() => { + if (actor.connection) { + actor.connection.getMessages().then(setMessages); + } + }, [actor.connection]); + + const sendToQueue = async (queueName: string) => { + if (actor.handle) { + await actor.handle.queue[queueName].send({ + priority: queueName, + timestamp: Date.now(), + }); + } + }; + + const receiveFromQueues = async (queues: string[]) => { + if (actor.connection) { + const received = await actor.connection.receiveFromQueues(queues, 5); + if (received.length > 0) { + setMessages((prev) => [ + ...prev, + ...received.map((r: { name: string; body: unknown }) => ({ + name: r.name, + body: r.body, + })), + ]); + } + } + }; + + const clearMessages = async () => { + if (actor.connection) { + await actor.connection.clearMessages(); + setMessages([]); + } + }; + + return ( +
+

Multi-Queue

+

+ Listen to multiple named queues simultaneously +

+ +
+ + + +
+ +
+ + + + +
+ +
+

Received Messages ({messages.length})

+ {messages.length === 0 ? ( +

No messages received yet

+ ) : ( +
    + {messages.map((msg, i) => ( +
  • + + {msg.name.toUpperCase()} + + + {JSON.stringify(msg.body)} + +
  • + ))} +
+ )} +
+
+ ); +} + +function TimeoutTab() { + const [timeoutMs, setTimeoutMs] = useState(3000); + const [isWaiting, setIsWaiting] = useState(false); + const [lastResult, setLastResult] = useState(null); + const [waitStartedAt, setWaitStartedAt] = useState(null); + + const actor = useActor({ name: "timeout", key: [instanceKey] }); + + const waitForMessage = async () => { + if (actor.connection) { + setIsWaiting(true); + setWaitStartedAt(Date.now()); + const result = await actor.connection.waitForMessage(timeoutMs); + setLastResult(result); + setIsWaiting(false); + setWaitStartedAt(null); + } + }; + + const sendMessage = async () => { + if (actor.handle) { + await actor.handle.queue.work.send({ text: "Hello!", sentAt: Date.now() }); + } + }; + + return ( +
+

Timeout

+

+ Demonstrate timeout option when no messages arrive +

+ +
+ + setTimeoutMs(Number(e.target.value))} + /> +
+ +
+ + +
+ + {isWaiting && waitStartedAt && ( +
+ +
+ )} + + {lastResult && ( +
+

{lastResult.timedOut ? "Timed Out" : "Message Received"}

+ {lastResult.message !== undefined && ( +

Message: {JSON.stringify(lastResult.message)}

+ )} +

Waited: {lastResult.waitedMs}ms

+
+ )} +
+ ); +} + +function CountdownTimer({ startedAt, timeoutMs }: { startedAt: number; timeoutMs: number }) { + const [remaining, setRemaining] = useState(timeoutMs); + + useEffect(() => { + const interval = setInterval(() => { + const elapsed = Date.now() - startedAt; + setRemaining(Math.max(0, timeoutMs - elapsed)); + }, 100); + return () => clearInterval(interval); + }, [startedAt, timeoutMs]); + + const progress = 1 - remaining / timeoutMs; + + return ( +
+
+
+
+ {(remaining / 1000).toFixed(1)}s remaining +
+ ); +} + +function WorkerTab() { + const [state, setState] = useState({ + status: "idle", + processed: 0, + lastJob: null, + }); + const [jobData, setJobData] = useState("task-1"); + + const actor = useActor({ name: "worker", key: [instanceKey] }); + + useEffect(() => { + if (actor.connection) { + const fetchState = async () => { + const s = await actor.connection!.getState(); + setState(s); + }; + fetchState(); + const interval = setInterval(fetchState, 1000); + return () => clearInterval(interval); + } + }, [actor.connection]); + + const submitJob = async () => { + if (actor.handle) { + await actor.handle.queue.jobs.send({ id: jobData, submittedAt: Date.now() }); + } + }; + + return ( +
+

Worker

+

+ Run handler consuming queue messages in a loop +

+ +
+
+ {state.status === "running" ? "Running" : "Idle"} +
+
+ Processed: + {state.processed} +
+
+ +
+ + setJobData(e.target.value)} + placeholder="Enter job identifier" + /> +
+ +
+ +
+ + {state.lastJob !== null && ( +
+

Last Processed Job

+
{JSON.stringify(state.lastJob, null, 2)}
+
+ )} +
+ ); +} + +function SelfSendTab() { + const [state, setState] = useState({ + sentCount: 0, + receivedCount: 0, + messages: [], + }); + const [messageBody, setMessageBody] = useState("self-message"); + + const actor = useActor({ name: "selfSender", key: [instanceKey] }); + + const refreshState = useCallback(async () => { + if (actor.connection) { + const s = await actor.connection.getState(); + setState(s); + } + }, [actor.connection]); + + useEffect(() => { + refreshState(); + }, [refreshState]); + + const sendToSelf = async () => { + if (actor.handle && actor.connection) { + await actor.handle.queue.self.send({ content: messageBody, sentAt: Date.now() }); + await actor.connection.incrementSentCount(); + await refreshState(); + } + }; + + const receiveFromSelf = async () => { + if (actor.connection) { + await actor.connection.receiveFromSelf(); + await refreshState(); + } + }; + + const clearMessages = async () => { + if (actor.connection) { + await actor.connection.clearMessages(); + await refreshState(); + } + }; + + return ( +
+

Self-Send

+

+ Actor sends messages to its own queue via inline client +

+ +
+
+ Sent: + {state.sentCount} +
+
+ Received: + {state.receivedCount} +
+
+ +
+ + setMessageBody(e.target.value)} + placeholder="Enter message content" + /> +
+ +
+ + + +
+ +
+

Received Messages ({state.messages.length})

+ {state.messages.length === 0 ? ( +

No messages received yet

+ ) : ( +
    + {state.messages.map((msg, i) => ( +
  • + {JSON.stringify(msg)} +
  • + ))} +
+ )} +
+
+ ); +} + +function KeepAwakeTab() { + const [state, setState] = useState({ + currentTask: null, + completedTasks: [], + }); + const [durationMs, setDurationMs] = useState(3000); + + const actor = useActor({ name: "keepAwake", key: [instanceKey] }); + + useEffect(() => { + if (actor.connection) { + const fetchState = async () => { + const s = await actor.connection!.getState(); + setState(s); + }; + fetchState(); + const interval = setInterval(fetchState, 500); + return () => clearInterval(interval); + } + }, [actor.connection]); + + const submitTask = async () => { + if (actor.handle) { + await actor.handle.queue.tasks.send({ durationMs }); + } + }; + + const clearTasks = async () => { + if (actor.connection) { + await actor.connection.clearTasks(); + const s = await actor.connection.getState(); + setState(s); + } + }; + + return ( +
+

Keep Awake

+

+ Consume queue message, then do long-running task wrapped in keepAwake() +

+ +
+ + setDurationMs(Number(e.target.value))} + /> +
+ +
+ + +
+ + {state.currentTask && ( +
+

Current Task

+ +
+ )} + +
+

Completed Tasks ({state.completedTasks.length})

+ {state.completedTasks.length === 0 ? ( +

No tasks completed yet

+ ) : ( +
    + {state.completedTasks.map((task) => ( +
  • + {task.id.slice(0, 8)}... + + Completed: {new Date(task.completedAt).toLocaleTimeString()} + +
  • + ))} +
+ )} +
+
+ ); +} + +function TaskProgress({ task }: { task: { id: string; startedAt: number; durationMs: number } }) { + const [progress, setProgress] = useState(0); + + useEffect(() => { + const interval = setInterval(() => { + const elapsed = Date.now() - task.startedAt; + setProgress(Math.min(1, elapsed / task.durationMs)); + }, 100); + return () => clearInterval(interval); + }, [task.startedAt, task.durationMs]); + + return ( +
+
+
+
+
+ Task: {task.id.slice(0, 8)}... + {Math.round(progress * 100)}% +
+
+ ); +} diff --git a/examples/queue-sandbox-vercel/frontend/main.tsx b/examples/queue-sandbox-vercel/frontend/main.tsx new file mode 100644 index 0000000000..2efcb334fc --- /dev/null +++ b/examples/queue-sandbox-vercel/frontend/main.tsx @@ -0,0 +1,12 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { App } from "./App.tsx"; + +const root = document.getElementById("root"); +if (!root) throw new Error("Root element not found"); + +createRoot(root).render( + + + , +); diff --git a/examples/queue-sandbox-vercel/index.html b/examples/queue-sandbox-vercel/index.html new file mode 100644 index 0000000000..413c852bf1 --- /dev/null +++ b/examples/queue-sandbox-vercel/index.html @@ -0,0 +1,312 @@ + + + + + + Queue Sandbox + + + +
+ + + diff --git a/examples/queue-sandbox-vercel/package.json b/examples/queue-sandbox-vercel/package.json new file mode 100644 index 0000000000..c90c72960c --- /dev/null +++ b/examples/queue-sandbox-vercel/package.json @@ -0,0 +1,41 @@ +{ + "name": "queue-sandbox-vercel", + "version": "2.0.21", + "private": true, + "type": "module", + "scripts": { + "dev": "vercel dev", + "build": "vite build", + "check-types": "tsc --noEmit" + }, + "dependencies": { + "@hono/node-server": "^1.19.7", + "@hono/node-ws": "^1.2.0", + "@rivetkit/react": "^2.0.38", + "hono": "^4.11.3", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "rivetkit": "^2.0.38" + }, + "devDependencies": { + "@types/node": "^22.13.9", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "@vitejs/plugin-react": "^4.2.0", + "tsx": "^3.12.7", + "typescript": "^5.5.2", + "vite": "^5.0.0" + }, + "template": { + "technologies": [ + "typescript", + "react" + ], + "tags": [ + "queues" + ], + "priority": 100, + "frontendPort": 5173 + }, + "license": "MIT" +} diff --git a/examples/queue-sandbox-vercel/src/actors.ts b/examples/queue-sandbox-vercel/src/actors.ts new file mode 100644 index 0000000000..c581fe152e --- /dev/null +++ b/examples/queue-sandbox-vercel/src/actors.ts @@ -0,0 +1,18 @@ +import { setup } from "rivetkit"; +import { sender } from "./actors/sender.ts"; +import { multiQueue } from "./actors/multi-queue.ts"; +import { timeout } from "./actors/timeout.ts"; +import { worker } from "./actors/worker.ts"; +import { selfSender } from "./actors/self-sender.ts"; +import { keepAwake } from "./actors/keep-awake.ts"; + +export const registry = setup({ + use: { + sender, + multiQueue, + timeout, + worker, + selfSender, + keepAwake, + }, +}); diff --git a/examples/queue-sandbox-vercel/src/actors/keep-awake.ts b/examples/queue-sandbox-vercel/src/actors/keep-awake.ts new file mode 100644 index 0000000000..dcf9a0a28e --- /dev/null +++ b/examples/queue-sandbox-vercel/src/actors/keep-awake.ts @@ -0,0 +1,70 @@ +import { actor } from "rivetkit"; + +export interface CurrentTask { + id: string; + startedAt: number; + durationMs: number; +} + +export interface CompletedTask { + id: string; + completedAt: number; +} + +export interface KeepAwakeState { + currentTask: CurrentTask | null; + completedTasks: CompletedTask[]; +} + +export const keepAwake = actor({ + state: { + currentTask: null as CurrentTask | null, + completedTasks: [] as CompletedTask[], + }, + async run(c) { + while (!c.abortSignal.aborted) { + const job = await c.queue.next("tasks", { timeout: 1000 }); + if (job) { + const taskId = crypto.randomUUID(); + const { durationMs } = job.body as { durationMs: number }; + + c.state.currentTask = { + id: taskId, + startedAt: Date.now(), + durationMs, + }; + c.broadcast("taskStarted", c.state.currentTask); + + // Wrap long-running work in keepAwake so actor doesn't sleep + await c.keepAwake( + new Promise((resolve) => setTimeout(resolve, durationMs)), + ); + + c.state.completedTasks.push({ id: taskId, completedAt: Date.now() }); + c.state.currentTask = null; + c.broadcast("taskCompleted", { + taskId, + completedTasks: c.state.completedTasks, + }); + } + } + }, + actions: { + getState(c): KeepAwakeState { + return { + currentTask: c.state.currentTask, + completedTasks: c.state.completedTasks, + }; + }, + clearTasks(c) { + c.state.completedTasks = []; + c.broadcast("taskCompleted", { + taskId: null, + completedTasks: [], + }); + }, + }, + options: { + sleepTimeout: 2000, + }, +}); diff --git a/examples/queue-sandbox-vercel/src/actors/multi-queue.ts b/examples/queue-sandbox-vercel/src/actors/multi-queue.ts new file mode 100644 index 0000000000..57d57e7ad8 --- /dev/null +++ b/examples/queue-sandbox-vercel/src/actors/multi-queue.ts @@ -0,0 +1,31 @@ +import { actor } from "rivetkit"; + +export interface QueueMessage { + name: string; + body: unknown; +} + +export const multiQueue = actor({ + state: { + messages: [] as QueueMessage[], + }, + actions: { + async receiveFromQueues(c, names: string[], count: number) { + const msgs = await c.queue.next(names, { count, timeout: 100 }); + if (msgs && msgs.length > 0) { + for (const msg of msgs) { + c.state.messages.push({ name: msg.name, body: msg.body }); + } + c.broadcast("messagesReceived", c.state.messages); + } + return msgs ?? []; + }, + getMessages(c): QueueMessage[] { + return c.state.messages; + }, + clearMessages(c) { + c.state.messages = []; + c.broadcast("messagesReceived", c.state.messages); + }, + }, +}); diff --git a/examples/queue-sandbox-vercel/src/actors/self-sender.ts b/examples/queue-sandbox-vercel/src/actors/self-sender.ts new file mode 100644 index 0000000000..bfd1b3cd1a --- /dev/null +++ b/examples/queue-sandbox-vercel/src/actors/self-sender.ts @@ -0,0 +1,48 @@ +import { actor } from "rivetkit"; + +export interface SelfSenderState { + sentCount: number; + receivedCount: number; + messages: unknown[]; +} + +export const selfSender = actor({ + state: { + sentCount: 0, + receivedCount: 0, + messages: [] as unknown[], + }, + actions: { + async receiveFromSelf(c) { + const msg = await c.queue.next("self", { timeout: 100 }); + if (msg) { + c.state.receivedCount += 1; + c.state.messages.push(msg.body); + c.broadcast("received", { + receivedCount: c.state.receivedCount, + message: msg.body, + }); + return msg.body; + } + return null; + }, + getState(c): SelfSenderState { + return { + sentCount: c.state.sentCount, + receivedCount: c.state.receivedCount, + messages: c.state.messages, + }; + }, + clearMessages(c) { + c.state.sentCount = 0; + c.state.receivedCount = 0; + c.state.messages = []; + c.broadcast("sent", { sentCount: 0 }); + c.broadcast("received", { receivedCount: 0, message: null }); + }, + incrementSentCount(c) { + c.state.sentCount += 1; + c.broadcast("sent", { sentCount: c.state.sentCount }); + }, + }, +}); diff --git a/examples/queue-sandbox-vercel/src/actors/sender.ts b/examples/queue-sandbox-vercel/src/actors/sender.ts new file mode 100644 index 0000000000..0db2c49a0b --- /dev/null +++ b/examples/queue-sandbox-vercel/src/actors/sender.ts @@ -0,0 +1,36 @@ +import { actor } from "rivetkit"; + +export interface ReceivedMessage { + name: string; + body: unknown; + receivedAt: number; +} + +export const sender = actor({ + state: { + messages: [] as ReceivedMessage[], + }, + actions: { + getMessages(c): ReceivedMessage[] { + return c.state.messages; + }, + async receiveOne(c) { + const msg = await c.queue.next("task", { timeout: 100 }); + if (msg) { + const received: ReceivedMessage = { + name: msg.name, + body: msg.body, + receivedAt: Date.now(), + }; + c.state.messages.push(received); + c.broadcast("messageReceived", c.state.messages); + return received; + } + return null; + }, + clearMessages(c) { + c.state.messages = []; + c.broadcast("messageReceived", c.state.messages); + }, + }, +}); diff --git a/examples/queue-sandbox-vercel/src/actors/timeout.ts b/examples/queue-sandbox-vercel/src/actors/timeout.ts new file mode 100644 index 0000000000..be0ff8fea7 --- /dev/null +++ b/examples/queue-sandbox-vercel/src/actors/timeout.ts @@ -0,0 +1,44 @@ +import { actor } from "rivetkit"; + +export interface TimeoutResult { + timedOut: boolean; + message?: unknown; + waitedMs: number; +} + +export interface TimeoutState { + lastResult: TimeoutResult | null; + waitStartedAt: number | null; +} + +export const timeout = actor({ + state: { + lastResult: null as TimeoutResult | null, + waitStartedAt: null as number | null, + }, + actions: { + async waitForMessage(c, timeoutMs: number): Promise { + const startedAt = Date.now(); + c.state.waitStartedAt = startedAt; + c.broadcast("waitStarted", { startedAt, timeoutMs }); + + const msg = await c.queue.next("work", { timeout: timeoutMs }); + + const waitedMs = Date.now() - startedAt; + const result: TimeoutResult = msg + ? { timedOut: false, message: msg.body, waitedMs } + : { timedOut: true, waitedMs }; + + c.state.lastResult = result; + c.state.waitStartedAt = null; + c.broadcast("waitCompleted", result); + return result; + }, + getState(c): TimeoutState { + return { + lastResult: c.state.lastResult, + waitStartedAt: c.state.waitStartedAt, + }; + }, + }, +}); diff --git a/examples/queue-sandbox-vercel/src/actors/worker.ts b/examples/queue-sandbox-vercel/src/actors/worker.ts new file mode 100644 index 0000000000..e7b710f5dc --- /dev/null +++ b/examples/queue-sandbox-vercel/src/actors/worker.ts @@ -0,0 +1,45 @@ +import { actor } from "rivetkit"; + +export interface WorkerState { + status: "idle" | "running"; + processed: number; + lastJob: unknown; +} + +export const worker = actor({ + state: { + status: "idle" as "idle" | "running", + processed: 0, + lastJob: null as unknown, + }, + async run(c) { + c.state.status = "running"; + c.broadcast("statusChanged", { + status: c.state.status, + processed: c.state.processed, + }); + + while (!c.abortSignal.aborted) { + const job = await c.queue.next("jobs", { timeout: 1000 }); + if (job) { + c.state.processed += 1; + c.state.lastJob = job.body; + c.broadcast("jobProcessed", { + processed: c.state.processed, + job: job.body, + }); + } + } + + c.state.status = "idle"; + }, + actions: { + getState(c): WorkerState { + return { + status: c.state.status, + processed: c.state.processed, + lastJob: c.state.lastJob, + }; + }, + }, +}); diff --git a/examples/queue-sandbox-vercel/src/server.ts b/examples/queue-sandbox-vercel/src/server.ts new file mode 100644 index 0000000000..95c8895f94 --- /dev/null +++ b/examples/queue-sandbox-vercel/src/server.ts @@ -0,0 +1,6 @@ +import { Hono } from "hono"; +import { registry } from "./actors.ts"; + +const app = new Hono(); +app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +export default app; diff --git a/examples/queue-sandbox-vercel/tsconfig.json b/examples/queue-sandbox-vercel/tsconfig.json new file mode 100644 index 0000000000..2a870bab03 --- /dev/null +++ b/examples/queue-sandbox-vercel/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "esnext", + "lib": [ + "esnext", + "dom" + ], + "jsx": "react-jsx", + "module": "esnext", + "moduleResolution": "bundler", + "types": [ + "node", + "vite/client" + ], + "noEmit": true, + "strict": true, + "skipLibCheck": true, + "allowImportingTsExtensions": true, + "rewriteRelativeImportExtensions": true + }, + "include": [ + "src/**/*", + "api/**/*", + "frontend/**/*" + ] +} diff --git a/examples/queue-sandbox-vercel/turbo.json b/examples/queue-sandbox-vercel/turbo.json new file mode 100644 index 0000000000..c3d3d3b9bb --- /dev/null +++ b/examples/queue-sandbox-vercel/turbo.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://turbo.build/schema.json", + "extends": [ + "//" + ] +} diff --git a/examples/queue-sandbox-vercel/vercel.json b/examples/queue-sandbox-vercel/vercel.json new file mode 100644 index 0000000000..64a8d9b467 --- /dev/null +++ b/examples/queue-sandbox-vercel/vercel.json @@ -0,0 +1,9 @@ +{ + "framework": "vite", + "rewrites": [ + { + "source": "/api/(.*)", + "destination": "/api" + } + ] +} diff --git a/examples/queue-sandbox-vercel/vite.config.ts b/examples/queue-sandbox-vercel/vite.config.ts new file mode 100644 index 0000000000..f9f0d5ec2f --- /dev/null +++ b/examples/queue-sandbox-vercel/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +export default defineConfig({ + plugins: [react()], +}); diff --git a/examples/workflow-sandbox-vercel/.gitignore b/examples/workflow-sandbox-vercel/.gitignore new file mode 100644 index 0000000000..e28f3d79db --- /dev/null +++ b/examples/workflow-sandbox-vercel/.gitignore @@ -0,0 +1,4 @@ +.actorcore +node_modules +dist +.vercel diff --git a/examples/workflow-sandbox-vercel/README.md b/examples/workflow-sandbox-vercel/README.md new file mode 100644 index 0000000000..c8314f8872 --- /dev/null +++ b/examples/workflow-sandbox-vercel/README.md @@ -0,0 +1,104 @@ +> **Note:** This is the Vercel-optimized version of the [workflow-sandbox](../workflow-sandbox) example. +> It uses the `hono/vercel` adapter and is configured for Vercel deployment. + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frivet-gg%2Frivet%2Ftree%2Fmain%2Fexamples%2Fworkflow-sandbox-vercel&project-name=workflow-sandbox-vercel) + +# Workflow Sandbox + +Interactive sandbox for testing all RivetKit workflow patterns. + +## Getting Started + +```sh +git clone https://github.com/rivet-dev/rivet.git +cd rivet/examples/workflow-sandbox +npm install +npm run dev +``` + + +## Features + +This example demonstrates all workflow features through a tabbed interface: + +- **Steps** - Multi-step order processing with automatic retries +- **Sleep** - Durable countdown timers that survive restarts +- **Loops** - Batch processing with persistent cursor state +- **Listen** - Approval queue with timeout-based decisions +- **Join** - Parallel data fetching (wait-all pattern) +- **Race** - Work vs timeout (first-wins pattern) +- **Rollback** - Compensating transactions with rollback handlers + +## Implementation + +Each workflow pattern is implemented as a separate actor with its own state and workflow loop. The key patterns demonstrated: + +### Steps +```typescript +await loopCtx.step("validate", async () => { /* ... */ }); +await loopCtx.step("charge", async () => { /* ... */ }); +await loopCtx.step("fulfill", async () => { /* ... */ }); +``` + +### Sleep +```typescript +await loopCtx.sleep("countdown", durationMs); +``` + +### Loops +```typescript +await ctx.loop({ + name: "batch-loop", + state: { cursor: 0 }, + run: async (loopCtx, state) => { + // Process batch + return Loop.continue({ cursor: state.cursor + 1 }); + }, +}); +``` + +### Listen +```typescript +const decision = await loopCtx.listenWithTimeout( + "wait-decision", + "decision-queue", + 30000 // 30 second timeout +); +``` + +### Join +```typescript +const results = await loopCtx.join("fetch-all", { + users: { run: async (ctx) => fetchUsers() }, + orders: { run: async (ctx) => fetchOrders() }, +}); +``` + +### Race +```typescript +const { winner, value } = await loopCtx.race("work-vs-timeout", [ + { name: "work", run: async (ctx) => doWork() }, + { name: "timeout", run: async (ctx) => ctx.sleep("wait", timeout) }, +]); +``` + +### Rollback +```typescript +await loopCtx.rollbackCheckpoint("payment-checkpoint"); + +await loopCtx.step({ + name: "charge-card", + run: async () => chargeCard(), + rollback: async () => refundCard(), +}); +``` + +See the implementation in [`src/actors.ts`](https://github.com/rivet-dev/rivet/tree/main/examples/workflow-sandbox/src/actors.ts). + +## Resources + +Read more about [workflows](/docs/workflows). + +## License + +MIT diff --git a/examples/workflow-sandbox-vercel/api/index.ts b/examples/workflow-sandbox-vercel/api/index.ts new file mode 100644 index 0000000000..07a830391f --- /dev/null +++ b/examples/workflow-sandbox-vercel/api/index.ts @@ -0,0 +1,3 @@ +import app from "../src/server.ts"; + +export default app; diff --git a/examples/workflow-sandbox-vercel/frontend/App.tsx b/examples/workflow-sandbox-vercel/frontend/App.tsx new file mode 100644 index 0000000000..18c16da037 --- /dev/null +++ b/examples/workflow-sandbox-vercel/frontend/App.tsx @@ -0,0 +1,956 @@ +import { useState, useEffect } from "react"; +import { createRivetKit } from "@rivetkit/react"; +import type { + registry, + Order, + Timer, + BatchJob, + ApprovalRequest, + DashboardState, + RaceTask, + Transaction, +} from "../src/actors.ts"; + +const { useActor } = createRivetKit( + `${location.origin}/api/rivet` +); + +// localStorage helpers for persisting actor keys across page refreshes +function usePersistedState(key: string, initial: T): [T, React.Dispatch>] { + const [state, setState] = useState(() => { + const stored = localStorage.getItem(key); + return stored ? JSON.parse(stored) : initial; + }); + + useEffect(() => { + localStorage.setItem(key, JSON.stringify(state)); + }, [key, state]); + + return [state, setState]; +} + +type Tab = + | "steps" + | "sleep" + | "loops" + | "listen" + | "join" + | "race" + | "rollback"; + +const TABS: { id: Tab; label: string; description: string }[] = [ + { id: "steps", label: "Steps", description: "Multi-step order processing" }, + { id: "sleep", label: "Sleep", description: "Durable countdown timers" }, + { id: "loops", label: "Loops", description: "Batch processing with cursor" }, + { id: "listen", label: "Listen", description: "Approval queue with timeout" }, + { id: "join", label: "Join", description: "Parallel data aggregation" }, + { id: "race", label: "Race", description: "Work vs timeout pattern" }, + { + id: "rollback", + label: "Rollback", + description: "Compensating transactions", + }, +]; + +export function App() { + const [activeTab, setActiveTab] = useState("steps"); + + return ( +
+
+

Workflow Sandbox

+

Test different RivetKit workflow patterns

+
+ + + +
+ {activeTab === "steps" && } + {activeTab === "sleep" && } + {activeTab === "loops" && } + {activeTab === "listen" && } + {activeTab === "join" && } + {activeTab === "race" && } + {activeTab === "rollback" && } +
+
+ ); +} + +// ============================================================================ +// STEPS DEMO - One actor per order +// ============================================================================ + +function StepsDemo() { + const [orderKeys, setOrderKeys] = usePersistedState("workflow-sandbox:orders", []); + + const createOrder = () => { + const orderId = `ORD-${Date.now().toString(36).toUpperCase()}`; + setOrderKeys((prev) => [orderId, ...prev]); + }; + + return ( +
+
+

Steps Demo

+

+ Sequential workflow steps with automatic retries. Each order goes + through validate, charge, and fulfill steps. +

+ +
+ +
+
+ {orderKeys.length === 0 && ( +

No orders yet. Create one to see the workflow!

+ )} + {orderKeys.map((orderId) => ( + + ))} +
+
+
+ ); +} + +function OrderCard({ orderId }: { orderId: string }) { + const actor = useActor({ name: "order", key: [orderId] }); + const [order, setOrder] = useState(null); + + useEffect(() => { + actor.connection?.getOrder().then(setOrder); + }, [actor.connection]); + + actor.useEvent("orderUpdated", setOrder); + + const getStatusColor = (status: Order["status"]) => { + switch (status) { + case "pending": + return "#8e8e93"; + case "validating": + case "charging": + case "fulfilling": + return "#ff9f0a"; + case "completed": + return "#30d158"; + case "failed": + return "#ff3b30"; + default: + return "#8e8e93"; + } + }; + + if (!order) return
Loading...
; + + return ( +
+
+ {order.id} + + {order.status} + +
+
+ {["Validate", "Charge", "Fulfill", "Complete"].map((step, idx) => ( +
idx ? "done" : ""} ${order.step === idx + 1 ? "active" : ""}`} + > +
+ {order.step > idx ? "✓" : idx + 1} +
+ {step} +
+ ))} +
+ {order.error &&
{order.error}
} +
+ ); +} + +// ============================================================================ +// SLEEP DEMO - One actor per timer +// ============================================================================ + +function SleepDemo() { + const [timerKeys, setTimerKeys] = usePersistedState< + { id: string; name: string; durationMs: number }[] + >("workflow-sandbox:timers", []); + const [duration, setDuration] = useState(10); + + const createTimer = () => { + const timerId = crypto.randomUUID(); + const name = `Timer ${timerKeys.length + 1}`; + setTimerKeys((prev) => [{ id: timerId, name, durationMs: duration * 1000 }, ...prev]); + }; + + return ( +
+
+

Sleep Demo

+

+ Durable sleep that survives restarts. Set a timer and watch it + countdown - even if you refresh the page! +

+
+ setDuration(parseInt(e.target.value) || 1)} + /> + seconds + +
+
+ +
+
+ {timerKeys.length === 0 && ( +

No timers yet. Create one to test durable sleep!

+ )} + {timerKeys.map((t) => ( + + ))} +
+
+
+ ); +} + +function TimerCard({ + timerId, + name, + durationMs, +}: { + timerId: string; + name: string; + durationMs: number; +}) { + const actor = useActor({ + name: "timer", + key: [timerId], + createWithInput: { name, durationMs }, + }); + const [timer, setTimer] = useState(null); + const [remaining, setRemaining] = useState(null); + + useEffect(() => { + actor.connection?.getTimer().then(setTimer); + }, [actor.connection]); + + actor.useEvent("timerStarted", setTimer); + actor.useEvent("timerCompleted", setTimer); + + useEffect(() => { + if (!timer) return; + if (timer.completedAt) { + setRemaining(0); + return; + } + + const update = () => { + const elapsed = Date.now() - timer.startedAt; + const left = Math.max(0, timer.durationMs - elapsed); + setRemaining(left); + }; + + update(); + const interval = setInterval(update, 100); + return () => clearInterval(interval); + }, [timer]); + + if (!timer || remaining === null) return
Loading...
; + + const isComplete = !!timer.completedAt; + const progress = isComplete + ? 100 + : ((timer.durationMs - remaining) / timer.durationMs) * 100; + + return ( +
+
+ {timer.name} + + {isComplete ? "Completed" : `${Math.ceil(remaining / 1000)}s`} + +
+
+
+
+
+ ); +} + +// ============================================================================ +// LOOPS DEMO - One actor per batch job +// ============================================================================ + +function LoopsDemo() { + const [jobKeys, setJobKeys] = usePersistedState("workflow-sandbox:jobs", []); + + const startJob = () => { + const jobId = `JOB-${Date.now().toString(36).toUpperCase()}`; + setJobKeys((prev) => [jobId, ...prev]); + }; + + return ( +
+
+

Loops Demo

+

+ Batch processing with persistent cursor state. Process 50 items in + batches of 5 - state persists across restarts. +

+ +
+ +
+
+ {jobKeys.length === 0 && ( +

No batch jobs yet. Start one to see loop processing!

+ )} + {jobKeys.map((jobId) => ( + + ))} +
+
+
+ ); +} + +function BatchJobCard({ jobId }: { jobId: string }) { + const actor = useActor({ + name: "batch", + key: [jobId], + createWithInput: { totalItems: 50, batchSize: 5 }, + }); + const [job, setJob] = useState(null); + + useEffect(() => { + actor.connection?.getJob().then(setJob); + }, [actor.connection]); + + actor.useEvent("stateChanged", setJob); + + if (!job) return
Loading...
; + + return ( +
+
+ {job.id} + {job.status} +
+
+
+ {job.processedTotal} + Items +
+
+ {job.batches.length} + Batches +
+
+
+
+
+
+ ); +} + +// ============================================================================ +// LISTEN DEMO - One actor per approval request +// ============================================================================ + +function ListenDemo() { + const [requestKeys, setRequestKeys] = usePersistedState< + { id: string; title: string; description: string }[] + >("workflow-sandbox:requests", []); + + const submitRequest = () => { + const requestId = crypto.randomUUID(); + const title = `Request ${requestKeys.length + 1}`; + setRequestKeys((prev) => [ + { id: requestId, title, description: "Please approve this request" }, + ...prev, + ]); + }; + + return ( +
+
+

Listen Demo

+

+ Approval workflow with 30-second timeout. Submit a request and + approve/reject it before it times out. +

+ +
+ +
+
+ {requestKeys.length === 0 && ( +

No requests yet. Submit one to test listen!

+ )} + {requestKeys.map((r) => ( + + ))} +
+
+
+ ); +} + +function ApprovalRequestCard({ + requestId, + title, + description, +}: { + requestId: string; + title: string; + description: string; +}) { + const actor = useActor({ + name: "approval", + key: [requestId], + createWithInput: { title, description }, + }); + const [request, setRequest] = useState(null); + + useEffect(() => { + actor.connection?.getRequest().then(setRequest); + }, [actor.connection]); + + actor.useEvent("requestCreated", setRequest); + actor.useEvent("requestUpdated", setRequest); + + const getStatusColor = (status: ApprovalRequest["status"]) => { + switch (status) { + case "pending": + return "#ff9f0a"; + case "approved": + return "#30d158"; + case "rejected": + return "#ff3b30"; + case "timeout": + return "#8e8e93"; + default: + return "#8e8e93"; + } + }; + + if (!request) return
Loading...
; + + const isPending = request.status === "pending" && !request.deciding; + const isDeciding = request.status === "pending" && request.deciding; + + return ( +
+
+ {request.title} + + {isDeciding ? "processing..." : request.status} + +
+ {isPending && ( + actor.connection?.approve(approver)} + onReject={(approver) => actor.connection?.reject(approver)} + /> + )} + {request.decidedBy && ( +
Decided by: {request.decidedBy}
+ )} +
+ ); +} + +function RequestCountdown({ + request, + onApprove, + onReject, +}: { + request: ApprovalRequest; + onApprove: (approver: string) => void; + onReject: (approver: string) => void; +}) { + const [remaining, setRemaining] = useState(30); + + useEffect(() => { + const update = () => { + const elapsed = Date.now() - request.createdAt; + const left = Math.max(0, 30000 - elapsed); + setRemaining(Math.ceil(left / 1000)); + }; + + update(); + const interval = setInterval(update, 1000); + return () => clearInterval(interval); + }, [request.createdAt]); + + return ( +
+ {remaining}s remaining + + +
+ ); +} + +// ============================================================================ +// JOIN DEMO - Single dashboard actor +// ============================================================================ + +function JoinDemo() { + const actor = useActor({ name: "dashboard", key: ["main"] }); + const [state, setState] = useState({ + data: null, + loading: false, + branches: { users: "pending", orders: "pending", metrics: "pending" }, + lastRefresh: null, + }); + + useEffect(() => { + actor.connection?.getState().then(setState); + }, [actor.connection]); + + actor.useEvent("stateChanged", setState); + + const getBranchColor = (status: string) => { + switch (status) { + case "pending": + return "#8e8e93"; + case "running": + return "#ff9f0a"; + case "completed": + return "#30d158"; + case "failed": + return "#ff3b30"; + default: + return "#8e8e93"; + } + }; + + return ( +
+
+

Join Demo

+

+ Parallel data fetching with join (wait-all). Fetch users, orders, and + metrics simultaneously. +

+ +
+ +
+
+ {(["users", "orders", "metrics"] as const).map((branch) => ( +
+ + {branch} + {state.branches[branch]} +
+ ))} +
+ + {state.data && ( +
+
+

Users

+
{state.data.users.count}
+
+ {state.data.users.activeToday} active today +
+
+
+

Orders

+
{state.data.orders.count}
+
+ ${state.data.orders.revenue.toLocaleString()} revenue +
+
+
+

Metrics

+
+ {state.data.metrics.pageViews.toLocaleString()} +
+
page views
+
+
+ )} +
+
+ ); +} + +// ============================================================================ +// RACE DEMO - One actor per race task +// ============================================================================ + +function RaceDemo() { + const [taskKeys, setTaskKeys] = usePersistedState< + { id: string; workDurationMs: number; timeoutMs: number }[] + >("workflow-sandbox:raceTasks", []); + const [workDuration, setWorkDuration] = useState(3000); + const [timeout, setTimeoutVal] = useState(5000); + + const runTask = () => { + const taskId = crypto.randomUUID(); + setTaskKeys((prev) => [ + { id: taskId, workDurationMs: workDuration, timeoutMs: timeout }, + ...prev, + ]); + }; + + return ( +
+
+

Race Demo

+

+ Race pattern - work vs timeout. If work completes before timeout, it + wins. Otherwise timeout wins. +

+
+
+ + setWorkDuration(parseInt(e.target.value) || 0)} + /> +
+
+ + setTimeoutVal(parseInt(e.target.value) || 0)} + /> +
+ +
+
+ +
+
+ {taskKeys.map((t) => ( + + ))} +
+
+
+ ); +} + +function RaceTaskCard({ + taskId, + workDurationMs, + timeoutMs, +}: { + taskId: string; + workDurationMs: number; + timeoutMs: number; +}) { + const actor = useActor({ + name: "race", + key: [taskId], + createWithInput: { workDurationMs, timeoutMs }, + }); + const [task, setTask] = useState(null); + const [elapsed, setElapsed] = useState(0); + const [showAnimation, setShowAnimation] = useState(true); + + useEffect(() => { + actor.connection?.getTask().then(setTask); + }, [actor.connection]); + + actor.useEvent("raceStarted", setTask); + actor.useEvent("raceCompleted", setTask); + + // Update elapsed time for animation + useEffect(() => { + if (!task) return; + + const updateElapsed = () => { + const now = task.completedAt ?? Date.now(); + const newElapsed = now - task.startedAt; + setElapsed(newElapsed); + + // Hide animation once both bars have completed + const maxDuration = Math.max(task.workDurationMs, task.timeoutMs); + if (newElapsed > maxDuration + 500) { + setShowAnimation(false); + } + }; + + updateElapsed(); + const interval = setInterval(updateElapsed, 50); + return () => clearInterval(interval); + }, [task]); + + if (!task) return
Loading...
; + + const workProgress = Math.min(100, (elapsed / task.workDurationMs) * 100); + const timeoutProgress = Math.min(100, (elapsed / task.timeoutMs) * 100); + const isCompleted = task.status !== "running"; + + // Show animation if still running or if recently completed + if (showAnimation) { + return ( +
+
+
+ Work ({task.workDurationMs}ms) +
+
+
+ {isCompleted && task.status === "work_won" && Winner!} +
+
+ Timeout ({task.timeoutMs}ms) +
+
+
+ {isCompleted && task.status === "timeout_won" && Winner!} +
+
+ {isCompleted && ( +
+ {task.status === "work_won" ? "Work completed first!" : "Timeout triggered!"} + {task.actualDurationMs}ms +
+ )} +
+ ); + } + + return ( +
+
+ {task.status === "work_won" ? "Work Won!" : "Timeout!"} +
+
+ Work: {task.workDurationMs}ms | Timeout: {task.timeoutMs}ms | Actual:{" "} + {task.actualDurationMs}ms +
+
+ ); +} + +// ============================================================================ +// ROLLBACK DEMO - One actor per transaction +// ============================================================================ + +function RollbackDemo() { + const [txKeys, setTxKeys] = usePersistedState< + { id: string; amount: number; shouldFail: boolean }[] + >("workflow-sandbox:transactions", []); + + const processPayment = (shouldFail: boolean) => { + const txId = crypto.randomUUID(); + const amount = Math.floor(50 + Math.random() * 200); + setTxKeys((prev) => [{ id: txId, amount, shouldFail }, ...prev]); + }; + + return ( +
+
+

Rollback Demo

+

+ Compensating transactions with rollback. Process payments with + automatic rollback on failure. +

+
+ + +
+
+ +
+
+ {txKeys.length === 0 && ( +

+ No transactions yet. Process a payment to see rollback! +

+ )} + {txKeys.map((t) => ( + + ))} +
+
+
+ ); +} + +function TransactionCard({ + txId, + amount, + shouldFail, +}: { + txId: string; + amount: number; + shouldFail: boolean; +}) { + const actor = useActor({ + name: "payment", + key: [txId], + createWithInput: { amount, shouldFail }, + }); + const [tx, setTx] = useState(null); + + useEffect(() => { + actor.connection?.getTransaction().then(setTx); + }, [actor.connection]); + + actor.useEvent("transactionStarted", setTx); + actor.useEvent("transactionUpdated", setTx); + actor.useEvent("transactionCompleted", setTx); + actor.useEvent("transactionFailed", setTx); + + const getStepColor = (status: string) => { + switch (status) { + case "pending": + return "#8e8e93"; + case "running": + return "#ff9f0a"; + case "completed": + return "#30d158"; + case "rolling_back": + return "#bf5af2"; + case "rolled_back": + return "#bf5af2"; + case "failed": + return "#ff3b30"; + default: + return "#8e8e93"; + } + }; + + const getStepIcon = (status: string) => { + switch (status) { + case "pending": + return "○"; + case "completed": + return "✓"; + case "rolled_back": + return "↩"; + default: + return "○"; + } + }; + + if (!tx) return
Loading...
; + + const isRollingBack = tx.status === "rolling_back"; + const hasRollback = tx.steps.some((s) => s.status === "rolled_back"); + + return ( +
+
+ ${tx.amount} + + {tx.status === "rolling_back" ? "↩ rolling back" : tx.status} + +
+ {isRollingBack && ( +
+ Compensating actions in progress... +
+ )} +
+ {tx.steps.map((step) => ( +
+ + {getStepIcon(step.status)} + + {step.name} + + {step.status} + {step.status === "rolled_back" && " ↩"} + +
+ ))} +
+ {hasRollback && tx.status === "failed" && ( +
+ All completed steps have been rolled back +
+ )} + {tx.error &&
{tx.error}
} +
+ ); +} diff --git a/examples/workflow-sandbox-vercel/frontend/main.tsx b/examples/workflow-sandbox-vercel/frontend/main.tsx new file mode 100644 index 0000000000..372f49c622 --- /dev/null +++ b/examples/workflow-sandbox-vercel/frontend/main.tsx @@ -0,0 +1,12 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { App } from "./App.tsx"; + +const root = document.getElementById("root"); +if (!root) throw new Error("Root element not found"); + +createRoot(root).render( + + + +); diff --git a/examples/workflow-sandbox-vercel/index.html b/examples/workflow-sandbox-vercel/index.html new file mode 100644 index 0000000000..662a01901b --- /dev/null +++ b/examples/workflow-sandbox-vercel/index.html @@ -0,0 +1,878 @@ + + + + + + Workflow Sandbox + + + +
+ + + diff --git a/examples/workflow-sandbox-vercel/package.json b/examples/workflow-sandbox-vercel/package.json new file mode 100644 index 0000000000..3db28d371f --- /dev/null +++ b/examples/workflow-sandbox-vercel/package.json @@ -0,0 +1,41 @@ +{ + "name": "workflow-sandbox-vercel", + "version": "2.0.21", + "private": true, + "type": "module", + "scripts": { + "dev": "vercel dev", + "build": "vite build", + "check-types": "tsc --noEmit" + }, + "dependencies": { + "@hono/node-server": "^1.19.7", + "@hono/node-ws": "^1.3.0", + "@rivetkit/react": "*", + "hono": "^4.11.3", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "rivetkit": "*" + }, + "devDependencies": { + "@types/node": "^22.13.9", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "@vitejs/plugin-react": "^4.2.0", + "tsx": "^3.12.7", + "typescript": "^5.5.2", + "vite": "^5.0.0" + }, + "template": { + "technologies": [ + "react", + "typescript" + ], + "tags": [ + "experimental" + ], + "frontendPort": 5173 + }, + "stableVersion": "0.8.0", + "license": "MIT" +} diff --git a/examples/workflow-sandbox-vercel/src/actors.ts b/examples/workflow-sandbox-vercel/src/actors.ts new file mode 100644 index 0000000000..24d87fdad9 --- /dev/null +++ b/examples/workflow-sandbox-vercel/src/actors.ts @@ -0,0 +1,63 @@ +// Workflow Sandbox - Actor Registry +// Each actor demonstrates a different workflow feature using actor-per-workflow pattern + +import { setup } from "rivetkit"; + +// Import actors from individual files +export { timer } from "./actors/timer.ts"; +export type { Timer, TimerInput } from "./actors/timer.ts"; + +export { order } from "./actors/order.ts"; +export type { Order, OrderStatus } from "./actors/order.ts"; + +export { batch } from "./actors/batch.ts"; +export type { BatchInfo, BatchJob, BatchJobInput } from "./actors/batch.ts"; + +export { approval } from "./actors/approval.ts"; +export type { + ApprovalRequest, + ApprovalRequestInput, + RequestStatus, +} from "./actors/approval.ts"; + +export { dashboard } from "./actors/dashboard.ts"; +export type { + DashboardData, + DashboardState, + UserStats, + OrderStats, + MetricsStats, + BranchStatus, +} from "./actors/dashboard.ts"; + +export { race } from "./actors/race.ts"; +export type { RaceTask, RaceTaskInput } from "./actors/race.ts"; + +export { payment } from "./actors/payment.ts"; +export type { + Transaction, + TransactionStep, + TransactionInput, +} from "./actors/payment.ts"; + +// Import for registry setup +import { timer } from "./actors/timer.ts"; +import { order } from "./actors/order.ts"; +import { batch } from "./actors/batch.ts"; +import { approval } from "./actors/approval.ts"; +import { dashboard } from "./actors/dashboard.ts"; +import { race } from "./actors/race.ts"; +import { payment } from "./actors/payment.ts"; + +// Registry setup +export const registry = setup({ + use: { + timer, + order, + batch, + approval, + dashboard, + race, + payment, + }, +}); diff --git a/examples/workflow-sandbox-vercel/src/actors/_helpers.ts b/examples/workflow-sandbox-vercel/src/actors/_helpers.ts new file mode 100644 index 0000000000..3c6eed304c --- /dev/null +++ b/examples/workflow-sandbox-vercel/src/actors/_helpers.ts @@ -0,0 +1,12 @@ +// Type helper - cast loop context to access actor-specific properties +// Only call these helpers INSIDE a step callback where state access is allowed +// biome-ignore lint/suspicious/noExplicitAny: Workflow context typing workaround +export type ActorLoopContext = { + state: S; + broadcast: (name: string, ...args: unknown[]) => void; +}; + +// biome-ignore lint/suspicious/noExplicitAny: Workflow context typing workaround +export function actorCtx(ctx: unknown): ActorLoopContext { + return ctx as any; +} diff --git a/examples/workflow-sandbox-vercel/src/actors/approval.ts b/examples/workflow-sandbox-vercel/src/actors/approval.ts new file mode 100644 index 0000000000..d6d5225c72 --- /dev/null +++ b/examples/workflow-sandbox-vercel/src/actors/approval.ts @@ -0,0 +1,110 @@ +// APPROVAL REQUEST (Listen Demo) +// Demonstrates: Message listening with timeout for approval workflows +// One actor per approval request - actor key is the request ID + +import { actor } from "rivetkit"; +import { Loop, workflow, workflowQueueName } from "rivetkit/workflow"; +import { actorCtx } from "./_helpers.ts"; + +export type RequestStatus = "pending" | "approved" | "rejected" | "timeout"; + +export type ApprovalRequest = { + id: string; + title: string; + description: string; + status: RequestStatus; + createdAt: number; + decidedAt?: number; + decidedBy?: string; + deciding?: boolean; // True when a decision is being processed +}; + +type State = ApprovalRequest; + +const QUEUE_DECISION = workflowQueueName("decision"); + +const APPROVAL_TIMEOUT_MS = 30000; + +export type ApprovalRequestInput = { + title?: string; + description?: string; +}; + +export const approval = actor({ + createState: (c, input: ApprovalRequestInput): ApprovalRequest => ({ + id: c.key[0] as string, + title: input?.title ?? "Untitled Request", + description: input?.description ?? "", + status: "pending", + createdAt: Date.now(), + }), + + actions: { + getRequest: (c): ApprovalRequest => c.state, + + approve: async (c, approver: string) => { + if (c.state.status !== "pending") return; + c.state.deciding = true; + c.broadcast("requestUpdated", c.state); + await c.queue.send(QUEUE_DECISION, { approved: true, approver }); + }, + + reject: async (c, approver: string) => { + if (c.state.status !== "pending") return; + c.state.deciding = true; + c.broadcast("requestUpdated", c.state); + await c.queue.send(QUEUE_DECISION, { approved: false, approver }); + }, + }, + + run: workflow(async (ctx) => { + await ctx.loop({ + name: "approval-loop", + run: async (loopCtx) => { + const c = actorCtx(loopCtx); + + await loopCtx.step("init-request", async () => { + ctx.log.info({ + msg: "waiting for approval decision", + requestId: c.state.id, + title: c.state.title, + }); + c.broadcast("requestCreated", c.state); + }); + + const decision = await loopCtx.listenWithTimeout<{ + approved: boolean; + approver: string; + }>("wait-decision", "decision", APPROVAL_TIMEOUT_MS); + + await loopCtx.step("update-status", async () => { + c.state.deciding = false; + if (decision === null) { + c.state.status = "timeout"; + ctx.log.info({ msg: "request timed out", requestId: c.state.id }); + } else if (decision.approved) { + c.state.status = "approved"; + c.state.decidedBy = decision.approver; + ctx.log.info({ + msg: "request approved", + requestId: c.state.id, + approver: decision.approver, + }); + } else { + c.state.status = "rejected"; + c.state.decidedBy = decision.approver; + ctx.log.info({ + msg: "request rejected", + requestId: c.state.id, + approver: decision.approver, + }); + } + c.state.decidedAt = Date.now(); + c.broadcast("requestUpdated", c.state); + }); + + return Loop.break(undefined); + }, + }); + }), +}); diff --git a/examples/workflow-sandbox-vercel/src/actors/batch.ts b/examples/workflow-sandbox-vercel/src/actors/batch.ts new file mode 100644 index 0000000000..921c055a72 --- /dev/null +++ b/examples/workflow-sandbox-vercel/src/actors/batch.ts @@ -0,0 +1,125 @@ +// BATCH PROCESSOR (Loops Demo) +// Demonstrates: Loop with persistent state (cursor) for batch processing +// One actor per batch job - actor key is the job ID + +import { actor } from "rivetkit"; +import { Loop, workflow } from "rivetkit/workflow"; +import { actorCtx } from "./_helpers.ts"; + +export type BatchInfo = { + id: number; + count: number; + processedAt: number; +}; + +export type BatchJob = { + id: string; + totalItems: number; + batchSize: number; + status: "running" | "stopped" | "completed"; + processedTotal: number; + currentBatch: number; + batches: BatchInfo[]; + startedAt: number; + completedAt?: number; +}; + +type State = BatchJob; + +function fetchBatch( + cursor: number, + batchSize: number, + totalItems: number +): { items: number[]; hasMore: boolean } { + const start = cursor * batchSize; + const end = Math.min(start + batchSize, totalItems); + const items = []; + for (let i = start; i < end; i++) { + items.push(i); + } + return { + items, + hasMore: end < totalItems, + }; +} + +export type BatchJobInput = { + totalItems?: number; + batchSize?: number; +}; + +export const batch = actor({ + createState: (c, input: BatchJobInput): BatchJob => ({ + id: c.key[0] as string, + totalItems: input?.totalItems ?? 50, + batchSize: input?.batchSize ?? 5, + status: "running", + processedTotal: 0, + currentBatch: 0, + batches: [], + startedAt: Date.now(), + }), + + actions: { + getJob: (c): BatchJob => c.state, + }, + + run: workflow(async (ctx) => { + await ctx.loop({ + name: "batch-loop", + state: { cursor: 0 }, + run: async (batchCtx, loopState: { cursor: number }) => { + const c = actorCtx(batchCtx); + + const batch = await batchCtx.step("fetch-batch", async () => { + ctx.log.info({ + msg: "processing batch", + jobId: c.state.id, + cursor: loopState.cursor, + }); + await new Promise((r) => setTimeout(r, 200 + Math.random() * 300)); + return fetchBatch(loopState.cursor, c.state.batchSize, c.state.totalItems); + }); + + await batchCtx.step("process-batch", async () => { + await new Promise((r) => setTimeout(r, 300 + Math.random() * 500)); + + const batchInfo: BatchInfo = { + id: loopState.cursor, + count: batch.items.length, + processedAt: Date.now(), + }; + + c.state.currentBatch = loopState.cursor; + c.state.processedTotal += batch.items.length; + c.state.batches.push(batchInfo); + + c.broadcast("batchProcessed", batchInfo); + c.broadcast("stateChanged", c.state); + + ctx.log.info({ + msg: "batch processed", + jobId: c.state.id, + cursor: loopState.cursor, + count: batch.items.length, + }); + }); + + if (!batch.hasMore) { + await batchCtx.step("mark-complete", async () => { + c.state.status = "completed"; + c.state.completedAt = Date.now(); + c.broadcast("stateChanged", c.state); + c.broadcast("processingComplete", { + totalBatches: loopState.cursor + 1, + totalItems: c.state.processedTotal, + }); + }); + return Loop.break(loopState.cursor + 1); + } + + return Loop.continue({ cursor: loopState.cursor + 1 }); + }, + }); + }), +}); diff --git a/examples/workflow-sandbox-vercel/src/actors/dashboard.ts b/examples/workflow-sandbox-vercel/src/actors/dashboard.ts new file mode 100644 index 0000000000..86887d466e --- /dev/null +++ b/examples/workflow-sandbox-vercel/src/actors/dashboard.ts @@ -0,0 +1,203 @@ +// DASHBOARD (Join Demo) +// Demonstrates: Parallel data fetching with join (wait-all) + +import { actor } from "rivetkit"; +import { Loop, workflow, workflowQueueName } from "rivetkit/workflow"; +import { actorCtx } from "./_helpers.ts"; + +export type UserStats = { + count: number; + activeToday: number; + newThisWeek: number; +}; + +export type OrderStats = { + count: number; + revenue: number; + avgOrderValue: number; +}; + +export type MetricsStats = { + pageViews: number; + sessions: number; + bounceRate: number; +}; + +export type DashboardData = { + users: UserStats; + orders: OrderStats; + metrics: MetricsStats; + fetchedAt: number; +}; + +export type BranchStatus = "pending" | "running" | "completed" | "failed"; + +export type DashboardState = { + data: DashboardData | null; + loading: boolean; + branches: { + users: BranchStatus; + orders: BranchStatus; + metrics: BranchStatus; + }; + lastRefresh: number | null; +}; + +type State = DashboardState; + +const QUEUE_REFRESH = workflowQueueName("refresh"); + +async function fetchUserStats(): Promise { + await new Promise((r) => setTimeout(r, 800 + Math.random() * 1200)); + return { + count: Math.floor(1000 + Math.random() * 500), + activeToday: Math.floor(100 + Math.random() * 200), + newThisWeek: Math.floor(20 + Math.random() * 80), + }; +} + +async function fetchOrderStats(): Promise { + await new Promise((r) => setTimeout(r, 600 + Math.random() * 1000)); + const count = Math.floor(50 + Math.random() * 150); + const revenue = Math.floor(5000 + Math.random() * 15000); + return { + count, + revenue, + avgOrderValue: Math.round(revenue / count), + }; +} + +async function fetchMetricsStats(): Promise { + await new Promise((r) => setTimeout(r, 400 + Math.random() * 800)); + return { + pageViews: Math.floor(10000 + Math.random() * 50000), + sessions: Math.floor(2000 + Math.random() * 8000), + bounceRate: Math.round(30 + Math.random() * 40), + }; +} + +export const dashboard = actor({ + state: { + data: null as DashboardData | null, + loading: false, + branches: { + users: "pending" as BranchStatus, + orders: "pending" as BranchStatus, + metrics: "pending" as BranchStatus, + }, + lastRefresh: null as number | null, + }, + + actions: { + refresh: async (c) => { + if (!c.state.loading) { + c.state.loading = true; + c.state.branches = { + users: "pending", + orders: "pending", + metrics: "pending", + }; + c.broadcast("stateChanged", c.state); + await c.queue.send(QUEUE_REFRESH, {}); + } + }, + + getState: (c): DashboardState => c.state, + }, + + run: workflow(async (ctx) => { + await ctx.loop({ + name: "refresh-loop", + run: async (loopCtx) => { + const c = actorCtx(loopCtx); + + await loopCtx.listen("wait-refresh", "refresh"); + + ctx.log.info({ msg: "starting dashboard refresh" }); + + const results = await loopCtx.join("fetch-all", { + users: { + run: async (branchCtx) => { + const bc = actorCtx(branchCtx); + + await branchCtx.step("mark-running", async () => { + bc.state.branches.users = "running"; + bc.broadcast("stateChanged", bc.state); + }); + + const data = await branchCtx.step("fetch-users", async () => { + return await fetchUserStats(); + }); + + await branchCtx.step("mark-complete", async () => { + bc.state.branches.users = "completed"; + bc.broadcast("stateChanged", bc.state); + }); + + return data; + }, + }, + orders: { + run: async (branchCtx) => { + const bc = actorCtx(branchCtx); + + await branchCtx.step("mark-running", async () => { + bc.state.branches.orders = "running"; + bc.broadcast("stateChanged", bc.state); + }); + + const data = await branchCtx.step("fetch-orders", async () => { + return await fetchOrderStats(); + }); + + await branchCtx.step("mark-complete", async () => { + bc.state.branches.orders = "completed"; + bc.broadcast("stateChanged", bc.state); + }); + + return data; + }, + }, + metrics: { + run: async (branchCtx) => { + const bc = actorCtx(branchCtx); + + await branchCtx.step("mark-running", async () => { + bc.state.branches.metrics = "running"; + bc.broadcast("stateChanged", bc.state); + }); + + const data = await branchCtx.step("fetch-metrics", async () => { + return await fetchMetricsStats(); + }); + + await branchCtx.step("mark-complete", async () => { + bc.state.branches.metrics = "completed"; + bc.broadcast("stateChanged", bc.state); + }); + + return data; + }, + }, + }); + + await loopCtx.step("save-data", async () => { + c.state.data = { + users: results.users, + orders: results.orders, + metrics: results.metrics, + fetchedAt: Date.now(), + }; + c.state.loading = false; + c.state.lastRefresh = Date.now(); + c.broadcast("stateChanged", c.state); + c.broadcast("refreshComplete", c.state.data); + }); + + ctx.log.info({ msg: "dashboard refresh complete" }); + + return Loop.continue(undefined); + }, + }); + }), +}); diff --git a/examples/workflow-sandbox-vercel/src/actors/order.ts b/examples/workflow-sandbox-vercel/src/actors/order.ts new file mode 100644 index 0000000000..12b5344744 --- /dev/null +++ b/examples/workflow-sandbox-vercel/src/actors/order.ts @@ -0,0 +1,89 @@ +// ORDER PROCESSOR (Steps Demo) +// Demonstrates: Sequential workflow steps with automatic retries +// One actor per order - actor key is the order ID + +import { actor } from "rivetkit"; +import { Loop, workflow } from "rivetkit/workflow"; +import { actorCtx } from "./_helpers.ts"; + +export type OrderStatus = + | "pending" + | "validating" + | "charging" + | "fulfilling" + | "completed" + | "failed"; + +export type Order = { + id: string; + status: OrderStatus; + step: number; + error?: string; + createdAt: number; + completedAt?: number; +}; + +type State = Order; + +async function simulateWork(name: string, failChance = 0.1): Promise { + await new Promise((resolve) => + setTimeout(resolve, 500 + Math.random() * 1000) + ); + if (Math.random() < failChance) { + throw new Error(`${name} failed (simulated)`); + } +} + +export const order = actor({ + createState: (c): Order => ({ + id: c.key[0] as string, + status: "pending", + step: 0, + createdAt: Date.now(), + }), + + actions: { + getOrder: (c): Order => c.state, + }, + + run: workflow(async (ctx) => { + await ctx.loop({ + name: "process-order", + run: async (loopCtx) => { + const c = actorCtx(loopCtx); + + await loopCtx.step("validate", async () => { + ctx.log.info({ msg: "processing order", orderId: c.state.id }); + c.state.status = "validating"; + c.state.step = 1; + c.broadcast("orderUpdated", c.state); + await simulateWork("validation", 0.05); + }); + + await loopCtx.step("charge", async () => { + c.state.status = "charging"; + c.state.step = 2; + c.broadcast("orderUpdated", c.state); + await simulateWork("payment", 0.1); + }); + + await loopCtx.step("fulfill", async () => { + c.state.status = "fulfilling"; + c.state.step = 3; + c.broadcast("orderUpdated", c.state); + await simulateWork("fulfillment", 0.05); + }); + + await loopCtx.step("complete", async () => { + c.state.status = "completed"; + c.state.step = 4; + c.state.completedAt = Date.now(); + c.broadcast("orderUpdated", c.state); + ctx.log.info({ msg: "order completed", orderId: c.state.id }); + }); + + return Loop.break(undefined); + }, + }); + }), +}); diff --git a/examples/workflow-sandbox-vercel/src/actors/payment.ts b/examples/workflow-sandbox-vercel/src/actors/payment.ts new file mode 100644 index 0000000000..c9ad701a82 --- /dev/null +++ b/examples/workflow-sandbox-vercel/src/actors/payment.ts @@ -0,0 +1,175 @@ +// PAYMENT PROCESSOR (Rollback Demo) +// Demonstrates: Rollback checkpoints with compensating actions +// One actor per transaction - actor key is the transaction ID + +import { actor } from "rivetkit"; +import { Loop, workflow } from "rivetkit/workflow"; +import { actorCtx } from "./_helpers.ts"; + +export type TransactionStep = { + name: string; + status: "pending" | "completed" | "rolled_back"; + completedAt?: number; + rolledBackAt?: number; +}; + +export type Transaction = { + id: string; + amount: number; + shouldFail: boolean; + status: + | "pending" + | "reserving" + | "charging" + | "completing" + | "completed" + | "rolling_back" + | "failed"; + steps: TransactionStep[]; + error?: string; + startedAt: number; + completedAt?: number; +}; + +type State = Transaction; + +export type TransactionInput = { + amount?: number; + shouldFail?: boolean; +}; + +export const payment = actor({ + createState: (c, input: TransactionInput): Transaction => ({ + id: c.key[0] as string, + amount: input?.amount ?? 100, + shouldFail: input?.shouldFail ?? false, + status: "pending", + steps: [ + { name: "reserve-inventory", status: "pending" }, + { name: "charge-card", status: "pending" }, + { name: "complete-order", status: "pending" }, + ], + startedAt: Date.now(), + }), + + actions: { + getTransaction: (c): Transaction => c.state, + }, + + run: workflow(async (ctx) => { + await ctx.loop({ + name: "payment-loop", + run: async (loopCtx) => { + const c = actorCtx(loopCtx); + + await loopCtx.step("init-payment", async () => { + ctx.log.info({ + msg: "starting payment processing", + txId: c.state.id, + amount: c.state.amount, + shouldFail: c.state.shouldFail, + }); + c.broadcast("transactionStarted", c.state); + }); + + await loopCtx.rollbackCheckpoint("payment-checkpoint"); + + // Step 1: Reserve inventory + await loopCtx.step({ + name: "reserve-inventory", + run: async () => { + c.state.status = "reserving"; + const step = c.state.steps.find( + (s) => s.name === "reserve-inventory" + ); + if (step) { + step.status = "completed"; + step.completedAt = Date.now(); + } + c.broadcast("transactionUpdated", c.state); + + await new Promise((r) => + setTimeout(r, 500 + Math.random() * 500) + ); + ctx.log.info({ msg: "inventory reserved", txId: c.state.id }); + return { reserved: true }; + }, + rollback: async () => { + // Set rolling_back status on first rollback + c.state.status = "rolling_back"; + const step = c.state.steps.find( + (s) => s.name === "reserve-inventory" + ); + if (step) { + step.status = "rolled_back"; + step.rolledBackAt = Date.now(); + } + ctx.log.info({ msg: "inventory released", txId: c.state.id }); + c.broadcast("transactionUpdated", c.state); + // Small delay so UI can show the rollback + await new Promise((r) => setTimeout(r, 400)); + }, + }); + + // Step 2: Charge card + await loopCtx.step({ + name: "charge-card", + run: async () => { + c.state.status = "charging"; + const step = c.state.steps.find((s) => s.name === "charge-card"); + if (step) { + step.status = "completed"; + step.completedAt = Date.now(); + } + c.broadcast("transactionUpdated", c.state); + + await new Promise((r) => + setTimeout(r, 500 + Math.random() * 500) + ); + + if (c.state.shouldFail) { + throw new Error("Payment declined (simulated)"); + } + + ctx.log.info({ msg: "card charged", txId: c.state.id }); + return { chargeId: `ch_${c.state.id}` }; + }, + rollback: async () => { + c.state.status = "rolling_back"; + const step = c.state.steps.find((s) => s.name === "charge-card"); + if (step) { + step.status = "rolled_back"; + step.rolledBackAt = Date.now(); + } + ctx.log.info({ msg: "charge refunded", txId: c.state.id }); + c.broadcast("transactionUpdated", c.state); + // Small delay so UI can show the rollback + await new Promise((r) => setTimeout(r, 400)); + }, + }); + + // Step 3: Complete order + await loopCtx.step({ + name: "complete-order", + run: async () => { + c.state.status = "completing"; + const step = c.state.steps.find((s) => s.name === "complete-order"); + if (step) step.status = "completed"; + c.broadcast("transactionUpdated", c.state); + + await new Promise((r) => + setTimeout(r, 300 + Math.random() * 300) + ); + + c.state.status = "completed"; + c.state.completedAt = Date.now(); + ctx.log.info({ msg: "order completed", txId: c.state.id }); + c.broadcast("transactionCompleted", c.state); + }, + }); + + return Loop.break(undefined); + }, + }); + }), +}); diff --git a/examples/workflow-sandbox-vercel/src/actors/race.ts b/examples/workflow-sandbox-vercel/src/actors/race.ts new file mode 100644 index 0000000000..173055591c --- /dev/null +++ b/examples/workflow-sandbox-vercel/src/actors/race.ts @@ -0,0 +1,112 @@ +// RACE RUNNER (Race Demo) +// Demonstrates: Race (parallel first-wins) for timeout patterns +// One actor per race task - actor key is the task ID + +import { actor } from "rivetkit"; +import { Loop, workflow } from "rivetkit/workflow"; +import { actorCtx } from "./_helpers.ts"; + +export type RaceTask = { + id: string; + workDurationMs: number; + timeoutMs: number; + status: "running" | "work_won" | "timeout_won"; + result?: string; + startedAt: number; + completedAt?: number; + actualDurationMs?: number; +}; + +type State = RaceTask; + +export type RaceTaskInput = { + workDurationMs?: number; + timeoutMs?: number; +}; + +export const race = actor({ + createState: (c, input: RaceTaskInput): RaceTask => ({ + id: c.key[0] as string, + workDurationMs: input?.workDurationMs ?? 2000, + timeoutMs: input?.timeoutMs ?? 3000, + status: "running", + startedAt: Date.now(), + }), + + actions: { + getTask: (c): RaceTask => c.state, + }, + + run: workflow(async (ctx) => { + await ctx.loop({ + name: "race-loop", + run: async (loopCtx) => { + const c = actorCtx(loopCtx); + + // Get durations inside a step since state is only available in steps + const { workDurationMs, timeoutMs, taskId } = await loopCtx.step( + "start-race", + async () => { + ctx.log.info({ + msg: "starting race", + taskId: c.state.id, + workDurationMs: c.state.workDurationMs, + timeoutMs: c.state.timeoutMs, + }); + c.broadcast("raceStarted", c.state); + return { + workDurationMs: c.state.workDurationMs, + timeoutMs: c.state.timeoutMs, + taskId: c.state.id, + }; + } + ); + + const { winner, value } = await loopCtx.race("work-vs-timeout", [ + { + name: "work", + run: async (branchCtx) => { + await branchCtx.sleep("simulate-work", workDurationMs); + return await branchCtx.step("complete-work", async () => { + return `Result for task ${taskId}`; + }); + }, + }, + { + name: "timeout", + run: async (branchCtx) => { + await branchCtx.sleep("timeout-wait", timeoutMs); + return null; + }, + }, + ]); + + await loopCtx.step("save-result", async () => { + c.state.completedAt = Date.now(); + c.state.actualDurationMs = c.state.completedAt - c.state.startedAt; + + if (winner === "work") { + c.state.status = "work_won"; + c.state.result = value as string; + ctx.log.info({ + msg: "work completed before timeout", + taskId: c.state.id, + durationMs: c.state.actualDurationMs, + }); + } else { + c.state.status = "timeout_won"; + ctx.log.info({ + msg: "timeout won the race", + taskId: c.state.id, + durationMs: c.state.actualDurationMs, + }); + } + + c.broadcast("raceCompleted", c.state); + }); + + return Loop.break(undefined); + }, + }); + }), +}); diff --git a/examples/workflow-sandbox-vercel/src/actors/timer.ts b/examples/workflow-sandbox-vercel/src/actors/timer.ts new file mode 100644 index 0000000000..d642dfa740 --- /dev/null +++ b/examples/workflow-sandbox-vercel/src/actors/timer.ts @@ -0,0 +1,69 @@ +// TIMER (Sleep Demo) +// Demonstrates: Durable sleep that survives restarts +// One actor per timer - actor key is the timer ID + +import { actor } from "rivetkit"; +import { Loop, workflow } from "rivetkit/workflow"; +import { actorCtx } from "./_helpers.ts"; + +export type Timer = { + id: string; + name: string; + durationMs: number; + startedAt: number; + completedAt?: number; +}; + +type State = Timer; + +export type TimerInput = { + name?: string; + durationMs?: number; +}; + +export const timer = actor({ + createState: (c, input: TimerInput): Timer => ({ + id: c.key[0] as string, + name: input?.name ?? "Timer", + durationMs: input?.durationMs ?? 10000, + startedAt: Date.now(), + }), + + actions: { + getTimer: (c): Timer => c.state, + }, + + run: workflow(async (ctx) => { + await ctx.loop({ + name: "timer-loop", + run: async (loopCtx) => { + const c = actorCtx(loopCtx); + + // Get duration inside a step since state is only available in steps + const durationMs = await loopCtx.step("start-timer", async () => { + ctx.log.info({ + msg: "starting timer", + timerId: c.state.id, + durationMs: c.state.durationMs, + }); + c.broadcast("timerStarted", c.state); + return c.state.durationMs; + }); + + await loopCtx.sleep("countdown", durationMs); + + await loopCtx.step("complete-timer", async () => { + c.state.completedAt = Date.now(); + c.broadcast("timerCompleted", c.state); + ctx.log.info({ msg: "timer completed", timerId: c.state.id }); + }); + + return Loop.break(undefined); + }, + }); + }), + + options: { + sleepTimeout: 1000, + }, +}); diff --git a/examples/workflow-sandbox-vercel/src/server.ts b/examples/workflow-sandbox-vercel/src/server.ts new file mode 100644 index 0000000000..8c065a6b5e --- /dev/null +++ b/examples/workflow-sandbox-vercel/src/server.ts @@ -0,0 +1,8 @@ +import { Hono } from "hono"; +import { registry } from "./actors.ts"; + +const app = new Hono(); + +app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); + +export default app; diff --git a/examples/workflow-sandbox-vercel/tsconfig.json b/examples/workflow-sandbox-vercel/tsconfig.json new file mode 100644 index 0000000000..2a870bab03 --- /dev/null +++ b/examples/workflow-sandbox-vercel/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "esnext", + "lib": [ + "esnext", + "dom" + ], + "jsx": "react-jsx", + "module": "esnext", + "moduleResolution": "bundler", + "types": [ + "node", + "vite/client" + ], + "noEmit": true, + "strict": true, + "skipLibCheck": true, + "allowImportingTsExtensions": true, + "rewriteRelativeImportExtensions": true + }, + "include": [ + "src/**/*", + "api/**/*", + "frontend/**/*" + ] +} diff --git a/examples/workflow-sandbox-vercel/turbo.json b/examples/workflow-sandbox-vercel/turbo.json new file mode 100644 index 0000000000..c3d3d3b9bb --- /dev/null +++ b/examples/workflow-sandbox-vercel/turbo.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://turbo.build/schema.json", + "extends": [ + "//" + ] +} diff --git a/examples/workflow-sandbox-vercel/vercel.json b/examples/workflow-sandbox-vercel/vercel.json new file mode 100644 index 0000000000..64a8d9b467 --- /dev/null +++ b/examples/workflow-sandbox-vercel/vercel.json @@ -0,0 +1,9 @@ +{ + "framework": "vite", + "rewrites": [ + { + "source": "/api/(.*)", + "destination": "/api" + } + ] +} diff --git a/examples/workflow-sandbox-vercel/vite.config.ts b/examples/workflow-sandbox-vercel/vite.config.ts new file mode 100644 index 0000000000..f9f0d5ec2f --- /dev/null +++ b/examples/workflow-sandbox-vercel/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +export default defineConfig({ + plugins: [react()], +}); diff --git a/progress.txt b/progress.txt new file mode 100644 index 0000000000..611f959811 --- /dev/null +++ b/progress.txt @@ -0,0 +1,43 @@ +# Progress Log + +## 2026-01-24 Session + +### Fixed: Workflow listenWithTimeout not waking on message arrival + +**Problem:** The `listenWithTimeout` and `listenUntil` methods in the workflow engine would not wake up when a message arrived - they would only wake when the deadline passed. This meant approval workflows and other human-in-the-loop patterns were broken. + +**Root Cause:** +1. `listenWithTimeout`/`listenUntil` threw `SleepError(deadline)` when no message was available +2. `SleepError` only carried a deadline, not message names to wait for +3. The execution loop only checked `sleepUntil` for these cases, not `waitingForMessages` +4. Result: workflows would sleep until deadline without subscribing to message notifications + +**Fix:** +1. Extended `SleepError` to optionally carry `messageNames` array +2. Modified `executeListenUntil` and `executeListenNUntilImpl` to pass message names when throwing `SleepError` +3. Updated `setSleepState` to include `waitingForMessages` in the result +4. Modified `executeLiveWorkflow` to wait for EITHER messages OR deadline when both are present + +**Additional Fix:** Race branches throwing SleepError + +When a race branch called `sleep()`, the `SleepError` was being treated as a branch failure instead of a yield to the scheduler. Fixed by: +1. Tracking yield errors (`SleepError`/`MessageWaitError`) separately in race execution +2. Re-throwing the yield error after `Promise.allSettled` to allow proper scheduler yielding +3. Keeping branch status as "running" for yield errors (not "failed") + +**Files Changed:** +- `rivetkit-typescript/packages/workflow-engine/src/errors.ts` - Extended `SleepError` with optional `messageNames` +- `rivetkit-typescript/packages/workflow-engine/src/context.ts` - Pass message names in `SleepError`, handle yield errors in race +- `rivetkit-typescript/packages/workflow-engine/src/index.ts` - Handle combined sleep+message waiting in execution loop + +**Testing:** +- All 128 workflow-engine unit tests pass +- All 18 rivetkit workflow tests pass +- Verified workflow-sandbox examples: + - Order (steps) - works + - Batch (loops) - works + - Approval (listen with timeout) - NOW WORKS + - Dashboard (join) - works + - Race - NOW WORKS + - Payment (rollback) - works + - Timer (sleep) - works diff --git a/rivetkit-typescript/packages/workflow-engine/src/context.ts b/rivetkit-typescript/packages/workflow-engine/src/context.ts index 32a6cd35d3..2c03d54e7a 100644 --- a/rivetkit-typescript/packages/workflow-engine/src/context.ts +++ b/rivetkit-typescript/packages/workflow-engine/src/context.ts @@ -1209,8 +1209,8 @@ export class WorkflowContextImpl implements WorkflowContextInterface { return message.data as T; } - // Message not available, yield to scheduler until deadline - throw new SleepError(deadline); + // Message not available, yield to scheduler until deadline or message + throw new SleepError(deadline, [messageName]); } async listenNWithTimeout( @@ -1377,8 +1377,8 @@ export class WorkflowContextImpl implements WorkflowContextInterface { if (!message) { // No message available - check if we should wait if (results.length === 0) { - // No messages yet - yield to scheduler until deadline - throw new SleepError(deadline); + // No messages yet - yield to scheduler until deadline or message + throw new SleepError(deadline, [messageName]); } // We have some messages - return what we have break; @@ -1642,6 +1642,8 @@ export class WorkflowContextImpl implements WorkflowContextInterface { let pendingCount = branches.length; const errors: Record = {}; const lateErrors: Array<{ name: string; error: string }> = []; + // Track scheduler yield errors - we need to propagate these after allSettled + let yieldError: SleepError | MessageWaitError | null = null; // Check for replay winners first for (const branch of branches) { @@ -1716,6 +1718,39 @@ export class WorkflowContextImpl implements WorkflowContextInterface { (error) => { pendingCount--; + // Track sleep/message errors - they need to bubble up to the scheduler + // We'll re-throw after allSettled to allow cleanup + if (error instanceof SleepError) { + // Track the earliest deadline + if ( + !yieldError || + !(yieldError instanceof SleepError) || + error.deadline < yieldError.deadline + ) { + yieldError = error; + } + branchStatus.status = "running"; // Keep as running since we'll resume + entry.dirty = true; + return; + } + if (error instanceof MessageWaitError) { + // Track message wait errors, prefer sleep errors with deadlines + if (!yieldError || !(yieldError instanceof SleepError)) { + if (!yieldError) { + yieldError = error; + } else if (yieldError instanceof MessageWaitError) { + // Merge message names + yieldError = new MessageWaitError([ + ...yieldError.messageNames, + ...error.messageNames, + ]); + } + } + branchStatus.status = "running"; // Keep as running since we'll resume + entry.dirty = true; + return; + } + if ( error instanceof CancelledError || error instanceof EvictedError @@ -1750,6 +1785,13 @@ export class WorkflowContextImpl implements WorkflowContextInterface { // Wait for all branches to complete or be cancelled await Promise.allSettled(branchPromises); + // If any branch needs to yield to the scheduler (sleep/message wait), + // save state and re-throw the error to exit the workflow execution + if (yieldError && !settled) { + await flush(this.storage, this.driver); + throw yieldError; + } + // Clean up entries from non-winning branches if (winnerName !== null) { for (const branch of branches) { diff --git a/rivetkit-typescript/packages/workflow-engine/src/errors.ts b/rivetkit-typescript/packages/workflow-engine/src/errors.ts index 9865355a13..b33357bdbd 100644 --- a/rivetkit-typescript/packages/workflow-engine/src/errors.ts +++ b/rivetkit-typescript/packages/workflow-engine/src/errors.ts @@ -32,10 +32,18 @@ export class RollbackCheckpointError extends Error { /** * Internal: Workflow should sleep until deadline. * This is thrown to yield control back to the scheduler. + * Optionally, the workflow can also wake early if certain messages arrive. */ export class SleepError extends Error { - constructor(public readonly deadline: number) { - super(`Sleeping until ${deadline}`); + constructor( + public readonly deadline: number, + public readonly messageNames?: string[], + ) { + super( + messageNames && messageNames.length > 0 + ? `Sleeping until ${deadline} or messages: ${messageNames.join(", ")}` + : `Sleeping until ${deadline}`, + ); this.name = "SleepError"; } } diff --git a/rivetkit-typescript/packages/workflow-engine/src/index.ts b/rivetkit-typescript/packages/workflow-engine/src/index.ts index c2378e9a0a..62eb0a8c27 100644 --- a/rivetkit-typescript/packages/workflow-engine/src/index.ts +++ b/rivetkit-typescript/packages/workflow-engine/src/index.ts @@ -380,12 +380,17 @@ async function setSleepState( driver: EngineDriver, workflowId: string, deadline: number, + messageNames?: string[], ): Promise> { storage.state = "sleeping"; await flush(storage, driver); await driver.setAlarm(workflowId, deadline); - return { state: "sleeping", sleepUntil: deadline }; + return { + state: "sleeping", + sleepUntil: deadline, + waitingForMessages: messageNames, + }; } async function setMessageWaitState( @@ -501,12 +506,47 @@ async function executeLiveWorkflow( return result; } - if (result.waitingForMessages && result.waitingForMessages.length > 0) { + const hasMessages = + result.waitingForMessages && result.waitingForMessages.length > 0; + const hasDeadline = result.sleepUntil !== undefined; + + if (hasMessages && hasDeadline) { + // Wait for EITHER a message OR the deadline (for listenWithTimeout) + try { + const messagePromise = driver.waitForMessages + ? awaitWithEviction( + driver.waitForMessages( + result.waitingForMessages!, + abortController.signal, + ), + abortController.signal, + ) + : waitForMessage( + runtime, + result.waitingForMessages!, + abortController.signal, + ); + const sleepPromise = waitForSleep( + runtime, + result.sleepUntil!, + abortController.signal, + ); + await Promise.race([messagePromise, sleepPromise]); + } catch (error) { + if (error instanceof EvictedError) { + return lastResult; + } + throw error; + } + continue; + } + + if (hasMessages) { try { if (driver.waitForMessages) { await awaitWithEviction( driver.waitForMessages( - result.waitingForMessages, + result.waitingForMessages!, abortController.signal, ), abortController.signal, @@ -514,7 +554,7 @@ async function executeLiveWorkflow( } else { await waitForMessage( runtime, - result.waitingForMessages, + result.waitingForMessages!, abortController.signal, ); } @@ -527,11 +567,11 @@ async function executeLiveWorkflow( continue; } - if (result.sleepUntil !== undefined) { + if (hasDeadline) { try { await waitForSleep( runtime, - result.sleepUntil, + result.sleepUntil!, abortController.signal, ); } catch (error) { @@ -809,6 +849,7 @@ async function executeWorkflow( driver, workflowId, error.deadline, + error.messageNames, ); }