persist

package
v1.14.0 Latest Latest
Warning

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

Go to latest
Published: Jul 16, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package persist defines the storage contract for ygo documents and provides helpers that turn stored update logs back into live Docs.

The package itself is storage-engine-agnostic. Reference implementations live under sub-packages: internal/persist/sqlite is the pure-Go modernc.org/sqlite backing. Implementations are expected to provide an append-only log of opaque V1 update bytes keyed by docName, plus a compaction primitive (Flush) that squashes the log into a single snapshot update.

Backends may additionally implement VersionStore (see versions.go) to provide named point-in-time document versions that live independently of the update log: history survives Flush and ClearDocument, and pruning history never touches live state.

The wire-format guarantee is one-way: bytes that go in via StoreUpdate are the bytes that come out via GetUpdates. The persist layer never inspects, decodes, re-encodes, or splits updates; it treats them as opaque blobs. This keeps the storage engine independent of the encoding-version evolution (V1 today, V2 later).

Compaction (Flush) is the one exception: the package-level Flush helper does decode-and-re-encode under the hood via the encoding layer, but the result is still a single opaque blob from the storage engine's perspective.

Index

Constants

This section is empty.

Variables

View Source
var ErrEmptyUpdate = errors.New("persist: update blob is empty")

ErrEmptyUpdate is returned by StoreUpdate when called with a zero-length update blob. A zero-length slice is the one unambiguous caller bug the persist layer rejects: it can never be a meaningful update, since the smallest valid V1 update is the two-byte empty update (a zero client-count varuint followed by the empty-delete-set terminator). The layer does not otherwise inspect blob structure — validating the encoding is the encoder's job — so that two-byte empty update is itself accepted and stored verbatim.

View Source
var ErrUnknownDocument = errors.New("persist: unknown document")

ErrUnknownDocument is returned by SaveVersion when docName has no stored updates: an empty document has no state worth versioning.

View Source
var ErrVersionNotFound = errors.New("persist: version not found")

ErrVersionNotFound is returned when a (docName, versionID) pair does not exist.

Functions

func GetDiff

func GetDiff(ctx context.Context, s Store, docName string, remoteSV []byte) ([]byte, error)

GetDiff returns the V1 update bytes carrying the blocks in docName's persisted state that the remote (per remoteSV) does not yet have. A nil or empty remoteSV is treated as the empty state vector — emit everything.

Returns nil for an unknown document.

remoteSV is the V1-wire-encoded form (the same shape that EncodeStateVector produces). Decoding happens here so callers can pass bytes straight from a sync-protocol message.

func GetStateVector

func GetStateVector(ctx context.Context, s Store, docName string) ([]byte, error)

GetStateVector returns the V1-encoded state vector of the persisted state for docName. Returns nil (not empty bytes) for an unknown document so callers can distinguish never-seen from known-but-empty (the latter is impossible — an empty doc has no stored updates and reports DocumentExists=false).

Implementation note: today this replays every update via LoadDoc to build the state vector. A future optimisation could maintain the SV in storage alongside the update log so reads avoid the replay cost; tracked in tech-debt.md. The current pass favours correctness over throughput.

func LoadDoc

func LoadDoc(ctx context.Context, s Store, docName string, opts doc.Options) (*doc.Doc, error)

LoadDoc reconstructs a Doc by replaying every stored update for docName in insertion order. Returns a fresh empty Doc when the document has no stored updates.

The returned Doc owns its own ClientID (a fresh random one unless overridden by opts.ClientID). Replayed updates do not affect the local client's clock; they integrate into the existing client lists the source documents produced.

func LoadVersion added in v1.1.0

func LoadVersion(ctx context.Context, vs VersionStore, docName string, versionID int64, opts doc.Options) (*doc.Doc, error)

LoadVersion reconstructs the document as it was at the given version. The returned Doc is independent of the live log; mutating it does not affect stored state.

func MergeUpdates

func MergeUpdates(updates [][]byte) ([]byte, error)

MergeUpdates decodes every blob in updates in order, applies them to a fresh Doc, and returns a single V1 update blob equivalent to the merged state. Returns nil when updates is empty so the caller can treat the result as a no-op trigger.

Backing implementations use this inside their Flush transaction: read updates, MergeUpdates them, delete the originals, insert the snapshot — all under one storage-level lock so concurrent writers see either the pre-flush log or the post-flush single blob, never a torn intermediate state.

Error handling: on any decode or apply failure the caller MUST NOT proceed with the destructive replace — the original updates are still the canonical state. Returning the error preserves that contract; the caller should abort the storage transaction.

func PruneVersions added in v1.1.0

func PruneVersions(ctx context.Context, vs VersionStore, docName string, keep int) (int, error)

PruneVersions deletes all but the newest keep versions of docName and returns how many were removed. keep <= 0 removes every version. Idempotent: a crash mid-prune leaves extra versions behind, and a re-run removes them; the live log is never touched.

func SaveVersion added in v1.1.0

func SaveVersion(ctx context.Context, s VersionedStore, docName, label string) (int64, error)

SaveVersion captures the current persisted state of docName as a new version labelled label and returns the version ID. The state is the merge of the live update log at call time; updates that land concurrently with the read may or may not be included (the version is a point-in-time capture, not a barrier).

Returns ErrUnknownDocument when docName has no stored updates.

Types

type Store

type Store interface {
	StoreUpdate(ctx context.Context, docName string, update []byte) error
	GetUpdates(ctx context.Context, docName string) ([][]byte, error)
	Flush(ctx context.Context, docName string) error
	DocumentExists(ctx context.Context, docName string) (bool, error)
	ListDocuments(ctx context.Context) ([]string, error)
	ClearDocument(ctx context.Context, docName string) error
	Close() error
}

Store is the persistence contract every backing implementation satisfies. Implementations must be safe for concurrent use from multiple goroutines.

Method contracts:

  • StoreUpdate appends an opaque update blob to docName's log. Empty blobs are rejected with ErrEmptyUpdate. The blob is written atomically; on error the log is unchanged.

  • GetUpdates returns every update blob stored for docName, in insertion order. Returns (nil, nil) — an empty slice and no error — for an unknown document.

  • Flush replaces all updates for docName with a single snapshot update equivalent to applying them all in order to a fresh Doc and re-encoding. Idempotent: calling on a doc with zero or one updates is a no-op. Flush is atomic: a reader observes either the pre-flush log or the single post-flush blob, never a torn state, and no data is lost. A concurrent StoreUpdate is serialized against it — the writer blocks until Flush commits, bounded by any backend busy timeout, past which, under sustained write contention, Flush may return a transient error the caller can retry.

  • DocumentExists reports whether docName has any stored updates.

  • ListDocuments returns the names of every document with at least one stored update. Order is implementation-defined.

  • ClearDocument removes every update for docName. Idempotent on unknown documents.

  • Close releases backing resources. Safe to call more than once; calls after the first return nil.

type VersionInfo added in v1.1.0

type VersionInfo struct {
	// ID is the storage-assigned identifier, monotonically increasing
	// per backend (not per document). Higher ID = newer version.
	ID int64
	// Label is the optional caller-supplied name ("before-migration",
	// "v2 draft"). Not unique; may be empty.
	Label string
	// CreatedAt is the backend-stamped creation time (UTC).
	CreatedAt time.Time
	// Size is the state blob length in bytes.
	Size int64
}

VersionInfo is the metadata of one stored document version. The state blob itself is fetched separately via GetVersionState so listing stays cheap for documents with many large versions.

type VersionStore added in v1.1.0

type VersionStore interface {
	SaveVersion(ctx context.Context, docName, label string, state []byte) (int64, error)
	ListVersions(ctx context.Context, docName string) ([]VersionInfo, error)
	GetVersionState(ctx context.Context, docName string, versionID int64) ([]byte, error)
	DeleteVersion(ctx context.Context, docName string, versionID int64) error
	RestoreVersion(ctx context.Context, docName string, versionID int64) error
}

VersionStore is the optional versioned-history extension of Store. A backend that implements it can capture named point-in-time versions of a document independent of the live update log: the live log can be flushed, compacted, or cleared without touching stored versions, and versions can be pruned without touching the live log.

Method contracts:

  • SaveVersion stores state (a self-contained V1 update blob carrying the full document state) as a new version of docName and returns its ID. Empty state is rejected with ErrEmptyUpdate.

  • ListVersions returns the metadata of every version of docName, oldest first. Returns (nil, nil) for a document with no versions.

  • GetVersionState returns the state blob of one version. ErrVersionNotFound if the (docName, versionID) pair is unknown.

  • DeleteVersion removes one version. Idempotent: deleting an unknown version is a no-op.

  • RestoreVersion replaces docName's LIVE update log with the version's state blob, atomically from the perspective of concurrent readers and writers. ErrVersionNotFound if unknown.

VersionStore deliberately does not embed Store so the package's v1.0 Store contract stays frozen; backends implement both, and helpers that need both take a VersionedStore.

type VersionedStore added in v1.1.0

type VersionedStore interface {
	Store
	VersionStore
}

VersionedStore is a backend that provides both the live update log and versioned history. The sqlite sub-package implements it.

Directories

Path Synopsis
Package sqlite is the reference persist.Store implementation backed by modernc.org/sqlite.
Package sqlite is the reference persist.Store implementation backed by modernc.org/sqlite.

Jump to

Keyboard shortcuts

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