@voyantjs/workflows-orchestrator-node
v0.107.10
Published
Node/Docker runtime primitives for @voyantjs/workflows-orchestrator, including a file-backed run store and local scheduler.
Readme
@voyantjs/workflows-orchestrator-node
Node/Docker runtime primitives for @voyantjs/workflows-orchestrator.
This package is the first building block for the Docker/GCE self-host target:
- file-backed
RunRecordStorefor single-node development and early self-host installs - file-backed snapshot run store for dashboard / self-host HTTP APIs
- Drizzle-backed PostgreSQL snapshot + wakeup stores for Docker/GCE installs
- local scheduler for workflow
scheduledeclarations - run-record snapshot helpers for dashboard-facing local state
- local sleep-alarm manager for
ctx.sleepwakeups in a single Node process - lease-based wakeup store + poller primitives for the future multi-process / Postgres-backed adapter
- persistent wakeup manager for file-backed self-host installs
- entry loading + HTTP/SSE server helpers for the Node self-host target
- failed-step resume dispatch for operator dashboards (
POST /api/runs/:id/resume) - package-owned Postgres migration runner for Docker/runtime boot flows
It is intentionally smaller than the future full Node adapter. The first goal is to move Node-specific runtime concerns out of the CLI and into a reusable package.
Self-host dispatch client
Operator admin processes can use the client helper instead of hand-rolling the self-host HTTP contract:
import { createNodeSelfHostWorkflowClient } from "@voyantjs/workflows-orchestrator-node";
const workflows = createNodeSelfHostWorkflowClient({
baseUrl: process.env.WORKFLOW_SERVER_URL!,
});
const rerun = await workflows.trigger({
workflowId: "checkout-finalize",
input,
tags: ["rerun:true"],
});
const resumed = await workflows.resume(parentRunId, {
workflowId: "checkout-finalize",
input,
resumeFromStep: "issue_invoice",
seedResults: {
validate_booking: { ok: true },
},
tags: ["resume:true"],
triggeredByUserId: userId,
});resume(...) starts a new run linked to the parent id. When the parent id is a
self-host snapshot id and seedResults is omitted, the server derives seed
entries from the parent run's successful journaled steps before the failed step.
When the parent id comes from an external admin recorder such as
@voyantjs/workflow-runs, pass workflowId, resumeFromStep, and
seedResults from that recorder's WorkflowResumeContext. Seeded steps are
replayed from the journal, so their side effects do not run again.
Service-backed package workflows
Package workflows can resolve host-provided services through
ctx.services.resolve(...). For example, @voyantjs/promotions exports the
promotions.reindex-all-products workflow, which resolves the bulk reindex
service registered under BULK_REINDEX_SERVICE_KEY.
For app-integrated Node runtimes, prefer the createApp() workflow runtime.
createApp() builds the module container, passes a read-only service resolver
to the driver factory, and the Node standalone driver forwards that resolver
into every workflow step:
import { createApp } from "@voyantjs/hono"
import type { HonoModule } from "@voyantjs/hono/module"
import { createNodeStandaloneDriver } from "@voyantjs/workflows-orchestrator-node"
import {
BULK_REINDEX_SERVICE_KEY,
promotionsHonoModule,
} from "@voyantjs/promotions"
const promotionsWorkflowServices: HonoModule = {
module: {
name: BULK_REINDEX_SERVICE_KEY,
service: bulkReindexProductsService,
},
}
const app = createApp({
db: () => db,
modules: [promotionsHonoModule, promotionsWorkflowServices],
workflows: {
driver: () => createNodeStandaloneDriver({ db }),
environment: "production",
},
})
await app.ready()The key point is that service registration happens in the host app and the
driver receives the framework's service resolver from createApp(); workflow
code stays package-owned and deployment-agnostic.
If you are still using the standalone self-host HTTP helper directly with an
entry file, pass the same read-only resolver to startNodeSelfHostServer() or
createNodeSelfHostDeps():
import { BULK_REINDEX_SERVICE_KEY } from "@voyantjs/promotions"
import type { ServiceResolver } from "@voyantjs/workflows/driver"
import { startNodeSelfHostServer } from "@voyantjs/workflows-orchestrator-node"
const services: ServiceResolver = {
resolve<T>(name: string): T {
if (name === BULK_REINDEX_SERVICE_KEY) {
return bulkReindexProductsService as T
}
throw new Error(`Service "${name}" is not registered`)
},
has(name: string): boolean {
return name === BULK_REINDEX_SERVICE_KEY
},
}
await startNodeSelfHostServer({
entryFile: "./dist/workflows.mjs",
databaseUrl: process.env.DATABASE_URL,
services,
})That direct helper is useful for existing entry-file deployments. New
service-backed package workflow integrations should usually migrate to
createApp({ workflows: { driver: () => createNodeStandaloneDriver({ db }) } })
so module services, event filters, manifest registration, and ctx.services
all come from one app composition path.
Postgres migrations
The PostgreSQL schema for the Node self-host target lives in
src/postgres-schema.ts and is versioned
through committed Drizzle migrations in drizzle/.
Apply migrations with:
DATABASE_URL='<postgres connection string>' \
pnpm --filter @voyantjs/workflows-orchestrator-node db:migrateOr run the same committed SQL migrations through the runtime-owned helper:
import { runPostgresMigrations } from "@voyantjs/workflows-orchestrator-node";
await runPostgresMigrations({
databaseUrl: process.env.DATABASE_URL!,
});Generate a new migration after editing the schema with:
pnpm --filter @voyantjs/workflows-orchestrator-node db:generate -- --name your_change_nameIntegration test
Run the Postgres-backed integration suite with a local database, for example:
TEST_DATABASE_URL='<postgres test connection string>' \
pnpm --filter @voyantjs/workflows-orchestrator-node test:integrationFor a Docker-first reference deployment, use
apps/workflows-selfhost-node-server, which now
includes a multi-stage image, a container entrypoint, and a compose
example that can run these migrations on boot.
The reference HTTP server also exposes GET /healthz, GET /readyz, and
GET /metrics. When PostgreSQL is configured, readiness includes a database
connectivity check. The metrics endpoint emits Prometheus-style gauges for
registered workflows/schedules, persisted runs by status, and persisted wakeups.
Operational notes for single-node and multi-instance Postgres deployments
live in docs/selfhost-node-ops.md.
