Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/expose-world-helpers.md
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.
3 changes: 3 additions & 0 deletions docs/content/docs/api-reference/workflow-api/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,7 @@ Workflow DevKit provides runtime functions that are used outside of workflow and
<Card href="/docs/api-reference/workflow-api/get-run" title="getRun()">
Get workflow run status and metadata without waiting for completion.
</Card>
<Card href="/docs/api-reference/workflow-api/world" title="World helpers">
Access the World singleton plus list/cancel APIs for runs, steps, events, and hooks.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Access the World singleton plus list/cancel APIs for runs, steps, events, and hooks.
Access the World API for programmatic access to run, step, event, and hook entities.

</Card>
</Cards>
149 changes: 149 additions & 0 deletions docs/content/docs/api-reference/workflow-api/world.mdx
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!”
Copy link
Member

Choose a reason for hiding this comment

The 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
Copy link
Member

Choose a reason for hiding this comment

The 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.
21 changes: 21 additions & 0 deletions docs/content/docs/foundations/starting-workflows.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,27 @@ export async function GET(request: Request) {
}
```

### Programmatically list and control runs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
### Programmatically list and control runs
### Programmatically query and control runs


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`.
Copy link
Member

Choose a reason for hiding this comment

The 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 UIor let trusted operators retry or cancel work—you can now call the same administrative helpers the CLI uses, straight from `workflow/api`.
If you want to show workflow progress directly in your product UI, or let trusted operators retry or cancel runs, you can call `getWorld()` to access the full low-level API. See the [World helpers reference](/docs/api-reference/workflow-api/world) for a list of capabilities.


```typescript lineNumbers
import { listRuns, cancelRun } from 'workflow/api';
Copy link
Member

Choose a reason for hiding this comment

The 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 getWorld instead


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
Expand Down
1 change: 1 addition & 0 deletions packages/workflow/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"@workflow/core": "workspace:*",
"@workflow/errors": "workspace:*",
"@workflow/typescript-plugin": "workspace:*",
"@workflow/world": "workspace:*",
"ms": "2.1.3",
"@workflow/next": "workspace:*",
"@workflow/nitro": "workspace:*",
Expand Down
Loading