@siglo/scheduler Adapter Guide
@siglo/scheduler provides background timer polling, distributed lock claiming, and cloud-native webhook dispatchers for Siglo durable workflows. It ensures that time-based durables (wait('24h'), waitForEvent('CONFIRM', { timeout: '7d' })) resume on time across container restarts, node crashes, and multi-worker Kubernetes clusters.
1. Overview & Architecture
Siglo workflows operate in-process without requiring a central coordinator daemon. However, when workflows suspend with long-running timers in durable environments (PostgreSQL, Redis), a mechanism is required to wake sleeping workflows when their expiration timestamps elapse.@siglo/scheduler solves this with three complementary dispatchers:
2. Installation & Requirements
Install@siglo/scheduler alongside @siglo/core and @siglo/postgres:
@siglo/core:^1.0.0@siglo/postgres:^1.0.0(forPostgresWaitPoller)pg:^8.0.0(or compatible PostgreSQL client with a.query()method)
3. Database Schema & Row-Level Locking
PostgresWaitPoller relies on the siglo_wait_records table. It provides an automated DDL initialization method: poller.ensureTable(). Prefer running migrations or poller.ensureTable() over manual table creation.
DDL Schema
The Advisory Resumption Query & Session Locking
To coordinate execution across concurrent worker pods,PostgresWaitPoller executes an advisory selection query to find due timers and accepted events:
- Advisory Selection & Session Locks: Selection is advisory. The storage adapter serializes execution using a database-session lock (
withWorkflowLock), claiming and completing wait records atomically. If a worker pod crashes mid-resumption, PostgreSQL automatically releases the session lock upon connection disconnect. - Accepted-Event Recovery: Records with
status = 'RESUMING'and a non-nullresume_payloadrepresent accepted resume events that survived process termination and will be picked up by surviving workers. - Lock Deprecation:
lockTimeoutMsis deprecated forPostgresStorageAdapteras lock lifecycles are tied to database sessions rather than manual timeout windows.
4. PostgresWaitPoller Usage
Starting the Background Poller in Worker Pods
5. MemoryWaitPoller (In-Memory Development)
For local development, integration tests, or single-process prototypes where a PostgreSQL cluster is unnecessary, use MemoryWaitPoller. It automatically inspects in-memory waiting workflows tracked by defaultDurableManager:
6. createSchedulerWebhookHandler (Cloud-Native Schedulers)
In serverless environments (AWS Lambda, Google Cloud Run) where long-running daemon processes cannot run, use external cloud scheduling services:
- AWS EventBridge Scheduler: Schedules one-time HTTP targets.
- Google Cloud Tasks / Cloud Scheduler: Dispatches webhook calls with specific timestamps.
- Cloudflare Queues / Scheduled Workers: Dispatches HTTP POST requests.
Setting Up the Webhook Dispatcher
Ingress Request Specification
The dispatcher expects aPOST request with JSON body:
HTTP Response Codes
7. Operational Best Practices
- Size
lockTimeoutMsAppropriately: SetlockTimeoutMsto at least the maximum expected runtime of the steps following the wait node (default: 60,000ms). If steps perform external HTTP calls with 15s timeouts, 60s prevents premature lock reclamation by peer workers. - Synchronize Clocks with NTP: Ensure all Kubernetes nodes and database instances use NTP synchronization (e.g., Amazon Time Sync Service, Google NTP) to prevent premature lock reclamation.
- Graceful Worker Shutdown:
Always
await poller.stop()in your processSIGTERMhandler. The method waits for any active in-flight query loop to finish before resolving. - Partition / Archive Completed Records:
Periodically archive records where
status IN ('RESUMED', 'FAILED')to keep theidx_siglo_wait_records_dueindex lean and ensure fast query execution.
8. API Reference
PostgresWaitPoller
constructor(options: PostgresPollerOptions)ensureTable(): Promise<void>pollOnce(): Promise<PollerReport>start(): voidstop(): Promise<void>isRunning(): boolean
MemoryWaitPoller
constructor(options: MemoryPollerOptions)pollOnce(): Promise<PollerReport>start(): voidstop(): Promise<void>isRunning(): boolean
createSchedulerWebhookHandler
createSchedulerWebhookHandler(options: CloudSchedulerWebhookOptions): WebhookHandler
