durable

package
v1.0.26-beta.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 31, 2026 License: MIT Imports: 25 Imported by: 0

Documentation

Overview

Package durable provides a single-writer WebSocket relay reference with a persistent operation log, bounded replay, and reconnect support.

It is intentionally separate from extensions: extensions is a bounded live relay, while durable owns a bbolt-backed transport log. It remains a reference for one active process and one persistent volume, not a clustered replication service. Applications must still supply TLS, authentication, authorization, concrete CRDT state/frontier checkpoints, durable outboxes, membership, and tombstone-GC policy.

Index

Constants

View Source
const Subprotocol = "crdt-durable-v1"

Subprotocol identifies the durable relay control and binary-event protocol. It is independent from CRDT frame versions and extensions.Subprotocol.

Variables

View Source
var (
	// ErrInvalidConfig reports a missing or unsafe durable relay configuration.
	ErrInvalidConfig = errors.New("crdt durable: invalid configuration")
	// ErrUnauthorized reports authentication or authorization failure.
	ErrUnauthorized = errors.New("crdt durable: unauthorized")
	// ErrConflictingDot reports a retry that reuses an existing Dot with a
	// different canonical payload. The existing binding remains authoritative.
	ErrConflictingDot = errors.New("crdt durable: conflicting dot")
	// ErrStoreFull reports that retaining another event would exceed the
	// configured operation-log budget. The server does not evict history.
	ErrStoreFull = errors.New("crdt durable: operation log limit reached")
	// ErrReplayUnavailable reports an invalid cursor or a replay that exceeds
	// the configured bounded replay window. Callers must bootstrap from a
	// validated application checkpoint instead of accepting a partial replay.
	ErrReplayUnavailable = errors.New("crdt durable: replay unavailable")
	// ErrCorruptStore reports damaged or internally inconsistent durable data.
	// The relay fails closed rather than guessing which operation to omit.
	ErrCorruptStore = errors.New("crdt durable: corrupt store")
	// ErrClosed reports use of a closed client or store.
	ErrClosed = errors.New("crdt durable: closed")
	// ErrQueueFull reports a bounded peer or client queue that cannot accept
	// another message without unbounded memory growth.
	ErrQueueFull = errors.New("crdt durable: queue full")
)

Functions

func DecodeChange added in v1.0.24

func DecodeChange(data []byte, maxMessageBytes, maxActorBytes int) (replica.Dot, []byte, error)

DecodeChange decodes a bounded durable-log envelope. Storage providers must construct a replica.Change with the expected manifest and policy before returning this data to a relay.

func EncodeChange added in v1.0.24

func EncodeChange(change replica.Change) ([]byte, error)

EncodeChange produces the canonical durable-log envelope for one validated CRDT change. The envelope is not authentication; callers still bind it to an authenticated manifest and actor at their transport boundary.

Types

type AppendResult added in v1.0.24

type AppendResult struct {
	Event     Event
	Duplicate bool
}

AppendResult records the outcome of one idempotent log append. A duplicate Dot is safe only when the store verified that its canonical payload is identical to the existing binding.

type Authenticate

type Authenticate func(*http.Request) (Peer, error)

Authenticate authenticates a request before the WebSocket upgrade.

type Authorize

type Authorize func(Peer, replica.Manifest, replica.Dot) error

Authorize binds a proposed CRDT change to the authenticated peer and exact manifest. At minimum it must prevent a peer from publishing another actor.

type AuthorizeSubscription

type AuthorizeSubscription func(Peer, replica.Manifest) error

AuthorizeSubscription controls replay/live-event access independently from write authorization.

type ClientConfig

type ClientConfig struct {
	Header              http.Header
	HTTPClient          *http.Client
	Policy              crdt.ProtocolPolicy
	MaxMessageBytes     int
	MaxActorBytes       int
	MaxQueuedChanges    int
	HandshakeTimeout    time.Duration
	WriteTimeout        time.Duration
	MinReconnectBackoff time.Duration
	MaxReconnectBackoff time.Duration
	Cursor              uint64
	OnEvent             func(Event) error
}

ClientConfig configures a reconnecting durable WebSocket client. OnEvent must durably install the concrete CRDT state and delivery frontier before it returns nil. Its transaction must also record event.Sequence as the resume cursor and settle any matching application outbox row.

type Config

type Config struct {
	Store                 Log
	Groups                []*Group
	Authenticate          Authenticate
	Authorize             Authorize
	AuthorizeSubscription AuthorizeSubscription
	OriginPatterns        []string
	MaxMessageBytes       int
	MaxActorBytes         int
	MaxQueuedEvents       int
	MaxQueuedBytes        int
	MaxReplayEvents       int
	MaxReplayBytes        int
	HandshakeTimeout      time.Duration
	WriteTimeout          time.Duration
	// Telemetry receives bounded, payload-free operational events for
	// handshake, replay, and append outcomes. A nil Reporter is the default
	// and adds no reporting work to relay paths.
	Telemetry *telemetry.Reporter
}

Config configures an authenticated durable WebSocket relay. Store and all authorization callbacks are required; the handler never starts a listener.

type Event

type Event struct {
	Sequence uint64
	Change   replica.Change
}

Event is one committed transport-log entry. Sequence is strictly increasing within its group and is the only valid durable replay cursor.

type Group

type Group struct {
	// contains filtered or unexported fields
}

Group owns the manifest, validation boundary, and live subscribers for one durable operation log. It does not own concrete application CRDT state.

func NewGroup

func NewGroup(config GroupConfig) (*Group, error)

NewGroup validates one immutable-by-convention manifest and requires a state-independent concrete CRDT validator.

func (*Group) Manifest

func (group *Group) Manifest() replica.Manifest

Manifest returns the group's immutable-by-convention manifest.

type GroupConfig

type GroupConfig struct {
	Manifest replica.Manifest
	Policy   crdt.ProtocolPolicy
	Validate Validate
}

GroupConfig defines one manifest-bound durable transport group.

type Handler

type Handler struct {
	// contains filtered or unexported fields
}

Handler is safe to mount into an application-owned HTTP server. It exposes only GET /ws and requires Subprotocol on every accepted connection.

func NewHandler

func NewHandler(config Config) (*Handler, error)

NewHandler validates a complete, bounded durable-relay configuration.

func (*Handler) ServeHTTP

func (handler *Handler) ServeHTTP(writer http.ResponseWriter, request *http.Request)

ServeHTTP exposes a single durable WebSocket endpoint at /ws.

type Log added in v1.0.24

type Log interface {
	Append(groupID string, change replica.Change) (AppendResult, error)
	Replay(groupID string, after, maxEvents, maxBytes uint64, manifest replica.Manifest, policy crdt.ProtocolPolicy, maxMessageBytes, maxActorBytes int) ([]Event, uint64, error)
	Closed() bool
}

Log is the durable-relay storage contract. Implementations must make Append atomic: a new event, its group-local sequence, the Dot-to-canonical-payload binding, and capacity accounting either all become durable or none do.

Replay must return a contiguous complete suffix or ErrReplayUnavailable; returning a prefix silently would let a receiver advance its cursor past missing CRDT changes. Implementations validate stored bytes against the manifest and policy supplied by the relay before returning them.

The relay never closes a Log. Its owner controls connection lifetime so a shared PostgreSQL or Redis client pool can serve more than one handler.

type Peer

type Peer struct {
	ID string
}

Peer is an authenticated application identity. ID must be stable and must never be copied from a client-controlled CRDT actor identifier.

type ReconnectClient

type ReconnectClient struct {
	// contains filtered or unexported fields
}

ReconnectClient reconnects with an application-provided durable cursor. Its in-memory queue is deliberately bounded and is not a replacement for an application outbox; persist an outgoing change before calling Publish.

func NewReconnectClient

func NewReconnectClient(endpoint string, manifest replica.Manifest, config ClientConfig) (*ReconnectClient, error)

NewReconnectClient validates configuration without making a network call.

func (*ReconnectClient) Cursor

func (client *ReconnectClient) Cursor() uint64

Cursor reports the highest event whose OnEvent callback succeeded during the current process. On restart, supply the cursor loaded from the same durable application transaction as the CRDT state/frontier.

func (*ReconnectClient) Err

func (client *ReconnectClient) Err() error

Err returns the last transient session error observed by Run. A successful handshake clears it; callers still own logging and operational policy.

func (*ReconnectClient) Publish

func (client *ReconnectClient) Publish(ctx context.Context, change replica.Change) error

Publish validates and queues one change for the next connected session. It only confirms bounded in-memory acceptance. The caller must retain its own durable outbox until it observes the echoed committed Event in OnEvent.

func (*ReconnectClient) Run

func (client *ReconnectClient) Run(ctx context.Context) error

Run maintains sessions until ctx is cancelled. It returns ErrReplayUnavailable without retrying because accepting a partial replay is unsafe; the caller must bootstrap a validated checkpoint first.

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store is a bbolt-backed, single-writer operation log. bbolt enforces an exclusive file lock; deployments must still schedule only one active relay process for a data file.

func OpenStore

func OpenStore(path string, config StoreConfig) (*Store, error)

OpenStore opens or creates a durable operation log at path with mode 0600. The parent directory must already be owned and protected by the host.

func (*Store) Append

func (store *Store) Append(groupID string, change replica.Change) (AppendResult, error)

Append transactionally binds a Dot to its canonical envelope and allocates the next group-local sequence for new data. The caller must validate the concrete CRDT delta before invoking Append.

func (*Store) Close

func (store *Store) Close() error

Close releases the database lock. Calls after Close fail with ErrClosed.

func (*Store) Closed added in v1.0.24

func (store *Store) Closed() bool

Closed reports whether Close has completed or is in progress. It allows a Handler to fail closed without taking ownership of the store's lifetime.

func (*Store) Replay

func (store *Store) Replay(groupID string, after, maxEvents, maxBytes uint64, manifest replica.Manifest, policy crdt.ProtocolPolicy, maxMessageBytes, maxActorBytes int) ([]Event, uint64, error)

Replay atomically reads every event after after in sequence order. It fails rather than returning a prefix when the caller's explicit replay budget cannot cover the entire missed suffix.

type StoreConfig

type StoreConfig struct {
	MaxEvents   uint64
	MaxBytes    uint64
	OpenTimeout time.Duration
}

StoreConfig bounds retained canonical event data per replication group. Both limits are required: durable replay must apply an explicit overload policy rather than retaining unbounded history.

type Validate

type Validate func([]byte) error

Validate checks a concrete CRDT delta before it is persisted or relayed. It must use application-selected bounds, make no application-state change, and return an error for an invalid payload. A frame checksum alone is not a sufficient semantic validator.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL