Documentation
¶
Overview ¶
Package duro is a durable dataflow DSL: reactive pipelines whose every stage runs as a checkpointed DBOS step. Pipelines are powered by samber/ro internally, but the public API only accepts duro stages, making it a compile error to insert a raw ro operator that could break durability.
A workflow body is a pipe of typed stages:
func OrderWorkflow(ctx duro.Context, o Order) (Confirmation, error) {
return duro.Run(ctx, o, duro.Pipe4(
duro.Step("validate", validateOrder),
duro.Step("reserve", reserveInventory),
duro.Step("charge", chargePayment, duro.WithMaxRetries(3)),
duro.Step("notify", sendConfirmation),
))
}
Durability relies on DBOS replay determinism: on recovery the workflow function re-executes and the Nth step call must be the same logical operation as in the original run. duro enforces this with three layers:
- Compile time: PipeN only accepts Stage values, which can only be built by this package's constructors. Concurrent or time-based ro operators cannot be expressed. Sources cannot be swapped either — Run feeds the workflow input through ro.Of internally.
- Construction time: Run checkpoints the pipeline's shape (ordered stage kinds and names) as a hidden first step named "duro.shape". If a replay constructs a different shape — non-deterministic pipeline construction, changed code — Run fails immediately instead of letting stages read misaligned checkpoints.
- Execution time: every stage asserts it runs on the goroutine the pipeline was subscribed on, failing fast if an operator smuggled in concurrency; and once any stage fails, a shared abort flag prevents items behind the failure from executing further stages (fail-fast, like sequential workflow code).
Escape hatches: Pure wraps a deterministic, side-effect-free transform that is NOT checkpointed (it re-executes on every replay — it must be pure), and UnsafeOperator admits an arbitrary ro operator with no safety guarantees beyond the runtime guards. Both participate in the shape fingerprint.
For parallelism, use FanOut (child workflows on a DBOS queue — bounded, distributed, per-child durability) or Parallel (concurrent steps in-process via dbos.Go — lightweight, no queue). Both preserve replay determinism: work is spawned and awaited in stream order on the workflow goroutine. On failure both drain surviving siblings by default; WithCancelSiblings (FanOut) and WithCancelSiblingSteps (Parallel) opt a stage into cancelling them instead, while still failing with the original error.
The rest of DBOS's workflow toolkit is available as stages: Delay (durable sleep), Send/Recv (durable mailbox messaging — external signals and human-in-the-loop pauses), SetEvent/GetEvent (progress events published and read durably), and ToStream/FromStream (durable streams written and drained durably). Messaging goes through typed channels — Topic, Event, Stream — declared once and referenced by both sides, so keys and payload types cannot drift; declare a channel with Portable() to serialize its payloads in DBOS's cross-language format.
Control flow is durable too: Branch and Switch route each item through embedded pipelines by a checkpointed decision, Loop repeats a pipeline until a checkpointed verdict says done, Rescue intercepts an embedded pipeline's failure with a checkpointed handler that swallows or rethrows it, Sub embeds a pipeline as one named stage, Via runs one for its effects and passes the original item through, and Collect folds the stream into a slice. Embedded pipelines are part of the shape fingerprint.
Pipelines are also registrable as first-class workflows: Register names a pipeline as a DBOS workflow, RegisterScheduled runs one on a cron schedule (typed Pipeline[time.Time, R]), RegisterDebounced collapses bursts of triggers into a single run, and RegisterWorkflow covers hand-written workflow functions. A pipeline another process enqueues is declared as a Job — its name and both types in one value — and registered with RegisterJob, so the name and types cannot drift between the binary that runs it and the binary that enqueues it. Runs are tracked by workflow ID from any process: Status/StatusAll reconcile a persisted ID against the engine, Attach reconnects to a live handle, and ForkFromStage restarts a completed or failed run from a named stage — optionally onto a different application version.
Example ¶
Example shows a DBOS workflow written as a durable pipeline: each stage runs as a checkpointed DBOS step, so a crashed workflow resumes after the last completed stage. Register the workflow with dbos.RegisterWorkflow and start it with dbos.RunWorkflow as usual.
package main
import (
"context"
"github.com/dbos-inc/dbos-transact-golang/dbos"
"github.com/lemonberrylabs/duro"
)
func main() {
type Order struct {
ID string
AmountCents int
}
type Receipt struct {
OrderID string
PaymentID string
}
chargeOrder := func(ctx dbos.DBOSContext, o Order) (Receipt, error) {
return duro.Run(ctx, o, duro.Pipe2(
duro.Step("charge", func(_ context.Context, o Order) (string, error) {
return "pay-" + o.ID, nil // call your payment provider here
}, duro.WithMaxRetries(3)),
duro.Step("receipt", func(_ context.Context, paymentID string) (Receipt, error) {
return Receipt{OrderID: o.ID, PaymentID: paymentID}, nil
}),
))
}
_ = chargeOrder
}
Output:
Index ¶
- Constants
- Variables
- func Cancel(ctx Context, workflowID string) error
- func RegisterQueues(ctx Context, queues ...Queue) error
- func Run[P, R any](ctx Context, in P, p Pipeline[P, R]) (R, error)
- func RunAll[P, R any](ctx Context, in P, p Pipeline[P, R]) ([]R, error)
- type App
- type Case
- type ChannelOption
- type ChildOption
- func WithCancelSiblings() ChildOption
- func WithCancelWatchInterval(d time.Duration) ChildOption
- func WithChildAppVersion(version string) ChildOption
- func WithChildAssumedRole(role string) ChildOption
- func WithChildAuthenticatedRoles(roles ...string) ChildOption
- func WithChildAuthenticatedUser(user string) ChildOption
- func WithChildDeduplicationID[T any](fn func(in T) string) ChildOption
- func WithChildDeduplicationPolicy(policy DeduplicationPolicy) ChildOption
- func WithChildDelay(d time.Duration) ChildOption
- func WithChildID[T any](fn func(in T) string) ChildOption
- func WithChildPartitionKey[T any](fn func(in T) string) ChildOption
- func WithChildPriority(priority uint) ChildOption
- func WithChildTimeout(d time.Duration) ChildOption
- func WithPortableChildren() ChildOption
- type Client
- func (c *Client) Cancel(workflowID string) error
- func (c *Client) ListRuns(opts ...ListOption) ([]RunStatus, error)
- func (c *Client) Resume(workflowID string) error
- func (c *Client) Shutdown(timeout time.Duration)
- func (c *Client) Status(workflowID string) (RunStatus, error)
- func (c *Client) StatusAll(workflowIDs ...string) ([]RunStatus, error)
- func (c *Client) Steps(workflowID string) ([]StepStatus, error)
- func (c *Client) WithContext(ctx context.Context) *Client
- type ClientConfig
- type Config
- type Context
- type Debouncer
- type DeduplicationPolicy
- type EnqueueOption
- type Event
- type Fork
- type Handle
- func Attach[R any](ctx Context, workflowID string) (Handle[R], error)
- func AttachJob[P, R any](ctx Context, job Job[P, R], workflowID string) (Handle[R], error)
- func Enqueue[P, R any](c *Client, queue Queue, job Job[P, R], input P, opts ...EnqueueOption) (Handle[R], error)
- func ForkFromStage[R any](ctx Context, f Fork) (Handle[R], error)
- type Job
- type ListOption
- func WithCreatedAfter(t time.Time) ListOption
- func WithCreatedBefore(t time.Time) ListOption
- func WithIDs(ids ...string) ListOption
- func WithInput() ListOption
- func WithLimit(n int) ListOption
- func WithNames(names ...string) ListOption
- func WithNewestFirst() ListOption
- func WithOffset(n int) ListOption
- func WithQueue(name string) ListOption
- func WithStates(states ...State) ListOption
- type Option
- type Pipeline
- func Pipe1[A, B any](s1 Stage[A, B]) Pipeline[A, B]
- func Pipe2[A, B, C any](s1 Stage[A, B], s2 Stage[B, C]) Pipeline[A, C]
- func Pipe3[A, B, C, D any](s1 Stage[A, B], s2 Stage[B, C], s3 Stage[C, D]) Pipeline[A, D]
- func Pipe4[A, B, C, D, E any](s1 Stage[A, B], s2 Stage[B, C], s3 Stage[C, D], s4 Stage[D, E]) Pipeline[A, E]
- func Pipe5[A, B, C, D, E, F any](s1 Stage[A, B], s2 Stage[B, C], s3 Stage[C, D], s4 Stage[D, E], s5 Stage[E, F]) Pipeline[A, F]
- func Pipe6[A, B, C, D, E, F, G any](s1 Stage[A, B], s2 Stage[B, C], s3 Stage[C, D], s4 Stage[D, E], s5 Stage[E, F], ...) Pipeline[A, G]
- func Pipe7[A, B, C, D, E, F, G, H any](s1 Stage[A, B], s2 Stage[B, C], s3 Stage[C, D], s4 Stage[D, E], s5 Stage[E, F], ...) Pipeline[A, H]
- func Pipe8[A, B, C, D, E, F, G, H, I any](s1 Stage[A, B], s2 Stage[B, C], s3 Stage[C, D], s4 Stage[D, E], s5 Stage[E, F], ...) Pipeline[A, I]
- type PipelineWorkflow
- func Register[P, R any](ctx Context, name string, p Pipeline[P, R], opts ...WorkflowRegistrationOption) *PipelineWorkflow[P, R]
- func RegisterJob[P, R any](ctx Context, job Job[P, R], p Pipeline[P, R], ...) *PipelineWorkflow[P, R]
- func RegisterScheduled[R any](ctx Context, name, cronSchedule string, p Pipeline[time.Time, R], ...) *PipelineWorkflow[time.Time, R]
- type Queue
- type QueueOption
- type RegisteredWorkflow
- type RunStatus
- type Stage
- func Branch[T, R any](name string, pred func(ctx context.Context, in T) (bool, error), ...) Stage[T, R]
- func Collect[T any](name string, opts ...StepOption) Stage[T, []T]
- func Delay[T any](name string, d time.Duration) Stage[T, T]
- func Expand[T, R any](name string, fn func(ctx context.Context, in T) ([]R, error), ...) Stage[T, R]
- func FanOut[T, R any](name string, queue Queue, wf WorkflowRef[T, R], opts ...ChildOption) Stage[T, R]
- func Filter[T any](name string, pred func(ctx context.Context, in T) (bool, error), ...) Stage[T, T]
- func FromStream[T, V any](name string, stream Stream[V], fn func(in T) (workflowID string), ...) Stage[T, V]
- func GetEvent[T, V any](name string, event Event[V], fn func(in T) (workflowID string), ...) Stage[T, V]
- func Loop[T any](name string, body Pipeline[T, T], ...) Stage[T, T]
- func Parallel[T, R any](name string, maxConcurrent int, fn func(ctx context.Context, in T) (R, error), ...) Stage[T, R]
- func Pure[T, R any](name string, fn func(in T) R) Stage[T, R]
- func Recv[T, M any](name string, topic Topic[M], timeout time.Duration) Stage[T, M]
- func Reduce[T, A any](name string, fn func(ctx context.Context, acc A, in T) (A, error), seed A, ...) Stage[T, A]
- func Rescue[T, R any](name string, p Pipeline[T, R], ...) Stage[T, R]
- func Send[T, M any](name string, topic Topic[M], ...) Stage[T, T]
- func SetEvent[T, V any](name string, event Event[V], fn func(in T) V) Stage[T, T]
- func Step[T, R any](name string, fn func(ctx context.Context, in T) (R, error), opts ...StepOption) Stage[T, R]
- func Sub[T, R any](name string, p Pipeline[T, R]) Stage[T, R]
- func Switch[T, R any](name string, route func(ctx context.Context, in T) (string, error), ...) Stage[T, R]
- func Tap[T any](name string, fn func(ctx context.Context, in T) error, opts ...StepOption) Stage[T, T]
- func ToStream[T any](name string, stream Stream[T]) Stage[T, T]
- func UnsafeOperator[T, R any](name string, op func(ro.Observable[T]) ro.Observable[R]) Stage[T, R]
- func Via[T, R any](name string, p Pipeline[T, R]) Stage[T, T]
- type StaleRunInfo
- type State
- type StepOption
- func WithBackoffFactor(factor float64) StepOption
- func WithBaseInterval(d time.Duration) StepOption
- func WithCancelSiblingSteps() StepOption
- func WithMaxInterval(d time.Duration) StepOption
- func WithMaxIterations(n int) StepOption
- func WithMaxRetries(n int) StepOption
- func WithRetryPredicate(pred func(error) bool) StepOption
- func WithTimeout(d time.Duration) StepOption
- type StepStatus
- type Stream
- type Topic
- type WorkerPoolOption
- type WorkflowFunc
- type WorkflowOption
- type WorkflowRef
- type WorkflowRegistrationOption
Examples ¶
Constants ¶
const ( // DeduplicationReject (the default) fails the enqueue of a child whose // deduplication ID is already held by an active child. DeduplicationReject = dbos.DeduplicationPolicyReject // DeduplicationReturnExisting returns the existing child's handle // instead, so both items observe the first child's result. DeduplicationReturnExisting = dbos.DeduplicationPolicyReturnExisting )
const ( // CancelWatcherName is the registered workflow name of duro's cancellation // watcher. Watcher runs are ordinary rows in the system database — one per // cancel-enabled FanOut batch — so they appear in ListRuns alongside // application runs; filter them in or out with WithNames(CancelWatcherName). // A watcher stuck non-terminal is worth an operator's attention. The name // is a durable identity: it never changes. CancelWatcherName = "duro.cancel-watcher" // CancelWatchQueueName is the duro-owned queue watcher runs execute on // (WithQueue(CancelWatchQueueName) lists them by queue). Also a durable // identity. CancelWatchQueueName = "duro.cancel-watch" )
The cancellation watcher: a duro-internal durable workflow enqueued alongside every cancel-enabled fan-out batch. It watches the same children the parent's await step watches and cancels on failure, redundantly and idempotently — but because it is a queued workflow, any executor can dequeue or recover it. That is what keeps cancellation from depending on the parent's executor staying alive: if the parent dies right after a child fails, the watcher still cancels the survivors. duro.New registers the watcher workflow and its queue on every app; both names are durable identities and must never change.
const DefaultReadTimeout = 30 * time.Second
DefaultReadTimeout bounds a Client's reads and remediations when ClientConfig.ReadTimeout is zero.
const ShapeStepName = "duro.shape"
ShapeStepName is the name of the hidden bookkeeping step Run records as the pipeline's first checkpoint. It holds the pipeline's shape fingerprint, which Run compares on replay to fail fast on non-deterministic pipeline construction.
const Unbounded = -1
Unbounded lifts Parallel's concurrency cap: every item's step is launched as soon as it arrives. Prefer a real bound — an unbounded Parallel over a large stream launches one in-process step per item with nothing holding it back. It exists so that removing the cap is something a caller states outright rather than something a zero-valued variable does by accident.
Variables ¶
var ErrAborted = errors.New("duro: pipeline aborted by an earlier stage failure")
ErrAborted marks stage executions skipped because an earlier stage already failed. It never surfaces from Run: the first failure is the pipeline's error, and ErrAborted only travels through already-terminated downstream observers.
var ErrMaxIterations = errors.New("duro: loop exceeded its maximum iterations")
ErrMaxIterations is returned by a Loop stage configured with WithMaxIterations whose predicate did not report done within that many iterations. Without the option a Loop is unbounded; see WithMaxIterations.
var ErrMultipleValues = errors.New("duro: pipeline emitted more than one value")
ErrMultipleValues is returned by Run when the pipeline emits more than one value. Run yields a single result, so more than one emission is ambiguous rather than something Run could silently pick from: end the pipeline with Reduce or Collect to fold the stream, or call RunAll to receive every value.
var ErrNoValue = errors.New("duro: pipeline completed without emitting a value")
ErrNoValue is returned by Run when the pipeline completes without emitting any value (for example, when a Filter stage drops every item).
var ErrRunActive = errors.New("duro: run is still active")
ErrRunActive is returned by Resume when the run is still live — pending, enqueued, or delayed. Resuming a live run would re-enqueue it while an executor may still be running it, a double execution; Cancel it first.
var ErrRunInFlight = errors.New("duro: run was cancelled mid-execution and its in-flight stage may still be running")
ErrRunInFlight is returned by Resume for a run that was cancelled after it had started executing. Cancellation lands at the next stage boundary, so the stage in flight keeps running on its executor until it finishes — and nothing DBOS records says when that is. Resuming under the same ID would re-enqueue the run while that stage may still be running, and would let the old executor carry on past it: a double execution. Re-run such a run under a new ID with ForkFromStage, which leaves this one cancelled.
var ErrRunNotFound = errors.New("duro: run not found")
ErrRunNotFound is returned by Status, Attach, Steps, Cancel, and Resume for an unknown workflow ID.
var ErrRunTerminal = errors.New("duro: run is already in a final state")
ErrRunTerminal is returned by Cancel and Resume when the run has already reached a final state the operation cannot change: any terminal state for Cancel; success or error for Resume, which only revives cancelled and retries-exceeded runs. Re-run a finished pipeline with ForkFromStage.
Functions ¶
func Cancel ¶ added in v0.9.0
Cancel stops a live run: an enqueued or delayed run never starts, and a pending one stops at its next stage boundary (the stage in flight finishes — keep stages idempotent). The run's queue is cleared and its state becomes StateCancelled. Runs already in a final state return ErrRunTerminal, so a cancel that changed nothing is never mistaken for one that did; unknown IDs return ErrRunNotFound.
Client.Cancel is the same operation from an enqueue-only process.
func RegisterQueues ¶ added in v0.2.0
RegisterQueues registers declared queues with DBOS. Register does this automatically for every queue its pipeline references; call RegisterQueues yourself only for pipelines run directly with Run/RunAll inside hand-written workflows.
func Run ¶
Run executes the pipeline durably inside a DBOS workflow, feeding it the input value and blocking until completion. It returns the pipeline's single emitted value or the first stage error. Call it as the body of a registered DBOS workflow function.
Run is for pipelines that produce exactly one result. A pipeline that emits nothing fails with ErrNoValue, and one that emits more than once fails with ErrMultipleValues rather than having Run pick a value on the caller's behalf — a stage like Expand or FanOut left unfolded would otherwise discard every result but one, silently and with nothing to notice. Fold the stream with Reduce or Collect, or use RunAll to receive every value.
Types ¶
type App ¶ added in v0.2.0
type App struct {
dbos.DBOSContext
// contains filtered or unexported fields
}
App owns the DBOS lifecycle so applications never touch it directly:
app, err := duro.New(ctx, duro.Config{Name: "orders", DatabaseURL: url})
wf := duro.Register(app, "invoice", invoicePipeline) // register everything...
err = app.Launch() // ...then launch
defer app.Shutdown(5 * time.Second)
handle, err := wf.Start(app, batch)
Launch also checks for stranded runs: in-flight workflows recorded under names no longer registered (a renamed pipeline) are reported as warnings instead of silently never recovering.
*App satisfies Context, so it can be passed wherever duro expects one. Calling raw dbos package functions directly is different: several inspect the concrete context type, so hand them Context() rather than the App itself.
func New ¶ added in v0.2.0
New initializes the application. Register pipelines and queues after New and before Launch. Options enable optional subsystems — see WithWorkerPool, WithRetention, and WithStaleRunWarning.
func (*App) Context ¶ added in v0.2.0
Context returns the underlying DBOS context — for calling raw dbos package functions directly. Everything in duro accepts the App itself.
func (*App) Launch ¶ added in v0.2.0
Launch starts DBOS: workflow recovery, queue runners, and schedulers. Call it after all registrations. It then warns about stranded runs — see App.
In worker-pool mode Launch lands the first heartbeat synchronously before starting queue runners (an executor must never be dequeuing while observably dead), then starts the heartbeat and sweeper goroutines.
func (*App) Resume ¶ added in v0.9.0
Resume revives a run under its original ID: it is re-enqueued and picked up by an executor on its application version, where completed stages replay from their checkpoints and execution continues from the first stage that never finished. The recovery-attempt count starts over. A run that still records its queue returns to it; because DBOS clears a run's queue when it cancels or dead-letters it, in practice the resumed run executes on DBOS's internal queue, outside any concurrency or rate limit of the queue it was originally enqueued on.
Exactly two kinds of run resume, because they are the ones no executor can still be executing: a run that exceeded its recovery attempts, and a run cancelled before it ever started (while enqueued or delayed). A run cancelled after it started returns ErrRunInFlight — its in-flight stage may still be running and DBOS records nothing that says when it stops — and is re-run under a new ID with ForkFromStage instead. Success and error runs are finished and return ErrRunTerminal rather than a silent no-op; fork those too. Pending, enqueued, and delayed runs return ErrRunActive: Cancel first. Unknown IDs return ErrRunNotFound. The state check is part of the transition itself — one guarded UPDATE — so concurrent Resumes cannot both apply.
The call is bounded by ctx's deadline, or by DefaultReadTimeout when it has none, and ends early if ctx is cancelled. It is a method on App rather than a function of a Context because the transition is duro's own SQL statement, run on the App's database connection. Client.Resume is the same operation from an enqueue-only process.
func (*App) Shutdown ¶ added in v0.2.0
Shutdown stops DBOS, waiting up to timeout for in-flight work to settle.
In worker-pool mode the ordering matters: the sweeper stops first, the heartbeat keeps beating through DBOS's drain (so runs still executing here stay fresh and un-takeable however long the drain runs), and only once DBOS has stopped is the lease tombstoned — so any runs that could not drain are adopted by a survivor on its next sweep with no stale wait. Follow Shutdown promptly with process exit.
timeout bounds the drain. The two steps around it are bounded separately and take milliseconds against a healthy database: stopping maintenance cancels whatever query it has in flight rather than waiting it out, and the tombstone is a single primary-key UPDATE bounded by a few seconds of its own. Budget timeout plus a small constant for the whole call — never let timeout consume the entire SIGTERM grace period, or the tombstone is the part that is lost.
Calling Shutdown more than once is safe; the extra calls do nothing.
type Case ¶ added in v0.4.0
type Case[T, R any] struct { // contains filtered or unexported fields }
Case pairs a route key with the pipeline that handles it; see Switch.
type ChannelOption ¶ added in v0.2.0
type ChannelOption func(*channelConfig)
ChannelOption configures a declared channel.
func Portable ¶ added in v0.2.0
func Portable() ChannelOption
Portable makes every payload written through the channel serialize in DBOS's cross-language portable JSON format, so non-Go DBOS applications (Python, TypeScript) can consume it. Readers need nothing special — DBOS decodes by each value's recorded serialization.
type ChildOption ¶ added in v0.2.0
type ChildOption func(*childConfig)
ChildOption configures the child workflows a FanOut stage enqueues. Policy options (priority, delay, timeout, auth, serialization, version) apply uniformly to every child; identity options (workflow ID, deduplication ID, partition key) derive a per-child value from the stream item. Item-derived options are typed by the stage's item type — FanOut panics at construction time if they were built for a different type.
func WithCancelSiblings ¶ added in v0.7.0
func WithCancelSiblings() ChildOption
WithCancelSiblings makes the first failed child cancel every sibling that is not yet in a terminal state — running (PENDING) and never-dequeued (ENQUEUED) children alike — instead of letting them run to completion in the background. Failure detection is order-independent: the stage watches all children while awaiting, so an early failure in a long batch cancels promptly rather than after every child ahead of it finishes. The stage still fails with the triggering child's error; cancelled siblings surface as CANCELLED but never mask it, on the live run and on recovery replay alike, so a Rescue around the stage always sees the original failure.
Choose it when sibling work is worthless once the batch's outcome is decided (cost-bearing generation, paid API calls); leave the default when every completion has value on its own (cleanup or deletion fan-outs, where more finished children on the failure path is strictly better).
Semantics that differ from the default, beyond cancellation itself:
- The stage's checkpoint layout changes: results are awaited and recorded as one step named after the stage, instead of one DBOS.getResult step per child. The option therefore participates in the pipeline's shape fingerprint — toggling it while runs are in flight trips the shape guard on recovery instead of misreading checkpoints. Treat toggling like any other pipeline edit.
- On failure the fan-out fails as a unit: nothing is emitted downstream. Without the option, results before the first failure in input order are emitted before the stage fails.
- Detection begins once the stage starts awaiting (all children enqueued). Enqueueing itself never blocks on child completion.
Cancellation does not cascade: DBOS cancels exactly the sibling children, and a cancelled child stops at the start of its next step. Workflows the cancelled child had itself started — grandchildren of this stage, including a nested FanOut's children — keep running to completion. To bound that cost, give the child's own fan-outs WithCancelSiblings (covers grandchild failures) and WithChildTimeout (bounds grandchild lifetime); cancelling a whole tree from outside remains dbos.CancelWorkflows over the grandchild IDs.
Cancellation is idempotent and re-issued on recovery: a parent that crashes mid-cancellation re-observes the failure when the stage resumes and cancels whatever is still live.
Nor does cancellation depend on this process surviving the await: alongside the batch, the stage enqueues duro's cancellation watcher — an internal durable workflow (registered by New as CancelWatcherName on the internal CancelWatchQueueName queue; both names are durable identities) that watches the same children and cancels redundantly. Any executor can dequeue or recover the watcher, so a failure is acted on even when the parent's executor dies mid-await. The stage requires the watcher to be registered — apps built with New always have it; a hand-rolled DBOS context without it fails the stage immediately with a clear error rather than silently weakening the guarantee.
func WithCancelWatchInterval ¶ added in v0.7.0
func WithCancelWatchInterval(d time.Duration) ChildOption
WithCancelWatchInterval tunes how often the cancellation watcher polls child statuses (default 5s). The watcher is the backstop for when the process awaiting the fan-out dies — while that process lives, the stage's own await detects failures within 250ms regardless of this setting, and after cancellation is issued the watcher re-checks immediately rather than waiting out an interval. Lower it when orphaned spend must be cut faster after an executor loss; raise it to shave database load on very long-running batches. Recorded in the watcher's durable input, so a deploy that changes it affects new batches only.
It requires WithCancelSiblings and a positive duration — FanOut panics at construction time otherwise.
func WithChildAppVersion ¶ added in v0.2.0
func WithChildAppVersion(version string) ChildOption
WithChildAppVersion pins children to a specific application version, overriding the parent's. This affects which executors recover them.
func WithChildAssumedRole ¶ added in v0.2.0
func WithChildAssumedRole(role string) ChildOption
WithChildAssumedRole records the assumed role on every child workflow's status.
func WithChildAuthenticatedRoles ¶ added in v0.2.0
func WithChildAuthenticatedRoles(roles ...string) ChildOption
WithChildAuthenticatedRoles records the authenticated roles on every child workflow's status.
func WithChildAuthenticatedUser ¶ added in v0.2.0
func WithChildAuthenticatedUser(user string) ChildOption
WithChildAuthenticatedUser records the authenticated user on every child workflow's status.
func WithChildDeduplicationID ¶ added in v0.2.0
func WithChildDeduplicationID[T any](fn func(in T) string) ChildOption
WithChildDeduplicationID derives a queue deduplication ID from each item. While a child holding the ID is active on the queue, enqueueing another with the same ID is rejected — or returns the existing child's handle under dbos.DeduplicationPolicyReturnExisting (see WithChildDeduplicationPolicy).
func WithChildDeduplicationPolicy ¶ added in v0.2.0
func WithChildDeduplicationPolicy(policy DeduplicationPolicy) ChildOption
WithChildDeduplicationPolicy sets how a colliding deduplication ID is handled (default DeduplicationReject).
func WithChildDelay ¶ added in v0.2.0
func WithChildDelay(d time.Duration) ChildOption
WithChildDelay delays each child's dequeue by d: children start in the DELAYED status and become runnable once the delay expires.
func WithChildID ¶ added in v0.2.0
func WithChildID[T any](fn func(in T) string) ChildOption
WithChildID derives each child's workflow ID from its item, making child runs idempotent under an application-level key (e.g. an order ID): starting the same pipeline twice re-attaches to the same children instead of spawning duplicates. Without it, child IDs derive from the parent's step counter, which is idempotent per parent run but not across runs.
func WithChildPartitionKey ¶ added in v0.2.0
func WithChildPartitionKey[T any](fn func(in T) string) ChildOption
WithChildPartitionKey derives each child's queue partition key from its item. The queue must be registered with dbos.WithPartitionQueue; each partition then gets its own concurrency limits.
func WithChildPriority ¶ added in v0.2.0
func WithChildPriority(priority uint) ChildOption
WithChildPriority sets every child's queue priority (lower runs first). The queue must be registered with dbos.WithPriorityEnabled.
func WithChildTimeout ¶ added in v0.2.0
func WithChildTimeout(d time.Duration) ChildOption
WithChildTimeout gives every child a durable workflow deadline of d from its enqueue time: the deadline is stored with the child's status, survives recovery, and cancels the child when it expires. A timed-out child fails the pipeline when its result is awaited.
func WithPortableChildren ¶ added in v0.2.0
func WithPortableChildren() ChildOption
WithPortableChildren stores each child's inputs, step outputs, events, messages, and streams in DBOS's cross-language portable JSON format, so non-Go DBOS applications can read them.
type Client ¶ added in v0.8.0
type Client struct {
// contains filtered or unexported fields
}
Client enqueues durable runs and reports their status without launching the engine — the abstraction for an enqueue-only process (a web tier) that starts workflows but must never execute them. It talks to the same system database as the workers, uses the same serialization, and reports status through the same mapping as the engine (Status/StatusAll), so a client and the workers can never disagree on what a run's state — or "terminal" — means.
c, err := duro.NewClient(ctx, duro.ClientConfig{DatabaseURL: url})
defer c.Shutdown(5 * time.Second)
h, err := duro.Enqueue(c, Jobs, InvoiceJob, batch)
status, err := c.Status(h.ID())
func NewClient ¶ added in v0.8.0
func NewClient(ctx context.Context, cfg ClientConfig) (*Client, error)
NewClient connects an enqueue-only client to the system database.
func (*Client) ListRuns ¶ added in v0.9.0
func (c *Client) ListRuns(opts ...ListOption) ([]RunStatus, error)
ListRuns lists durable runs with the same options, mapping, and failed-run treatment as the engine's ListRuns — an admin tier's view of every run in the system, from a process that registers no workflows.
func (*Client) Resume ¶ added in v0.9.0
Resume revives a cancelled or retries-exceeded run, as the engine's Resume does — the same guarded transition, on the client's own connection. The run executes on a worker; the client never runs it.
func (*Client) Shutdown ¶ added in v0.8.0
Shutdown aborts any read still in flight and closes the client's system-database connections. WithContext views share those connections, so Shutdown on any of them closes the client for all. Calling it more than once is safe.
func (*Client) Status ¶ added in v0.8.0
Status fetches a run's current status by workflow ID, using the same mapping as the engine's Status.
func (*Client) StatusAll ¶ added in v0.8.0
StatusAll is the batch form of Status, sharing the engine's mapping core.
func (*Client) Steps ¶ added in v0.9.0
func (c *Client) Steps(workflowID string) ([]StepStatus, error)
Steps lists a run's executed steps, as the engine's Steps does.
func (*Client) WithContext ¶ added in v0.9.0
WithContext returns a view of the client whose reads and remediations are bounded by ctx as well as by ReadTimeout — the seam for a request handler:
runs, err := c.WithContext(r.Context()).ListRuns(duro.WithLimit(50))
A call cut short by ctx's deadline fails with an error wrapping context.DeadlineExceeded, one cut short by its cancellation with context.Canceled, so HTTP timeout classification sees the right cause.
The view shares the client's connections and its enqueue path; Enqueue and Handle waits are not bounded by ctx (dbos.Client offers no context for them). ctx must be non-nil.
type ClientConfig ¶ added in v0.8.0
type ClientConfig struct {
// DatabaseURL is the Postgres URL of the DBOS system database — the same one
// the workers use.
DatabaseURL string
// Logger receives client and DBOS logs; slog.Default() when nil.
Logger *slog.Logger
// ReadTimeout bounds every read and remediation call — Status, StatusAll,
// ListRuns, Steps, Cancel, Resume — at DefaultReadTimeout when zero. The
// bound covers the whole call, however many queries it issues. DBOS
// retries a failed database read indefinitely (backing off to 30s between
// attempts) until its context ends, so without a bound an outage parks a
// goroutine per call until the database returns; with one, each call
// returns an error wrapping context.DeadlineExceeded at the deadline. Set
// it below your request deadline and page ListRuns so no single call
// needs longer. Negative is an error.
//
// It does not cover Enqueue, or waiting on a Handle: dbos.Client offers
// no context for those.
ReadTimeout time.Duration
}
ClientConfig configures a duro Client.
type Config ¶ added in v0.2.0
type Config struct {
// Name identifies the application in the system database.
Name string
// DatabaseURL is the Postgres URL of the DBOS system database.
DatabaseURL string
// Logger receives duro and DBOS logs; slog.Default() when nil.
Logger *slog.Logger
// ApplicationVersion pins the DBOS application version — the single most
// operationally consequential DBOS knob. Recovery is scoped to it: a run is
// only ever recovered (or, under worker-pool mode, taken over) by an
// executor on the same version. Leaving it empty keeps DBOS's default (a
// hash of the binary), which changes on every rebuild — so every in-flight
// run strands the moment you deploy new code, recoverable only by rolling
// back to the exact previous binary. Pin it to a value you control (a git
// SHA, a release tag) so a redeploy of the same logical version resumes
// in-flight work. The DBOS__APPVERSION environment variable, when set,
// overrides this field.
ApplicationVersion string
// ExecutorID sets this process's executor identity. Empty keeps DBOS's
// default ("local"). Worker-pool mode (WithWorkerPool) requires a distinct
// ID per process and assigns a unique one when this is empty. The
// DBOS__VMID environment variable, when set, overrides this field.
ExecutorID string
}
Config configures a duro application.
type Context ¶ added in v0.2.0
type Context = dbos.DBOSContext
Context is the durable execution context every workflow runs under: it carries the checkpoint state duro's stages record to. It is DBOS's context type under a duro name (a type alias), so it satisfies context.Context and remains directly usable with any dbos API — but declaring and running workflows never requires importing dbos:
func Process(ctx duro.Context, job Job) (Result, error)
type Debouncer ¶ added in v0.2.0
type Debouncer[P, R any] struct { // contains filtered or unexported fields }
Debouncer collapses bursts of pipeline starts into a single run; see RegisterDebounced.
func RegisterDebounced ¶ added in v0.2.0
func RegisterDebounced[P, R any](ctx Context, name string, p Pipeline[P, R], opts ...dbos.DebouncerOption) *Debouncer[P, R]
RegisterDebounced registers the pipeline as a workflow and returns its debouncer. Cap the total postponement with dbos.WithDebouncerTimeout. Like Register, call it after New and before Launch.
func (*Debouncer[P, R]) Debounce ¶ added in v0.2.0
func (d *Debouncer[P, R]) Debounce(ctx Context, key string, delay time.Duration, input P) (Handle[R], error)
Debounce postpones the pipeline's start by delay. Every further call with the same key pushes the start back and replaces the input; when the delay lapses, the pipeline runs once with the last input. Different keys debounce independently. Every call returns a handle to the same eventual run.
type DeduplicationPolicy ¶ added in v0.2.0
type DeduplicationPolicy = dbos.DeduplicationPolicy
DeduplicationPolicy controls how a colliding child deduplication ID is handled; see WithChildDeduplicationPolicy.
type EnqueueOption ¶ added in v0.8.0
type EnqueueOption func(*enqueueConfig)
EnqueueOption configures a client Enqueue.
func WithClientApplicationVersion ¶ added in v0.8.0
func WithClientApplicationVersion(version string) EnqueueOption
WithClientApplicationVersion pins the enqueued run to a specific application version, so only workers on that version dequeue it. By default a client stamps no version (NULL), meaning any worker version may run it — the right default for a web tier that should not need redeploying in lockstep with the workers.
func WithClientWorkflowID ¶ added in v0.8.0
func WithClientWorkflowID(id string) EnqueueOption
WithClientWorkflowID assigns the run's workflow ID — the idempotency key. Enqueuing the same ID twice re-attaches to the first run instead of starting another.
type Event ¶ added in v0.2.0
type Event[V any] struct { // contains filtered or unexported fields }
Event is a typed key-value event channel: a value of type V published on a workflow under one key. Write with a SetEvent stage; read with a GetEvent stage or Event.Get.
var Progress = duro.NewEvent[int]("last-item")
func NewEvent ¶ added in v0.2.0
func NewEvent[V any](key string, opts ...ChannelOption) Event[V]
NewEvent declares a typed event key.
type Fork ¶ added in v0.2.0
type Fork struct {
WorkflowID string // the pipeline run to fork
Stage string // the stage to restart from (its first execution, for stages that ran per item)
// ForkedID names the forked run; auto-generated when empty.
ForkedID string
// ApplicationVersion pins the forked run to a different code version —
// the recovery tool for rerunning a workflow on fixed code after a bad
// deploy.
ApplicationVersion string
// Queue enqueues the forked run on the named queue instead of running it
// on the internal one; QueuePartitionKey partitions it there.
Queue string
QueuePartitionKey string
}
Fork describes where to restart an existing pipeline run. WorkflowID and Stage are required; every other field is optional and its zero value means "inherit from the original run".
type Handle ¶ added in v0.2.0
type Handle[R any] struct { // contains filtered or unexported fields }
Handle tracks one durable pipeline run. It is returned by Start and ForkFromStage; Result blocks until the run completes.
func Attach ¶ added in v0.4.0
Attach reconnects to an existing run by workflow ID and returns its handle — how a restarted process awaits a result instead of just polling Status. R must match the workflow's result type; prefer AttachJob, which takes it from the job's declaration instead.
func AttachJob ¶ added in v0.8.0
AttachJob is Attach with the result type taken from a Job rather than written out at the call site, so it cannot drift from the registration. Use it whenever the run belongs to a job you declared.
func Enqueue ¶ added in v0.8.0
func Enqueue[P, R any](c *Client, queue Queue, job Job[P, R], input P, opts ...EnqueueOption) (Handle[R], error)
Enqueue places a run on the queue for a worker to execute, and returns a handle to it. The job supplies the registered workflow name and both types, so the call cannot name a pipeline that does not exist, or disagree with it about the input or result type — register the same job on the workers with RegisterJob. The run is serialized identically to an engine-side enqueue, so workers decode it transparently.
By default no application version is stamped (any worker version may run it); override with WithClientApplicationVersion.
func ForkFromStage ¶ added in v0.2.0
ForkFromStage restarts an existing pipeline run from a named stage: stages before it replay from the original run's checkpoints, the named stage and everything after re-execute. The pipeline's shape guard replays too, so a fork onto changed pipeline code fails fast instead of misreading checkpoints.
type Job ¶ added in v0.8.0
type Job[P, R any] struct { // contains filtered or unexported fields }
Job is a registered pipeline's durable identity: its workflow name and both of its types in one declaration, shared by the process that registers the pipeline and every process that enqueues or awaits it.
It exists because a workflow name crossing a process boundary is otherwise three unchecked assumptions at once. Enqueuing by bare string, a typo produces a run no worker can ever dispatch — inserted successfully, reported as a healthy "enqueued", and waiting forever; a wrong input type fails to deserialize in another process long after the call site; a wrong result type surfaces when the caller reads the result. A Job makes all three the compiler's problem: declare it once beside the pipeline and both sides refer to the same value.
// package jobs, imported by the worker and the web tier alike
var Invoice = duro.NewJob[Batch, Invoice]("invoice")
// worker
duro.RegisterJob(app, jobs.Invoice, invoicePipeline)
// web tier — name and both types come from the same declaration
handle, err := duro.Enqueue(client, Queue, jobs.Invoice, batch)
The zero Job is invalid; build one with NewJob.
type ListOption ¶ added in v0.9.0
type ListOption func(*listConfig)
ListOption narrows, pages, or enriches a ListRuns query. Every filter is conjunctive: a run is returned only if it matches all of them.
func WithCreatedAfter ¶ added in v0.9.0
func WithCreatedAfter(t time.Time) ListOption
WithCreatedAfter restricts the listing to runs created at or after t. A zero time applies no bound.
func WithCreatedBefore ¶ added in v0.9.0
func WithCreatedBefore(t time.Time) ListOption
WithCreatedBefore restricts the listing to runs created at or before t. A zero time applies no bound.
func WithIDs ¶ added in v0.9.0
func WithIDs(ids ...string) ListOption
WithIDs restricts the listing to the given workflow IDs. Given no IDs, nothing matches. Unlike StatusAll, results follow the listing's sort order, not the order of the IDs.
func WithInput ¶ added in v0.9.0
func WithInput() ListOption
WithInput loads each run's stored input into RunStatus.Input, as JSON text. It is off by default so the listing stays payload-free like StatusAll.
func WithLimit ¶ added in v0.9.0
func WithLimit(n int) ListOption
WithLimit caps the number of runs returned; page with WithOffset. Without it the listing is unbounded. n must be positive: ListRuns reports a zero or negative limit as an error rather than silently returning nothing.
func WithNames ¶ added in v0.9.0
func WithNames(names ...string) ListOption
WithNames restricts the listing to runs of the named pipelines — their registered workflow names, as in Register, RegisterJob, or Job.Name. Given no names, nothing matches.
func WithNewestFirst ¶ added in v0.9.0
func WithNewestFirst() ListOption
WithNewestFirst orders the listing by creation time descending. The default is oldest first.
func WithOffset ¶ added in v0.9.0
func WithOffset(n int) ListOption
WithOffset skips the first n runs of the listing, for paging with WithLimit. n must not be negative.
func WithQueue ¶ added in v0.9.0
func WithQueue(name string) ListOption
WithQueue restricts the listing to runs currently recorded on the named queue (a Queue's Name). DBOS clears a run's queue when it is cancelled or exceeds its recovery attempts, so those runs match no queue.
func WithStates ¶ added in v0.9.0
func WithStates(states ...State) ListOption
WithStates restricts the listing to runs in any of the given states. Given no states, nothing matches.
type Option ¶ added in v0.8.0
type Option func(*appOptions)
Option configures a duro App; pass Options to New after the Config.
func WithRetention ¶ added in v0.8.0
WithRetention batch-deletes terminal runs that completed more than d ago, bounding the otherwise unbounded growth of workflow history (DBOS open source has no built-in retention). Each maintenance cycle deletes at most one batch, so a large backlog clears gradually over many cycles instead of in one long transaction; deletion holds its own advisory lock, never the sweeper's, so retention can never delay a takeover. Usable with or without WithWorkerPool.
Scope warning: DBOS records no application name on a run, so retention deletes every terminal run in the system database older than d — including runs belonging to other duro or DBOS applications that share it. Give each application its own database (or DBOS schema) before enabling this.
func WithStaleRunWarning ¶ added in v0.8.0
func WithStaleRunWarning(age time.Duration, hook ...func(StaleRunInfo)) Option
WithStaleRunWarning surfaces runs older than age that are still waiting or running, split by whether this executor's application version can recover them (see StaleRunInfo) — so stranded runs, which DBOS otherwise reports nowhere, become visible. It logs a warning with the counts; pass an optional hook to also receive them programmatically (invoked only when there is something to report). Usable with or without WithWorkerPool.
Choose age above the longest a healthy run legitimately takes. Runs parked in a Delay stage stay PENDING for the whole pause, so an age below your longest Delay reports healthy runs as stale. (Debounced runs, which park in DELAYED, are already excluded.)
Scope warning: DBOS records no application name on a run, so the counts cover every run in the system database, including those of other applications sharing it.
func WithSweepInterval ¶ added in v0.8.0
WithSweepInterval sets how often each process runs its maintenance cycle (default 30s): the scan for dead executors to take over, plus retention and the stale-run warning, which run on the same cadence. It is a plain Option, not a WorkerPoolOption, because it governs all three — an app using only WithRetention or WithStaleRunWarning can still tune it.
Fleet-wide the work is serialized by Postgres advisory locks, so this is per-process cadence, not global.
func WithWorkerPool ¶ added in v0.8.0
func WithWorkerPool(opts ...WorkerPoolOption) Option
WithWorkerPool enables worker-pool mode: liveness heartbeats plus a sweeper that takes over the PENDING runs of dead executors (see the package-level worker-pool documentation). Every process in the fleet must enable it, and each needs a distinct executor identity — one is generated when neither Config.ExecutorID nor DBOS__VMID is set.
type Pipeline ¶
type Pipeline[P, R any] struct { // contains filtered or unexported fields }
Pipeline is a composed chain of stages from input P to result R. Pipelines are immutable and stateless: build them once (package level is fine) and run them from any workflow with Run or RunAll.
func Pipe4 ¶
func Pipe4[A, B, C, D, E any](s1 Stage[A, B], s2 Stage[B, C], s3 Stage[C, D], s4 Stage[D, E]) Pipeline[A, E]
Pipe4 composes a pipeline from 4 stages.
func Pipe5 ¶
func Pipe5[A, B, C, D, E, F any](s1 Stage[A, B], s2 Stage[B, C], s3 Stage[C, D], s4 Stage[D, E], s5 Stage[E, F]) Pipeline[A, F]
Pipe5 composes a pipeline from 5 stages.
func Pipe6 ¶
func Pipe6[A, B, C, D, E, F, G any](s1 Stage[A, B], s2 Stage[B, C], s3 Stage[C, D], s4 Stage[D, E], s5 Stage[E, F], s6 Stage[F, G]) Pipeline[A, G]
Pipe6 composes a pipeline from 6 stages.
type PipelineWorkflow ¶ added in v0.2.0
type PipelineWorkflow[P, R any] struct { // contains filtered or unexported fields }
PipelineWorkflow is a pipeline registered as a DBOS workflow. Every registration shares one generic runner method, so DBOS's configured instance mechanism keys each registration by the pipeline's name — that is what ConfigName returns, and why runs must go through Start (which selects this instance) rather than a bare dbos.RunWorkflow.
func Register ¶ added in v0.2.0
func Register[P, R any](ctx Context, name string, p Pipeline[P, R], opts ...WorkflowRegistrationOption) *PipelineWorkflow[P, R]
Register turns a pipeline into a registered DBOS workflow under the given name, and registers every queue the pipeline references. Call it after New and before Launch; run the result with Start. Workflow-level registration options (recovery attempts via dbos.WithMaxRetries, ...) pass through opts.
The name is the pipeline's durable identity: in-flight runs are recovered by looking it up, so it must be registered on every process start. Launch warns about runs whose name is no longer registered.
func RegisterJob ¶ added in v0.8.0
func RegisterJob[P, R any](ctx Context, job Job[P, R], p Pipeline[P, R], opts ...WorkflowRegistrationOption) *PipelineWorkflow[P, R]
RegisterJob registers a pipeline under a Job's name, tying the registration to the same declaration the enqueuing side uses. Prefer it over Register for any pipeline another process enqueues with Enqueue: the Job carries the name and both types, so a rename or a type change is a compile error on both sides instead of a run that strands on the queue. Otherwise identical to Register.
func RegisterScheduled ¶ added in v0.2.0
func RegisterScheduled[R any](ctx Context, name, cronSchedule string, p Pipeline[time.Time, R], opts ...WorkflowRegistrationOption) *PipelineWorkflow[time.Time, R]
RegisterScheduled registers the pipeline as a scheduled (cron) workflow: every tick starts a durable run whose input is the scheduled time. The schedule uses cron syntax with seconds precision ("*/30 * * * * *" = every 30 seconds). Requiring Pipeline[time.Time, R] makes the DBOS rule that scheduled workflows take a time.Time input a compile-time guarantee.
func (*PipelineWorkflow[P, R]) ConfigName ¶ added in v0.2.0
func (w *PipelineWorkflow[P, R]) ConfigName() string
ConfigName implements dbos.ConfiguredInstance: the workflow name uniquely keys this pipeline's registration.
func (*PipelineWorkflow[P, R]) Start ¶ added in v0.2.0
func (w *PipelineWorkflow[P, R]) Start(ctx Context, in P, opts ...WorkflowOption) (Handle[R], error)
Start runs (or, with dbos.WithQueue, enqueues) the pipeline as a durable workflow and returns its handle. It accepts any WorkflowOption — workflow ID, queue, priority, deduplication, auth. For a durable deadline, pass a context derived with dbos.WithTimeout.
type Queue ¶ added in v0.2.0
type Queue struct {
// contains filtered or unexported fields
}
Queue is a declared DBOS workflow queue. Declare it once (package level is fine) and reference the value everywhere it is used — the queue's name lives in exactly one place, so writer and reader can never drift:
var Jobs = duro.NewQueue("jobs", duro.WithConcurrency(4))
...
duro.FanOut("process", Jobs, duro.Workflow(ProcessJob))
Queues referenced by a pipeline are registered automatically when the pipeline is registered with Register; pipelines run directly with Run/RunAll (no Register) need RegisterQueues before workflows start enqueueing.
func NewQueue ¶ added in v0.2.0
func NewQueue(name string, opts ...QueueOption) Queue
NewQueue declares a queue. Declaring is side-effect free; registration happens through Register (automatic for the pipeline's queues) or RegisterQueues.
type QueueOption ¶ added in v0.2.0
type QueueOption func(*queueConfig)
QueueOption configures a declared queue.
func WithConcurrency ¶ added in v0.2.0
func WithConcurrency(n int) QueueOption
WithConcurrency caps how many workflows from the queue run concurrently across all executors.
func WithPartitions ¶ added in v0.2.0
func WithPartitions() QueueOption
WithPartitions makes the queue partitioned: children enqueued with WithChildPartitionKey get per-partition concurrency limits.
func WithPriorities ¶ added in v0.2.0
func WithPriorities() QueueOption
WithPriorities enables priority scheduling: children enqueued with WithChildPriority run lowest-number-first.
func WithRateLimit ¶ added in v0.2.0
func WithRateLimit(limit int, period time.Duration) QueueOption
WithRateLimit caps how many workflows may start within each period — backpressure for external services.
func WithWorkerConcurrency ¶ added in v0.2.0
func WithWorkerConcurrency(n int) QueueOption
WithWorkerConcurrency caps how many workflows from the queue a single executor runs concurrently.
type RegisteredWorkflow ¶ added in v0.3.0
type RegisteredWorkflow[P, R any] struct { // contains filtered or unexported fields }
RegisteredWorkflow is a hand-written workflow function registered under a durable name; see RegisterWorkflow. It is a WorkflowRef, so it passes directly as a FanOut child.
func RegisterWorkflow ¶ added in v0.3.0
func RegisterWorkflow[P, R any](ctx Context, name string, fn WorkflowFunc[P, R], opts ...WorkflowRegistrationOption) *RegisteredWorkflow[P, R]
RegisterWorkflow registers a hand-written workflow function under the given name. Reach for it only when a workflow needs imperative control flow around its pipelines — branching between them, looping, post-processing a RunAll — since Register covers pipelines themselves. Call it before Launch; the name is the workflow's durable identity, so register the same function under the same name on every process start. Workflow-level registration options pass through opts.
func (*RegisteredWorkflow[P, R]) Start ¶ added in v0.3.0
func (w *RegisteredWorkflow[P, R]) Start(ctx Context, in P, opts ...WorkflowOption) (Handle[R], error)
Start runs (or, with dbos.WithQueue, enqueues) the workflow and returns its handle. It accepts any WorkflowOption, like PipelineWorkflow's Start.
type RunStatus ¶ added in v0.4.0
type RunStatus struct {
ID string
Name string // registered workflow (pipeline) name
State State
// Err is the recorded failure for failed runs — nil otherwise. Cancelled
// and retries-exceeded runs that recorded no error get a synthesized one,
// so Err is always non-nil when State.Failed().
Err error
CreatedAt time.Time
UpdatedAt time.Time
// StartedAt is when a queued run was dequeued to start. DBOS records no
// start time for a run started directly on its executor, so it is zero
// for those — and while a queued run is still enqueued or delayed.
StartedAt time.Time
CompletedAt time.Time // zero until terminal
ApplicationVersion string
ExecutorID string // the executor that last ran (or is running) it
// Attempts counts how many times execution has been started — 1 for a run
// that completed without recovery, more after crashes or takeovers, 0
// while still waiting on a queue.
Attempts int
// QueueName is the queue the run was enqueued on; "" for a run started
// directly on its executor. DBOS clears it when a run is cancelled or
// exceeds its recovery attempts.
QueueName string
// ParentID is the run that started this one — set for FanOut children and
// other child workflows, "" for top-level runs.
ParentID string
ForkedFrom string // original run's ID when this run was forked
// Input is the run's input as JSON text — json.RawMessage(status.Input)
// re-emits it. It is empty unless the run was listed with WithInput:
// loading payloads is what the status path otherwise avoids. It is the
// stored JSON, not a decoded value, so it is available from an
// enqueue-only Client that registers no workflow types. (A string rather
// than a byte slice keeps RunStatus comparable.)
Input string
}
RunStatus is the cheap status view of a run: no input or output payloads are loaded or deserialized, making it safe for polling paths. The one exception is Input, which ListRuns fills only when asked with WithInput.
func ListRuns ¶ added in v0.9.0
func ListRuns(ctx Context, opts ...ListOption) ([]RunStatus, error)
ListRuns lists durable runs — every registered pipeline and workflow, from any process attached to the system database — filtered, paged, and ordered by the options. With no options it returns every run, oldest first; page with WithLimit and WithOffset. Runs are reported through the same mapping as Status, with the same failed-run treatment: a failed run's recorded error is fetched in a second query scoped to the failed runs on the page, so RunStatus.Err is populated exactly as Status would populate it. No payloads are loaded unless WithInput asks for the input.
runs, err := duro.ListRuns(app,
duro.WithNames("invoice"),
duro.WithStates(duro.StateError, duro.StateRetriesExceeded),
duro.WithNewestFirst(), duro.WithLimit(50))
Client.ListRuns is the same query from an enqueue-only process.
func Status ¶ added in v0.4.0
Status fetches a run's current status by workflow ID — the reconcile primitive for consumers that persist run IDs and check on them later. It works from any process attached to the same system database; no Handle needed.
func StatusAll ¶ added in v0.4.0
StatusAll is the batch form of Status: it returns the status of every listed run that exists, in the requested order, silently omitting unknown IDs (compare lengths to detect them).
The common case is one payload-free query. DBOS stores a run's failure message alongside its output, so when the batch contains failed runs their recorded errors are fetched in a second query scoped to just those runs — healthy polling stays cheap, failure reasons still surface.
type Stage ¶
type Stage[T, R any] struct { // contains filtered or unexported fields }
Stage is one typed pipeline segment. Stages are nominal: only this package's constructors can build them, which is what keeps arbitrary ro operators out of durable pipelines at compile time.
func Branch ¶ added in v0.4.0
func Branch[T, R any](name string, pred func(ctx context.Context, in T) (bool, error), then, els Pipeline[T, R], opts ...StepOption) Stage[T, R]
Branch is durable two-way dispatch: the predicate runs as a checkpointed step and each item flows through then or els accordingly. Both arms must produce the same output type — the compiler holds routing honest.
func Collect ¶ added in v0.4.0
func Collect[T any](name string, opts ...StepOption) Stage[T, []T]
Collect folds the stream into a slice of every item, in order — the standard final stage for a registered pipeline that should return all values rather than the last one. An empty stream yields an empty slice.
func Delay ¶ added in v0.2.0
Delay is a durable pause: each item passing through sleeps for d via dbos.Sleep, which checkpoints the wake-up deadline — a workflow recovered mid-sleep resumes sleeping only for the remaining time, and a replayed sleep completes instantly. Use it for pacing between durable stages; remember it applies per item on multi-item streams.
func Expand ¶
func Expand[T, R any](name string, fn func(ctx context.Context, in T) ([]R, error), opts ...StepOption) Stage[T, R]
Expand is a durable one-to-many transform (a flattening FlatMap): fn runs as a checkpointed DBOS step and each element of its result is emitted downstream in order.
func FanOut ¶ added in v0.2.0
func FanOut[T, R any](name string, queue Queue, wf WorkflowRef[T, R], opts ...ChildOption) Stage[T, R]
FanOut is a durable parallel map: each item starts the referenced workflow as a child on the queue, and once the stream completes, results are awaited and emitted downstream in input order. Parallelism, rate limits, and distribution across processes are governed entirely by the queue's declaration:
var Jobs = duro.NewQueue("jobs", duro.WithConcurrency(4))
...
duro.Pipe3(
duro.Expand("explode", split),
duro.FanOut("process", Jobs, duro.Workflow(ProcessJob)),
duro.Reduce("merge", merge, seed),
)
The child can be a hand-written DBOS workflow (wrap it with Workflow) or a registered pipeline (pass the *PipelineWorkflow directly). Child workflows are configured with ChildOptions — identity (WithChildID, WithChildDeduplicationID, WithChildPartitionKey), scheduling (WithChildPriority, WithChildDelay, WithChildTimeout), and metadata (WithChildAuthenticatedUser, WithChildAppVersion, WithPortableChildren).
FanOut is the sanctioned form of concurrency inside a duro pipeline: it is deterministic because children are enqueued in stream order (child workflow IDs derive from the parent's step counter, so a recovered parent re-attaches to its children instead of spawning duplicates) and awaited in that same order (each result is checkpointed in the parent). Every child is itself a durable workflow.
On the first child failure, FanOut fails the pipeline with that child's error. By default, children queued behind it are independent durable workflows and run to completion in the background — the right call when every completion has value on its own (cleanup, deletion). When surviving siblings are wasted spend once the batch has failed, opt into WithCancelSiblings, which cancels every non-terminal sibling promptly while still failing the stage with the original error.
func Filter ¶
func Filter[T any](name string, pred func(ctx context.Context, in T) (bool, error), opts ...StepOption) Stage[T, T]
Filter is a durable filter: the predicate runs as a checkpointed DBOS step, so effectful or non-deterministic predicates still replay consistently on recovery. Items for which the predicate returns false are dropped.
func FromStream ¶ added in v0.2.0
func FromStream[T, V any](name string, stream Stream[V], fn func(in T) (workflowID string), opts ...StepOption) Stage[T, V]
FromStream durably drains the stream written by another workflow: fn derives the source workflow ID from the item, the stream is read to its close (blocking while the writer is still active), and each value is emitted downstream in order — the item itself is discarded, like Recv. The whole read runs as one checkpointed step, so a recovered workflow replays the values it already collected instead of re-reading a stream that may have changed. Pair it with WithTimeout to bound how long the stage waits for the writer to finish.
func GetEvent ¶ added in v0.2.0
func GetEvent[T, V any](name string, event Event[V], fn func(in T) (workflowID string), timeout time.Duration) Stage[T, V]
GetEvent durably reads the event published by another workflow: fn derives the source workflow ID from the item, and the event's value is emitted downstream in place of the item (reshape beforehand if you need both). The read blocks until the event is set or the timeout elapses, and is checkpointed — a recovered workflow replays the value it already observed instead of re-reading.
func Loop ¶ added in v0.4.0
func Loop[T any](name string, body Pipeline[T, T], until func(ctx context.Context, in T) (bool, error), opts ...StepOption) Stage[T, T]
Loop durably repeats the body pipeline until the until predicate — a checkpointed step — reports done, then emits the final value. Each iteration feeds the body's last emitted value back in (a body that emits nothing drops the item, like Filter). On replay the recorded predicate verdicts reproduce the exact iteration count. Pair the body with Delay for durable polling. Applied per item on multi-item streams.
Iterations are unbounded, and a durable loop is more durable than a bug deserves: a predicate that can never report done keeps checkpointing and resumes across restarts. Give the loop a natural bound (track attempts in T and fail past a limit), or stop a runaway run with dbos.CancelWorkflow.
func Parallel ¶ added in v0.2.0
func Parallel[T, R any](name string, maxConcurrent int, fn func(ctx context.Context, in T) (R, error), opts ...StepOption) Stage[T, R]
Parallel is a durable parallel Step: items execute fn concurrently as DBOS steps within the workflow process, at most maxConcurrent at a time (unbounded if maxConcurrent <= 0), and results are emitted downstream in input order once the stream completes.
Parallel is the in-process, lightweight sibling of FanOut: no queue and no child workflows, just concurrent steps inside the current workflow. Use FanOut when work should distribute across processes, survive independently, or obey queue-level rate limits; use Parallel when a bounded burst of concurrent steps in this process is enough.
Determinism is preserved because each step's ID is assigned on the workflow goroutine at launch time, in stream order (dbos.Go exists precisely for this), and outcomes are collected in that same order. On recovery, completed steps replay from their checkpoints without re-running fn.
If any step fails, results for items before the failure are still emitted downstream (matching sequential fail-fast semantics), and the pipeline fails with the first error in input order. By default every remaining step is drained to completion first; opt into WithCancelSiblingSteps to cancel in-flight siblings and skip unstarted items instead, while still failing with the first genuine error.
func Pure ¶
Pure is a non-durable transform: fn is NOT checkpointed and re-executes on every replay, so it must be deterministic and side-effect free. Use it for cheap reshaping between durable stages; anything effectful or fallible belongs in Step.
func Recv ¶ added in v0.2.0
Recv durably waits for the next message on the topic and emits it downstream, consuming one message per upstream item (the item itself is discarded — reshape beforehand if you need it). Receipt is checkpointed, so a recovered workflow does not consume a second message. A zero or negative timeout means dbos.Recv's no-wait behavior; if no message arrives in time, the stage fails the pipeline.
Recv is how a pipeline pauses for an external signal — a payment confirmation, a human approval — sent to this workflow's ID with a Send stage or Topic.Send from anywhere.
func Reduce ¶
func Reduce[T, A any](name string, fn func(ctx context.Context, acc A, in T) (A, error), seed A, opts ...StepOption) Stage[T, A]
Reduce is a durable fold: each accumulation runs as a checkpointed DBOS step, and the final accumulator is emitted when the source completes.
func Rescue ¶ added in v0.5.0
func Rescue[T, R any](name string, p Pipeline[T, R], handler func(ctx context.Context, in T, cause error) (R, error), opts ...StepOption) Stage[T, R]
Rescue is a durable except block: it runs the embedded pipeline for each item and intercepts its failure. On success the embedded pipeline's emissions flow downstream unchanged. On failure — after the embedded stages' own retry policies are exhausted — handler runs as a checkpointed step and decides the outcome: return a fallback R and nil error to swallow (the fallback is emitted downstream and the outer pipeline continues), or return an error to fail the outer pipeline with it (transform, or return cause unchanged to rethrow). handler receives the item that entered the embedded pipeline, so a pass-through swallow is `return in, nil` when the types match. opts apply to the handler step, like Branch's route opts.
A failed embedded run is treated as a unit: its partial emissions are discarded and only the handler's fallback is emitted. A successful embedded run that emits nothing drops the item, like Filter. Because the handler is a checkpointed step, effectful handlers (failure reports, warn logs) replay consistently, and the rescue decision is durable: recovery never flips a swallowed failure into a propagated one or vice versa. The cause is replay-stable by construction: the handler always receives a plain error carrying exactly the terminal failure's message — the same value a recovered run replays — so the handler cannot behave differently on recovery than it did live. Match causes by message; error identities (errors.Is/As) are deliberately not preserved, because they cannot survive recovery. A decision that needs the typed error belongs in the failing step itself or its WithRetryPredicate, which always see the live error.
Applied per item on multi-item streams. Only failures inside the embedded pipeline are rescued: upstream failures, and errors from the handler itself, propagate as usual. Rescue nests — the innermost enclosing Rescue wins — and Pipe1(Rescue(name, p, handler)) is the whole-pipeline except block.
func Send ¶ added in v0.2.0
func Send[T, M any](name string, topic Topic[M], fn func(in T) (destinationID string, message M, err error)) Stage[T, T]
Send durably sends one message per item on the topic (dbos.Send is checkpointed, so a recovered workflow does not re-send). fn derives the destination workflow ID and the message from the item, which passes through unchanged. The message type is the topic's — a mismatch with the receiving side is a compile error.
func SetEvent ¶ added in v0.2.0
SetEvent durably publishes the event on the workflow for each item and passes the item through unchanged. Read it with a GetEvent stage or Event.Get — the classic use is exposing pipeline progress to the outside world while the workflow runs.
func Step ¶
func Step[T, R any](name string, fn func(ctx context.Context, in T) (R, error), opts ...StepOption) Stage[T, R]
Step is a durable Map: it transforms each item by running fn as a checkpointed DBOS step. On recovery, completed executions are replayed from the database instead of re-running fn.
func Sub ¶ added in v0.4.0
Sub embeds a pipeline as a single named stage — reuse a pipeline segment across pipelines without a wrapper workflow. Unlike Branch/Switch/Loop, Sub applies to the whole stream, not per item: an embedded Reduce folds everything flowing through it.
func Switch ¶ added in v0.4.0
func Switch[T, R any](name string, route func(ctx context.Context, in T) (string, error), cases ...Case[T, R]) Stage[T, R]
Switch is durable multi-way dispatch: route runs as a checkpointed step, and each item flows through the case pipeline matching the returned key — on replay the recorded key routes the item down the same arm. A key with no matching case fails the pipeline. Applied per item on multi-item streams; every arm's outputs are emitted downstream in stream order.
func Tap ¶
func Tap[T any](name string, fn func(ctx context.Context, in T) error, opts ...StepOption) Stage[T, T]
Tap is a durable side effect: fn runs as a checkpointed DBOS step and the item passes through unchanged.
func ToStream ¶ added in v0.2.0
ToStream durably appends each item to the workflow's stream and passes it through unchanged; the stream is closed when the pipeline completes. Readers drain it with a FromStream stage or Stream.Read — the way to expose a pipeline's per-item output while it is still running, instead of waiting for the final result. The stream's type is the pipeline's item type, checked at compile time.
func UnsafeOperator ¶
func UnsafeOperator[T, R any](name string, op func(ro.Observable[T]) ro.Observable[R]) Stage[T, R]
UnsafeOperator admits an arbitrary ro operator into a durable pipeline. duro cannot guarantee replay determinism for it: the operator must be synchronous, order-preserving, and deterministic, or recovery will fail — loudly if step names misalign or execution changes goroutines, silently if identically-named steps swap positions. Prefer the safe constructors.
func Via ¶ added in v0.6.0
Via runs the embedded pipeline for each item — durably, to completion — then emits the ORIGINAL item downstream, discarding the embedded pipeline's emissions. Tap is to Step what Via is to Sub: the embedded pipeline exists for its effects (typically a FanOut of child workflows or a Parallel fleet), not its outputs, and the item that entered continues on afterwards. That is what lets a pipeline whose state must survive a fan-out stay a single registered pipeline instead of a hand-written workflow around several Run calls.
Via never drops items: a successful embedded run that emits nothing (a Filter dropped everything) still passes the original item through — embedded emissions are ignored entirely, zero included. This is the deliberate contrast with Sub, whose emissions ARE the stream. The re-emitted item is replay-stable because it came from upstream checkpoints; Via itself records no step.
An embedded failure fails the outer pipeline exactly like any stage failure — partial embedded effects before it remain checkpointed. Wrap in Rescue to swallow: Rescue(name, Pipe1(Via(...)), handler) is a best-effort fan-out that continues with the original item either way. Applied per item on multi-item streams, in stream order.
type StaleRunInfo ¶ added in v0.8.0
type StaleRunInfo struct {
// SameVersion counts non-terminal runs older than the warning age on this
// executor's application version — in limbo, but recoverable by this fleet.
SameVersion int
// OtherVersion counts non-terminal runs older than the warning age on other
// application versions — not recoverable here until an executor on their
// version runs (see Config.ApplicationVersion).
OtherVersion int
}
StaleRunInfo reports the counts a stale-run warning surfaces on each sweep (see WithStaleRunWarning). Stranded runs otherwise emit no signal at all.
type State ¶ added in v0.4.0
type State string
State is a run's lifecycle state.
const ( StatePending State = "pending" // running or ready to run StateEnqueued State = "enqueued" // waiting on a queue StateDelayed State = "delayed" // waiting for its start delay StateSuccess State = "success" StateError State = "error" // completed with an error StateCancelled State = "cancelled" StateRetriesExceeded State = "retries_exceeded" // exceeded max recovery attempts )
type StepOption ¶
type StepOption func(*stepConfig)
StepOption configures how a durable stage executes as a DBOS step.
func WithBackoffFactor ¶ added in v0.2.0
func WithBackoffFactor(factor float64) StepOption
WithBackoffFactor sets the exponential multiplier applied to the retry delay after each attempt (default 2.0).
func WithBaseInterval ¶
func WithBaseInterval(d time.Duration) StepOption
WithBaseInterval sets the initial delay between retries (default 100ms).
func WithCancelSiblingSteps ¶ added in v0.7.0
func WithCancelSiblingSteps() StepOption
WithCancelSiblingSteps makes the first failed step of a Parallel stage cancel its sibling steps instead of letting the stage drain them: in-flight siblings have their step contexts cancelled (the step function must honor context cancellation for this to take effect, the same contract WithTimeout documents), and items whose steps have not started skip the function entirely. The stage still fails with the first genuinely-failed step's error, in input order — cancelled and skipped siblings never mask it, on the live run and on recovery replay alike, so a Rescue around the stage always sees the original failure.
Skipped and cancelled items still occupy their pre-assigned step slots, recording a marker error instead of running the function — the slot count stays deterministic, which is what keeps step IDs aligned on recovery (a recovered workflow that continues past the stage, e.g. through Rescue, would otherwise read misaligned checkpoints). Because the layout is unchanged, the option does not alter the pipeline's shape fingerprint. If the workflow is recovered and reaches the stage again, items with no recorded outcome re-execute, exactly as without the option.
A step's own retry policy runs before cancellation triggers: siblings are cancelled only when a step's error escapes its retries, matching when the stage would fail.
It applies only to Parallel; every other stage constructor rejects it at construction time. Sequential stages have nothing to cancel — an earlier failure already prevents later items from executing. For the child-workflow equivalent on FanOut, see WithCancelSiblings.
func WithMaxInterval ¶ added in v0.2.0
func WithMaxInterval(d time.Duration) StepOption
WithMaxInterval caps the delay between retries (default 5s).
func WithMaxIterations ¶ added in v0.8.0
func WithMaxIterations(n int) StepOption
WithMaxIterations bounds a Loop: if the predicate has not reported done after n iterations, the stage fails with ErrMaxIterations instead of looping forever. A durable loop outlives the process running it, so an unbounded one whose predicate can never be satisfied keeps checkpointing across restarts until someone cancels the workflow by hand.
The bound is enforced from the checkpointed iteration count, so a recovered run resumes with the same budget it had rather than a fresh one.
It applies only to Loop; every other stage constructor rejects it at construction time.
func WithMaxRetries ¶
func WithMaxRetries(n int) StepOption
WithMaxRetries sets the maximum number of automatic retries for the stage when its function returns an error. Zero (the default) means no retries. This is the step-level retry limit (DBOS's WithStepMaxRetries), distinct from workflow recovery attempts, which are configured at registration.
func WithRetryPredicate ¶ added in v0.2.0
func WithRetryPredicate(pred func(error) bool) StepOption
WithRetryPredicate restricts which errors are retried: when the stage function returns an error for which pred is false, the stage stops immediately with that error even if retries remain. Use it to spend retries on transient failures only.
func WithTimeout ¶ added in v0.2.0
func WithTimeout(d time.Duration) StepOption
WithTimeout bounds each execution attempt of the stage function: the step context is cancelled after d and the attempt fails with the context's error. Retries get a fresh deadline. DBOS has no native step timeout, so the deadline is enforced in-process per attempt — the stage function must honor context cancellation for the timeout to take effect. For a durable deadline on a whole pipeline, start its workflow from a context derived with dbos.WithTimeout; for child workflows, see WithChildTimeout.
type StepStatus ¶ added in v0.9.0
type StepStatus struct {
ID int // position in the run's step sequence, from 0
Name string // the stage name; ShapeStepName for the shape checkpoint that opens every pipeline run
// Err is the error the step recorded, nil when it succeeded. A recorded
// step error is final for that step: retries happen before recording.
Err error
// ChildID is the child run this step started — set for FanOut children
// and other child workflows, "" for ordinary steps.
ChildID string
StartedAt time.Time
CompletedAt time.Time
}
StepStatus is the record of one step a run has executed: its position, name, outcome, and timing. Step outputs are never decoded or returned.
func Steps ¶ added in v0.9.0
func Steps(ctx Context, workflowID string) ([]StepStatus, error)
Steps lists the steps a run has executed so far, in execution order — what a pipeline has checkpointed, which is exactly what replays on recovery. A pipeline run opens with the ShapeStepName checkpoint, then one entry per durable stage execution (per item for stages that ran per item; Pure stages record nothing). A run that has not started yet has no steps. Unknown IDs return ErrRunNotFound.
Step outputs are never decoded or returned, but DBOS's step query still reads the output column, so inspecting a run whose stages checkpoint large values costs that bandwidth — Steps is cheap in memory, not free on the wire. Prefer Status for polling; reserve Steps for inspection.
Client.Steps is the same query from an enqueue-only process.
type Stream ¶ added in v0.2.0
type Stream[V any] struct { // contains filtered or unexported fields }
Stream is a typed durable stream channel: values of type V appended by one workflow and drained by readers. Write with a ToStream stage; read with a FromStream stage or Stream.Read.
var Receipts = duro.NewStream[Receipt]("receipts")
func NewStream ¶ added in v0.2.0
func NewStream[V any](key string, opts ...ChannelOption) Stream[V]
NewStream declares a typed stream key.
func (Stream[V]) Read ¶ added in v0.2.0
Read drains the stream written by the given workflow, blocking until the writer closes it or becomes inactive; closed reports whether the stream was cleanly closed. Read is for clients — inside a pipeline use the FromStream stage, which checkpoints the read so replay never observes a changed stream.
type Topic ¶ added in v0.2.0
type Topic[M any] struct { // contains filtered or unexported fields }
Topic is a typed mailbox channel: messages of type M sent to a workflow's mailbox under one topic name. Write with a Send stage or Topic.Send; read with a Recv stage.
var Approvals = duro.NewTopic[Approval]("approvals")
func NewTopic ¶ added in v0.2.0
func NewTopic[M any](name string, opts ...ChannelOption) Topic[M]
NewTopic declares a typed mailbox topic.
type WorkerPoolOption ¶ added in v0.8.0
type WorkerPoolOption func(*appOptions)
WorkerPoolOption tunes worker-pool mode; pass WorkerPoolOptions to WithWorkerPool.
func WithHeartbeatInterval ¶ added in v0.8.0
func WithHeartbeatInterval(d time.Duration) WorkerPoolOption
WithHeartbeatInterval sets how often each process refreshes its liveness lease (default 10s). Keep it well below the stale threshold.
func WithStaleThreshold ¶ added in v0.8.0
func WithStaleThreshold(d time.Duration) WorkerPoolOption
WithStaleThreshold sets how long a lease may go unrefreshed before its executor is considered dead and its PENDING runs are taken over (default 60s). Must be well above WithHeartbeatInterval plus worst-case scheduling jitter and GC pauses — too low and a merely-slow process is treated as dead and its live run is run a second time elsewhere. Avoid sub-second values in production.
type WorkflowFunc ¶ added in v0.2.0
WorkflowFunc is a hand-written durable workflow function, the kind RegisterWorkflow registers and Workflow adapts into a FanOut child. Registered pipelines (Register) never touch this type.
type WorkflowOption ¶ added in v0.6.0
type WorkflowOption = dbos.WorkflowOption
WorkflowOption configures how a single run starts (DBOS's option type under a duro name, like Context): WithWorkflowID is the common one, and any dbos.WorkflowOption works unchanged. The alias lets consumer helpers name the type without importing dbos.
func WithWorkflowID ¶ added in v0.2.0
func WithWorkflowID(id string) WorkflowOption
WithWorkflowID assigns a run's workflow ID — the standard idempotency key: starting the same ID twice re-attaches to the first run instead of running again. Every other WorkflowOption passes through Start unchanged.
type WorkflowRef ¶ added in v0.2.0
type WorkflowRef[T, R any] interface { // contains filtered or unexported methods }
WorkflowRef identifies a workflow FanOut can start: a registered pipeline (*PipelineWorkflow, which carries its own dispatch metadata) or a hand-written DBOS workflow wrapped with Workflow.
func Workflow ¶ added in v0.2.0
func Workflow[T, R any](fn WorkflowFunc[T, R]) WorkflowRef[T, R]
Workflow adapts a hand-written, dbos-registered workflow function into a WorkflowRef:
duro.FanOut("process", Jobs, duro.Workflow(ProcessJob))
type WorkflowRegistrationOption ¶ added in v0.6.0
type WorkflowRegistrationOption = dbos.WorkflowRegistrationOption
WorkflowRegistrationOption configures how a pipeline or workflow function is registered (DBOS's option type under a duro name); Register and RegisterWorkflow accept these. The alias lets consumer helpers name the type without importing dbos.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
fleet
command
Command fleet demonstrates duro's worker-pool mode: a fleet of interchangeable workers that recover each other's runs, an enqueue-only web tier that starts work without running an engine, and an admin view on that same client that lists, inspects, cancels, and resumes runs.
|
Command fleet demonstrates duro's worker-pool mode: a fleet of interchangeable workers that recover each other's runs, an enqueue-only web tier that starts work without running an engine, and an admin view on that same client that lists, inspects, cancels, and resumes runs. |
|
housekeeping
command
Package main demonstrates duro's operational toolkit: cron pipelines (RegisterScheduled), burst-collapsing (RegisterDebounced), surgical replay of a finished run from a named stage (ForkFromStage), and the durable identity contract behind pipeline names (the stranded-run warning).
|
Package main demonstrates duro's operational toolkit: cron pipelines (RegisterScheduled), burst-collapsing (RegisterDebounced), surgical replay of a finished run from a named stage (ForkFromStage), and the durable identity contract behind pipeline names (the stranded-run warning). |
|
orders
command
|
|
|
payments
command
Package main demonstrates duro's signal and resilience toolkit on a payment flow: retries with a predicate, per-attempt timeouts, durable pauses, human-in-the-loop approval over a typed Topic, progress Events, and a receipt Stream drained by a second pipeline.
|
Package main demonstrates duro's signal and resilience toolkit on a payment flow: retries with a predicate, per-attempt timeouts, durable pauses, human-in-the-loop approval over a typed Topic, progress Events, and a receipt Stream drained by a second pipeline. |
|
thumbnails
command
Package main demonstrates duro's parallelism toolkit on a thumbnail rendering fleet: declared queues with concurrency/rate/priority/partition controls, FanOut child options (idempotent IDs, deduplication, timeouts, delays, auth), a hand-written child workflow using duro.Context, in-process bounded Parallel, and a registered pipeline used directly as a FanOut child.
|
Package main demonstrates duro's parallelism toolkit on a thumbnail rendering fleet: declared queues with concurrency/rate/priority/partition controls, FanOut child options (idempotent IDs, deduplication, timeouts, delays, auth), a hand-written child workflow using duro.Context, in-process bounded Parallel, and a registered pipeline used directly as a FanOut child. |
|
triage
command
Package main demonstrates duro's control-flow combinators on a support ticket triage pipeline: Switch dispatches by category, a nested Branch escalates urgent bugs, Loop durably polls an external system, Sub reuses a shared notification segment, Rescue scopes error handling to a best-effort segment (and to the whole pipeline), Via fans each resolution out to archive systems and passes the resolution through, and Collect folds the batch into a report — all inside one registered pipeline, no hand-written workflow function.
|
Package main demonstrates duro's control-flow combinators on a support ticket triage pipeline: Switch dispatches by category, a nested Branch escalates urgent bugs, Loop durably polls an external system, Sub reuses a shared notification segment, Rescue scopes error handling to a best-effort segment (and to the whole pipeline), Via fans each resolution out to archive systems and passes the resolution through, and Collect folds the batch into a report — all inside one registered pipeline, no hand-written workflow function. |