@cascade-flow/backend-postgres

中文文档
0.2.31发布于 2026/08/12MITTypeScriptES Module近 30 天下载 2787

PostgreSQL backend implementation for CascadeFlow workflow orchestrator

原始使用文档
文档目录34 节
  1. Features
  2. Installation
  3. Quick Start
  4. Basic Usage
  5. With CLI
  6. Programmatic Usage
  7. Connection String Format
  8. Database Schema
  9. Event Tables
  10. Registry Tables
  11. Supporting Tables
  12. Migration Strategy
  13. Current Migrations
  14. Adding New Migrations
  15. Idempotency Best Practices
  16. Architecture Details
  17. Event Sourcing
  18. Events-as-Queue Pattern
  19. Atomic Step Claiming
  20. Time Precision
  21. Testing
  22. Integration Tests
  23. Docker PostgreSQL for Testing
  24. Performance Considerations
  25. Indexes
  26. Connection Pooling
  27. JSONB Storage
  28. Switching Database Libraries
  29. Comparison to FileSystemBackend
  30. Troubleshooting
  31. Connection Issues
  32. Migration Failures
  33. Performance Issues
  34. See Also

Backend Postgres

PostgreSQL-based implementation of the Backend interface for the workflow orchestrator.

Features

  • Event Sourcing: Immutable append-only event storage in PostgreSQL
  • Automatic Schema Initialization: Idempotent migrations run automatically on first connection
  • Race-Safe Step Claiming: Atomic step claiming using SELECT FOR UPDATE SKIP LOCKED
  • Events-as-Queue Pattern: Queue state derived from events (no separate queue table)
  • JSONB Storage: Flexible schema evolution with JSONB columns
  • Full TypeScript Support: Complete type safety across all operations
  • Easy Database Migration: All pg usage isolated in db.ts for easy library swapping

Installation

bun add backend-postgres pg
bun add -d @types/pg

Quick Start

Basic Usage

import { PostgresBackend } from "backend-postgres";

// Create backend with connection string
const backend = new PostgresBackend("postgres://user:password@localhost:5432/workflow_db");

// The database schema is automatically initialized on first use
// No manual migration commands needed!

// Use with the runner
import { runAll } from "runner";
const result = await runAll(workflow, input, { backend });

// Don't forget to close the connection pool when done
await backend.close();

With CLI

You can use the Postgres backend with the CLI by setting the POSTGRES_URL environment variable:

# Set the Postgres connection string
export POSTGRES_URL="postgres://user:password@localhost:5432/workflow_db"

# If POSTGRES_URL uses transaction-pooled PgBouncer, use a direct connection
# for schema migrations and concurrent index creation.
export POSTGRES_MIGRATIONS_URL="postgres://user:password@direct-host:5432/workflow_db"

# Run workflows
wfo run my-workflow --input '{"key": "value"}'

# Queue-based execution
wfo worker start  # Uses Postgres backend
wfo submit my-workflow --input '{...}'

Programmatic Usage

import { PostgresBackend } from "backend-postgres";
import { Client } from "client";

const backend = new PostgresBackend(process.env.POSTGRES_URL!);
const client = new Client({ backend });

// Submit a workflow run
const runId = await client.submit({
  workflowSlug: "my-workflow",
  input: { message: "Hello" },
  tags: ["production"],
  idempotencyKey: "unique-key-123", // Optional deduplication
});

// Wait for completion
const result = await client.waitForCompletion(runId);
console.log("Output:", result.output);

// Cleanup
await backend.close();

When constructing the backend directly with a transaction-pooled runtime URL, pass the direct migration URL as the fourth constructor argument:

const backend = new PostgresBackend(
  process.env.POSTGRES_URL!,
  "cascadeflow",
  undefined,
  process.env.POSTGRES_MIGRATIONS_URL
);

Connection String Format

The connection string follows the standard PostgreSQL format:

postgres://[user[:password]@][host][:port][/database][?option=value]

Examples:

# Local development
postgres://postgres:postgres@localhost:5432/workflow_dev

# With SSL
postgres://user:[email protected]:5432/workflows?sslmode=require

# Unix socket
postgres://user@/dbname?host=/var/run/postgresql

# Cloud providers (example: Neon, Supabase, etc.)
postgres://user:[email protected]/dbname?sslmode=require

Database Schema

The backend creates and manages the following tables:

Event Tables

workflow_events - Workflow-level events

  • id (serial) - Primary key
  • event_id (text) - Microsecond timestamp string
  • workflow_slug (text) - Workflow identifier
  • run_id (text) - Run identifier
  • timestamp_us (bigint) - Event timestamp in microseconds
  • category (text) - Always 'workflow'
  • type (text) - Event type (WorkflowStarted, WorkflowCompleted, etc.)
  • data (jsonb) - Full event payload

step_events - Step-level events

  • id (serial) - Primary key
  • event_id (text) - Microsecond timestamp string
  • workflow_slug (text) - Workflow identifier
  • run_id (text) - Run identifier
  • step_id (text) - Step identifier
  • timestamp_us (bigint) - Event timestamp in microseconds
  • category (text) - Always 'step'
  • type (text) - Event type (StepScheduled, StepStarted, etc.)
  • data (jsonb) - Full event payload

Registry Tables

workflow_metadata - Workflow registry

  • slug (text) - Primary key
  • name (text) - Display name
  • description (text) - Optional description
  • input_schema_json (jsonb) - JSON Schema for input validation
  • tags (text[]) - Tags array

step_definitions - Step definitions

  • workflow_slug (text) - References workflow_metadata(slug)
  • id (text) - Step identifier
  • dependencies (jsonb) - Step dependencies
  • export_output (boolean) - Whether output is exported
  • input_schema_json (jsonb) - JSON Schema for step input
  • timeout_ms (integer) - Step timeout
  • max_retries (integer) - Maximum retry attempts
  • retry_delay_ms (integer) - Delay between retries

Supporting Tables

step_outputs - Serialized step outputs

  • workflow_slug, run_id, step_id, attempt_number - Composite primary key
  • output (jsonb) - Step output data

idempotency_keys - Deduplication

  • hash (text) - Primary key (SHA256 of idempotency key)
  • run_id (text) - Associated run ID

Migration Strategy

The backend uses idempotent migrations that are safe to rerun. Migrations are automatically executed on the first database connection.

Current Migrations

  1. Migration 001: Create tables - Uses CREATE TABLE IF NOT EXISTS
  2. Migration 002: Create indexes - Uses CREATE INDEX IF NOT EXISTS

Adding New Migrations

To add a new migration:

  1. Add a new migration function to src/migrations.ts:
async function migration003_addNewFeature(pool: Pool): Promise<void> {
  const client = await pool.connect();
  try {
    // Your migration code here (must be idempotent!)
    await client.query(`
      ALTER TABLE workflow_metadata
      ADD COLUMN IF NOT EXISTS new_field TEXT
    `);

    console.log("[Migration 003] Added new feature");
  } catch (error) {
    console.error("[Migration 003] Error:", error);
    throw error;
  } finally {
    client.release();
  }
}
  1. Add it to the runMigrations() function:
export async function runMigrations(pool: Pool): Promise<void> {
  await migration001_createTables(pool);
  await migration002_createIndexes(pool);
  await migration003_addNewFeature(pool);  // Add here
}

Idempotency Best Practices

All migrations must be idempotent (safe to run multiple times):

✅ Good (idempotent):

CREATE TABLE IF NOT EXISTS my_table (...);
CREATE INDEX IF NOT EXISTS idx_name ON table (column);
ALTER TABLE table ADD COLUMN IF NOT EXISTS new_col TEXT;

❌ Bad (not idempotent):

CREATE TABLE my_table (...);  -- Fails if table exists
ALTER TABLE table ADD COLUMN new_col TEXT;  -- Fails if column exists

Architecture Details

Event Sourcing

All state changes are stored as immutable events:

// Events are never updated or deleted
await backend.appendEvent(workflowSlug, runId, {
  category: "step",
  type: "StepStarted",
  eventId: "1234567890123456",
  timestampUs: 1234567890123456,
  stepId: "my-step",
  workerId: "worker-1",
  // ... other event-specific fields
});

// Current state is computed by projecting events
const events = await backend.loadEvents(workflowSlug, runId);
const currentState = projectRunStateFromEvents(events, workflowSlug);

Events-as-Queue Pattern

There is no separate "queue" table. Queue state is derived by projecting the RunSubmitted event along with workflow events:

// Submitting creates a RunSubmitted event
const runId = await backend.submitRun({
  workflowSlug: "my-workflow",
  input: { data: "..." },
});

// Listing runs projects events to compute status
const runs = await backend.listRuns({
  statuses: ["pending", "running"],
});

Atomic Step Claiming

Step claiming uses PostgreSQL's SELECT FOR UPDATE SKIP LOCKED for race-safe concurrency:

// Multiple workers can safely claim different steps
const claimed = await backend.claimScheduledStep(
  workflowSlug,
  runId,
  stepId,
  "worker-1"
);

// Only one worker succeeds per step
console.log(claimed); // true or false

Implementation (simplified):

BEGIN;

-- Acquire lock (SKIP LOCKED prevents blocking)
SELECT * FROM step_events
WHERE workflow_slug = $1 AND run_id = $2 AND step_id = $3
ORDER BY timestamp_us DESC
LIMIT 1
FOR UPDATE SKIP LOCKED;

-- Verify step is in scheduled state
-- Write StepStarted event

COMMIT;

Time Precision

All timestamps use microsecond precision (timestampUs fields) for correct event ordering in concurrent execution:

// Example: 1762916097401523 (microseconds since epoch)
const timestamp = getMicrosecondTimestamp();

Testing

Integration Tests

Integration tests require a running PostgreSQL database:

# Set test database URL
export POSTGRES_TEST_URL="postgres://postgres:postgres@localhost:5432/workflow_test"

# Run tests
bun test

# Run specific test file
bun test tests/integration/postgres-backend.integration.test.ts

Docker PostgreSQL for Testing

# Start PostgreSQL in Docker
docker run -d \
  --name postgres-test \
  -e POSTGRES_PASSWORD=postgres \
  -e POSTGRES_DB=workflow_test \
  -p 5432:5432 \
  postgres:16

# Run tests
export POSTGRES_TEST_URL="postgres://postgres:postgres@localhost:5432/workflow_test"
bun test

# Cleanup
docker stop postgres-test
docker rm postgres-test

Performance Considerations

Indexes

The backend creates indexes optimized for common query patterns:

  • Event lookups: (workflow_slug, run_id, timestamp_us)
  • Step claiming: (workflow_slug, run_id, step_id, type, timestamp_us)
  • Registry queries: (workflow_slug)

Connection Pooling

The backend uses pg.Pool for efficient connection management. Configure pool size via connection string:

postgres://user:pass@host/db?max=20&min=5&idle_timeout=10000

JSONB Storage

JSONB columns allow flexible schema evolution but have storage/query trade-offs:

  • ✅ Flexible schema changes without migrations
  • ✅ Can index specific JSONB fields if needed
  • ⚠️ Slightly larger storage than normalized tables
  • ⚠️ Complex queries on nested JSONB can be slower

For high-volume production use, consider adding indexes on frequently-queried JSONB fields:

CREATE INDEX idx_event_type ON workflow_events ((data->>'type'));

Switching Database Libraries

All pg usage is isolated in src/db.ts, making it easy to swap to another library:

  1. Install new library (e.g., postgres, kysely, drizzle)
  2. Update src/db.ts to use new library's API
  3. Keep function signatures the same
  4. Update src/migrations.ts if needed

The PostgresBackend class in src/index.ts doesn't need changes.

Comparison to FileSystemBackend

FeatureFileSystemBackendPostgresBackend
StorageLocal filesystemPostgreSQL database
ConcurrencyFile locksDatabase transactions
ScalabilitySingle machineMulti-machine clusters
PersistenceLocal .runs/ directoryRemote database
SetupZero configRequires PostgreSQL server
Best forDevelopment, single-machineProduction, distributed workers

Troubleshooting

Connection Issues

// Error: connection refused
// Fix: Check PostgreSQL is running and connection string is correct

// Error: authentication failed
// Fix: Verify username/password in connection string

// Error: database does not exist
// Fix: Create database first: createdb workflow_db

Migration Failures

// Error: relation already exists
// Fix: Ensure migrations use IF NOT EXISTS

// Error: permission denied
// Fix: Grant required permissions to database user

Performance Issues

# Check for missing indexes
EXPLAIN ANALYZE SELECT ...;

# Monitor connection pool
SELECT * FROM pg_stat_activity WHERE datname = 'workflow_db';

# Check table sizes
SELECT pg_size_pretty(pg_total_relation_size('step_events'));

See Also

版本与发布记录

@cascade-flow/backend-postgres 共发布 31 个版本,首次发布于 2025/11/20;过去 12 个月发布了 31 个版本。

版本发布日期距今
0.2.31latest2026/08/122 个月前
0.2.302026/08/122 个月前
0.2.292026/08/122 个月前
0.2.282026/02/238 个月前
0.2.272026/02/238 个月前
0.2.262026/02/208 个月前

关于 @cascade-flow/backend-postgres 的常见问题

@cascade-flow/backend-postgres 是什么?

PostgreSQL backend implementation for CascadeFlow workflow orchestrator

如何安装 @cascade-flow/backend-postgres?

在项目目录运行 npm install @cascade-flow/backend-postgres,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:@cascade-flow/backend-interface(^0.2.17)、typescript(^5)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@cascade-flow/backend-postgres 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@cascade-flow/backend-postgres 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@cascade-flow/backend-postgres 可以商用吗?

@cascade-flow/backend-postgres 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@cascade-flow/backend-postgres 的下载量有多少?

根据 npm 官方下载统计,@cascade-flow/backend-postgres 在 2026/10/09 – 2026/11/07 这 30 天内被下载 2,787 次,日均约 93 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@cascade-flow/backend-postgres 还在维护吗?

最新版本 0.2.31 发布于 2026/08/12(2 个月前);至今共发布 31 个版本,其中过去 12 个月发布了 31 个;首次发布于 2025/11/20。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。