spaceobjects

package
v0.4.3 Latest Latest
Warning

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

Go to latest
Published: Sep 23, 2026 License: MIT Imports: 45 Imported by: 0

Documentation

Overview

Selective sync by tree type (SYN-18).

A Store built with SelectiveTypes head-syncs its space in full — every tree id and its heads are known and the sync diff converges — but downloads, stores and materializes only trees whose root changeType is in the set. Every other tree is recorded as:

  • a heads-only STUB entry in any-sync's headstorage (heads + CommonSnapshot, zero change rows), which is what makes the headsync diff converge — without it the refused id would reappear in every sync round's missing list forever;
  • a SKIP MARKER row in the per-space `<spaceId>_skiplist` collection, which keeps boot catch-up from force-loading the stub and lets a future type-set widening find what to re-fetch.

The two writes are not atomic (different stores) and don't need to be: a marker without a stub leaves the diff divergent, so the next sync round re-probes and rewrites the stub; a stub without a marker makes catch-up call Get on the id, which re-probes and rewrites the marker.

Skip decisions ride two paths, both anchored on the raw root change (the tree id is the cid of the root bytes, so the type can't be spoofed once the cid checks out):

  • remote fetches go out as PROBE requests (root + current heads, no change bodies); selectiveTreeValidator classifies the response before anything is persisted and either skips, or triggers one full re-fetch for a selected type;
  • head updates for locally-missing trees carry the root too, and the treesyncer's PullFilter (Store.ShouldPullTree) refreshes the stub heads without any round trip.

ACL, settings and key-value sync are reserved paths outside tree-type filtering and stay full; the tech space never sets SelectiveTypes.

Package spaceobjects owns the per-space cache of *object.Object instances. One Store per loaded space; each Store wires the space's shared VersionAllocator + the SDK DB into per-objectId Controllers.

Hides the detail that any-sync trees are loaded inside the commonspace.Space (which is itself ocache-managed). Callers ask for Get(objectId) and get back a ready *object.Object — under the hood we go through anysyncx.GetSpace, BuildTree, ColdRestore.

MVP scope: every Object registers the same handler set — SystemPropertiesHandler on "properties", typetype.PropertyHandler on "defs", DefaultHandler on "shortIds". Type-ness is convention, not a separate object kind. Schema validation is wired through the LiveRegistry (built-in + registered + user-type schemas): strict on local writes (pre-flight), defensive per-op on apply.

Index

Constants

View Source
const DetachedCollection = "_detached"

DetachedCollection is the on-disk collection that holds parked changes — those whose DataVersion references a (typeId, shortId) the apply path didn't know yet. Schema:

{
  id:        "<changeId>",
  spaceId:   string,
  objectId:  string,
  addSeq:    number,
  changeId:  string,
  timestamp: number,
  payload:   binary,           // wire-format bytes from the codec
  pending:   ["typeId:shortId", ...]   // unsatisfied pairs
}

Indexed implicitly by id (the changeId). Drains scan all rows; for MVP scale this is fine — a full multi-peer cold-restore at scale needs an index on `pending` later.

View Source
const SpaceObjectsCollection = "objects"

SpaceObjectsCollection is the on-disk collection name for the per-space values collection. Holds one row per object (regular AND type — types are just objects too with `any.name = "Movie"` alongside everyone else), keyed by objectId. The CRDT-side dataset name (properties.Dataset = "objects") matches by convention but is independent.

Variables

View Source
var (
	LiveTypeRowsFilter       = liveMarkerRowsFilter(typetype.MetaTypeMarker)
	LiveCollectionRowsFilter = liveMarkerRowsFilter(collectiontype.MetaMarker)
)

LiveTypeRowsFilter selects live type objects (`any.type == "__type__"`) from the per-space objects collection; LiveCollectionRowsFilter the live collection objects. Built once with the typed query package; shared with the API listings so the marker/tombstone condition has a single definition.

View Source
var ErrHistoryUnavailable = errors.New("spaceobjects: history index unavailable for this store")

ErrHistoryUnavailable — this store has no history index (DisableHistory: the tech space carries internal bookkeeping only, no history surface).

View Source
var ErrUnknownDataset = errors.New("spaceobjects: unknown dataset")

ErrUnknownDataset is returned by DataVersion when the caller asks for a dataset the store doesn't know how to version.

View Source
var ReservedTypeIds = map[string]struct{}{
	anytype.TypeId:        {},
	spaceindex.TypeId:     {},
	typetype.TypeId:       {},
	collectiontype.TypeId: {},
}

ReservedTypeIds are the synthetic built-ins: they own a namespace in the static schema and a row in Types().List, so a caller-registered type may not claim one.

Functions

func StaticTypeParts

func StaticTypeParts(t handler.Type, modules types.Modules) (*types.CompiledType, error)

StaticTypeParts compiles a registered type's parts into the view a runtime declaration produces: one CompiledPart per declared part (or one implicit part per dataset, keyed by the dataset name, when the type declares none), each static dataset carrying its declared schema under its own name, each module dataset its collection. Ids are the keys — static declarations have no records. Same order and folding rules as the runtime compile: parts and the flat dataset view by key, `uses` filtered to the type's dataset keys and sorted.

func ValidateExternalCollections

func ValidateExternalCollections(extTypes []handler.Type, extCollections []handler.Collection, modules []handler.Module) error

ValidateExternalCollections checks the caller-supplied collections: non-empty unique ids, disjoint from the registered types and the reserved ids, and well-formed property declarations. Called once at sdk.Open after ValidateExternalTypes.

func ValidateExternalModules

func ValidateExternalModules(extTypes []handler.Type, modules []handler.Module) error

ValidateExternalModules checks the caller-supplied modules against the built-in and external datasets, the reserved type ids, and each other. Called once at sdk.Open after ValidateExternalTypes.

func ValidateExternalTypes

func ValidateExternalTypes(extTypes []handler.Type) error

ValidateExternalTypes checks the caller-supplied catalog against the built-in dataset names, against each other, and for internal well-formedness. Returns the first error encountered. Called once at sdk.Open before any Store is constructed.

Types

type CreateOpts

type CreateOpts struct {
	ChangeType    string
	ChangePayload []byte
	// Unencrypted creates a plaintext (node-readable) tree: the root
	// payload carries IsEncrypted:false and the object's ChangeType
	// must be a registered plaintext class (object.PlaintextSpec) so
	// every change ships unencrypted. Regular objects leave it false.
	Unencrypted bool
}

CreateOpts is the input to Store.Create. ChangeType lets the caller stamp a one-shot label on the root any-sync change ("type" vs "object") for log / debug reads of the DAG.

type DeriveOpts

type DeriveOpts struct {
	ChangeType    string
	ChangePayload []byte
	// ParentId binds the derived tree to a parent so any-sync cascade-
	// deletes it with the parent. It is also hashed into the derived id.
	ParentId string
	// Unencrypted derives a plaintext (node-readable) tree — see
	// CreateOpts.Unencrypted. The flag is not part of the derived root,
	// so it does not change the id: validateEncryptionClass pins it per
	// ChangeType, and every deriver of a ChangeType must pass the same
	// value.
	Unencrypted bool
}

DeriveOpts is the input to Store.Derive. ChangePayload is the seed hashed into the derived id.

type DetachedRow

type DetachedRow struct {
	ChangeId  string
	SpaceId   string
	ObjectId  string
	AddSeq    uint64
	OrderId   string
	Timestamp int64
	Payload   []byte
	Pending   []types.DataVersionPair
	// Dataset is the parked change's target dataset. Lets Drain skip
	// rows whose dataset is still unregistered without paying the
	// payload decode. Empty on rows parked by older SDKs (skipped
	// check).
	Dataset string
}

DetachedRow is the stable view of one parked change. Fields map 1:1 to the on-disk shape.

OrderId carries any-sync's per-tree lexid for the change; the drain path stamps it back onto crdt.Change.VersionId before apply so LWW gating uses any-sync's ordering, not a fresh local one.

type NamedSchema

type NamedSchema struct {
	Name   string
	Schema schema.Dataset
	// Owners are the types that declare the dataset: one for a
	// registered or namespaced dataset, every type declaring a shared
	// dataset of the module for a canonical collection; empty for
	// space-level built-ins. External indexers key their gating on it.
	Owners []string
	// Module is the serving module (records for the generic kind);
	// empty for built-ins and registered-type datasets. Shared marks a
	// module's canonical collection.
	Module string
	Shared bool
}

NamedSchema pairs a dataset name with its declared schema. Returned by Schemas for consumer discovery.

type ObjectChange

type ObjectChange struct {
	ObjectId string
	ApplySeq uint64
	Deleted  bool
}

ObjectChange is the payload of the change-index live feed: an object in this space applied a change that advanced its per-space applySeq watermark. Consumers use it to mark the object dirty for re-indexing. ApplySeq covers every apply source — DAG changes, the account mirror's injected applies, device-local writes.

Deleted is true when the object was purged (object deletion): the entry carries a fresh applySeq strictly greater than the object's last content change, so the consumer evicts it in the same ordered stream as edits.

type ObjectMembers

type ObjectMembers struct {
	Type        string
	Collections []string
}

ObjectMembers is what an object is: its one type (`any.type` — the marker on a definition object, empty when it has none) and the collections it belongs to (`any.collections`), read from the shared per-space `objects` row. A missing row reads as nothing at all.

func ObjectMembersOfRow

func ObjectMembersOfRow(v *anyenc.Value) ObjectMembers

ObjectMembersOfRow decodes the membership fields of an objects row.

type RowEvent

type RowEvent struct {
	ObjectId string
	Deleted  bool
}

RowEvent notifies a structural transition of one row in the per-space `objects` collection: Created fires when a change first materialises the row, Deleted when it tombstones. The account mirror keys its replay (carrier values waiting for the row) and its GC (drop carrier records of deleted objects) off these.

type SeedHeadsProvider

type SeedHeadsProvider func(ctx context.Context, objectId string) ([][]string, error)

SeedHeadsProvider returns the account's published read frontiers for an object (one set per device row, own rows included), nil when none. Injected by the space layer from the read-sync service — consulted before first-sight seeding so a fresh device lands on the account's REAL read state whenever it already synced (the tech space usually syncs before chat trees do).

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store owns per-object Controllers and handed-out *object.Object instances for one space. Constructed once per loaded space by the space-level service.

Backed by ocache: LoadFunc builds Object + Controller + tree + ColdRestore atomically; concurrent Get on the same id is serialized by ocache. TTL-evicted Objects detach their synctree listener via TryClose, so stale tree references can't fire callbacks on a freshly-loaded peer Object.

func NewStore

func NewStore(app *anysyncx.App, db anystore.DB, signKey crypto.PrivKey, spaceId string, alloc *object.VersionAllocator, extTypes []handler.Type, extCollections []handler.Collection, modules []handler.Module) *Store

NewStore constructs a regular type/properties-backed Store. The allocator is per-space (shared across all objects in this space).

extTypes is the caller-supplied type catalog and modules the caller-supplied dataset modules. Each type's handlers and each module's canonical collection are wired onto every per-object Controller built by this store, alongside the built-in system handlers; module instances a type declares join through the runtime catalog. Validation happens in ValidateExternalTypes / ValidateExternalModules — call them before NewStore at the SDK boundary so collisions are caught at Open time.

func NewStoreWithConfig

func NewStoreWithConfig(cfg StoreConfig) *Store

NewStoreWithConfig constructs a Store from cfg. The async drainer is built and started here — it lives until Close.

func (*Store) Allocator

func (s *Store) Allocator() *object.VersionAllocator

Allocator returns the shared per-space VersionAllocator. Exposed so the space-level service can reuse it for restore-time bumping.

func (*Store) ChangedObjects

func (s *Store) ChangedObjects(ctx context.Context, since uint64, limit int) ([]ObjectChange, error)

ChangedObjects returns objects in this space whose persisted max applySeq exceeds `since`, ascending, capped at limit (0 = no cap). Page by passing the last returned ApplySeq as the next `since`. Backs the change-index pull / catch-up path.

func (*Store) CheckDeriveParent

func (s *Store) CheckDeriveParent(ctx context.Context, childId, parentId string) (childPresent bool, err error)

CheckDeriveParent runs deriveChildGate for the child childId bound to parentId against this space's head storage; childPresent reports a child already stored here. Derive runs it before creating; a caller that must refuse before Derive (Objects.Derive with no type yet) runs it first so a missing parent is reported as such.

func (*Store) CheckWrite

func (s *Store) CheckWrite() error

CheckWrite applies the gates; nil = writable. Exposed for write entry points that mutate outside the store's own DAG-write funnel (tree deletion, file-node uploads).

func (*Store) Classify

func (s *Store) Classify(ctx context.Context, id string) (properties.OwnerKind, error)

Classify reports what a definition id names on this device: a type (registered, reserved, or a live `__type__` row), a collection (registered or a live `__collection__` row), or unknown (nothing resolvable here — a definition that has not synced yet, or no definition at all). The properties handler consults it on the local write pre-flight so a known id lands only in its own slot.

func (*Store) Close

func (s *Store) Close() error

Close shuts down per-Store background workers (drainer + dispatcher) and tears down the object cache (which closes every resident Object). Safe to call multiple times.

func (*Store) Create

func (s *Store) Create(ctx context.Context, opts CreateOpts) (*object.Object, error)

Create makes a new object on the space and returns its bound *object.Object. The caller-supplied opts.ChangeType / ChangePayload land on the root any-sync change; for the MVP they're informational only.

func (*Store) DataVersion

func (s *Store) DataVersion(dataset string) (string, error)

DataVersion looks up the DataVersion stamp for a known dataset. Used by space.Modify to populate crdt.Change.DataVersion. The map is the union of the built-ins and any external Registrations supplied at construction time.

func (*Store) DataVersionFor

func (s *Store) DataVersionFor(ctx context.Context, dataset string) (string, error)

DataVersionFor resolves the DataVersion stamp for any known dataset: the static map for built-ins / config-registered datasets, and for runtime datasets the owning type's latest shortId encoded as a `typeId:shortId` pair — so peers gate the data change against the writer's schema state (dataVersionForTypes model). Falls back to the defs handler version when the type has no shortId rows yet (fresh hand-built state; the string parse-fails and passes the gate).

func (*Store) DatasetDecl

func (s *Store) DatasetDecl(dataset string) (schema.Dataset, bool)

DatasetDecl resolves any known dataset's schema declaration: config-registered datasets first, then the runtime catalog. Built-ins are absent on purpose (their declarations are SDK-internal).

func (*Store) DatasetDefs

func (s *Store) DatasetDefs(ctx context.Context, typeId string) ([]types.CompiledDataset, error)

DatasetDefs returns the compiled dataset definitions of one type object (deterministic fold of its `datasets` records), every part's datasets flattened.

func (*Store) DatasetHeadIds

func (s *Store) DatasetHeadIds(ctx context.Context, typeId, key string) ([]string, error)

DatasetHeadIds lists the live head ids declaring key on the type object, duplicates included. See types.DatasetHeadIds.

func (*Store) DatasetHeadKey

func (s *Store) DatasetHeadKey(ctx context.Context, typeId, defId string) (string, error)

DatasetHeadKey resolves a live head's dataset key by record id. See types.DatasetHeadKey.

func (*Store) DatasetOwners

func (s *Store) DatasetOwners(dataset string) ([]string, bool)

DatasetOwners returns the types that own a dataset — one for a registered-type or namespaced dataset, the current declaring set for a module's canonical collection (possibly empty: nothing declares it yet) — and false for built-in / unknown datasets. Used by the write path to enforce that an object implements an owner before writing into the dataset.

func (*Store) DeleteTree

func (s *Store) DeleteTree(ctx context.Context, treeId string) error

DeleteTree reflects an any-sync tree deletion into local state: it tombstones the tree in any-sync storage, purges the object's materialized state from the SDK DB, and evicts the cached object. Wired into the SpaceRegistry's DeleteTree route so the deletion manager's per-tree cleanup pass reclaims both any-sync storage and our on-disk + in-memory state.

Ordering is deliberate: tree.Delete() runs FIRST, so any-sync marks the tree deleted (a permanent, cross-device-authoritative flag) and then rejects every further apply to it — the purge below therefore cannot race a concurrent materialization of the same object.

A purge failure is PROPAGATED, not swallowed: any-sync advances the tree from Queued to Deleted only after this callback returns success, so returning an error keeps it Queued and the deletion loop re-fires the callback until the purge commits — the at-least-once self-heal for a crash between tree.Delete() and the purge.

func (*Store) Derive

func (s *Store) Derive(ctx context.Context, opts DeriveOpts) (*object.Object, error)

Derive makes a deterministic object on the space. Idempotent — a second Derive with the same opts.ChangePayload returns the same objectId. If the tree already exists locally, ocache's per-id LoadFunc serialization deduplicates parallel callers.

A child (opts.ParentId set) that is already stored here is loaded as is, whatever its parent's local state: any-sync stores a child in any arrival order, so a synced child may precede its parent. Creating a child goes through deriveChildGate, which owns the parent rules here; the root is built by the pure derivation so no builder-side check runs ahead of the gate.

func (*Store) DeriveId

func (s *Store) DeriveId(ctx context.Context, opts DeriveOpts) (string, error)

DeriveId computes the deterministic objectId Derive(opts) would produce, WITHOUT creating or loading anything. Pure: the root change is built in memory from SpaceId, ChangeType, ChangePayload and ParentId, which are the id. Unencrypted is not in the root; it is pinned per ChangeType by validateEncryptionClass. Read paths use this to resolve lazily-created objects and treat a missing tree as "no rows yet".

func (*Store) Detached

func (s *Store) Detached(ctx context.Context) (anystore.Collection, error)

Detached returns the per-space `_detached` collection, opening it on first call. Concurrency-safe.

func (*Store) Drain

func (s *Store) Drain(ctx context.Context) error

Drain scans the detached collection, re-checks each parked change's pending list, and replays any that are now satisfied. Idempotent — call as often as you like: passes are serialized, so two callers can't both collect a row before either unparks it and double-replay it (a replayed change re-tracks as unread if the user read it between the deliveries).

func (*Store) Drop

func (s *Store) Drop(objectId string)

Drop evicts the cached *object.Object for objectId. Used after any-sync DeleteTree fires (so a subsequent Get rebuilds or fails with the appropriate any-sync deletion error) and by the cold-restore catch-up pass to release Objects ASAP.

Does NOT touch the any-store collections — record rows persist until a separate cleanup pass. v1: leave them; queries skip tombstones, and a deleted object's id is content-addressable so it never reuses.

func (*Store) EnsureApplySeq

func (s *Store) EnsureApplySeq(ctx context.Context) error

EnsureApplySeq forces the one-off applySeq backfill eagerly. The backfill takes a WriteTx, and applySeqMeta runs it inside a sync.Once. Two code paths reach that Once with opposite lock orders: an apply (Controller.ApplyChangeWithResult) already holds any-store's write mutex and then calls the allocator's seed → applySeqMeta (wants the Once); the consumer feed (ChangedObjects) takes the Once and then the backfill's WriteTx wants the write mutex. Concurrently that writeMu<->Once inversion deadlocks. Running the backfill here — once, single-threaded at store load, before any apply holds the write mutex or the feed reads concurrently — drains the Once so neither runtime path ever performs the backfill WriteTx under a held lock.

func (*Store) EnsureDatasetRegistered

func (s *Store) EnsureDatasetRegistered(ctx context.Context, objectId, dataset string)

EnsureDatasetRegistered makes sure the RESIDENT controller for objectId (if any) carries the dataset's CURRENT registration. A controller built before a runtime dataset was defined lacks its handler; one built before a field was added/removed carries a stale SchemaRev. Both fix by eviction — the next Get rebuilds through buildRegs, which reads the current catalog snapshot. Lazy and demand-driven: cost lands only on the first touch per object, never as a fleet-wide sweep on schema apply.

func (*Store) ExternalCollections

func (s *Store) ExternalCollections() []handler.Collection

ExternalCollections returns the caller-registered collections (config.Config.Collections).

func (*Store) ExternalTypes

func (s *Store) ExternalTypes() []handler.Type

ExternalTypes returns the caller-supplied type catalog passed at construction time (config.Config.Types). Read-only — the slice is shared, callers must not mutate. Surfaced for the public space.Types() API to enumerate registered catalog entries alongside user-created types.

func (*Store) Generation

func (s *Store) Generation(ctx context.Context) (string, error)

Generation returns the per-space rebuild epoch (minted at store load via EnsureApplySeq). A consumer whose stored generation differs must reset its cursor to 0 and full-reindex — an sdk.db rebuild renumbered the applySeq axis, so deletions are re-established by absence from a live snapshot.

func (*Store) Get

func (s *Store) Get(ctx context.Context, objectId string) (*object.Object, error)

Get returns the *object.Object for objectId, lazy-loading on first access via the ocache LoadFunc. Concurrent Get on the same id share one load; the LoadFunc runs Controller build, tree open, SetTree, and ColdRestore atomically before returning, so peers never observe a partial Object.

func (*Store) HasDatasetDefs

func (s *Store) HasDatasetDefs(ctx context.Context, typeId string) (bool, error)

HasDatasetDefs reports whether anything was ever declared on the type object, removed definitions included. See types.HasDatasetDefs.

func (*Store) HasTree

func (s *Store) HasTree(ctx context.Context, treeId string) (bool, error)

HasTree reports whether the tree exists in local storage (deleted trees count as existing — TreeDeleted distinguishes them).

func (*Store) HistoryIndex

func (s *Store) HistoryIndex(ctx context.Context) (*history.Index, error)

HistoryIndex returns the per-space version-history index, opening it on first use. The skip list is derived from the registered datasets' SkipHistory flags. Open failures are returned but NOT cached — the next call retries. Every successful call also flushes pending stale marks and deferred history purges recorded while the index was unavailable.

Never call this from inside an apply WriteTx (the collection DDL would nest in — and be reverted with — the apply tx while the opened handles survive in memory); the apply hook reads the atomic pointer instead and defers indexing via historyPendingStale until an out-of-tx caller (newController, a history query) has opened it.

func (*Store) HistoryReplayRegs

func (s *Store) HistoryReplayRegs() ([]crdt.HandlerReg, []string, error)

HistoryReplayRegs returns a fresh handler-reg set plus the shared dataset names for a history scratch replay (history.ViewParams). Fresh per call: handlers like properties.New hold registry pointers and must not be shared with live controllers' mutable state.

func (*Store) IsTreeSkipped

func (s *Store) IsTreeSkipped(ctx context.Context, treeId string) (bool, error)

IsTreeSkipped reports whether treeId carries a skip marker. Always false outside selective mode.

func (*Store) IterDetached

func (s *Store) IterDetached(ctx context.Context, fn func(row DetachedRow) bool) error

IterDetached calls fn for every parked change. The visitor reads each row out before advancing — safe against the iter-buffer- reuse issue, since we copy fields out of the value as we go.

Iteration is best-effort (errors decoding individual rows are skipped with the rest still visited). Stops early on fn returning false.

func (*Store) MarkTreeDeleted

func (s *Store) MarkTreeDeleted(ctx context.Context, treeId string) error

MarkTreeDeleted is the soft-delete callback any-sync fires when it catches a settings-tree deletion for a tree not present in local storage (a device that never synced the object, or a re-fired callback after tree.Delete() already removed it). Purge any materialized state and evict the cache; there is no local tree to tombstone. Like DeleteTree, a purge failure is returned so the deletion loop re-fires until it commits.

func (*Store) MaxApplySeq

func (s *Store) MaxApplySeq(ctx context.Context) (uint64, error)

MaxApplySeq returns the highest per-object applySeq persisted in this space — the current upper bound of the change-index cursor.

func (*Store) ModuleGrants

func (s *Store) ModuleGrants(members map[string]struct{}) []string

ModuleGrants returns the module namespaces an object carrying `members` may hold on its objects row: every module one of the member types declares a dataset of. The properties handler consults it on the local write pre-flight.

func (*Store) Modules

func (s *Store) Modules() types.Modules

Modules returns the compile-time module catalog (records included). Nil-safe: a nil store carries the built-in records module only.

func (*Store) NotifyDrainer

func (s *Store) NotifyDrainer(pair types.DataVersionPair)

NotifyDrainer is the public hook used by callers (e.g. the space service on first-touch) to trigger an asynchronous Drain pass. The afterApply path notifies internally; this is for out-of-band wakeups.

func (*Store) ObjectMembers

func (s *Store) ObjectMembers(ctx context.Context, objectId string) (ObjectMembers, error)

ObjectMembers reads the object's membership off its row.

func (*Store) OpenObjectCollection

func (s *Store) OpenObjectCollection(ctx context.Context, objectId, dataset string) (anystore.Collection, error)

OpenObjectCollection opens the per-object any-store collection `{objectId}/{dataset}` without binding the any-sync tree. Read-only callers (Properties.Get, queries against per-object datasets, type `defs` reads, etc.) take this route to skip BuildSyncTree + ColdRestore — the persisted any-store state is already what they want.

Open-only: returns anystore.ErrCollectionNotFound if the writer hasn't initialised the dataset yet. Callers should treat that as "no records" — equivalent to an empty controller.

Ownership note: handles opened through here bypass the controller's eviction-time release (Controller.CloseOwnedCollections) — they stay in any-store's registry until process exit. Today's callers are the types registry reads (`<typeId>_propertyDefs` / `<typeId>_shortIds` via spaceimpl/types.go) and the tech-space accountvalues dataset — bounded O(types + tech-space objects), deliberately out of scope for the per-object eviction fix. Don't route per-object DATA datasets through here; those belong to the object's controller.

func (*Store) Park

func (s *Store) Park(ctx context.Context, row DetachedRow) error

Park inserts (or replaces) a parked change. The pending list is the (typeId, shortId) pairs the change still needs before it can land. Returns nil even on idempotent re-park (already parked).

func (*Store) PartIds

func (s *Store) PartIds(ctx context.Context, typeId, key string) ([]string, error)

PartIds lists the live part ids declaring key on the type object, duplicates included. See types.PartIds.

func (*Store) PurgeObjects

func (s *Store) PurgeObjects(ctx context.Context, objectIds []string) error

PurgeObjects hard-removes the local projection for a batch of objectIds in ONE WriteTx (row removal + del-stamp per materialized id), then reclaims per-object collections listing collection names ONCE, drops the cache, and emits one deletion feed entry per stamped id. Used by the startup deletion-reconcile to purge many stale rows off the SDK.Open path without the O(N x all-collections) cost of per-id purgeObject. Ids with neither a live `objects` row nor a scoped `_meta` row (never materialized here) are skipped. Idempotent.

func (*Store) PutTreeFromPayload

func (s *Store) PutTreeFromPayload(ctx context.Context, payload treestorage.TreeStorageCreatePayload) (*object.Object, error)

PutTreeFromPayload binds a tree delivered by a remote peer. Used by the SpaceRegistry's PutTree route — any-sync's space-sync delivers a TreeStorageCreatePayload for a tree we don't have locally yet. Falls back to BuildTree on ErrTreeExists, matching the Derive idempotency contract.

func (*Store) ReadState

func (s *Store) ReadState() *readstate.Engine

ReadState returns the per-space read/unread engine, nil when no dataset in this space opted into tracking.

func (*Store) Registry

func (s *Store) Registry() *types.LiveRegistry

Registry returns the per-space LiveRegistry — read-only lookups for property kinds, known shortIds, and latest shortIds. Used by the writer-side stamping path and the apply-time gate.

func (*Store) RegularObjectCount

func (s *Store) RegularObjectCount(ctx context.Context) int

RegularObjectCount returns the count of rows in the per-space `objects` collection — one row per user-visible regular object. Used by the sync-status rollup as the Total denominator.

Returns 0 if the collection hasn't been opened yet (cold start before any object lives in this space) or on read error — both produce the right rollup result (Synced/0 = trivially Synced).

func (*Store) ReservedCarrier

func (s *Store) ReservedCarrier(typeId string) bool

ReservedCarrier reports whether typeId is a user type declaring a dataset of a reserved module (handler.Module.Reserved): the consumer's own install root, carried by nothing else. Registered types with a static declaration of the module are not carriers — the static part is the consumer's compiled-in choice to make the module attachable. The properties handler consults it on the local write pre-flight.

func (*Store) RuntimeDataset

func (s *Store) RuntimeDataset(dataset string) (types.CompiledDataset, bool)

RuntimeDataset resolves a runtime dataset's compiled declaration by collection name — one atomic snapshot load.

func (*Store) Schemas

func (s *Store) Schemas() []NamedSchema

Schemas returns the declared schema of every dataset this store hosts — the same schemas its controllers enforce. Used by the space layer to expose dataset discovery to consumers.

func (*Store) SelectiveMode

func (s *Store) SelectiveMode() bool

SelectiveMode reports whether this store filters trees by root changeType (cfg.Sync.TreeTypes non-empty and this is not the tech space).

func (*Store) SelfIdentity

func (s *Store) SelfIdentity() string

SelfIdentity returns this replica's account identity (empty when the store has no signing key — raw/test mode).

func (*Store) SetGlobalGate

func (s *Store) SetGlobalGate(fn func() error)

SetGlobalGate installs an account-wide write gate consulted on every user-authored synced write next to the per-store gate. Nil clears it.

func (*Store) SetSeedHeadsProvider

func (s *Store) SetSeedHeadsProvider(p SeedHeadsProvider)

SetSeedHeadsProvider wires the provider. Call before objects load.

func (*Store) SetWriteGateErr

func (s *Store) SetWriteGateErr(err error)

SetWriteGateErr installs (non-nil) or clears (nil) the user-write gate — see Store.writeGateErr. Safe at any time; cached Objects read the live value on every write.

func (*Store) SharedObjects

func (s *Store) SharedObjects(ctx context.Context) (anystore.Collection, error)

SharedObjects returns the per-space `objects` collection, opening it on first call. Callers can hit it directly for cross-object queries (find all rows where any.name = X, etc.). First open also installs the standing read-side indexes.

func (*Store) ShouldPullTree

func (s *Store) ShouldPullTree(ctx context.Context, treeId string, raw *treechangeproto.RawTreeChangeWithId, heads []string) bool

ShouldPullTree is the PullFilter decision for a head update on a locally-missing tree. True = fetch as usual. False = the tree's type is not selected; the stub heads were refreshed from the update and the fetch is swallowed.

Anything that can't be classified safely (no/invalid root) returns true — the fetch path's validator is the authoritative gate.

func (*Store) SpaceId

func (s *Store) SpaceId() string

SpaceId returns the id of the space this store serves.

func (*Store) StartReindexSweep

func (s *Store) StartReindexSweep()

StartReindexSweep rebuilds, in the background, every object this store materialized with a stale handler version, instead of waiting for each one to be opened.

The lazy path alone would leave the per-space `objects` collection mixing rows built by the old handler with rows built by the new one for as long as some object stays unopened — and a sort or filter over a rebuilt field reads both shapes. The sweep closes that window.

Idempotent, one sweep per store. A store with nothing stale pays one _meta scan and stops. Errors are logged, never fatal: whatever the sweep misses, the lazy path still rebuilds on first touch.

func (*Store) SubEngine

func (s *Store) SubEngine() *subscribe.Engine

SubEngine returns the per-space live-query engine. Used by the space layer to back Query.Subscribe.

func (*Store) SubscribeChanges

func (s *Store) SubscribeChanges(cb func(ObjectChange)) (cancel func())

SubscribeChanges registers cb on the change-index feed; it fires once per applied change in this space with (objectId, applySeq). cb runs synchronously on the apply path — keep it small or hand off. The returned cancel is idempotent.

The feed is best-effort live notification, not a durable queue: a consumer that misses events (crash, slow cb, was offline) recovers by re-running ChangedObjects from its last persisted cursor.

func (*Store) SubscribeRowEvents

func (s *Store) SubscribeRowEvents(cb func(RowEvent)) (cancel func())

SubscribeRowEvents registers cb for objects-collection row creations and deletions. cb runs synchronously on the apply path. The returned cancel is idempotent.

func (*Store) SystemSchemas

func (s *Store) SystemSchemas() []NamedSchema

SystemSchemas returns the declared schema of this store's system datasets only (the tech space's spaces/profile/devices/…).

func (*Store) TreeDeleted

func (s *Store) TreeDeleted(ctx context.Context, treeId string) (bool, error)

TreeDeleted reports whether any-sync's head storage records treeId as deleted — the permanent, cross-device-authoritative deletion flag (set once, never cleared, no inbound path resurrects the tree). Lets consumers tell a deleted object (whose local row was hard-removed) apart from one that was never materialized.

func (*Store) TreeIdsByChangeType

func (s *Store) TreeIdsByChangeType(ctx context.Context, changeType string) ([]string, error)

TreeIdsByChangeType returns the ids of every materialized tree in this space whose root changeType equals changeType. The root is the typed source of truth — signed, content-addressed (the tree id is the cid of the root bytes) and cleartext, so keyed and keyless readers classify identically and a peer can't relabel a tree into the result.

Two-phase like the spacesync catch-up: collect ids while the headstorage iterator is open, classify after. Trees whose root can't be read are skipped — heads-only stubs (selective sync) have no change rows by construction, and an unreadable root is not evidence of the requested type.

func (*Store) TreeIsDerived

func (s *Store) TreeIsDerived(ctx context.Context, treeId string) (isDerived bool, present bool, err error)

TreeIsDerived reports whether treeId is a DERIVED object — the root's IsDerived flag, mirrored into head storage at tree-storage creation (see objecttree.createTreeStorage) — and whether the tree is present in local storage. A tree absent locally reports (false, false, nil): its class can't be read, so the caller can't classify it.

Distinguishing a derived owner from a signed one is what lets its payloads child pick a derivation: a derived object cannot be a tree parent (objecttree.ErrDerivedParent, refused by deriveChildGate), so a derived owner's payloads object is derived unparented (see payloads.DerivedOwnerSeed) while a signed owner's stays parented. Mirrors TreeDeleted's head-storage lookup.

func (*Store) TypeParts

func (s *Store) TypeParts(ctx context.Context, typeId string) (*types.CompiledType, error)

TypeParts returns the compiled parts of one type object with their datasets. Nil when the type declares nothing.

func (*Store) Unpark

func (s *Store) Unpark(ctx context.Context, changeId string) error

Unpark removes the parked change with the given id.

type StoreConfig

type StoreConfig struct {
	App     *anysyncx.App
	DB      anystore.DB
	SignKey crypto.PrivKey
	SpaceId string
	Alloc   *object.VersionAllocator

	// ExtTypes is the caller-supplied type catalog.
	ExtTypes []handler.Type

	// ExtCollections is the caller-supplied collection catalog.
	ExtCollections []handler.Collection

	// Modules are the caller-supplied dataset modules (the built-in
	// records module is always present).
	Modules []handler.Module

	// SystemDatasets are extra ungated built-ins for this store.
	SystemDatasets []SystemDataset

	// DisableHistory keeps the history index closed — for stores with
	// no history surface (the tech space).
	DisableHistory bool

	// SelectiveTypes is the selective-sync tree-type allowlist. Only
	// the regular-space path (NewStore) sets it — the tech space is
	// always fully synced. See selective.go.
	SelectiveTypes []string
}

StoreConfig is the input to NewStoreWithConfig. Every store runs the same path: LiveRegistry, shared `objects` collection, built-in handler set, runtime dataset catalog, schema gate. ExtTypes and SystemDatasets extend the handler set.

type SystemDataset

type SystemDataset struct {
	Reg         crdt.HandlerReg
	DataVersion string
}

SystemDataset is a per-store type-less built-in: a handler registered on every controller of that store plus the hardcoded DataVersion its writers stamp. The tech space registers its spaces/profile/devices/… datasets this way.

Jump to

Keyboard shortcuts

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