Documentation
¶
Overview ¶
Package document is the CRDT document plane for KindDocument entities (collaborative documents: page-builder documents, annotations).
THIS PASS DEFINES THE SEAM ONLY. The port below and the crdt_updates / crdt_snapshots migrations exist from phase 1 so the contract is stable; sync transport and materialization are deferred (see DESIGN.md).
Intended semantics (normative for the future implementation):
- Updates are appended to the crdt_updates table (append-only, per-doc monotonic sequence). The merge engine comes from grove's crdt / crdt-js packages — referenced, never reimplemented here.
- Bidirectional sync rides the subscription hub's connection layer WITHOUT conflation and WITHOUT update coalescing (Hub.PublishRaw is the seam) — CRDT updates must arrive complete and in order.
- After a per-document quiet window, materialization snapshots the merged state into the entity's relational row and emits ONE ordinary domain event (<entity>.updated, version++) through the outbox, so projections, search and audit see CRDT documents as normal entities.
- Materialization runs post-merge validation: CRDTs converge but do not guarantee business validity; violations flag the document for resolution instead of silently materializing.
- Awareness/presence (cursors, who's online) is ephemeral Redis pub/sub, never persisted.
Index ¶
- func FoldChange(engine *crdt.MergeEngine, state *crdt.State, c *crdt.ChangeRecord) bool
- func ProjectValues(state *crdt.State) map[string]any
- func ValidateUpdate(engine *crdt.MergeEngine, changes []crdt.ChangeRecord) error
- type HistoryPurger
- type HistoryReader
- type HistoryUpdate
- type Materialized
- type SegmentInfo
- type SegmentLister
- type Store
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func FoldChange ¶ added in v0.0.5
func FoldChange(engine *crdt.MergeEngine, state *crdt.State, c *crdt.ChangeRecord) bool
FoldChange applies one change record onto state.Fields[c.Field] — the single fold every document store (postgres adapter, fakes) shares.
Application rides grove's canonical crdt.ApplyChange, DEGRADING rather than failing when a record cannot be applied: logs written by older producers (the pre-ApplyChange lossy fold, older clients) may hold records with missing typed payloads, and one such row must never poison a document (a hard error here would make Sync/Snapshot/materialization fail forever — the log is already durable). Degradation ladder:
- crdt.ApplyChange — full typed application;
- legacy value-level LWW-style merge (the old fold's behavior);
- deterministic skip (same outcome on every replica folding this log).
The returned bool reports whether the record contributed state (false = skipped); callers may meter or log skips but must not fail on them.
func ProjectValues ¶ added in v0.0.5
ProjectValues projects a merged CRDT state onto column-keyed values — the single projection every document store (postgres adapter, fakes) materializes with: counters resolve to their total, sets and lists to element arrays, text to its visible string, lww/document to the stored value.
func ValidateUpdate ¶ added in v0.0.5
func ValidateUpdate(engine *crdt.MergeEngine, changes []crdt.ChangeRecord) error
ValidateUpdate structurally validates one decoded update before it is durably appended: every record must be applicable (dry-run ApplyChange against empty state), so malformed payloads are rejected at the door instead of degrading later folds.
Types ¶
type HistoryPurger ¶ added in v0.0.4
HistoryPurger is an optional capability: delete a document's offloaded history (segment blobs + index rows). Consumers type-assert for it (e.g. the admin delete path purges history when a document entity is deleted).
type HistoryReader ¶ added in v0.0.4
type HistoryReader interface {
// ReadHistory returns every update with seqLo <= seq <= seqHi, in seq
// order, drawn from sealed segments and the live update log.
ReadHistory(ctx context.Context, docID string, seqLo, seqHi int64) ([]HistoryUpdate, error)
}
HistoryReader is an optional capability on document stores that offload history to the blob plane: it reconstructs a raw update range from sealed file segments plus any still-in-DB rows. Stores that do not offload need not implement it; consumers type-assert (like core/blob's Presigner/Ranger).
type HistoryUpdate ¶ added in v0.0.4
type HistoryUpdate struct {
Seq int64 `json:"seq"`
Update json.RawMessage `json:"update"`
}
HistoryUpdate is one raw update from the offloaded log, keyed by its seq. Update is the verbatim JSON-encoded []crdt.ChangeRecord.
type Materialized ¶
type Materialized struct {
DocID string
Snapshot json.RawMessage
// Version is the aggregate version assigned by the LAST materialization
// event; it advances only when the quiet-window snapshot lands.
Version int64
}
Materialized is a point-in-time snapshot of a document's merged state.
type SegmentInfo ¶ added in v0.0.4
type SegmentInfo struct {
SegSeq int64 `json:"segSeq"`
SeqLo int64 `json:"seqLo"`
SeqHi int64 `json:"seqHi"`
UpdateCount int64 `json:"updateCount"`
ByteSize int64 `json:"byteSize"`
At time.Time `json:"at"`
}
SegmentInfo is the metadata for one sealed history segment (a contiguous [SeqLo, SeqHi] range of the update log stored as a blob). Blob keys are an internal storage detail and are intentionally not exposed.
type SegmentLister ¶ added in v0.0.4
type SegmentLister interface {
ListSegments(ctx context.Context, docID string) ([]SegmentInfo, error)
}
SegmentLister is an optional capability on document stores that offload history to the blob plane: it lists a document's sealed segments (newest storage-shape metadata, not the update bytes). Consumers type-assert for it.
type Store ¶
type Store interface {
// ApplyUpdate appends one encoded CRDT update to the document's log.
ApplyUpdate(ctx context.Context, docID string, update []byte) error
// Sync returns the updates the caller is missing, given its encoded
// state vector (the CRDT engine's diff protocol).
Sync(ctx context.Context, docID string, stateVector []byte) ([]byte, error)
// Snapshot returns the current merged state of the document.
Snapshot(ctx context.Context, docID string) (Materialized, error)
// Compact folds the update log into a snapshot row and trims the log
// (SnapshotEvery from the entity's CRDTSpec governs cadence).
Compact(ctx context.Context, docID string) error
}
Store is the document-plane port. The production implementation lives in adapters/postgres backed by crdt_updates + crdt_snapshots; fabriqtest.FakeDocumentStore is a real in-memory implementation the contract suite in this package runs against.