storage

package module
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Aug 30, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

README

storage

Neutral, stdlib-only storage contracts for durable session/workspace persistence.

storage defines five small primitives:

  • Ledger — an append-only, CAS-sequenced record log.
  • Leaser — renewable single-writer ownership fenced by a monotonic epoch.
  • KV — revision-CAS key/value metadata.
  • Blobs — content-addressed immutable bytes.
  • OrderedIndex — durable records with immutable acceptance order and current ranked and due views.

It also provides typed errors, the AppendDefinite ambiguity resolver, an in-memory reference backend (memstore), and reusable backend conformance suites (storetest). Consumers depend only on these interfaces; concrete backends (fsstore, natsstore, …) live in their own modules.

Composition

The two constructors deliberately have different compatibility contracts. NewComposite assembles the legacy four primitives (Ledger, Leaser, KV, and Blobs) and leaves OrderedIndex nil; it never synthesizes an index for legacy callers. NewCompositeWithOrderedIndex requires and wires all five primitives, so a consumer that needs ordered records can fail at composition time. memstore.New() returns a complete five-primitive composite for examples and tests.

This module has zero third-party dependencies and will keep it that way.

Optional Blob reader lifecycle

The planned v0.6.0 release leaves the base Blobs contract unchanged and adds the compatible BlobReaderLifecycle capability. Providers opt in by publishing a positive reader-close bound and guaranteeing concrete non-nil readers, concurrent-safe Read and Close, stable idempotent Close classification, non-EOF terminal reads after Close, and bounded provider-controlled waits. Consumers that require bounded shutdown can test for this capability without excluding existing Blobs implementations from other uses.

Documentation

Overview

Package storage defines five neutral storage primitives — Ledger (an append-only, CAS-sequenced record log), Leaser (a single-writer epoch lease), KV (revision-CAS metadata), Blobs (content-addressed immutable bytes), and OrderedIndex (durable records with immutable acceptance order and current ranked and due views) — plus a typed error taxonomy, ValidateName, and the AppendDefinite ambiguity resolver.

Names and keys are canonical by construction (see ValidateName), so no two valid names alias one backend location. Every backend must accept ledger payloads and KV values up to 1 MiB; larger payloads are the engine's responsibility to offload to Blobs.

Index

Constants

View Source
const MaxOrderedPageLimit = 1000

MaxOrderedPageLimit is the inclusive maximum number of records a single OrderedIndex listing may request.

View Source
const MaxOrderedValueBytes = 1 << 20

MaxOrderedValueBytes is the largest value an OrderedIndex implementation must accept. Callers that need larger values must store the bulk content in Blobs and keep a reference in Value.

View Source
const MaxStableKeyBytes = 256

MaxStableKeyBytes is the inclusive maximum length, in UTF-8 bytes, of an OrderedID StableKey.

Variables

This section is empty.

Functions

func AppendDefinite

func AppendDefinite(ctx context.Context, l Ledger, name string, expected uint64, payload []byte) error

AppendDefinite turns any Append into a definite outcome. On AmbiguousError it retries the identical append once; on conflict (from either attempt) it reads the record at expected+1 and byte-compares: equal payload means the original landed (success); a foreign payload means this writer has been fenced (ConflictError). A second ambiguous outcome surfaces AmbiguousError unresolved.

func ValidateDue added in v0.5.0

func ValidateDue(due Due) error

ValidateDue validates Due's discriminated state and its canonical NotDue representation.

func ValidateName

func ValidateName(name string) error

ValidateName returns a non-nil *InvalidNameError if name violates the storage name grammar, or nil if it is valid. The grammar is canonical by construction: empty, ".", and ".." segments are unrepresentable, so no two valid names alias one backend location.

A valid name is 1..512 bytes of one or more segments joined by single '/', with no leading, trailing, or doubled '/'. Each segment starts with a byte in [a-z0-9] and continues with bytes in [a-z0-9_.-].

func ValidateOrderedID added in v0.5.0

func ValidateOrderedID(id OrderedID) error

ValidateOrderedID validates the storage-name components of id and its opaque StableKey. Namespace and OrderingScope deliberately retain ValidateName's canonical path grammar; StableKey deliberately does not.

func ValidateOrderedLimit added in v0.5.0

func ValidateOrderedLimit(limit int) error

ValidateOrderedLimit reports whether limit is a valid OrderedIndex page limit.

func ValidateOrderedRecord added in v0.5.0

func ValidateOrderedRecord(record OrderedRecord) error

ValidateOrderedRecord validates an OrderedRecord's externally observable representation. Providers use it when constructing a record snapshot and callers may use it before asserting a returned record in their own code.

func ValidateOrderedValue added in v0.5.0

func ValidateOrderedValue(value []byte) error

ValidateOrderedValue reports whether value falls within OrderedIndex's required value capacity. Nil and empty values are valid.

func ValidateStableKey added in v0.5.0

func ValidateStableKey(key StableKey) error

ValidateStableKey reports whether key is a valid opaque StableKey.

Types

type AmbiguousError

type AmbiguousError struct {
	Name     string
	Expected uint64
	Cause    error
}

AmbiguousError reports a ledger append whose acknowledgement was lost or timed out: the record may or may not have been committed at Expected. Cause carries the underlying transport/timeout error and may be nil.

func (*AmbiguousError) Error

func (e *AmbiguousError) Error() string

func (*AmbiguousError) Unwrap

func (e *AmbiguousError) Unwrap() error

Unwrap returns the underlying cause (possibly nil).

type AppendVerifyError

type AppendVerifyError struct {
	Name  string
	Seq   uint64
	Cause error
}

AppendVerifyError reports that AppendDefinite could not read the record at Seq to resolve an ambiguous/conflicting append. Fail closed: the caller must treat the append outcome as unknown. Cause carries the underlying read failure (the Read error or the cursor's non-EOF Next error) and is exposed via Unwrap.

func (*AppendVerifyError) Error

func (e *AppendVerifyError) Error() string

func (*AppendVerifyError) Unwrap

func (e *AppendVerifyError) Unwrap() error

Unwrap returns the underlying read failure so callers can errors.Is/As it.

type BlobConflictError

type BlobConflictError struct {
	Key string
}

BlobConflictError reports a blob Put where Key already exists with different content (blob writes are content-addressed and immutable per key).

func (*BlobConflictError) Error

func (e *BlobConflictError) Error() string

type BlobNotFoundError

type BlobNotFoundError struct {
	Key string
}

BlobNotFoundError reports that a blob is absent at Key.

func (*BlobNotFoundError) Error

func (e *BlobNotFoundError) Error() string

type BlobReaderLifecycle added in v0.6.0

type BlobReaderLifecycle interface {
	Blobs
	BlobReaderCloseBound() time.Duration
}

BlobReaderLifecycle is an optional Blobs capability for providers whose Get readers support bounded shutdown. BlobReaderCloseBound returns a positive, documented upper bound for Close itself and for any active provider-controlled Read to return after Close begins. A successful Get returns a usable, concrete non-nil reader. Its Read and Close methods are safe to call concurrently. Close is idempotent with a stable success/failure classification: repeated calls either both succeed, or their errors are equivalent under errors.Is. After Close returns, every Read returns zero bytes and a non-nil error other than io.EOF; that terminal error is provider-specific.

type Blobs

type Blobs interface {
	Put(ctx context.Context, key string, r io.Reader) error
	Get(ctx context.Context, key string) (io.ReadCloser, error)
	Delete(ctx context.Context, key string) error
	List(ctx context.Context, prefix string) ([]string, error)
}

Blobs holds bulk immutable bytes (large-record offload; workspace snapshots). Put streams; keys are content-addressed by callers. Existing byte-identical content is a success/no-op; existing different content returns *BlobConflictError and leaves the original object unchanged. Delete is idempotent: deleting an absent key succeeds.

type Composite

type Composite struct {
	Ledger
	Leaser
	KV
	Blobs
	OrderedIndex OrderedIndex
}

Composite holds the storage primitives assembled where dependencies are wired, never inside engines. OrderedIndex remains a named field rather than an embedded primitive because Ledger, KV, and Blobs already have colliding method names when promoted together. OrderedIndex is the one field that may be nil — NewComposite leaves it so — which is what RequireOrderedIndex is for.

func NewComposite

func NewComposite(l Ledger, le Leaser, kv KV, bl Blobs) (*Composite, error)

NewComposite assembles the legacy four-primitive Composite, rejecting any nil primitive up front so a partially-wired backend fails at the composition root rather than at first use. It intentionally leaves OrderedIndex nil for backwards compatibility: existing callers do not need to change. SessionStore (and any other component that requires ordered records) is responsible for rejecting a Composite whose OrderedIndex is nil at its own composition boundary.

func NewCompositeWithOrderedIndex added in v0.5.0

func NewCompositeWithOrderedIndex(l Ledger, le Leaser, kv KV, bl Blobs, oi OrderedIndex) (*Composite, error)

NewCompositeWithOrderedIndex assembles all five storage primitives. Unlike NewComposite, it requires OrderedIndex so a component whose contract uses ordered records can fail during wiring rather than on its first operation.

func (*Composite) RequireOrderedIndex added in v0.5.0

func (c *Composite) RequireOrderedIndex() (OrderedIndex, error)

RequireOrderedIndex returns the composite's OrderedIndex, or *IncompleteCompositeError naming it as missing. NewComposite deliberately leaves the field nil, so any consumer that needs ordered records has to check; this gives that check one typed, non-panicking form instead of an ad-hoc nil comparison at each use site. A nil *Composite is treated as a composite that has no ordered index rather than dereferenced.

type ConflictError

type ConflictError struct {
	Name     string
	Expected uint64
}

ConflictError reports a ledger compare-and-swap that failed because the caller appended at the wrong expected sequence (the head had moved).

func (*ConflictError) Error

func (e *ConflictError) Error() string

type Cursor

type Cursor interface {
	Next(ctx context.Context) (Record, error) // io.EOF when drained
	Close() error
}

type Due added in v0.5.0

type Due struct {
	State      DueState
	UnixMillis int64
}

Due is the mutable due-time state of an ordered record.

type DueCursor added in v0.5.0

type DueCursor string

DueCursor is a provider-issued, opaque, versioned continuation token for ListDue. A nonempty token is valid only for the exact namespace, due bound, and due query that issued it. ListDue must return *InvalidOrderedCursorError with Kind DueCursorKind for a malformed token, an unknown token version, a token issued for another cursor kind, or a query mismatch. A cursor conveys position, not authority: a provider re-checks the namespace and due bound against the live request and never trusts them from the token.

type DuePage added in v0.5.0

type DuePage struct {
	Records    []OrderedRecord
	NextCursor DueCursor
}

DuePage is a page from ListDue. An empty NextCursor denotes an exhausted result set.

type DueState added in v0.5.0

type DueState uint8

DueState records whether an OrderedRecord participates in due-time listings.

const (
	// NotDue means a record is absent from ListDue results. Its UnixMillis must
	// be zero, which gives the non-due state one canonical representation.
	NotDue DueState = iota

	// DueAt means UnixMillis is an absolute UTC Unix timestamp in milliseconds
	// and the record participates in ListDue results.
	DueAt
)

type IncompleteCompositeError

type IncompleteCompositeError struct {
	Missing []string
}

IncompleteCompositeError reports that NewComposite or NewCompositeWithOrderedIndex was handed one or more nil primitives. Missing names them in field order (Ledger, Leaser, KV, Blobs, OrderedIndex) so the assembly site knows exactly which providers were not wired.

func (*IncompleteCompositeError) Error

func (e *IncompleteCompositeError) Error() string

type InvalidDueError added in v0.5.0

type InvalidDueError struct {
	Due  Due
	Rule string
}

InvalidDueError reports a Due state that is not canonical or uses an unknown discriminator.

func (*InvalidDueError) Error added in v0.5.0

func (e *InvalidDueError) Error() string

type InvalidNameError

type InvalidNameError struct {
	Name string
	Rule string
}

InvalidNameError reports a name that violates the storage grammar: one or more segments joined by single '/', each segment matching [a-z0-9][a-z0-9_.-]*, no leading/trailing '/', at most 512 bytes total.

func (*InvalidNameError) Error

func (e *InvalidNameError) Error() string

type InvalidOrderedCursorError added in v0.5.0

type InvalidOrderedCursorError struct {
	Kind         OrderedCursorKind
	Rule         OrderedCursorRule
	CursorLength uint16
}

InvalidOrderedCursorError reports a malformed, unknown-version, wrong-kind, or cross-query opaque OrderedIndex continuation cursor. Kind identifies the listing operation that rejected it. Rule is a safe classification. CursorLength is the raw token's bounded byte length; neither it nor Error exposes raw opaque cursor contents.

func NewInvalidOrderedCursorError added in v0.5.0

func NewInvalidOrderedCursorError(kind OrderedCursorKind, cursor string, rule OrderedCursorRule) *InvalidOrderedCursorError

NewInvalidOrderedCursorError constructs an error for cursor while retaining only its safe, bounded byte length. Providers must use it rather than placing raw cursor contents in an error or log message.

func (*InvalidOrderedCursorError) Error added in v0.5.0

func (e *InvalidOrderedCursorError) Error() string

type InvalidOrderedLimitError added in v0.5.0

type InvalidOrderedLimitError struct {
	Limit int
	Max   int
}

InvalidOrderedLimitError reports a List* limit outside the inclusive range 1..Max.

func (*InvalidOrderedLimitError) Error added in v0.5.0

func (e *InvalidOrderedLimitError) Error() string

type InvalidOrderedRecordError added in v0.5.0

type InvalidOrderedRecordError struct {
	ID   OrderedID
	Rule string
}

InvalidOrderedRecordError reports an OrderedRecord that violates an invariant that crosses multiple fields.

func (*InvalidOrderedRecordError) Error added in v0.5.0

func (e *InvalidOrderedRecordError) Error() string

type InvalidStableKeyError added in v0.5.0

type InvalidStableKeyError struct {
	Rule            string
	StableKeyLength uint16
}

InvalidStableKeyError reports an OrderedID StableKey that is empty, too long, or not valid UTF-8. Stable keys are opaque and are not constrained by the storage name grammar. StableKeyLength is the input's byte length capped at 65,535; the error never retains or renders the raw key.

func (*InvalidStableKeyError) Error added in v0.5.0

func (e *InvalidStableKeyError) Error() string

type KV

type KV interface {
	Get(ctx context.Context, key string) (val []byte, rev uint64, err error)
	Put(ctx context.Context, key string, expectedRev uint64, val []byte) (rev uint64, err error)
	Keys(ctx context.Context, prefix string) ([]string, error)
	Delete(ctx context.Context, key string) error
}

KV holds small CAS'd metadata (the session catalog). Revisions are per-key, strictly increasing; Put with expectedRev 0 requires the key to be absent.

type KeyNotFoundError

type KeyNotFoundError struct {
	Key string
}

KeyNotFoundError reports that a KV key is absent.

func (*KeyNotFoundError) Error

func (e *KeyNotFoundError) Error() string

type Lease

type Lease interface {
	Epoch() uint64
	Lost() <-chan struct{}             // closed once when ownership is lost (Release, expiry, takeover)
	Release(ctx context.Context) error // idempotent; may cross the network, so ctx bounds it
}

type LeaseHeldError

type LeaseHeldError struct {
	Name        string
	HolderEpoch uint64
}

LeaseHeldError reports an Acquire that was refused because another holder owns the lease at HolderEpoch.

func (*LeaseHeldError) Error

func (e *LeaseHeldError) Error() string

type LeaseLostError

type LeaseLostError struct {
	Name  string
	Epoch uint64
}

LeaseLostError reports a write attempted after the caller's lease at Epoch was lost or expired (fenced by a newer holder).

func (*LeaseLostError) Error

func (e *LeaseLostError) Error() string

type Leaser

type Leaser interface {
	Acquire(ctx context.Context, name string) (Lease, error)
}

Leaser grants renewable, exclusive, epoch-fenced ownership of a name. A provider maintains and renews a live grant by its native mechanism; Lease.Lost closes when it can no longer safely assert ownership, including Release, expiry, or takeover. Acquire fails with *LeaseHeldError while a live holder exists, and a later grant of the same name has a strictly greater epoch. Acquire and Release must remain correct across independent calls: no grant may depend on retaining one connection or session. Release is idempotent, including after loss, and cannot free a later holder.

type Ledger

type Ledger interface {
	Append(ctx context.Context, name string, expected uint64, payload []byte) error
	Read(ctx context.Context, name string, from uint64) (Cursor, error)
	Tip(ctx context.Context, name string) (uint64, error)
	Delete(ctx context.Context, name string) error
}

Ledger addresses many ledgers by name. Append commits payload as the record immediately after sequence `expected` (CAS on the tip; expected == 0 means the ledger must be empty). The committed record's seq is expected+1 by definition, so Append returns no sequence. Sequences are 1-based, contiguous, immutable.

Append outcomes are a tri-state:

  • nil — committed, definitely.
  • *ConflictError — something already occupies expected+1. Definite: the record did not land.
  • *AmbiguousError — the outcome is unknown (lost ack / lost COMMIT response). Only networked backends may return this; fs and memory never do.

Any other error is a definite failure (fail closed, tip unadvanced).

Edge semantics:

  • Absent == empty. A never-written (or deleted) ledger behaves as empty: Tip returns 0; Read yields an immediately-drained cursor; Append with expected 0 creates it implicitly; Delete of an absent ledger is a no-op success (idempotent).
  • Reads beyond the tip are empty, not errors. Any from > tip (including tip+1) yields a drained cursor. Cursors are bounded: they observe the tip as of Read and never tail later appends.
  • Payloads are caller-owned. A backend must not reuse or mutate Record.Payload after Next returns. Zero-length payloads are legal.
  • Listings are canonical: KV.Keys and Blobs.List return lexicographically ascending, duplicate-free results.

type OrderedAmbiguousError added in v0.5.0

type OrderedAmbiguousError struct {
	Operation OrderedOperation
	ID        OrderedID
	Cause     error
}

OrderedAmbiguousError reports a networked OrderedIndex mutation whose acknowledgement was lost or timed out, so the mutation may or may not have committed. Cause carries the underlying transport error and may be nil.

func (*OrderedAmbiguousError) Error added in v0.5.0

func (e *OrderedAmbiguousError) Error() string

func (*OrderedAmbiguousError) Unwrap added in v0.5.0

func (e *OrderedAmbiguousError) Unwrap() error

Unwrap returns the underlying cause (possibly nil).

type OrderedCursorKind added in v0.5.0

type OrderedCursorKind string

OrderedCursorKind identifies the query family that rejected a cursor.

const (
	// RankedCursorKind identifies a ListRanked continuation cursor.
	RankedCursorKind OrderedCursorKind = "ranked"

	// DueCursorKind identifies a ListDue continuation cursor.
	DueCursorKind OrderedCursorKind = "due"
)

func (OrderedCursorKind) String added in v0.5.0

func (k OrderedCursorKind) String() string

type OrderedCursorRule added in v0.5.0

type OrderedCursorRule uint8

OrderedCursorRule classifies why a provider rejected an opaque cursor. Its String form is fixed so error rendering never relies on provider-supplied token text.

const (
	// OrderedCursorMalformed reports a syntactically malformed token.
	OrderedCursorMalformed OrderedCursorRule = iota + 1

	// OrderedCursorUnknownVersion reports an unsupported token version.
	OrderedCursorUnknownVersion

	// OrderedCursorWrongKind reports a token issued for another cursor family.
	OrderedCursorWrongKind

	// OrderedCursorQueryMismatch reports a token bound to another query.
	OrderedCursorQueryMismatch
)

func (OrderedCursorRule) String added in v0.5.0

func (r OrderedCursorRule) String() string

type OrderedDeletedError added in v0.5.0

type OrderedDeletedError struct {
	ID OrderedID
}

OrderedDeletedError reports an Update attempted against a logical tombstone. Tombstones cannot be resurrected through OrderedIndex.

func (*OrderedDeletedError) Error added in v0.5.0

func (e *OrderedDeletedError) Error() string

type OrderedID added in v0.5.0

type OrderedID struct {
	Namespace     string
	OrderingScope string
	StableKey     StableKey
}

OrderedID identifies an ordered record. The (Namespace, OrderingScope) pair is the order scope: the unit within which Order strictly increases and is never reused. The same OrderingScope in another Namespace is a separate order scope with its own independent stream, though a provider may serve every scope from one shared underlying sequence, so order values are not comparable across scopes.

type OrderedIndex added in v0.5.0

type OrderedIndex interface {
	// Get validates id first, then returns the current record, including a
	// logical tombstone. An identity that has never been created returns
	// *OrderedRecordNotFoundError.
	Get(ctx context.Context, id OrderedID) (OrderedRecord, error)

	// Create validates id first and then atomically inspects that identity. If
	// it already exists, including as a tombstone, Create returns its canonical
	// stored record with created == false without validating the candidate
	// rankingScope, value, rank, or due state. Only when the identity is absent
	// does Create validate those candidate fields, assign revision 1, and
	// allocate a nonzero immutable order strictly greater than every order the
	// scope has allocated before. It need not be the next integer: allocation
	// may be sparse.
	Create(ctx context.Context, id OrderedID, rankingScope string, value []byte, rank Rank, due Due) (record OrderedRecord, created bool, err error)

	// Update validates id first. An absent record returns
	// *OrderedRecordNotFoundError and a tombstone returns *OrderedDeletedError,
	// in both cases regardless of expectedRevision. For a live record, Update
	// validates candidate Value, Rank, and Due before comparing expectedRevision:
	// an invalid candidate returns its validation error, while a valid stale
	// revision returns *OrderedRevisionConflictError. A valid match replaces
	// only Value, Rank, and Due and advances Revision exactly once. It cannot
	// change identity, ranking scope, or immutable order. If Revision cannot
	// advance, it returns *OrderedRevisionExhaustedError unchanged.
	Update(ctx context.Context, id OrderedID, expectedRevision uint64, value []byte, rank Rank, due Due) (OrderedRecord, error)

	// Delete validates id first. An absent record returns
	// *OrderedRecordNotFoundError. A tombstone returns its canonical existing
	// state regardless of expectedRevision, including a retry with the
	// pre-delete revision. For a live record, a stale expectedRevision returns
	// *OrderedRevisionConflictError; a matching revision advances exactly once,
	// sets Deleted, clears Rank to Rank{} and Due to Due{State: NotDue,
	// UnixMillis: 0}, and preserves Value, RankingScope, identity, and immutable
	// Order. If Revision cannot advance, it returns
	// *OrderedRevisionExhaustedError unchanged.
	Delete(ctx context.Context, id OrderedID, expectedRevision uint64) (OrderedRecord, error)

	// ListOrdered returns the immutable acceptance-order stream, including
	// tombstones, in ascending Order after the exclusive numeric afterOrder.
	// Passing zero starts at the beginning of the (Namespace, OrderingScope)
	// stream.
	ListOrdered(ctx context.Context, namespace string, orderingScope string, afterOrder uint64, limit int) (OrderedPage, error)

	// ListRanked returns current, nondeleted ranked records in descending
	// (rank, stable_key, ordering_scope) order. OrderingScope is consulted only
	// after the frozen (rank, stable_key) pair ties, making pagination total
	// without narrowing valid identities. after is a provider-issued opaque,
	// versioned, query-bound token. A malformed, unknown-version, wrong-kind,
	// or query-mismatched token returns *InvalidOrderedCursorError with Kind
	// RankedCursorKind.
	//
	// Pagination resumes from the frozen (rank, stable_key, ordering_scope)
	// tuple the cursor names, not from a snapshot of the result set. A record
	// whose rank changes between two pages therefore moves relative to that
	// frozen position: it is skipped if it moves to an already-passed position
	// and returned twice if it moves ahead of one. This is inherent to
	// keyset pagination over a live view; a sweep that must see every record
	// exactly once has to reconcile by identity, not by page.
	ListRanked(ctx context.Context, namespace string, rankingScope string, after RankedCursor, limit int) (RankedPage, error)

	// ListDue returns current, nondeleted DueAt records with UnixMillis no later
	// than dueAtOrBefore in ascending (due_at, stable_key, ordering_scope)
	// order. OrderingScope is consulted only after the frozen (due_at,
	// stable_key) pair ties. after is opaque and bound to namespace, the fixed
	// due bound, and this exact due query. A malformed, unknown-version,
	// wrong-kind, or query-mismatched token returns *InvalidOrderedCursorError
	// with Kind DueCursorKind.
	//
	// Like ListRanked, ListDue resumes from the frozen (due_at, stable_key,
	// ordering_scope) tuple the cursor names, so a record whose due time moves
	// across that position between pages is skipped or returned twice.
	ListDue(ctx context.Context, namespace string, dueAtOrBefore int64, after DueCursor, limit int) (DuePage, error)
}

OrderedIndex provides a durable record collection with immutable acceptance order plus current ranked and due views. Implementations validate relevant inputs at their public boundary using the validators below. Each method's validation, lookup, and CAS precedence is authoritative; this overview does not impose an order beyond those method-specific rules. Every method that accepts an OrderedID (Get, Create, Update, and Delete) validates it first with ValidateOrderedID before inspecting or mutating a record. A canceled context may return its ordinary context error. A networked mutation with an indeterminate outcome returns *OrderedAmbiguousError; local implementations never do. Nonempty ranked and due cursors are provider-issued opaque versioned tokens; implementations must fail closed with *InvalidOrderedCursorError of the matching cursor Kind if one is malformed, has an unknown version, has the wrong cursor kind, or does not bind to the exact request query.

All returned Records and their Value slices are snapshots owned by the caller. Implementations must copy caller Value before retaining it and must copy Value before returning records.

type OrderedOperation added in v0.5.0

type OrderedOperation string

OrderedOperation identifies a mutation whose outcome could be ambiguous.

const (
	// OrderedCreateOperation identifies Create.
	OrderedCreateOperation OrderedOperation = "create"

	// OrderedUpdateOperation identifies Update.
	OrderedUpdateOperation OrderedOperation = "update"

	// OrderedDeleteOperation identifies Delete.
	OrderedDeleteOperation OrderedOperation = "delete"
)

type OrderedPage added in v0.5.0

type OrderedPage struct {
	Records        []OrderedRecord
	NextAfterOrder uint64
}

OrderedPage is a page from ListOrdered. NextAfterOrder is zero when no rows were returned; otherwise it is the final immutable order in Records and may be passed as afterOrder to resume the acceptance-order stream.

type OrderedRecord added in v0.5.0

type OrderedRecord struct {
	ID           OrderedID
	RankingScope string
	Revision     uint64
	Order        uint64
	Due          Due
	Rank         Rank
	Value        []byte
	Deleted      bool
}

OrderedRecord is the complete state of one ordered index row. Revision and Order are provider-assigned: Revision is always nonzero; Create assigns 1, and every successful Update or live Delete advances it exactly once. Revision never wraps to zero: if a provider cannot advance the maximal uint64 it returns *OrderedRevisionExhaustedError without changing state. Order is nonzero, immutable, strictly increasing within its order scope, and never reused there — including after a tombstone. It is deliberately NOT required to be contiguous or 1-based: a provider may allocate order from a JetStream stream sequence or a shared SQL sequence, so orders may be sparse and may be shared across order scopes. Callers resume from an exclusive order cursor, for which density buys nothing, and must never infer a position, a count, or a scope from an order value. Callers own Value after it is returned; implementations must not retain or later mutate caller-owned Value slices.

type OrderedRecordNotFoundError added in v0.5.0

type OrderedRecordNotFoundError struct {
	ID OrderedID
}

OrderedRecordNotFoundError reports an ordered identity that has never been created. Tombstoned records are distinct from absent records.

func (*OrderedRecordNotFoundError) Error added in v0.5.0

type OrderedRevisionConflictError added in v0.5.0

type OrderedRevisionConflictError struct {
	ID               OrderedID
	ExpectedRevision uint64
	ActualRevision   uint64
}

OrderedRevisionConflictError reports an OrderedIndex compare-and-swap whose current revision did not equal ExpectedRevision. ActualRevision is the current revision observed by a backend when it can determine it; a backend that cannot safely disclose it leaves it zero.

func (*OrderedRevisionConflictError) Error added in v0.5.0

type OrderedRevisionExhaustedError added in v0.5.0

type OrderedRevisionExhaustedError struct {
	ID       OrderedID
	Revision uint64
}

OrderedRevisionExhaustedError reports a mutation that cannot advance a live OrderedRecord revision without overflowing uint64. The provider leaves the record unchanged.

func (*OrderedRevisionExhaustedError) Error added in v0.5.0

type OrderedValueTooLargeError added in v0.5.0

type OrderedValueTooLargeError struct {
	Size int
	Max  int
}

OrderedValueTooLargeError reports an OrderedIndex value over the supported maximum size in bytes.

func (*OrderedValueTooLargeError) Error added in v0.5.0

func (e *OrderedValueTooLargeError) Error() string

type PathReporter

type PathReporter interface {
	StoragePaths() []string
}

PathReporter is an optional capability implemented by providers that persist data on the local filesystem. StoragePaths returns the provider's canonical local roots in a caller-owned slice. Remote and in-memory providers need not implement it.

type Rank added in v0.5.0

type Rank struct {
	Ranked bool
	Value  int64
}

Rank is the mutable rank state of an ordered record. A record with Ranked false is absent from ListRanked results.

type RankedCursor added in v0.5.0

type RankedCursor string

RankedCursor is a provider-issued, opaque, versioned continuation token for ListRanked. A nonempty token is valid only for the exact namespace, ranking scope, and ranked query that issued it. ListRanked must return *InvalidOrderedCursorError with Kind RankedCursorKind for a malformed token, an unknown token version, a token issued for another cursor kind, or a query mismatch. A cursor conveys position, not authority: a provider re-checks the namespace and ranking scope against the live request and never trusts them from the token.

type RankedPage added in v0.5.0

type RankedPage struct {
	Records    []OrderedRecord
	NextCursor RankedCursor
}

RankedPage is a page from ListRanked. An empty NextCursor denotes an exhausted result set.

type Record

type Record struct {
	Seq     uint64
	Payload []byte
}

type RecordNotFoundError

type RecordNotFoundError struct {
	Name string
	Seq  uint64
}

RecordNotFoundError reports that a ledger has no record at the requested Seq.

func (*RecordNotFoundError) Error

func (e *RecordNotFoundError) Error() string

type StableKey added in v0.5.0

type StableKey string

StableKey is the opaque, stable identity component of an ordered record. It is deliberately not a storage name or path: any valid UTF-8 value from 1 through MaxStableKeyBytes bytes is accepted, including slashes, uppercase letters, punctuation, and Unicode.

Directories

Path Synopsis
Package memstore is the in-memory reference backend for storage's five primitives.
Package memstore is the in-memory reference backend for storage's five primitives.
Package storetest provides backend-conformance suites for the five storage primitives — Ledger, Leaser, KV, Blobs, and OrderedIndex.
Package storetest provides backend-conformance suites for the five storage primitives — Ledger, Leaser, KV, Blobs, and OrderedIndex.

Jump to

Keyboard shortcuts

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