Skip to content
Merged
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/nitro-auto-world-start.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@workflow/nitro': minor
---

Start the workflow World automatically at server boot via a generated Nitro plugin, so self-hosted Nitro apps (Nitro v2/v3, Nuxt, Express/Hono/Fastify on Nitro) recover in-flight runs after a restart with no manual wiring. Skipped on Vercel deploys.
11 changes: 1 addition & 10 deletions docs/content/docs/v4/deploying/recovering-in-flight-runs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,7 @@ export async function register() {

### Nitro, Nuxt, Express, Hono, Fastify (Nitro)

Add a [Nitro server plugin](https://nitro.build/guide/plugins) (Nuxt: `server/plugins/`) that starts the World at boot:

```ts title="server/plugins/workflow.ts"
import { defineNitroPlugin } from 'nitro/~internal/runtime/plugin';
import { ensureWorldStarted } from 'workflow/runtime';

export default defineNitroPlugin(() => {
void ensureWorldStarted();
});
```
No action required — the `@workflow/nitro` integration registers a Nitro server plugin that starts the World at boot for you. (Not on Vercel deploys, where the push-based Vercel World needs no boot recovery.)

### SvelteKit

Expand Down
11 changes: 1 addition & 10 deletions docs/content/docs/v5/deploying/recovering-in-flight-runs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,7 @@ export async function register() {

### Nitro, Nuxt, Express, Hono, Fastify (Nitro)

Add a [Nitro server plugin](https://nitro.build/guide/plugins) (Nuxt: `server/plugins/`) that starts the World at boot:

```ts title="server/plugins/workflow.ts"
import { defineNitroPlugin } from 'nitro/~internal/runtime/plugin';
import { ensureWorldStarted } from 'workflow/runtime';

export default defineNitroPlugin(() => {
void ensureWorldStarted();
});
```
No action required — the `@workflow/nitro` integration registers a Nitro server plugin that starts the World at boot for you. (Not on Vercel deploys, where the push-based Vercel World needs no boot recovery.)

### SvelteKit

Expand Down
48 changes: 48 additions & 0 deletions packages/nitro/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,14 @@ export default {
'workflow/workflows.mjs'
);

// Start the World once at server boot (Nitro server plugin) so in-flight
// runs recover after a restart without needing a workflow operation.
// Covers self-hosted Nitro apps (Nitro v2/v3, Nuxt). Skipped on Vercel:
// the Vercel World's start() is a no-op (push-based — VQS redelivers).
if (!isVercelDeploy) {
addStartupPlugin(nitro);
}

// Nitro v3+ Vercel deploy: configure function rules for the combined
// flow handler so it gets the queue triggers + max duration that the
// workflow runtime needs. Workflow-required fields (`maxDuration`,
Expand Down Expand Up @@ -292,6 +300,46 @@ export default {
},
} satisfies NitroModule;

/**
* Auto-register a Nitro server plugin that starts the World once at app boot,
* so boot-time recovery (`reenqueueActiveRuns` for queue-backed self-hosted
* Worlds) runs after a restart without requiring a workflow operation to wake
* the process.
*
* The plugin is emitted as a real file in the build dir and imports
* `workflow/runtime` via a *bare* dynamic import (resolved by the bundler) —
* mirroring a hand-written Nitro plugin. This shares the same runtime module
* the flow handler loads, avoiding the CJS/ESM dual-load that a build-time
* `file://` import would trigger against the bundled flow handler.
* `ensureWorldStarted()` caches its start promise on `globalThis`, so the World
* is started exactly once even though the flow handler also reaches the runtime.
*
* Not registered for Vercel deploys (the Vercel World's start() is a no-op, and
* there is nothing to recover at boot for a push-based world).
*/
function addStartupPlugin(nitro: Nitro) {
const dir = join(nitro.options.buildDir, 'workflow');
mkdirSync(dir, { recursive: true });
const pluginPath = join(dir, 'start-world-plugin.mjs');
writeFileSync(
pluginPath,
/* js */ `// Auto-generated by @workflow/nitro — starts the workflow World at server boot.
export default () => {
import('workflow/runtime')
.then(({ ensureWorldStarted }) => ensureWorldStarted())
.catch((error) => {
console.error('[workflow] Failed to start World on server startup:', error);
});
};
`
);

nitro.options.plugins ||= [];
if (!nitro.options.plugins.includes(pluginPath)) {
nitro.options.plugins.push(pluginPath);
}
}

const DASHBOARD_VIRTUAL_ID = '#workflow/dashboard-handler';

function addDashboardHandler(nitro: Nitro) {
Expand Down
1 change: 0 additions & 1 deletion workbench/nitro-v3/nitro.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,4 @@ import { defineConfig } from 'nitro';
export default defineConfig({
modules: ['workflow/nitro'],
serverDir: './',
plugins: ['plugins/start-pg-world.ts'],
});
15 changes: 0 additions & 15 deletions workbench/nitro-v3/plugins/start-pg-world.ts

This file was deleted.

Loading