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
- Variables
- func DecodeChange(data []byte, maxMessageBytes, maxActorBytes int) (replica.Dot, []byte, error)
- func EncodeChange(change replica.Change) ([]byte, error)
- type AppendResult
- type Authenticate
- type Authorize
- type AuthorizeSubscription
- type ClientConfig
- type Config
- type Event
- type Group
- type GroupConfig
- type Handler
- type Log
- type Peer
- type ReconnectClient
- type RevalidateSubscription
- type StateVectorLog
- type Store
- func (store *Store) Append(groupID string, change replica.Change) (AppendResult, error)
- func (store *Store) CatchUp(groupID string, vector replica.Frontier, maxEvents, maxBytes uint64, ...) ([]Event, uint64, error)
- func (store *Store) Close() error
- func (store *Store) Closed() bool
- func (store *Store) Replay(groupID string, after, maxEvents, maxBytes uint64, manifest replica.Manifest, ...) ([]Event, uint64, error)
- type StoreConfig
- type Validate
Constants ¶
const StateVectorSubprotocol = "crdt-durable-v2"
StateVectorSubprotocol identifies the optional state-vector catch-up protocol. A v2 peer proves only its own installed Dot prefixes; the vector is neither authentication, a receipt, nor permission to compact tombstones.
const Subprotocol = "crdt-durable-v1"
Subprotocol identifies the stable cursor-resume durable relay protocol. It is independent from CRDT frame versions and extensions.Subprotocol.
Variables ¶
var ( // ErrInvalidConfig reports a missing or unsafe durable relay configuration. ErrInvalidConfig = errors.New("crdt durable: invalid configuration") 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") // 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") // calculate a complete bounded suffix for a durable delivery frontier. // Callers must fall back to a valid cursor replay or bootstrap from a // validated application checkpoint; accepting a partial catch-up is unsafe. ErrStateVectorUnavailable = errors.New("crdt durable: state-vector catch-up 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
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
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
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 ¶
Authenticate authenticates a request before the WebSocket upgrade.
type Authorize ¶
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 ¶
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
MaxStateVectorEntries int
HandshakeTimeout time.Duration
WriteTimeout time.Duration
PingInterval time.Duration
PingTimeout time.Duration
MinReconnectBackoff time.Duration
MaxReconnectBackoff time.Duration
Cursor uint64
// StateVector returns the contiguous frontier from the same durable
// application checkpoint as the CRDT state. When set, the client requests
// the v2 bounded missing-Dot catch-up protocol on every reconnect.
StateVector func() replica.Frontier
// OnCatchUp persists the state/frontier after all v2 catch-up events and
// records highWater as the durable cursor in the same transaction. It is
// required with StateVector because a skipped log event is never proof that
// its payload was installed.
OnCatchUp func(highWater uint64) error
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
// RevalidateSubscription is optional. When provided, it runs before every
// heartbeat so the host can close revoked or expired long-lived sessions.
RevalidateSubscription RevalidateSubscription
OriginPatterns []string
MaxMessageBytes int
MaxActorBytes int
MaxQueuedEvents int
MaxQueuedBytes int
MaxReplayEvents int
MaxReplayBytes int
MaxStateVectorEntries int
HandshakeTimeout time.Duration
WriteTimeout time.Duration
PingInterval time.Duration
PingTimeout 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 ¶
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.
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 ¶
NewHandler validates a complete, bounded durable-relay configuration.
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.
type RevalidateSubscription ¶ added in v1.0.31
RevalidateSubscription is invoked periodically for an established subscription. Hosts use it to apply session expiry or revocation policy to long-lived connections; a failure closes only that connection.
type StateVectorLog ¶ added in v1.0.31
type StateVectorLog interface {
Log
CatchUp(groupID string, vector replica.Frontier, maxEvents, maxBytes uint64, manifest replica.Manifest, policy crdt.ProtocolPolicy, maxMessageBytes, maxActorBytes int) ([]Event, uint64, error)
}
StateVectorLog is an optional Log capability for bounded recovery from a replica.Frontier. CatchUp must return every event whose Dot is not covered by vector, ordered by durable sequence, or return an error. It must never return a convenient partial result.
The base Log contract intentionally remains cursor-based so existing Redis, PostgreSQL, and host implementations continue to work with v1 clients.
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 ¶
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) CatchUp ¶ added in v1.0.31
func (store *Store) CatchUp(groupID string, vector replica.Frontier, maxEvents, maxBytes uint64, manifest replica.Manifest, policy crdt.ProtocolPolicy, maxMessageBytes, maxActorBytes int) ([]Event, uint64, error)
CatchUp returns the complete bounded set of events not covered by vector. The vector represents only contiguous locally installed Dot prefixes. A vector ahead of retained log data, or a required suffix beyond the caller's explicit limits, fails closed rather than silently declaring a client synced.
func (*Store) Closed ¶ added in v1.0.24
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 ¶
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.