diff --git a/.changeset/expose-world-helpers.md b/.changeset/expose-world-helpers.md
new file mode 100644
index 000000000..9eff81654
--- /dev/null
+++ b/.changeset/expose-world-helpers.md
@@ -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.
diff --git a/docs/content/docs/api-reference/workflow-api/index.mdx b/docs/content/docs/api-reference/workflow-api/index.mdx
index 6f84c3998..a6a59a36e 100644
--- a/docs/content/docs/api-reference/workflow-api/index.mdx
+++ b/docs/content/docs/api-reference/workflow-api/index.mdx
@@ -25,4 +25,7 @@ Workflow DevKit provides runtime functions that are used outside of workflow and
Get workflow run status and metadata without waiting for completion.
+
+ Access the World singleton plus list/cancel APIs for runs, steps, events, and hooks.
+
diff --git a/docs/content/docs/api-reference/workflow-api/world.mdx b/docs/content/docs/api-reference/workflow-api/world.mdx
new file mode 100644
index 000000000..dda3750c7
--- /dev/null
+++ b/docs/content/docs/api-reference/workflow-api/world.mdx
@@ -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!”
+
+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
+
+### 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.
diff --git a/docs/content/docs/foundations/starting-workflows.mdx b/docs/content/docs/foundations/starting-workflows.mdx
index fb3d7aa31..0929a23c1 100644
--- a/docs/content/docs/foundations/starting-workflows.mdx
+++ b/docs/content/docs/foundations/starting-workflows.mdx
@@ -210,6 +210,27 @@ export async function GET(request: Request) {
}
```
+### Programmatically list 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`.
+
+```typescript lineNumbers
+import { listRuns, cancelRun } from 'workflow/api';
+
+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
diff --git a/packages/workflow/package.json b/packages/workflow/package.json
index dc441eb65..880c6efd4 100644
--- a/packages/workflow/package.json
+++ b/packages/workflow/package.json
@@ -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:*",
diff --git a/packages/workflow/src/api.test.ts b/packages/workflow/src/api.test.ts
new file mode 100644
index 000000000..3d5a0f2ee
--- /dev/null
+++ b/packages/workflow/src/api.test.ts
@@ -0,0 +1,426 @@
+import { setWorld } from '@workflow/core/runtime';
+import type {
+ CreateEventRequest,
+ CreateHookRequest,
+ CreateStepRequest,
+ CreateWorkflowRunRequest,
+ Event,
+ Hook,
+ ListEventsByCorrelationIdParams,
+ ListEventsParams,
+ ListHooksParams,
+ ListWorkflowRunStepsParams,
+ ListWorkflowRunsParams,
+ MessageId,
+ PaginatedResponse,
+ QueuePayload,
+ QueuePrefix,
+ Step,
+ UpdateStepRequest,
+ UpdateWorkflowRunRequest,
+ ValidQueueName,
+ WorkflowRun,
+ World,
+} from '@workflow/world';
+import {
+ afterEach,
+ beforeEach,
+ describe,
+ expect,
+ it,
+ type Mock,
+ vi,
+} from 'vitest';
+import {
+ cancelRun,
+ closeStream,
+ createEvent,
+ createHook,
+ createQueueHandler,
+ createRun,
+ createStep,
+ disposeHook,
+ getDeploymentId,
+ getHook,
+ getStep,
+ getWorkflowRun,
+ listEvents,
+ listEventsByCorrelationId,
+ listHooks,
+ listRuns,
+ listSteps,
+ pauseRun,
+ queue,
+ readFromStream,
+ resumeRun,
+ startWorld,
+ updateRun,
+ updateStep,
+ writeToStream,
+} from './api';
+
+// Utility types to strongly type the mocked World instance
+type FnMock = T extends (...args: infer A) => infer R ? Mock : never;
+type RunsMock = { [K in keyof World['runs']]: FnMock };
+type StepsMock = { [K in keyof World['steps']]: FnMock };
+type EventsMock = { [K in keyof World['events']]: FnMock };
+type HooksMock = { [K in keyof World['hooks']]: FnMock };
+
+interface InstrumentedWorld extends World {
+ runs: RunsMock;
+ steps: StepsMock;
+ events: EventsMock;
+ hooks: HooksMock;
+ getDeploymentId: FnMock;
+ queue: FnMock;
+ createQueueHandler: FnMock;
+ writeToStream: FnMock;
+ closeStream: FnMock;
+ readFromStream: FnMock;
+ start?: FnMock>;
+}
+
+const sampleRun: WorkflowRun = {
+ runId: 'run_123',
+ deploymentId: 'dep_abc',
+ status: 'running',
+ workflowName: 'exampleWorkflow',
+ executionContext: {},
+ input: [],
+ createdAt: new Date(),
+ updatedAt: new Date(),
+};
+
+const sampleStep: Step = {
+ runId: 'run_123',
+ stepId: 'step_1',
+ stepName: 'Example Step',
+ status: 'pending',
+ input: [],
+ attempt: 1,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+};
+
+const sampleHook: Hook = {
+ runId: 'run_123',
+ hookId: 'hook_1',
+ token: 'token',
+ ownerId: 'owner',
+ projectId: 'project',
+ environment: 'development',
+ createdAt: new Date(),
+};
+
+const sampleEvent: Event = {
+ runId: 'run_123',
+ eventId: 'event_1',
+ eventType: 'workflow_started',
+ createdAt: new Date(),
+};
+
+const paginate = (data: T[]): PaginatedResponse => ({
+ data,
+ cursor: null,
+ hasMore: false,
+});
+
+function createMockWorld(): InstrumentedWorld {
+ return {
+ runs: {
+ create: vi.fn(),
+ get: vi.fn(),
+ update: vi.fn(),
+ list: vi.fn(),
+ cancel: vi.fn(),
+ pause: vi.fn(),
+ resume: vi.fn(),
+ },
+ steps: {
+ create: vi.fn(),
+ get: vi.fn(),
+ update: vi.fn(),
+ list: vi.fn(),
+ },
+ events: {
+ create: vi.fn(),
+ list: vi.fn(),
+ listByCorrelationId: vi.fn(),
+ },
+ hooks: {
+ create: vi.fn(),
+ get: vi.fn(),
+ getByToken: vi.fn(),
+ list: vi.fn(),
+ dispose: vi.fn(),
+ },
+ getDeploymentId: vi.fn(),
+ queue: vi.fn(),
+ createQueueHandler: vi.fn(),
+ writeToStream: vi.fn(),
+ closeStream: vi.fn(),
+ readFromStream: vi.fn(),
+ start: vi.fn(),
+ } as InstrumentedWorld;
+}
+
+describe('workflow/api world helpers', () => {
+ let mockWorld: InstrumentedWorld;
+
+ beforeEach(() => {
+ mockWorld = createMockWorld();
+ setWorld(mockWorld);
+ });
+
+ afterEach(() => {
+ setWorld(undefined);
+ vi.restoreAllMocks();
+ });
+
+ it('delegates run helpers to the active world singleton', async () => {
+ const createInput: CreateWorkflowRunRequest = {
+ deploymentId: 'dep_abc',
+ workflowName: 'exampleWorkflow',
+ input: [],
+ };
+ const updateInput: UpdateWorkflowRunRequest = { status: 'completed' };
+ const listParams: ListWorkflowRunsParams = {
+ workflowName: 'exampleWorkflow',
+ };
+
+ mockWorld.runs.create.mockResolvedValue(sampleRun);
+ mockWorld.runs.get.mockResolvedValue(sampleRun);
+ mockWorld.runs.update.mockResolvedValue(sampleRun);
+ mockWorld.runs.list.mockResolvedValue(paginate([sampleRun]));
+ mockWorld.runs.cancel.mockResolvedValue(sampleRun);
+ mockWorld.runs.pause.mockResolvedValue(sampleRun);
+ mockWorld.runs.resume.mockResolvedValue(sampleRun);
+
+ await expect(createRun(createInput)).resolves.toBe(sampleRun);
+ expect(mockWorld.runs.create).toHaveBeenCalledWith(createInput);
+
+ await expect(getWorkflowRun(sampleRun.runId, undefined)).resolves.toBe(
+ sampleRun
+ );
+ expect(mockWorld.runs.get).toHaveBeenCalledWith(sampleRun.runId, undefined);
+
+ await expect(updateRun(sampleRun.runId, updateInput)).resolves.toBe(
+ sampleRun
+ );
+ expect(mockWorld.runs.update).toHaveBeenCalledWith(
+ sampleRun.runId,
+ updateInput
+ );
+
+ await expect(listRuns(listParams)).resolves.toEqual(paginate([sampleRun]));
+ expect(mockWorld.runs.list).toHaveBeenCalledWith(listParams);
+
+ await expect(cancelRun(sampleRun.runId)).resolves.toBe(sampleRun);
+ expect(mockWorld.runs.cancel).toHaveBeenCalledWith(
+ sampleRun.runId,
+ undefined
+ );
+
+ await expect(pauseRun(sampleRun.runId)).resolves.toBe(sampleRun);
+ expect(mockWorld.runs.pause).toHaveBeenCalledWith(
+ sampleRun.runId,
+ undefined
+ );
+
+ await expect(resumeRun(sampleRun.runId)).resolves.toBe(sampleRun);
+ expect(mockWorld.runs.resume).toHaveBeenCalledWith(
+ sampleRun.runId,
+ undefined
+ );
+ });
+
+ it('supports the “list runs then cancel” programmatic use case', async () => {
+ mockWorld.runs.list.mockResolvedValue(paginate([sampleRun]));
+ mockWorld.runs.cancel.mockResolvedValue(sampleRun);
+
+ const runs = await listRuns();
+ expect(runs.data[0].runId).toBe(sampleRun.runId);
+
+ await cancelRun(sampleRun.runId);
+ expect(mockWorld.runs.cancel).toHaveBeenCalledWith(
+ sampleRun.runId,
+ undefined
+ );
+ });
+
+ it('delegates step helpers', async () => {
+ const createStepInput: CreateStepRequest = {
+ stepId: 'step_1',
+ stepName: 'Example Step',
+ input: [],
+ };
+ const updateStepInput: UpdateStepRequest = { status: 'completed' };
+ const listStepsParams: ListWorkflowRunStepsParams = { runId: 'run_123' };
+
+ mockWorld.steps.create.mockResolvedValue(sampleStep);
+ mockWorld.steps.get.mockResolvedValue(sampleStep);
+ mockWorld.steps.update.mockResolvedValue(sampleStep);
+ mockWorld.steps.list.mockResolvedValue(paginate([sampleStep]));
+
+ await expect(createStep(sampleRun.runId, createStepInput)).resolves.toBe(
+ sampleStep
+ );
+ expect(mockWorld.steps.create).toHaveBeenCalledWith(
+ sampleRun.runId,
+ createStepInput
+ );
+
+ await expect(getStep(sampleRun.runId, sampleStep.stepId)).resolves.toBe(
+ sampleStep
+ );
+ expect(mockWorld.steps.get).toHaveBeenCalledWith(
+ sampleRun.runId,
+ sampleStep.stepId,
+ undefined
+ );
+
+ await expect(
+ updateStep(sampleRun.runId, sampleStep.stepId, updateStepInput)
+ ).resolves.toBe(sampleStep);
+ expect(mockWorld.steps.update).toHaveBeenCalledWith(
+ sampleRun.runId,
+ sampleStep.stepId,
+ updateStepInput
+ );
+
+ await expect(listSteps(listStepsParams)).resolves.toEqual(
+ paginate([sampleStep])
+ );
+ expect(mockWorld.steps.list).toHaveBeenCalledWith(listStepsParams);
+ });
+
+ it('delegates event and hook helpers', async () => {
+ const createEventInput: CreateEventRequest = {
+ eventType: 'workflow_started',
+ };
+ const listEventParams: ListEventsParams = { runId: sampleRun.runId };
+ const listByCorrelationParams: ListEventsByCorrelationIdParams = {
+ correlationId: 'corr_1',
+ };
+ const createHookInput: CreateHookRequest = {
+ hookId: 'hook_1',
+ token: 'token',
+ };
+ const listHookParams: ListHooksParams = {};
+
+ mockWorld.events.create.mockResolvedValue(sampleEvent);
+ mockWorld.events.list.mockResolvedValue(paginate([sampleEvent]));
+ mockWorld.events.listByCorrelationId.mockResolvedValue(
+ paginate([sampleEvent])
+ );
+
+ mockWorld.hooks.create.mockResolvedValue(sampleHook);
+ mockWorld.hooks.get.mockResolvedValue(sampleHook);
+ mockWorld.hooks.list.mockResolvedValue(paginate([sampleHook]));
+ mockWorld.hooks.dispose.mockResolvedValue(sampleHook);
+
+ await expect(createEvent(sampleRun.runId, createEventInput)).resolves.toBe(
+ sampleEvent
+ );
+ expect(mockWorld.events.create).toHaveBeenCalledWith(
+ sampleRun.runId,
+ createEventInput,
+ undefined
+ );
+
+ await expect(listEvents(listEventParams)).resolves.toEqual(
+ paginate([sampleEvent])
+ );
+ expect(mockWorld.events.list).toHaveBeenCalledWith(listEventParams);
+
+ await expect(
+ listEventsByCorrelationId(listByCorrelationParams)
+ ).resolves.toEqual(paginate([sampleEvent]));
+ expect(mockWorld.events.listByCorrelationId).toHaveBeenCalledWith(
+ listByCorrelationParams
+ );
+
+ await expect(createHook(sampleRun.runId, createHookInput)).resolves.toBe(
+ sampleHook
+ );
+ expect(mockWorld.hooks.create).toHaveBeenCalledWith(
+ sampleRun.runId,
+ createHookInput,
+ undefined
+ );
+
+ await expect(getHook(sampleHook.hookId)).resolves.toBe(sampleHook);
+ expect(mockWorld.hooks.get).toHaveBeenCalledWith(
+ sampleHook.hookId,
+ undefined
+ );
+
+ await expect(listHooks(listHookParams)).resolves.toEqual(
+ paginate([sampleHook])
+ );
+ expect(mockWorld.hooks.list).toHaveBeenCalledWith(listHookParams);
+
+ await expect(disposeHook(sampleHook.hookId)).resolves.toBe(sampleHook);
+ expect(mockWorld.hooks.dispose).toHaveBeenCalledWith(
+ sampleHook.hookId,
+ undefined
+ );
+ });
+
+ it('delegates queue, stream, and lifecycle helpers', async () => {
+ const queueName = '__wkf_workflow_example' as ValidQueueName;
+ const queuePayload: QueuePayload = { runId: sampleRun.runId };
+ const queuePrefix = '__wkf_step_' as QueuePrefix;
+ const messageHandler = vi.fn(async () => undefined);
+ const queueHandler = vi.fn(
+ async (_req: Request) => new Response(null, { status: 200 })
+ );
+
+ mockWorld.getDeploymentId.mockResolvedValue('dep_abc');
+ mockWorld.queue.mockResolvedValue({ messageId: 'msg_1' as MessageId });
+ mockWorld.createQueueHandler.mockReturnValue(queueHandler as any);
+ mockWorld.writeToStream.mockResolvedValue();
+ mockWorld.closeStream.mockResolvedValue();
+ const stream = new ReadableStream({
+ start(controller) {
+ controller.close();
+ },
+ });
+ mockWorld.readFromStream.mockResolvedValue(stream);
+ mockWorld.start?.mockResolvedValue(undefined);
+
+ await expect(getDeploymentId()).resolves.toBe('dep_abc');
+ expect(mockWorld.getDeploymentId).toHaveBeenCalledTimes(1);
+
+ await expect(queue(queueName, queuePayload)).resolves.toEqual({
+ messageId: 'msg_1',
+ });
+ expect(mockWorld.queue).toHaveBeenCalledWith(
+ queueName,
+ queuePayload,
+ undefined
+ );
+
+ const createdHandler = createQueueHandler(queuePrefix, messageHandler);
+ expect(mockWorld.createQueueHandler).toHaveBeenCalledWith(
+ queuePrefix,
+ messageHandler
+ );
+ expect(createdHandler).toBe(queueHandler);
+
+ await writeToStream('stream_1', 'payload');
+ expect(mockWorld.writeToStream).toHaveBeenCalledWith('stream_1', 'payload');
+
+ await closeStream('stream_1');
+ expect(mockWorld.closeStream).toHaveBeenCalledWith('stream_1');
+
+ await expect(readFromStream('stream_1')).resolves.toBe(stream);
+ expect(mockWorld.readFromStream).toHaveBeenCalledWith(
+ 'stream_1',
+ undefined
+ );
+
+ await startWorld();
+ expect(mockWorld.start).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/packages/workflow/src/api.ts b/packages/workflow/src/api.ts
index 81baccd20..d9580f9ff 100644
--- a/packages/workflow/src/api.ts
+++ b/packages/workflow/src/api.ts
@@ -1,7 +1,40 @@
+import { getWorld } from '@workflow/core/runtime';
+import type {
+ CancelWorkflowRunParams,
+ CreateEventParams,
+ CreateEventRequest,
+ CreateHookRequest,
+ CreateStepRequest,
+ CreateWorkflowRunRequest,
+ Event,
+ GetHookParams,
+ GetStepParams,
+ GetWorkflowRunParams,
+ Hook,
+ ListEventsByCorrelationIdParams,
+ ListEventsParams,
+ ListHooksParams,
+ ListWorkflowRunStepsParams,
+ ListWorkflowRunsParams,
+ MessageId,
+ PaginatedResponse,
+ PauseWorkflowRunParams,
+ QueuePayload,
+ QueuePrefix,
+ ResumeWorkflowRunParams,
+ Step,
+ UpdateStepRequest,
+ UpdateWorkflowRunRequest,
+ ValidQueueName,
+ WorkflowRun,
+ World,
+} from '@workflow/world';
+
export {
type Event,
getHookByToken,
getRun,
+ getWorld,
Run,
resumeHook,
resumeWebhook,
@@ -11,3 +44,268 @@ export {
type WorkflowReadableStreamOptions,
type WorkflowRun,
} from '@workflow/core/runtime';
+
+export type {
+ CancelWorkflowRunParams,
+ CreateEventParams,
+ CreateEventRequest,
+ CreateHookRequest,
+ CreateStepRequest,
+ CreateWorkflowRunRequest,
+ GetHookParams,
+ GetStepParams,
+ GetWorkflowRunParams,
+ Hook,
+ ListEventsByCorrelationIdParams,
+ ListEventsParams,
+ ListHooksParams,
+ ListWorkflowRunStepsParams,
+ ListWorkflowRunsParams,
+ MessageId,
+ PaginatedResponse,
+ PauseWorkflowRunParams,
+ QueuePayload,
+ QueuePrefix,
+ ResumeWorkflowRunParams,
+ Step,
+ UpdateStepRequest,
+ UpdateWorkflowRunRequest,
+ ValidQueueName,
+} from '@workflow/world';
+
+/**
+ * Creates a workflow run using the configured World implementation.
+ */
+export function createRun(
+ data: CreateWorkflowRunRequest
+): Promise {
+ return getWorld().runs.create(data);
+}
+
+/**
+ * Retrieves a workflow run record without wrapping it in the `Run` helper.
+ */
+export function getWorkflowRun(
+ runId: string,
+ params?: GetWorkflowRunParams
+): Promise {
+ return getWorld().runs.get(runId, params);
+}
+
+/**
+ * Updates a workflow run with partial data.
+ */
+export function updateRun(
+ runId: string,
+ data: UpdateWorkflowRunRequest
+): Promise {
+ return getWorld().runs.update(runId, data);
+}
+
+/**
+ * Lists workflow runs with optional filtering/pagination.
+ */
+export function listRuns(
+ params?: ListWorkflowRunsParams
+): Promise> {
+ return getWorld().runs.list(params);
+}
+
+/**
+ * Cancels a workflow run.
+ */
+export function cancelRun(
+ runId: string,
+ params?: CancelWorkflowRunParams
+): Promise {
+ return getWorld().runs.cancel(runId, params);
+}
+
+/**
+ * Pauses a workflow run.
+ */
+export function pauseRun(
+ runId: string,
+ params?: PauseWorkflowRunParams
+): Promise {
+ return getWorld().runs.pause(runId, params);
+}
+
+/**
+ * Resumes a previously paused workflow run.
+ */
+export function resumeRun(
+ runId: string,
+ params?: ResumeWorkflowRunParams
+): Promise {
+ return getWorld().runs.resume(runId, params);
+}
+
+/**
+ * Creates a step record for a workflow run.
+ */
+export function createStep(
+ runId: string,
+ data: CreateStepRequest
+): Promise {
+ return getWorld().steps.create(runId, data);
+}
+
+/**
+ * Retrieves a workflow step.
+ */
+export function getStep(
+ runId: string | undefined,
+ stepId: string,
+ params?: GetStepParams
+): Promise {
+ return getWorld().steps.get(runId, stepId, params);
+}
+
+/**
+ * Updates a workflow step.
+ */
+export function updateStep(
+ runId: string,
+ stepId: string,
+ data: UpdateStepRequest
+): Promise {
+ return getWorld().steps.update(runId, stepId, data);
+}
+
+/**
+ * Lists steps for a workflow run.
+ */
+export function listSteps(
+ params: ListWorkflowRunStepsParams
+): Promise> {
+ return getWorld().steps.list(params);
+}
+
+/**
+ * Creates an event associated with a workflow run.
+ */
+export function createEvent(
+ runId: string,
+ data: CreateEventRequest,
+ params?: CreateEventParams
+): Promise {
+ return getWorld().events.create(runId, data, params);
+}
+
+/**
+ * Lists events for a workflow run.
+ */
+export function listEvents(
+ params: ListEventsParams
+): Promise> {
+ return getWorld().events.list(params);
+}
+
+/**
+ * Lists events filtered by correlation ID across runs.
+ */
+export function listEventsByCorrelationId(
+ params: ListEventsByCorrelationIdParams
+): Promise> {
+ return getWorld().events.listByCorrelationId(params);
+}
+
+/**
+ * Creates a hook for a workflow run.
+ */
+export function createHook(
+ runId: string,
+ data: CreateHookRequest,
+ params?: GetHookParams
+): Promise {
+ return getWorld().hooks.create(runId, data, params);
+}
+
+/**
+ * Retrieves a hook by ID.
+ */
+export function getHook(hookId: string, params?: GetHookParams): Promise {
+ return getWorld().hooks.get(hookId, params);
+}
+
+/**
+ * Lists hooks with optional filters.
+ */
+export function listHooks(
+ params: ListHooksParams
+): Promise> {
+ return getWorld().hooks.list(params);
+}
+
+/**
+ * Disposes an existing hook.
+ */
+export function disposeHook(
+ hookId: string,
+ params?: GetHookParams
+): Promise {
+ return getWorld().hooks.dispose(hookId, params);
+}
+
+/**
+ * Returns the deployment ID used by the underlying queue implementation.
+ */
+export function getDeploymentId(): Promise {
+ return getWorld().getDeploymentId();
+}
+
+/**
+ * Enqueues a message for workflow or step execution.
+ */
+export function queue(
+ queueName: ValidQueueName,
+ message: QueuePayload,
+ opts?: { deploymentId?: string; idempotencyKey?: string }
+): Promise<{ messageId: MessageId }> {
+ return getWorld().queue(queueName, message, opts);
+}
+
+/**
+ * Creates an HTTP handler for the provided queue prefix.
+ */
+export function createQueueHandler(
+ queueNamePrefix: QueuePrefix,
+ handler: Parameters[1]
+): ReturnType {
+ return getWorld().createQueueHandler(queueNamePrefix, handler);
+}
+
+/**
+ * Writes chunked data to a named stream.
+ */
+export function writeToStream(
+ name: string,
+ chunk: string | Uint8Array
+): Promise {
+ return getWorld().writeToStream(name, chunk);
+}
+
+/**
+ * Closes a named stream.
+ */
+export function closeStream(name: string): Promise {
+ return getWorld().closeStream(name);
+}
+
+/**
+ * Reads data from a named stream.
+ */
+export function readFromStream(
+ name: string,
+ startIndex?: number
+): Promise> {
+ return getWorld().readFromStream(name, startIndex);
+}
+
+/**
+ * Starts any background services provided by the configured World.
+ */
+export async function startWorld(): Promise {
+ await getWorld().start?.();
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 01614c57d..ee47ed064 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -823,6 +823,9 @@ importers:
'@workflow/typescript-plugin':
specifier: workspace:*
version: link:../typescript-plugin
+ '@workflow/world':
+ specifier: workspace:*
+ version: link:../world
ms:
specifier: 2.1.3
version: 2.1.3
@@ -14367,6 +14370,14 @@ snapshots:
chai: 5.2.1
tinyrainbow: 2.0.0
+ '@vitest/mocker@3.2.4(vite@7.1.11(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0))':
+ dependencies:
+ '@vitest/spy': 3.2.4
+ estree-walker: 3.0.3
+ magic-string: 0.30.19
+ optionalDependencies:
+ vite: 7.1.11(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0)
+
'@vitest/mocker@3.2.4(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0))':
dependencies:
'@vitest/spy': 3.2.4
@@ -20141,7 +20152,7 @@ snapshots:
dependencies:
'@types/chai': 5.2.2
'@vitest/expect': 3.2.4
- '@vitest/mocker': 3.2.4(vite@7.1.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0))
+ '@vitest/mocker': 3.2.4(vite@7.1.11(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.0))
'@vitest/pretty-format': 3.2.4
'@vitest/runner': 3.2.4
'@vitest/snapshot': 3.2.4