-
Notifications
You must be signed in to change notification settings - Fork 94
Expose world helpers via workflow/api (v2) #259
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "workflow": patch | ||
| --- | ||
|
|
||
| Expose `getWorld` plus every `World` helper via `workflow/api`, add delegation tests, and document how to access the singleton programmatically. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| --- | ||
| title: World helpers | ||
| --- | ||
|
|
||
| # Accessing the World singleton | ||
|
|
||
| > “Is there any public API to access the active default World? I'd like to be able to list active runs in my app, like I can via the CLI. I could store them myself, but if there is an API for this that works across testing and deployment, that'd be even better!” | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's skip this |
||
| Yes. Import `getWorld` or any of the helper functions below directly from `workflow/api`. They proxy to the same singleton that powers the CLI, whether you're running locally with the embedded world, in tests (via `setWorld`), or on Vercel. | ||
|
|
||
| ```typescript | ||
| import { | ||
| getWorld, | ||
| listRuns, | ||
| listSteps, | ||
| cancelRun, | ||
| queue, | ||
| } from 'workflow/api'; | ||
|
|
||
| const runs = await listRuns({ status: 'running', pagination: { limit: 10 } }); | ||
| await cancelRun(runs.data[0].runId); | ||
|
|
||
| // You still have full access to the underlying World instance when needed. | ||
| const world = getWorld(); | ||
| await world.start?.(); | ||
| ``` | ||
|
|
||
| ## Example: List and cancel runs in a Next.js route | ||
|
|
||
| ```typescript filename="app/api/workflows/route.ts" | ||
| import { listRuns, cancelRun } from 'workflow/api'; | ||
|
|
||
| export async function GET() { | ||
| const runs = await listRuns({ status: 'running', pagination: { limit: 20 } }); | ||
| return Response.json({ runs: runs.data }); | ||
| } | ||
|
|
||
| export async function POST(request: Request) { | ||
| const { runId } = await request.json(); | ||
| await cancelRun(runId); | ||
| return Response.json({ ok: true }); | ||
| } | ||
| ``` | ||
|
|
||
| This works the same way in development and production—just make sure the usual `WORKFLOW_*` environment variables are present so the correct world implementation can be chosen. | ||
|
|
||
| ## Helper catalog | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we might want to generate these from the interface, so they don't get outdated. For now, we could just link to the latest interface.ts file in the npm package? |
||
|
|
||
| ### Run helpers | ||
|
|
||
| | Function | Description | | ||
| | --- | --- | | ||
| | `createRun(data)` | Calls `world.runs.create` directly. Useful for advanced tooling/tests. | | ||
| | `getWorkflowRun(id, params?)` | Fetches a workflow run record without wrapping it in the `Run` class. | | ||
| | `updateRun(id, data)` | Partially updates a run record. | | ||
| | `listRuns(params?)` | Lists runs with optional filters/pagination. | | ||
| | `cancelRun(id, params?)` | Cancels a run. | | ||
| | `pauseRun(id, params?)` / `resumeRun(id, params?)` | Pause/resume administrative helpers. | | ||
|
|
||
| ### Step helpers | ||
|
|
||
| | Function | Description | | ||
| | --- | --- | | ||
| | `createStep(runId, data)` | Inserts a step record. | | ||
| | `getStep(runId, stepId, params?)` | Retrieves a single step. | | ||
| | `updateStep(runId, stepId, data)` | Updates status/attempt metadata. | | ||
| | `listSteps(params)` | Lists steps for a run (with pagination + `resolveData`). | | ||
|
|
||
| ### Events & hooks | ||
|
|
||
| | Function | Description | | ||
| | --- | --- | | ||
| | `createEvent(runId, data, params?)` | Writes workflow/step events. | | ||
| | `listEvents(params)` | Lists events for a run. | | ||
| | `listEventsByCorrelationId(params)` | Lists events for a correlation ID across runs. | | ||
| | `createHook(runId, data, params?)` | Creates a hook. | | ||
| | `getHook(id, params?)` | Fetches hook metadata. | | ||
| | `listHooks(params)` | Lists hooks. | | ||
| | `disposeHook(id, params?)` | Disposes a hook. | | ||
| | `getHookByToken(token, params?)` | Continues to be exported for convenience. | | ||
|
|
||
| ### Queue, streams, and lifecycle helpers | ||
|
|
||
| | Function | Description | | ||
| | --- | --- | | ||
| | `getDeploymentId()` | Returns the deployment ID that queue operations will use. | | ||
| | `queue(name, payload, opts?)` | Enqueue workflow/step invocations manually. | | ||
| | `createQueueHandler(prefix, handler)` | Builds queue HTTP handlers (the same API the runtime uses). | | ||
| | `writeToStream(name, chunk)` / `closeStream(name)` / `readFromStream(name, startIndex?)` | Direct streaming helpers. | | ||
| | `startWorld()` | Invokes `world.start?.()` if provided by your world implementation. | | ||
|
|
||
| ## Testing tips | ||
|
|
||
| Use `setWorld` to stub custom worlds in tests so that the helpers continue to work without hitting real infrastructure: | ||
|
|
||
| ```typescript | ||
| import { setWorld } from 'workflow/runtime'; | ||
| import { listRuns } from 'workflow/api'; | ||
| import type { World } from '@workflow/world'; | ||
|
|
||
| beforeEach(() => { | ||
| const mockWorld: World = { | ||
| // Provide a minimal mock World | ||
| runs: { | ||
| list: async () => ({ data: [], cursor: null, hasMore: false }), | ||
| create: async () => { throw new Error('not implemented'); }, | ||
| get: async () => { throw new Error('not implemented'); }, | ||
| update: async () => { throw new Error('not implemented'); }, | ||
| cancel: async () => { throw new Error('not implemented'); }, | ||
| pause: async () => { throw new Error('not implemented'); }, | ||
| resume: async () => { throw new Error('not implemented'); }, | ||
| }, | ||
| steps: { | ||
| create: async () => { throw new Error('not implemented'); }, | ||
| get: async () => { throw new Error('not implemented'); }, | ||
| update: async () => { throw new Error('not implemented'); }, | ||
| list: async () => ({ data: [], cursor: null, hasMore: false }), | ||
| }, | ||
| events: { | ||
| create: async () => { throw new Error('not implemented'); }, | ||
| list: async () => ({ data: [], cursor: null, hasMore: false }), | ||
| listByCorrelationId: async () => ({ data: [], cursor: null, hasMore: false }), | ||
| }, | ||
| hooks: { | ||
| create: async () => { throw new Error('not implemented'); }, | ||
| get: async () => { throw new Error('not implemented'); }, | ||
| getByToken: async () => { throw new Error('not implemented'); }, | ||
| list: async () => ({ data: [], cursor: null, hasMore: false }), | ||
| dispose: async () => { throw new Error('not implemented'); }, | ||
| }, | ||
| getDeploymentId: async () => 'test', | ||
| queue: async () => ({ messageId: 'msg_test' }), | ||
| createQueueHandler: () => async () => new Response(), | ||
| writeToStream: async () => {}, | ||
| closeStream: async () => {}, | ||
| readFromStream: async () => new ReadableStream(), | ||
| }; | ||
| setWorld(mockWorld); | ||
| }); | ||
|
|
||
| afterEach(() => setWorld(undefined)); | ||
|
|
||
| it('lists runs without contacting real services', async () => { | ||
| const runs = await listRuns(); | ||
| expect(runs.data).toHaveLength(0); | ||
| }); | ||
| ``` | ||
|
|
||
| This mirrors how the new automated tests stub the world singleton. | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -210,6 +210,27 @@ export async function GET(request: Request) { | |||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| ### Programmatically list and control runs | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| If you want to show workflow progress directly in your product UI—or let trusted operators retry or cancel work—you can now call the same administrative helpers the CLI uses, straight from `workflow/api`. | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| ```typescript lineNumbers | ||||||
| import { listRuns, cancelRun } from 'workflow/api'; | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This example should be moved to /docs/api-reference/workflow-api/world and use |
||||||
|
|
||||||
| export async function GET() { | ||||||
| const runs = await listRuns({ status: 'running', pagination: { limit: 20 } }); | ||||||
| return Response.json(runs.data); | ||||||
| } | ||||||
|
|
||||||
| export async function POST(request: Request) { | ||||||
| const { runId } = await request.json(); | ||||||
| await cancelRun(runId); | ||||||
| return Response.json({ ok: true }); | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| Need lower-level access? Call `getWorld()` (also exported from `workflow/api`) to reach the singleton directly, or `setWorld()` from `workflow/runtime` in your tests to install a mock world. See the [World helpers reference](/docs/api-reference/workflow-api/world) for every available function. | ||||||
|
|
||||||
| --- | ||||||
|
|
||||||
| ## Next Steps | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.