Skip to main content
cl-pipelines is infrastructure-agnostic by design. It never talks to a database or job queue directly — instead, it calls two narrow interfaces that you implement for your stack. This means the same pipeline logic works with Convex, PostgreSQL, Redis, or any other backend without changing a line of phase code.

StorageAdapter

The StorageAdapter persists job status, checkpoint state, and log entries. You implement five methods:
(jobId: string) => Promise<JobRecord | null>
Returns the current job record, or null if the job doesn’t exist yet. The library calls this at the start of every advancePhase to read the current checkpoint.
(jobId: string, status: PipelineStatus, error?: string) => Promise<void>
Updates the job’s status. When called with an error (e.g. "error" status), the error string should be persisted. When called without an error (e.g. "complete"), any previously stored error should be cleared.
(jobId: string, checkpoint: Checkpoint<TState> | null) => Promise<void>
Writes or clears the checkpoint. Called with null when the pipeline completes.
(jobId: string, entry: LogEntry) => Promise<void>
Appends a single log entry to the job’s log. Called each time a phase calls ctx.log().
(jobId: string) => Promise<void>
Clears all log entries for a job. The library exposes this method for consumers to call (for example, before a full retry), but does not call it automatically.

SchedulerAdapter

The SchedulerAdapter triggers advancePhase after each phase completes. It has one method:
(jobId: string, delayMs: number) => Promise<void>
Enqueue a call to advancePhase(jobId) to run after delayMs milliseconds. The library always calls this with delayMs: 0 in v0.1. Your implementation should delegate to your scheduler’s equivalent of “run as soon as possible.”

In-memory adapters

For local development and unit tests, cl-pipelines ships two fully-functional in-memory adapters:
scheduler._bind() and storage._inspect() are test-only hooks (prefixed with _ to signal this). In production, your SchedulerAdapter.scheduleAdvance delegates to a real durable scheduler, and you read job state through your StorageAdapter.getJob.

Implementing your own adapter

To implement a StorageAdapter for a custom backend, create a plain object (or class) that satisfies the interface:
If you’re using Convex, skip the custom implementation — use the pre-built createConvexStorageAdapter and createConvexSchedulerAdapter from @claritylabs/cl-pipelines/convex. See the Convex adapter guide.