Documentation
¶
Overview ¶
Package duraturo makes existing Go code durable without rewrites.
Three concepts, nothing else:
- ledger — durable truth: runs and their memoized records, stored in tables the caller already owns (pkg/ledger; adapters map, never migrate)
- queue — flow and clock: delivery of run IDs under fenced, time-bounded claims (pkg/queue); disposable, rebuildable from the ledger
- worker — a pull loop, not a service (pkg/worker); it can be the submitting process itself or something else entirely
Wrap a function with Activity, wrap non-determinism with Step, run a worker. On crash or retry the workflow re-executes from the top: calls with recorded results return them instantly, the first unrecorded call executes for real. Runs replay any number of times, or error out.
Outside a run every wrapped function is a plain Go call — code keeps working unchanged in tests, scripts, and codebases mid-migration.
Index ¶
- func Emit(ctx context.Context, payload any) error
- func Event[O any](ctx context.Context, name string) (O, error)
- func Exec[I, O any](ctx context.Context, c *Client, a *ActivityFn[I, O], in I, opts ...StartOption) (O, error)
- func IdempotencyKey(ctx context.Context) string
- func InRun(ctx context.Context) bool
- func NonRetryable(err error) error
- func PriorDeltas(ctx context.Context) ([][]byte, error)
- func Step[O any](ctx context.Context, name string, fn func(context.Context) (O, error)) (O, error)
- type ActivityFn
- type Client
- type ClientOption
- type DecodeError
- type Handle
- type RunFailedError
- type RunInfo
- type StartOption
- type Void
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Emit ¶
Emit appends a delta to the run's stream, tagged with the activity currently executing. Deltas are advisory flow — sealed or superseded by checkpoints, streamed to watchers, never truth. Outside a run, or when the queue has no DeltaLog capability, Emit is a no-op.
func Event ¶
Event is wait-as-absence: it returns the named event record's payload if one exists, and parks the run if not. There is no suspend mechanism — an event is simply a record that doesn't exist yet. The run stops at its frontier (the worker settles the queue item; the run stays pending) and resumes when anything writes the record and enqueues the run: a Client.Signal call, a row inserted into your own table plus an enqueue, or the janitor's backstop poll.
The returned run.ErrParked must propagate: return it up like any error.
Call Event from the workflow body (the function handed to Start), not from inside a nested activity: record keys are parent-scoped, and Client.Signal targets top-level "event:<name>#<k>" keys. An event awaited inside a nested activity needs its prefixed record written by hand.
func Exec ¶
func Exec[I, O any](ctx context.Context, c *Client, a *ActivityFn[I, O], in I, opts ...StartOption) (O, error)
Exec is Start + Result: synchronous composition.
func IdempotencyKey ¶
IdempotencyKey returns "{runID}:{recordKey}" for the activity currently executing — a stable key to hand downstream systems (payment providers, mail APIs) so duraturo's at-least-once side effects become effective-once where the callee deduplicates. Empty outside a wrapped call.
func NonRetryable ¶
NonRetryable marks an error terminal: the failure is recorded (memoized) and the run fails without burning further attempts. Re-exported from pkg/run for the common case.
func PriorDeltas ¶
PriorDeltas returns the payloads the currently-executing activity emitted on previous attempts — raw material for provider-side resume (for example, prompt-prefix continuation of an interrupted stream). The read is exposed; nothing is applied automatically.
func Step ¶
Step records inline non-determinism: time, random values, generated IDs, one-off reads. Inside a run it executes once ever — the first recorded value is the value forever. Outside a run it just runs fn.
at, err := duraturo.Step(ctx, "completed-at",
func(context.Context) (time.Time, error) { return time.Now(), nil })
Give distinct names to steps whose program order may vary: steps carry no input hash, so a same-name reorder cannot be detected the way it is for activities.
Types ¶
type ActivityFn ¶
type ActivityFn[I, O any] struct { // contains filtered or unexported fields }
ActivityFn is a durable function handle. Declare once at package level — the Go analogue of a decorator:
var ChargePayment = duraturo.Activity("charge-payment", chargePayment)
The name is the correlation contract: records belong to it, releases roll forward against it, and a breaking input/output change means a new name ("charge-payment.v2") — the one deliberate act.
func Activity ¶
Activity wraps fn as a named durable function and registers it in run.DefaultRegistry. Duplicate names panic at init.
func ActivityIn ¶
func ActivityIn[I, O any](reg *run.Registry, name string, fn func(context.Context, I) (O, error)) *ActivityFn[I, O]
ActivityIn registers into a specific registry — test isolation, or fleets serving disjoint activity sets.
func (*ActivityFn[I, O]) Call ¶
func (a *ActivityFn[I, O]) Call(ctx context.Context, in I) (O, error)
Call executes the activity: memoized inside a run, a plain function call outside one.
func (*ActivityFn[I, O]) CallKeyed ¶
func (a *ActivityFn[I, O]) CallKeyed(ctx context.Context, key string, in I) (O, error)
CallKeyed executes the activity under an explicit record key. Explicit keys are order-independent — the escape hatch for calls whose program order may vary (keyed fan-out) — and become part of the idempotency key handed downstream.
func (*ActivityFn[I, O]) Name ¶
func (a *ActivityFn[I, O]) Name() string
Name returns the registered activity name.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client submits runs and observes them. It is the producer side of the two interfaces; a worker embedded in the same process consumes from the same pair.
func New ¶
New composes a duraturo client from any Ledger and Queue implementation. Nothing is created, nothing is migrated: the implementations own storage.
type ClientOption ¶
type ClientOption func(*Client)
ClientOption configures a Client.
func WithCodec ¶
func WithCodec(c run.Codec) ClientOption
WithCodec overrides the default JSON codec. Client and worker must agree; the recorded ContentType makes a mismatch fail loudly.
func WithDefaultMaxAttempts ¶
func WithDefaultMaxAttempts(n int) ClientOption
WithDefaultMaxAttempts sets the retry budget applied to runs that don't set their own (default 5).
type DecodeError ¶
DecodeError reports a payload that would not round-trip through the codec — a recorded output that no longer decodes into today's type, or an unserializable value. Always terminal: retrying deterministic corruption is a hazard, not resilience. Fix: versioned names for breaking type changes ("charge-payment.v2").
func (*DecodeError) Error ¶
func (e *DecodeError) Error() string
func (*DecodeError) Unwrap ¶
func (e *DecodeError) Unwrap() error
type Handle ¶
type Handle[O any] struct { // contains filtered or unexported fields }
Handle observes one run.
func HandleFor ¶
HandleFor re-attaches to an existing run — after a process restart, or from a different process entirely.
func Start ¶
func Start[I, O any](ctx context.Context, c *Client, a *ActivityFn[I, O], in I, opts ...StartOption) (*Handle[O], error)
Start durably submits a run of a: ledger Accept, then queue Enqueue. A nil error means accepted-and-queued — "acknowledged means durable". Start is a free function because Go methods cannot introduce type parameters.
func (*Handle[O]) Result ¶
Result blocks until the run is terminal and returns its output, polling the ledger with backoff. A failed run returns *RunFailedError. Backends with the ledger.RunGetter capability are polled with a point read; others fall back to Load (which also prefetches the record history every poll — implement GetRun on custom ledgers).
type RunFailedError ¶
RunFailedError is the terminal failure of a run, surfaced by Handle.Result.
func (*RunFailedError) Error ¶
func (e *RunFailedError) Error() string
type StartOption ¶
type StartOption func(*startCfg)
StartOption configures one submission.
func WithMaxAttempts ¶
func WithMaxAttempts(n int) StartOption
WithMaxAttempts overrides the run's retry budget: the maximum number of FAILED executions (retryable errors, crashes discovered by lease expiry) before the run is terminally failed. Waiting never counts — park/resume cycles and janitor backstop polls are free.
func WithParent ¶
func WithParent(runID string) StartOption
WithParent records lineage (forks and child runs read it; informational in v1).
func WithRunID ¶
func WithRunID(id string) StartOption
WithRunID supplies the run ID — which doubles as the submit idempotency key. Starting an existing ID is a no-op returning a handle to the existing run; natural keys ("order-1234") make resubmission safe by construction.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
adapters
|
|
|
postgres
module
|
|
|
redis
module
|
|
|
pkg
|
|
|
ledger
Package ledger defines the durable-truth interface: where runs and their memoized records live.
|
Package ledger defines the durable-truth interface: where runs and their memoized records live. |
|
ledger/ledgertest
Package ledgertest is the executable contract for ledger.Ledger: a conformance suite every implementation must pass.
|
Package ledgertest is the executable contract for ledger.Ledger: a conformance suite every implementation must pass. |
|
queue
Package queue defines the flow-and-clock interface: delivery of run IDs to workers under time-bounded, fenced claims.
|
Package queue defines the flow-and-clock interface: delivery of run IDs to workers under time-bounded, fenced claims. |
|
queue/queuetest
Package queuetest is the conformance suite every queue backend must pass.
|
Package queuetest is the conformance suite every queue backend must pass. |
|
replay
Package replay is duraturo's correctness heart: the memoized re-execution of a run.
|
Package replay is duraturo's correctness heart: the memoized re-execution of a run. |
|
run
Package run defines duraturo's leaf types: runs, records, deltas, and the sentinel errors every layer speaks.
|
Package run defines duraturo's leaf types: runs, records, deltas, and the sentinel errors every layer speaks. |
|
worker
Package worker implements duraturo's consumer: the loop that claims runs from the queue, re-executes them under replay to their frontier, and persists exactly one outcome per claim.
|
Package worker implements duraturo's consumer: the loop that claims runs from the queue, re-executes them under replay to their frontier, and persists exactly one outcome per claim. |