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.
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 ¶
- Variables
- func GetDiff(ctx context.Context, s Store, docName string, remoteSV []byte) ([]byte, error)
- func GetStateVector(ctx context.Context, s Store, docName string) ([]byte, error)
- func LoadDoc(ctx context.Context, s Store, docName string, opts doc.Options) (*doc.Doc, error)
- func MergeUpdates(updates [][]byte) ([]byte, error)
- type Store
Constants ¶
This section is empty.
Variables ¶
var ErrEmptyUpdate = errors.New("persist: update blob is empty")
ErrEmptyUpdate is returned by StoreUpdate when called with a zero-length update blob. Empty updates are rejected because they signal a caller bug: a valid V1 update always carries at least the client-count varuint (one byte for zero) plus the empty-delete-set terminator (one byte). Zero bytes can never be a valid update.
Functions ¶
func GetDiff ¶
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 ¶
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 ¶
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 MergeUpdates ¶
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.
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. The snapshot is computed inside the storage transaction so concurrent StoreUpdate calls cannot interleave (they block until Flush commits).
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.