Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
734b4df
[core] Make getWorld interface asynchronous
VaguelySerious Feb 5, 2026
f82f218
Fixes
VaguelySerious Feb 5, 2026
a0d8202
Fixes
VaguelySerious Feb 5, 2026
924314c
Merge branch 'main' into peter/async-world
VaguelySerious Feb 5, 2026
b086650
Fix: Race condition in getWorld() and getWorldHandlers() allows multi…
vercel[bot] Feb 5, 2026
efff295
Maybe fix tests
VaguelySerious Feb 5, 2026
1d67f3f
Merge branch 'main' into peter/async-world
VaguelySerious Feb 5, 2026
15193e8
Fix build
VaguelySerious Feb 6, 2026
1871507
Revert "Fix build"
VaguelySerious Feb 6, 2026
fba0775
Merge branch 'main' into peter/async-world
VaguelySerious Apr 3, 2026
4dcfbc2
Fix build and test errors after merge
VaguelySerious Apr 3, 2026
b16728c
Address PR review feedback
VaguelySerious Apr 3, 2026
81a548b
Fix: try require() before dynamicImport for custom world modules
VaguelySerious Apr 3, 2026
dec7335
merge: resolve conflicts with main
VaguelySerious Apr 7, 2026
4300ff0
fix: await getWorld() in resilient start e2e test
VaguelySerious Apr 7, 2026
a5b0e82
fix: don't encrypt hook metadata so webhook handler can read it
VaguelySerious Apr 8, 2026
8345fcf
fix: update encryption capability minVersion after v5 version reset
VaguelySerious Apr 8, 2026
8a5acb5
fix: update capabilities test for new encryption minVersion
VaguelySerious Apr 8, 2026
e129d92
revert: keep encrypting hook metadata in suspension handler
VaguelySerious Apr 8, 2026
9220639
fix: sync package versions to 5.0.0-beta.0 and revert capabilities ch…
VaguelySerious Apr 8, 2026
1c94485
Merge branch 'main' into peter/async-world
VaguelySerious Apr 8, 2026
3fa3b6a
Flip module resolution order: dynamicImport() before require()
VaguelySerious Apr 8, 2026
bd2a0d8
Merge main into peter/async-world
VaguelySerious Apr 9, 2026
1c663f6
Fix merge artifacts: duplicate import and missing features field
VaguelySerious Apr 9, 2026
9cb5a8b
Merge branch 'main' into peter/async-world
VaguelySerious Apr 9, 2026
2653faf
docs: document async getWorld() and update World SDK skill
VaguelySerious Apr 9, 2026
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
6 changes: 6 additions & 0 deletions .changeset/ninety-dancers-brush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@workflow/core": patch
"@workflow/cli": patch
---

**BREAKING CHANGE**: Make `getWorld` and `createWorld` asynchronous to support ESM dynamic imports for custom world modules. All callers must now `await getWorld()`.
Comment on lines +1 to +6
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Suggested change
---
"@workflow/core": patch
"@workflow/cli": patch
---
**BREAKING CHANGE**: Make `getWorld` and `createWorld` asynchronous to support ESM dynamic imports for custom world modules. All callers must now `await getWorld()`.
---
"@workflow/core": minor
"@workflow/cli": minor
---
**BREAKING CHANGE**: Make `getWorld` and `createWorld` asynchronous to support ESM dynamic imports for custom world modules. All callers must now `await getWorld()`.

12 changes: 6 additions & 6 deletions docs/content/docs/api-reference/workflow-api/get-world.mdx
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
---
title: getWorld
description: Access the World instance for low-level storage, queuing, and streaming operations.
description: Async function that resolves the World instance for low-level storage, queuing, and streaming operations.
type: reference
summary: Use getWorld to access low-level workflow storage, queuing, and streaming backends directly.
summary: Async function that resolves the World instance for low-level workflow storage, queuing, and streaming backends.
prerequisites:
- /docs/deploying
---

Retrieves the World instance for direct access to workflow storage, queuing, and streaming backends. This function returns a `World` which provides low-level access to manage workflow runs, steps, events, and hooks.
Retrieves the World instance for direct access to workflow storage, queuing, and streaming backends. This async function returns a `Promise<World>` which provides low-level access to manage workflow runs, steps, events, and hooks.

Use this function when you need direct access to the underlying workflow infrastructure, such as listing all runs, querying events, or implementing custom workflow management logic.

```typescript lineNumbers
import { getWorld } from "workflow/runtime";

const world = getWorld(); // [!code highlight]
const world = await getWorld(); // [!code highlight]
```

## API Signature
Expand All @@ -25,7 +25,7 @@ This function does not accept any parameters.

### Returns

Returns a `World` object:
Returns a `Promise<World>` object:

<TSDoc
definition={`
Expand Down Expand Up @@ -79,7 +79,7 @@ export async function GET(req: Request) {
const cursor = url.searchParams.get("cursor") ?? undefined;

try {
const world = getWorld(); // [!code highlight]
const world = await getWorld(); // [!code highlight]
const runs = await world.runs.list({
pagination: { cursor },
resolveData: "none",
Expand Down
2 changes: 1 addition & 1 deletion docs/content/docs/api-reference/workflow-api/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ The API package is for access and introspection of workflow data to inspect runs
Get workflow run status and metadata without waiting for completion.
</Card>
<Card href="/docs/api-reference/workflow-api/get-world" title="getWorld()">
Get direct access to workflow storage, queuing, and streaming backends.
Async: resolve the World instance for storage, queuing, and streaming backends.
</Card>
<Card href="/docs/api-reference/workflow-api/world" title="World SDK">
Low-level API for inspecting runs, steps, events, hooks, streams, and queues.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: World SDK
description: Low-level API for inspecting and managing workflow runs, steps, events, hooks, streams, and queues.
type: overview
summary: Access workflow infrastructure directly via getWorld() for building observability dashboards, admin tools, and custom integrations.
summary: Access workflow infrastructure via await getWorld() for building observability dashboards, admin tools, and custom integrations.
prerequisites:
- /docs/api-reference/workflow-api/get-world
keywords:
Expand All @@ -19,7 +19,7 @@ The World SDK provides direct access to workflow infrastructure — runs, steps,
```typescript lineNumbers
import { getWorld } from "workflow/runtime";

const world = getWorld(); // [!code highlight]
const world = await getWorld(); // [!code highlight]
```

## Interfaces
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ const hydrated = hydrateResourceIOWithKey(step, key); // [!code highlight]
import { getWorld } from "workflow/runtime";
import { parseStepName, parseWorkflowName } from "workflow/observability"; // [!code highlight]

const world = getWorld();
const world = await getWorld();
const run = await world.runs.get(runId, { resolveData: "none" });
console.log("Workflow:", parseWorkflowName(run.workflowName)?.shortName); // [!code highlight]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Queue methods live directly on the `world` object (not nested). They dispatch in
```typescript lineNumbers
import { getWorld } from "workflow/runtime";

const world = getWorld(); // [!code highlight]
const world = await getWorld(); // [!code highlight]
// Queue methods are called directly on world — e.g. world.queue()
```

Expand Down
16 changes: 8 additions & 8 deletions docs/content/docs/api-reference/workflow-api/world/storage.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ The World storage interface exposes four sub-interfaces for querying workflow da
```typescript lineNumbers
import { getWorld } from "workflow/runtime";

const world = getWorld(); // [!code highlight]
const world = await getWorld(); // [!code highlight]
```

---
Expand Down Expand Up @@ -304,7 +304,7 @@ const result = await world.hooks.list({ // [!code highlight]
```typescript lineNumbers
import { getWorld } from "workflow/runtime";

const world = getWorld();
const world = await getWorld();
let cursor: string | undefined;

const runs = await world.runs.list({ // [!code highlight]
Expand All @@ -319,7 +319,7 @@ cursor = runs.cursor; // pass to next call for pagination
```typescript lineNumbers
import { getWorld } from "workflow/runtime";

const world = getWorld();
const world = await getWorld();

// Full data (default) — includes serialized input/output
const run = await world.runs.get(runId); // [!code highlight]
Expand All @@ -336,7 +336,7 @@ const lightweight = await world.runs.get(runId, { // [!code highlight]
import { getWorld } from "workflow/runtime";
import { parseStepName } from "workflow/observability"; // [!code highlight]

const world = getWorld();
const world = await getWorld();
const steps = await world.steps.list({ // [!code highlight]
runId,
resolveData: "none",
Expand All @@ -358,7 +358,7 @@ const progress = steps.data.map((step) => {
import { getWorld } from "workflow/runtime";
import { hydrateResourceIO, observabilityRevivers } from "workflow/observability"; // [!code highlight]

const world = getWorld();
const world = await getWorld();
const step = await world.steps.get(runId, stepId); // [!code highlight]
const hydrated = hydrateResourceIO(step, observabilityRevivers); // [!code highlight]
console.log(hydrated.input, hydrated.output);
Expand All @@ -369,7 +369,7 @@ console.log(hydrated.input, hydrated.output);
```typescript lineNumbers
import { getWorld } from "workflow/runtime";

const world = getWorld();
const world = await getWorld();
await world.events.create(runId, { // [!code highlight]
eventType: "run_cancelled", // [!code highlight]
}); // [!code highlight]
Expand All @@ -380,7 +380,7 @@ await world.events.create(runId, { // [!code highlight]
```typescript lineNumbers
import { getWorld } from "workflow/runtime";

const world = getWorld();
const world = await getWorld();
const hook = await world.hooks.getByToken(token); // [!code highlight]
console.log(hook.runId, hook.metadata); // [!code highlight]
```
Expand All @@ -390,7 +390,7 @@ console.log(hook.runId, hook.metadata); // [!code highlight]
```typescript lineNumbers
import { getWorld } from "workflow/runtime";

const world = getWorld();
const world = await getWorld();
const events = await world.events.list({ runId }); // [!code highlight]

for (const event of events.data) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ keywords:
- stream lifecycle
---

Stream methods live on `world.streams` (the `streams` sub-object of the `world` object returned by `getWorld()`). Use them to write chunks, read streams, and manage stream lifecycle outside of the standard `getWritable()` pattern.
Stream methods live on `world.streams` (the `streams` sub-object of the `World` instance returned by `await getWorld()`). Use them to write chunks, read streams, and manage stream lifecycle outside of the standard `getWritable()` pattern.

<Callout type="info">
For most streaming use cases, use [`getWritable()`](/docs/api-reference/workflow/get-writable) inside steps. Direct stream methods are for advanced scenarios like building custom stream consumers or managing streams from outside a workflow.
Expand All @@ -32,7 +32,7 @@ Stream methods live on `world.streams` (the `streams` sub-object of the `world`
```typescript lineNumbers
import { getWorld } from "workflow/runtime";

const world = getWorld(); // [!code highlight]
const world = await getWorld(); // [!code highlight]
// Stream methods are called on world.streams — e.g. world.streams.write()
```

Expand Down Expand Up @@ -183,7 +183,7 @@ export async function GET(req: Request) {
const url = new URL(req.url);
const streamName = url.searchParams.get("name") ?? "default";
const runId = url.searchParams.get("runId")!;
const world = getWorld();
const world = await getWorld();
const readable = await world.streams.get(runId, streamName); // [!code highlight]

return new Response(readable, {
Expand All @@ -197,7 +197,7 @@ export async function GET(req: Request) {
```typescript lineNumbers
import { getWorld } from "workflow/runtime";

const world = getWorld();
const world = await getWorld();
let cursor: string | undefined;

do {
Expand Down
13 changes: 9 additions & 4 deletions docs/content/docs/deploying/world/postgres-world.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ Create an `instrumentation.ts` file in your project root:
export async function register() {
if (process.env.NEXT_RUNTIME !== "edge") {
const { getWorld } = await import("workflow/runtime");
await getWorld().start?.();
const world = await getWorld();
await world.start?.();
}
}
```
Expand All @@ -73,7 +74,8 @@ import type { ServerInit } from "@sveltejs/kit";

export const init: ServerInit = async () => {
const { getWorld } = await import("workflow/runtime");
await getWorld().start?.();
const world = await getWorld();
await world.start?.();
};
```

Expand All @@ -92,7 +94,8 @@ import { defineNitroPlugin } from "nitro/~internal/runtime/plugin";

export default defineNitroPlugin(async () => {
const { getWorld } = await import("workflow/runtime");
await getWorld().start?.();
const world = await getWorld();
await world.start?.();
});
```

Expand Down Expand Up @@ -168,7 +171,8 @@ For higher worker concurrency, Graphile Worker recommends setting `maxPoolSize`

### Programmatic configuration

{/* @skip-typecheck: incomplete code sample */}
{/*@skip-typecheck: incomplete code sample*/}

```typescript title="workflow.config.ts" lineNumbers
import { createWorld } from "@workflow/world-postgres";

Expand Down Expand Up @@ -200,6 +204,7 @@ Deploy your application to any cloud that supports long-running servers:
- Platform-as-a-Service providers (Railway, Render, Fly.io, etc.)

Ensure your deployment has:

1. Network access to your PostgreSQL database
2. Environment variables configured correctly
3. The `start()` function called on server initialization
Expand Down
Loading
Loading