Documentation
¶
Index ¶
- Variables
- func AllocateShortId(entityId string, exists ExistsCheck) (string, error)
- func As[T any, P interface{ ... }](ent AttrGetter) (P, bool)
- func Decode(data []byte, v any) error
- func Empty[T comparable](x T) bool
- func Encode(v any) ([]byte, error)
- func ExtractBase58Suffix(entityId string) (string, bool)
- func InitSystemEntities(save func(*Entity) error) error
- func Is(ent AttrGetter, kind Id) bool
- func MigrateEntityStore(ctx context.Context, log *slog.Logger, client *clientv3.Client, ...) (migrated int, skipped int, err error)
- func MigrateShortIds(ctx context.Context, log *slog.Logger, client *clientv3.Client, ...) (migrated int, skipped int, err error)
- func MustKeyword(str string) types.Keyword
- func TryAs(ent AttrGetter, ea EntityAs) bool
- func ValidKeyword(str string) bool
- func WithOverwrite(opts *entityOpts)
- type Attr
- func Any(id Id, v any) Attr
- func Bool(id Id, v bool) Attr
- func Bytes(id Id, b []byte) Attr
- func Component(id Id, attrs []Attr) Attr
- func Diff(a, b *Entity) []Attr
- func Duration(id Id, v time.Duration) Attr
- func Float64(id Id, v float64) Attr
- func Int(id Id, value int) Attr
- func Int64(id Id, value int64) Attr
- func Keyword[T Keywordable](id Id, v T) Attr
- func Label(id Id, key, val string) Attr
- func MustGet(e AttrGetter, name Id) Attr
- func Named[T Keywordable](v T) Attr
- func Pair(id Id, x, y Value) Attr
- func Ref(id Id, v Id) Attr
- func SortedAttrs(attrs []Attr) []Attr
- func String(id Id, value string) Attr
- func Time(id Id, v time.Time) Attr
- func Uint64(id Id, v uint64) Attr
- type AttrGetter
- type AttrOp
- type AttrOpCode
- type AttrOps
- type AttributeSchema
- type CleanupOptions
- type CleanupStats
- type EncodedDomain
- type EncodedSchema
- type Entity
- func (e *Entity) Attrs() []Attr
- func (e *Entity) Clone() *Entity
- func (e *Entity) Compare(other *Entity) int
- func (e *Entity) Fixup() error
- func (e *Entity) ForceID()
- func (e *Entity) Get(name Id) (Attr, bool)
- func (e *Entity) GetAll(name Id) []Attr
- func (e *Entity) GetCreatedAt() time.Time
- func (e *Entity) GetRevision() int64
- func (e *Entity) GetUpdatedAt() time.Time
- func (e *Entity) Id() Id
- func (e *Entity) MarshalCBOR() ([]byte, error)
- func (e *Entity) MarshalJSON() ([]byte, error)
- func (e *Entity) Merge(other *Entity) error
- func (e *Entity) ReadInfo(val any) error
- func (e *Entity) Remove(id Id) bool
- func (e *Entity) Set(newAttr Attr) bool
- func (e *Entity) SetCreatedAt(ts time.Time) bool
- func (e *Entity) SetID(id Id) bool
- func (e *Entity) SetRevision(rev int64)
- func (e *Entity) SetUpdatedAt(ts time.Time) bool
- func (e *Entity) ShortId() string
- func (e *Entity) Timeless() *Entity
- func (e *Entity) UnmarshalCBOR(data []byte) error
- func (e *Entity) UnmarshalJSON(data []byte) error
- func (e *Entity) Update(attrs []Attr) error
- type EntityAs
- type EntityComponent
- func (e *EntityComponent) Attrs() []Attr
- func (e *EntityComponent) Compare(other *EntityComponent) int
- func (e *EntityComponent) Equal(other *EntityComponent) bool
- func (e *EntityComponent) Get(name Id) (Attr, bool)
- func (e *EntityComponent) GetAll(name Id) []Attr
- func (e *EntityComponent) MarshalCBOR() ([]byte, error)
- func (e *EntityComponent) MarshalJSON() ([]byte, error)
- func (e *EntityComponent) ReadInfo(val any) error
- func (e *EntityComponent) UnmarshalCBOR(data []byte) error
- func (e *EntityComponent) UnmarshalJSON(data []byte) error
- type EntityGetter
- type EntityOp
- type EntityOpType
- type EntityOption
- type EntityStore
- type EtcdStore
- func (s *EtcdStore) AssertLease(ctx context.Context, lease int64) error
- func (s *EtcdStore) BuildUniqueKeyForTest(attr Attr) string
- func (s *EtcdStore) CleanupStaleCollectionEntries(ctx context.Context, log *slog.Logger, opts CleanupOptions) (*CleanupStats, error)
- func (s *EtcdStore) ClearSchemaCache()
- func (s *EtcdStore) Client() *clientv3.Client
- func (s *EtcdStore) CollectionPrefix(ctx context.Context, collection string) (string, error)
- func (s *EtcdStore) CreateEntity(ctx context.Context, entity *Entity, opts ...EntityOption) (*Entity, error)
- func (s *EtcdStore) CreateLease(ctx context.Context, ttl int64) (int64, error)
- func (s *EtcdStore) CreateSession(ctx context.Context, ttl int64) ([]byte, error)
- func (s *EtcdStore) DeleteEntity(ctx context.Context, id Id) error
- func (s *EtcdStore) DeletePrefixCount(ctx context.Context, prefix string) (int64, error)
- func (s *EtcdStore) EnsureEntity(ctx context.Context, entity *Entity, opts ...EntityOption) (*Entity, bool, error)
- func (s *EtcdStore) GetAttributeSchema(ctx context.Context, id Id) (*AttributeSchema, error)
- func (s *EtcdStore) GetEntities(ctx context.Context, ids []Id) ([]*Entity, error)
- func (s *EtcdStore) GetEntity(ctx context.Context, id Id) (*Entity, error)
- func (s *EtcdStore) GetEntityAtRevision(ctx context.Context, id Id, rev int64) (*Entity, error)
- func (s *EtcdStore) GetOneIndex(ctx context.Context, attr Attr) (Id, error)
- func (s *EtcdStore) IndexPrefix(ctx context.Context, attr Attr) (string, error)
- func (s *EtcdStore) ListAllEntityIDs(ctx context.Context) ([]Id, error)
- func (s *EtcdStore) ListCollection(ctx context.Context, collection string) ([]Id, error)
- func (s *EtcdStore) ListIndex(ctx context.Context, attr Attr) ([]Id, error)
- func (s *EtcdStore) ListIndexRevision(ctx context.Context, attr Attr) ([]Id, int64, error)
- func (s *EtcdStore) ListSessionEntities(ctx context.Context, session []byte) ([]Id, error)
- func (s *EtcdStore) PatchEntity(ctx context.Context, current *Entity, opts ...EntityOption) (*Entity, error)
- func (s *EtcdStore) PingSession(ctx context.Context, session []byte) error
- func (s *EtcdStore) Prefix() string
- func (s *EtcdStore) Reindex(ctx context.Context, log *slog.Logger, opts ReindexOptions) (*ReindexStats, error)
- func (s *EtcdStore) ReplaceEntity(ctx context.Context, current *Entity, opts ...EntityOption) (*Entity, error)
- func (s *EtcdStore) RevokeLease(ctx context.Context, lease int64) error
- func (s *EtcdStore) RevokeSession(ctx context.Context, session []byte) error
- func (s *EtcdStore) UpdateEntity(ctx context.Context, id Id, changes *Entity, opts ...EntityOption) (*Entity, error)
- func (s *EtcdStore) WatchEntity(ctx context.Context, id Id) (chan EntityOp, error)
- func (s *EtcdStore) WatchIndex(ctx context.Context, attr Attr, fromRev int64) (clientv3.WatchChan, error)
- type ExistsCheck
- type FileStore
- func (s *FileStore) CreateEntity(ctx context.Context, entity *Entity, opts ...EntityOption) (*Entity, error)
- func (s *FileStore) DeleteEntity(_ context.Context, id Id) error
- func (s *FileStore) GetAttributeSchema(ctx context.Context, id Id) (*AttributeSchema, error)
- func (s *FileStore) GetEntities(_ context.Context, ids []Id) ([]*Entity, error)
- func (s *FileStore) GetEntity(_ context.Context, id Id) (*Entity, error)
- func (s *FileStore) ListCollection(_ context.Context, collection string) ([]Id, error)
- func (s *FileStore) ListIndex(ctx context.Context, attr Attr) ([]Id, error)
- func (s *FileStore) UpdateEntity(ctx context.Context, id Id, updates *Entity, opts ...EntityOption) (*Entity, error)
- type Id
- type Keywordable
- type Meta
- type MigrateOptions
- type MigrateShortIdOptions
- type MockStore
- func (m *MockStore) AddEntity(id Id, entity *Entity)
- func (m *MockStore) CreateEntity(ctx context.Context, entity *Entity, opts ...EntityOption) (*Entity, error)
- func (m *MockStore) CreateSession(ctx context.Context, id int64) ([]byte, error)
- func (m *MockStore) DeleteEntity(ctx context.Context, id Id) error
- func (m *MockStore) EnsureEntity(ctx context.Context, entity *Entity, opts ...EntityOption) (*Entity, bool, error)
- func (m *MockStore) GetAttributeSchema(ctx context.Context, id Id) (*AttributeSchema, error)
- func (m *MockStore) GetEntities(ctx context.Context, ids []Id) ([]*Entity, error)
- func (m *MockStore) GetEntity(ctx context.Context, id Id) (*Entity, error)
- func (m *MockStore) GetEntityAtRevision(ctx context.Context, id Id, rev int64) (*Entity, error)
- func (m *MockStore) ListCollection(ctx context.Context, collection string) ([]Id, error)
- func (m *MockStore) ListIndex(ctx context.Context, attr Attr) ([]Id, error)
- func (m *MockStore) ListIndexRevision(ctx context.Context, attr Attr) ([]Id, int64, error)
- func (m *MockStore) ListSessionEntities(ctx context.Context, id []byte) ([]Id, error)
- func (m *MockStore) Now() time.Time
- func (m *MockStore) PatchEntity(ctx context.Context, entity *Entity, opts ...EntityOption) (*Entity, error)
- func (m *MockStore) PingSession(ctx context.Context, id []byte) error
- func (m *MockStore) RemoveEntity(id Id)
- func (m *MockStore) ReplaceEntity(ctx context.Context, entity *Entity, opts ...EntityOption) (*Entity, error)
- func (m *MockStore) RevokeSession(ctx context.Context, id []byte) error
- func (m *MockStore) UpdateEntity(ctx context.Context, id Id, entity *Entity, opts ...EntityOption) (*Entity, error)
- func (m *MockStore) WaitForEntityWatcher(ctx context.Context, id Id) error
- func (m *MockStore) WaitForIndexWatcher(ctx context.Context, attr Attr) error
- func (m *MockStore) WatchEntity(ctx context.Context, id Id) (chan EntityOp, error)
- func (m *MockStore) WatchFromRevsCopy() []int64
- func (m *MockStore) WatchIndex(ctx context.Context, attr Attr, fromRev int64) (clientv3.WatchChan, error)
- type OldEntity
- type OldMeta
- type ReindexOptions
- type ReindexStats
- type SchemaCache
- type SchemaField
- type Store
- type Validator
- func (v *Validator) ValidateAttribute(ctx context.Context, attr *Attr) error
- func (v *Validator) ValidateAttributes(ctx context.Context, attrs []Attr) error
- func (v *Validator) ValidateEntity(ctx context.Context, entity *Entity) error
- func (v *Validator) ValidateUpdate(ctx context.Context, newAttrs, originalAttrs []Attr) error
- type Value
- func AnyValue(v any) Value
- func ArrayValue(values ...any) Value
- func BoolValue(v bool) Value
- func BytesValue(b []byte) Value
- func ComponentValue(vals ...any) Value
- func DurationValue(v time.Duration) Value
- func Float64Value(v float64) Value
- func Int64Value(v int64) Value
- func IntValue(v int) Value
- func KeywordValue[T Keywordable](v T) Value
- func LabelValue(key, val string) Value
- func RefValue(v Id) Value
- func StringValue(value string) Value
- func TimeValue(v time.Time) Value
- func Uint64Value(v uint64) Value
- func (v Value) Any() any
- func (v Value) Array() []Value
- func (v Value) Bool() bool
- func (v Value) Bytes() []byte
- func (v Value) Clone() Value
- func (v Value) Compare(w Value) int
- func (v Value) Component() *EntityComponent
- func (v Value) Duration() time.Duration
- func (v Value) Equal(w Value) bool
- func (v Value) Float64() float64
- func (v Value) Id() types.Id
- func (v Value) Int64() int64
- func (v Value) Keyword() types.Keyword
- func (v Value) Kind() ValueKind
- func (v Value) Label() types.Label
- func (v *Value) MarshalCBOR() ([]byte, error)
- func (v Value) MarshalJSON() ([]byte, error)
- func (v Value) MarshalYAML() (any, error)
- func (v Value) String() string
- func (v Value) Time() time.Time
- func (v Value) Uint64() uint64
- func (v *Value) UnmarshalCBOR(b []byte) error
- func (v *Value) UnmarshalJSON(b []byte) error
- func (v *Value) UnmarshalYAML(node *yaml.Node) error
- type ValueKind
Constants ¶
This section is empty.
Variables ¶
var ( ErrEntityNotFound = errors.New("entity not found") ErrEntityAlreadyExists = errors.New("entity already exists") ErrAttributeNotFound = errors.New("attribute not found") ErrInvalidAttribute = errors.New("invalid attribute") ErrSchemaNotFound = errors.New("schema not found") ErrShortIdExhausted = errors.New("failed to allocate unique short-id") )
Common errors
var ErrNotFound = errors.New("entity not found")
Functions ¶
func AllocateShortId ¶ added in v0.7.0
func AllocateShortId(entityId string, exists ExistsCheck) (string, error)
AllocateShortId generates a short, globally-unique identifier for an entity. It first tries to derive the short-id from the entity's base58 suffix, falling back to random generation if no suffix is available or all suffix-derived candidates collide.
func Empty ¶
func Empty[T comparable](x T) bool
func ExtractBase58Suffix ¶ added in v0.7.0
ExtractBase58Suffix extracts the high-entropy base58 portion from an entity ID. Returns the base58 portion and true if found, or empty string and false otherwise.
Handles all entity ID categories:
- Category 1 (named): "app/blog" → ("", false)
- Category 2 (name+prefix+base58): "blog-vCZ1eUgSgNd28ed6vt2DgY" → ("CZ1eUgSgNd28ed6vt2DgY", true)
- Category 3 (namespace/prefix-base58): "sandbox/blog-web-CZAtBvhsMNbG38MceikkB" → ("CZAtBvhsMNbG38MceikkB", true)
- Category 4 (kind-base58): "deployment-CZ1eUgSgNd28ed6vt2DgY" → ("CZ1eUgSgNd28ed6vt2DgY", true)
func InitSystemEntities ¶
func Is ¶
func Is(ent AttrGetter, kind Id) bool
func MigrateEntityStore ¶
func MigrateEntityStore(ctx context.Context, log *slog.Logger, client *clientv3.Client, opts MigrateOptions) (migrated int, skipped int, err error)
MigrateEntityStore migrates entities from the old CBOR format (with struct fields) to the new format (with attribute-based metadata).
This migration:
- Reads all entities from etcd under the given prefix
- Detects entities in the old format (ID/Revision/CreatedAt/UpdatedAt as struct fields)
- Converts them to the new format (ID/Revision/CreatedAt/UpdatedAt as attributes)
- Writes the migrated entities back to etcd
Returns the number of entities migrated, skipped, and any error encountered.
func MigrateShortIds ¶ added in v0.7.0
func MigrateShortIds(ctx context.Context, log *slog.Logger, client *clientv3.Client, opts MigrateShortIdOptions) (migrated int, skipped int, err error)
MigrateShortIds backfills db/short-id for all entities that don't have one. This is idempotent — entities that already have a short-id are skipped.
func MustKeyword ¶
MustKeyword converts a string to a Keyword, panicking if it's not valid
func TryAs ¶
func TryAs(ent AttrGetter, ea EntityAs) bool
func ValidKeyword ¶
ValidKeyword checks if a string is a valid keyword
func WithOverwrite ¶
func WithOverwrite(opts *entityOpts)
Types ¶
type Attr ¶
func Diff ¶
Diff returns the difference between two entities. Returns attributes that are in entity 'a' but not in entity 'b'
func Keyword ¶
func Keyword[T Keywordable](id Id, v T) Attr
func MustGet ¶
func MustGet(e AttrGetter, name Id) Attr
func Named ¶
func Named[T Keywordable](v T) Attr
func SortedAttrs ¶
type AttrGetter ¶
type AttrOpCode ¶
type AttrOpCode int
const ( AttrOpCodeNone AttrOpCode = iota AttrOpCodeAdd AttrOpCodeRemove )
type AttributeSchema ¶
type AttributeSchema struct {
ID Id
Doc string
Type Id
ElemType Id
Unique uniq
EnumValues []Value
AllowMany bool
Index bool
Session bool
Predicate []*Entity
CheckProgs []string
Tags []string
}
AttributeSchema defines the schema for an attribute
func GetAttributeSchemasByTag ¶
func GetAttributeSchemasByTag(ctx context.Context, store Store, tag string) ([]AttributeSchema, error)
GetAttributeSchemasByTag queries all attributes that have the specified tag and returns their schemas
type CleanupOptions ¶ added in v0.12.0
type CleanupOptions struct {
// DryRun scans and counts stale entries but deletes nothing.
DryRun bool
// MaxDeletes caps how many stale entries a single pass removes. A large
// legacy backlog then drains over several passes rather than one thundering
// sweep. Zero means unbounded (the manual reindex "big hammer").
MaxDeletes int
// BatchPause is slept after every cleanupDeleteBatchSize deletes to
// rate-limit write pressure. Zero disables pacing.
BatchPause time.Duration
}
CleanupOptions bounds a single stale-cleanup pass so it can run continuously in the background without overwhelming the store.
type CleanupStats ¶ added in v0.12.0
type CleanupStats struct {
CollectionEntriesScanned int64
StaleEntriesFound int64
StaleEntriesRemoved int64
// CASConflicts counts entries that changed between scan and delete (the slot
// was legitimately recreated/re-leased), so the CAS guard skipped them.
CASConflicts int64
RemovedByCollection map[string]int64
}
CleanupStats reports what a stale-cleanup pass observed and did. RemovedByCollection breaks removals down by index/collection so a persistent, post-drain leak shows up as a specific collection that keeps re-accumulating orphans rather than hiding in an aggregate count.
type EncodedDomain ¶
type EncodedSchema ¶
type EncodedSchema struct {
Domain string `json:"domain" cbor:"domain"`
Name string `json:"name" cbor:"name"`
Version string `json:"version" cbor:"version"`
Kinds []string `json:"kinds" cbor:"kinds"`
Fields []*SchemaField `json:"fields" cbor:"fields"`
PrimaryKind string `json:"primary_kind" cbor:"primary_kind"`
}
func (*EncodedSchema) GetField ¶
func (es *EncodedSchema) GetField(name string) *SchemaField
type Entity ¶
type Entity struct {
// contains filtered or unexported fields
}
Entity represents an entity with a set of attributes
func New ¶
New constructs a slice of attributes from a variadic list of Attr, []Attr, func() []Attr, or key-value pairs.
func (*Entity) GetCreatedAt ¶
func (*Entity) GetRevision ¶
func (*Entity) GetUpdatedAt ¶
func (*Entity) MarshalCBOR ¶
func (*Entity) MarshalJSON ¶
func (*Entity) SetID ¶
SetID sets the entity ID (db/id attribute) and returns true if it replaced an existing ID
func (*Entity) SetRevision ¶
func (*Entity) ShortId ¶ added in v0.7.0
ShortId returns the entity's short-id, or empty string if not set.
func (*Entity) Timeless ¶
Timeless returns a copy of the entity without CreatedAt, UpdatedAt and Revision attributes
func (*Entity) UnmarshalCBOR ¶
func (*Entity) UnmarshalJSON ¶
type EntityAs ¶
type EntityAs interface {
Is(ag AttrGetter) bool
Decode(ag AttrGetter)
}
type EntityComponent ¶
type EntityComponent struct {
// contains filtered or unexported fields
}
func (*EntityComponent) Attrs ¶
func (e *EntityComponent) Attrs() []Attr
Attrs returns a clone of the component's attributes
func (*EntityComponent) Compare ¶
func (e *EntityComponent) Compare(other *EntityComponent) int
func (*EntityComponent) Equal ¶
func (e *EntityComponent) Equal(other *EntityComponent) bool
func (*EntityComponent) GetAll ¶
func (e *EntityComponent) GetAll(name Id) []Attr
func (*EntityComponent) MarshalCBOR ¶
func (e *EntityComponent) MarshalCBOR() ([]byte, error)
func (*EntityComponent) MarshalJSON ¶
func (e *EntityComponent) MarshalJSON() ([]byte, error)
func (*EntityComponent) ReadInfo ¶
func (e *EntityComponent) ReadInfo(val any) error
func (*EntityComponent) UnmarshalCBOR ¶
func (e *EntityComponent) UnmarshalCBOR(data []byte) error
func (*EntityComponent) UnmarshalJSON ¶
func (e *EntityComponent) UnmarshalJSON(data []byte) error
type EntityGetter ¶
type EntityOp ¶
type EntityOp struct {
Type EntityOpType
*Entity
}
type EntityOpType ¶
type EntityOpType int
const ( EntityOpCreate EntityOpType = iota EntityOpUpdate EntityOpDelete EntityOpStated )
type EntityOption ¶
type EntityOption func(*entityOpts)
func BondToSession ¶
func BondToSession(session []byte) EntityOption
func WithFromRevision ¶
func WithFromRevision(revision int64) EntityOption
func WithSession ¶
func WithSession(session []byte) EntityOption
type EntityStore ¶
type EntityStore interface {
GetEntity(ctx context.Context, id Id) (*Entity, error)
GetAttributeSchema(ctx context.Context, name Id) (*AttributeSchema, error)
CreateEntity(ctx context.Context, entity *Entity, opts ...EntityOption) (*Entity, error)
UpdateEntity(ctx context.Context, id Id, entity *Entity, opts ...EntityOption) (*Entity, error)
DeleteEntity(ctx context.Context, id Id) error
ListIndex(ctx context.Context, attr Attr) ([]Id, error)
ListCollection(ctx context.Context, collection string) ([]Id, error)
}
type EtcdStore ¶
type EtcdStore struct {
// contains filtered or unexported fields
}
EtcdStore implements Store using etcd
func NewEtcdStore ¶
func NewEtcdStore(ctx context.Context, log *slog.Logger, client *clientv3.Client, prefix string) (*EtcdStore, error)
NewEtcdStore creates a new etcd-backed entity store
func (*EtcdStore) AssertLease ¶
func (*EtcdStore) BuildUniqueKeyForTest ¶ added in v0.7.0
BuildUniqueKeyForTest exposes buildUniqueKey for use in tests.
func (*EtcdStore) CleanupStaleCollectionEntries ¶ added in v0.12.0
func (s *EtcdStore) CleanupStaleCollectionEntries(ctx context.Context, log *slog.Logger, opts CleanupOptions) (*CleanupStats, error)
CleanupStaleCollectionEntries scans collection (index) entries and removes those whose backing entity is authoritatively absent under a linearizable read.
Safety rests on creates being atomic: CreateEntity writes the entity key and its collection entries in one transaction, so a collection entry whose entity is absent is genuinely orphaned, with no create-in-flight race. Each delete is still CAS'd on the entry's mod-revision captured at scan time, so a slot legitimately recreated between scan and delete fails the compare and is left untouched (the ABA guard). The pass is idempotent and bounded, so it is safe to run repeatedly as a background sweep; repetition is what makes it resumable.
func (*EtcdStore) ClearSchemaCache ¶ added in v0.4.0
func (s *EtcdStore) ClearSchemaCache()
ClearSchemaCache clears the in-memory schema cache, forcing subsequent GetAttributeSchema calls to re-read from etcd. This is used before reindexing to ensure fresh schema definitions are used.
func (*EtcdStore) CollectionPrefix ¶
func (*EtcdStore) CreateEntity ¶
func (s *EtcdStore) CreateEntity( ctx context.Context, entity *Entity, opts ...EntityOption, ) (*Entity, error)
CreateEntity implements Store interface
func (*EtcdStore) CreateLease ¶
func (*EtcdStore) CreateSession ¶
func (*EtcdStore) DeleteEntity ¶
DeleteEntity implements Store interface.
The delete is atomic: the entity key, its index (collection) entries, and its unique-value keys are all removed in a single revision-guarded transaction. If they were removed separately (as they once were, with the index deletes issued as immediate unconditional Deletes before the entity-key txn), a concurrent no-OCC patch could slip into the gap and re-add index entries after the entity was gone, leaking a stale index entry (MIR-1334).
Because the txn is guarded on the entity's ModRevision, a concurrent write that bumps the revision between our read and the txn makes it fail. Pools are patched on every reconcile tick via the no-OCC path, so a single-shot delete could lose that race repeatedly; we re-read and retry a bounded number of times so the delete wins against a busy reconcile loop instead of spuriously reporting the entity as already gone.
func (*EtcdStore) DeletePrefixCount ¶
DeletePrefixCount deletes all keys with prefix and returns count
func (*EtcdStore) EnsureEntity ¶
func (s *EtcdStore) EnsureEntity( ctx context.Context, entity *Entity, opts ...EntityOption, ) (*Entity, bool, error)
EnsureEntity creates an entity only if it doesn't already exist (idempotent create) The db/id attribute must be present in the attributes to identify the entity Returns (entity, true, nil) if created, (entity, false, nil) if already exists
func (*EtcdStore) GetAttributeSchema ¶
GetAttributeSchema implements Store interface
func (*EtcdStore) GetEntities ¶
func (*EtcdStore) GetEntityAtRevision ¶ added in v0.4.0
GetEntityAtRevision reads an entity at a specific etcd revision. This is used to retrieve entity data for delete events where the entity may no longer exist at the current revision.
func (*EtcdStore) GetOneIndex ¶ added in v0.7.0
GetOneIndex looks up a single entity by an indexed attribute value. Returns the entity ID if exactly one match is found, or ErrNotFound.
func (*EtcdStore) IndexPrefix ¶
func (*EtcdStore) ListAllEntityIDs ¶
ListAllEntityIDs returns all entity IDs in the store
func (*EtcdStore) ListCollection ¶
func (*EtcdStore) ListIndexRevision ¶ added in v0.9.1
ListIndexRevision lists the ids matching attr and returns the etcd revision the listing was read at. The revision lets a caller resume a watch from that point (gap-free) instead of re-reconciling on every reconnect.
func (*EtcdStore) ListSessionEntities ¶
func (*EtcdStore) PatchEntity ¶
func (s *EtcdStore) PatchEntity( ctx context.Context, current *Entity, opts ...EntityOption, ) (*Entity, error)
PatchEntity merges attributes into an existing entity For cardinality=one attributes, replaces the value For cardinality=many attributes, adds to existing values The db/id attribute must be present in the attributes to identify the entity
func (*EtcdStore) PingSession ¶
func (*EtcdStore) Reindex ¶ added in v0.4.0
func (s *EtcdStore) Reindex(ctx context.Context, log *slog.Logger, opts ReindexOptions) (*ReindexStats, error)
Reindex rebuilds all index (collection) entries for every entity in the store. If opts.CleanupStale is true, it also scans for and removes stale collection entries that point to non-existent entities.
func (*EtcdStore) ReplaceEntity ¶
func (s *EtcdStore) ReplaceEntity( ctx context.Context, current *Entity, opts ...EntityOption, ) (*Entity, error)
ReplaceEntity atomically replaces all attributes of an entity The db/id attribute must be present in the attributes to identify the entity
func (*EtcdStore) RevokeLease ¶
func (*EtcdStore) RevokeSession ¶
func (*EtcdStore) UpdateEntity ¶
func (s *EtcdStore) UpdateEntity( ctx context.Context, id Id, changes *Entity, opts ...EntityOption, ) (*Entity, error)
UpdateEntity implements Store interface
func (*EtcdStore) WatchEntity ¶
type ExistsCheck ¶ added in v0.7.0
ExistsCheck is a function that reports whether a given short-id candidate is already in use.
type FileStore ¶
type FileStore struct {
// contains filtered or unexported fields
}
FileStore provides CRUD operations for entities
func NewFileStore ¶
NewFileStore creates a new entity store with the given base path
func (*FileStore) CreateEntity ¶
func (s *FileStore) CreateEntity(ctx context.Context, entity *Entity, opts ...EntityOption) (*Entity, error)
CreateEntity creates a new entity with the given type and attributes
func (*FileStore) DeleteEntity ¶
DeleteEntity deletes an entity by ID
func (*FileStore) GetAttributeSchema ¶
GetAttributeSchema retrieves an attribute schema by ID
func (*FileStore) GetEntities ¶
GetEntities retrieves multiple entities by their IDs
func (*FileStore) ListCollection ¶
func (*FileStore) UpdateEntity ¶
func (s *FileStore) UpdateEntity(ctx context.Context, id Id, updates *Entity, opts ...EntityOption) (*Entity, error)
UpdateEntity updates an existing entity
type Id ¶
const ( DBId Id = "db/id" DBShortId Id = "db/short-id" Ident Id = "db/ident" Doc Id = "db/doc" Uniq Id = "db/uniq" Cardinality Id = "db/cardinality" Type Id = "db/type" EnumValues Id = "db/enumValues" EntityElemType Id = "db/elementType" UniqueId Id = "db/unique.identity" UniqueValue Id = "db/unique.value" CardinalityOne Id = "db/cardinality.one" CardinalityMany Id = "db/cardinality.many" TypeAny Id = "db/type.any" TypeRef Id = "db/type.ref" TypeStr Id = "db/type.str" TypeKeyword Id = "db/type.keyword" TypeInt Id = "db/type.int" TypeFloat Id = "db/type.float" TypeBool Id = "db/type.bool" TypeTime Id = "db/type.time" TypeEnum Id = "db/type.enum" TypeArray Id = "db/type.array" TypeDuration Id = "db/type.duration" TypeComponent Id = "db/type.component" TypeLabel Id = "db/type.label" TypeBytes Id = "db/type.bytes" Index Id = "db/index" Session Id = "db/session" Tag Id = "db/tag" AttrSession Id = "db/attr.session" EntityAttrs Id = "db/entity.attrs" EntityPreds Id = "db/entity.preds" Ensure Id = "db/ensure" AttrPred Id = "db/attr.pred" Program Id = "db/program" EntityKind Id = "entity/kind" EntitySchema Id = "entity/schema" SchemaKind Id = "schema/kind" PredIP Id = "db/pred.ip" PredCIDR Id = "db/pred.cidr" Schema Id = "db/schema" TTL Id = "db/entity.ttl" Revision Id = "db/entity.revision" CreatedAt Id = "db/entity.created" UpdatedAt Id = "db/entity.updated" )
type Keywordable ¶
type Meta ¶
type Meta struct {
*Entity `json:"entity" cbor:"entity"`
Revision int64 `json:"version" cbor:"version"`
Previous int64 `json:"previous" cbor:"previous"`
}
func MigrateMetaFromBytes ¶
MigrateMetaFromBytes attempts to decode CBOR bytes as Meta, automatically migrating from old format if needed. This is used for read-time migration of persisted entity files.
func (*Meta) GetRevision ¶
func (*Meta) MarshalCBOR ¶
func (*Meta) MarshalJSON ¶
func (*Meta) UnmarshalCBOR ¶
func (*Meta) UnmarshalJSON ¶
type MigrateOptions ¶
type MigrateOptions struct {
// DryRun prevents writing changes back to etcd
DryRun bool
// Prefix is the etcd key prefix to scan (default: "/entity/")
Prefix string
}
MigrateOptions configures the migration behavior
type MigrateShortIdOptions ¶ added in v0.7.0
MigrateShortIdOptions configures the short-id migration behavior.
type MockStore ¶
type MockStore struct {
Entities map[Id]*Entity
OnWatchIndex func(ctx context.Context, attr Attr) (clientv3.WatchChan, error)
GetEntitiesFunc func(ctx context.Context, ids []Id) ([]*Entity, error)
OnListIndex func(ctx context.Context, attr Attr) ([]Id, error) // Hook to track ListIndex calls
NowFunc func() time.Time // Optional function to override current time
// WatchFromRevs records the fromRev argument of every WatchIndex call, in
// order, so tests can assert resume behavior.
WatchFromRevs []int64
// contains filtered or unexported fields
}
func NewMockStore ¶
func NewMockStore() *MockStore
func (*MockStore) AddEntity ¶
AddEntity is a thread-safe helper to directly add an entity to the mock store
func (*MockStore) CreateEntity ¶
func (*MockStore) CreateSession ¶
func (*MockStore) EnsureEntity ¶
func (*MockStore) GetAttributeSchema ¶
func (*MockStore) GetEntities ¶
func (*MockStore) GetEntityAtRevision ¶ added in v0.4.0
func (*MockStore) ListCollection ¶
func (*MockStore) ListIndexRevision ¶ added in v0.9.1
ListIndexRevision returns the matching ids along with a revision. The mock uses the highest entity revision currently in the store as a monotonic proxy for the cluster revision, which is sufficient for resume-cursor tests.
func (*MockStore) ListSessionEntities ¶
ListSessionEntities
func (*MockStore) PatchEntity ¶
func (*MockStore) PingSession ¶
PingSession
func (*MockStore) RemoveEntity ¶
RemoveEntity is a thread-safe helper to directly remove an entity from the mock store
func (*MockStore) ReplaceEntity ¶
func (*MockStore) RevokeSession ¶
RevokeSession
func (*MockStore) UpdateEntity ¶
func (*MockStore) WaitForEntityWatcher ¶ added in v0.4.0
WaitForEntityWatcher blocks until at least one watcher is registered for the given entity ID, or the context is cancelled.
func (*MockStore) WaitForIndexWatcher ¶ added in v0.3.0
WaitForIndexWatcher blocks until at least one watcher is registered for the given attribute, or the context is cancelled. This is useful in tests to ensure a watch is established before performing operations that should trigger watch notifications.
func (*MockStore) WatchEntity ¶
WatchEntity registers a watcher for an entity and returns a channel that receives updates
func (*MockStore) WatchFromRevsCopy ¶ added in v0.9.1
WatchFromRevsCopy returns a snapshot of the fromRev arguments seen by WatchIndex, for tests asserting resume behavior.
type OldEntity ¶
type OldEntity struct {
ID types.Id `cbor:"id"`
Revision int64 `cbor:"revision,omitempty"`
CreatedAt int64 `cbor:"created_at"` // milliseconds since epoch
UpdatedAt int64 `cbor:"updated_at"` // milliseconds since epoch
Attrs []Attr `cbor:"attrs"`
}
OldEntity represents the entity format before the migration to attribute-based metadata. In the old format, ID, Revision, CreatedAt, and UpdatedAt were struct fields in the CBOR encoding.
type OldMeta ¶
type OldMeta struct {
Entity OldEntity `cbor:"entity"`
Revision int64 `cbor:"version"`
Previous int64 `cbor:"previous"`
}
OldMeta represents the Meta format before the entity migration where the embedded Entity might be in OldEntity format
type ReindexOptions ¶ added in v0.4.0
ReindexOptions controls the behavior of a reindex operation.
type ReindexStats ¶ added in v0.4.0
type ReindexStats struct {
EntitiesProcessed int64
IndexesRebuilt int64
CollectionEntriesScanned int64
StaleEntriesFound int64
StaleEntriesRemoved int64
}
ReindexStats holds statistics about a reindex operation.
type SchemaCache ¶
type SchemaCache struct {
// contains filtered or unexported fields
}
func NewSchemaCache ¶
func NewSchemaCache(store Store) (*SchemaCache, error)
func (*SchemaCache) GetKindSchema ¶
func (f *SchemaCache) GetKindSchema(ctx context.Context, id Id) (*EncodedSchema, error)
func (*SchemaCache) GetSchema ¶
func (f *SchemaCache) GetSchema(ctx context.Context, schemaId Id) (*EncodedDomain, error)
type SchemaField ¶
type SchemaField struct {
Name string `json:"name" cbor:"name"`
Type string `json:"type" cbor:"type"`
Id Id `json:"id" cbor:"id"`
Many bool `json:"many,omitempty" cbor:"many,omitempty"`
EnumValues map[string]Id `json:"enum_values,omitempty" cbor:"enum_values,omitempty"`
Component *EncodedSchema `json:"component,omitempty" cbor:"component,omitempty"`
}
type Store ¶
type Store interface {
GetEntity(ctx context.Context, id Id) (*Entity, error)
GetEntityAtRevision(ctx context.Context, id Id, rev int64) (*Entity, error)
GetEntities(ctx context.Context, ids []Id) ([]*Entity, error)
WatchEntity(ctx context.Context, id Id) (chan EntityOp, error)
GetAttributeSchema(ctx context.Context, name Id) (*AttributeSchema, error)
CreateEntity(ctx context.Context, entity *Entity, opts ...EntityOption) (*Entity, error)
UpdateEntity(ctx context.Context, id Id, entity *Entity, opts ...EntityOption) (*Entity, error)
ReplaceEntity(ctx context.Context, entity *Entity, opts ...EntityOption) (*Entity, error)
PatchEntity(ctx context.Context, entity *Entity, opts ...EntityOption) (*Entity, error)
EnsureEntity(ctx context.Context, entity *Entity, opts ...EntityOption) (*Entity, bool, error)
DeleteEntity(ctx context.Context, id Id) error
WatchIndex(ctx context.Context, attr Attr, fromRev int64) (clientv3.WatchChan, error)
ListIndex(ctx context.Context, attr Attr) ([]Id, error)
// ListIndexRevision is like ListIndex but also returns the etcd revision the
// listing was read at, so a caller can resume a watch from that point.
ListIndexRevision(ctx context.Context, attr Attr) ([]Id, int64, error)
ListCollection(ctx context.Context, collection string) ([]Id, error)
CreateSession(ctx context.Context, ttl int64) ([]byte, error)
RevokeSession(ctx context.Context, session []byte) error
PingSession(ctx context.Context, session []byte) error
ListSessionEntities(ctx context.Context, session []byte) ([]Id, error)
}
type Validator ¶
type Validator struct {
// contains filtered or unexported fields
}
Validator provides methods to validate attribute values against their schemas
func NewValidator ¶
func NewValidator(store EntityStore) *Validator
NewValidator creates a new attribute validator
func (*Validator) ValidateAttribute ¶
ValidateAttribute validates a single attribute against its schema
func (*Validator) ValidateAttributes ¶
func (*Validator) ValidateEntity ¶
ValidateEntity validates all attributes in an entity against their schemas
func (*Validator) ValidateUpdate ¶ added in v0.12.0
ValidateUpdate validates the merged attributes of an update or patch. It behaves like ValidateAttributes but skips the reference-existence check for any reference attribute whose value is unchanged from the original entity.
A patch is only responsible for the references it sets. A reference that was already present and valid, whose target has since been deleted out from under the entity, must not block an unrelated change such as a status-only patch. Otherwise an entity holding a now-dangling reference becomes impossible to patch at all, and any controller reconciling it spins forever. See MIR-1320.
type Value ¶
type Value struct {
// contains filtered or unexported fields
}
func AnyValue ¶
AnyValue returns a Value for the supplied value.
If the supplied value is of type Value, it is returned unmodified.
Given a value of one of Go's predeclared string, bool, or (non-complex) numeric types, AnyValue returns a Value of kind KindString, KindBool, KindUint64, KindInt64, or KindFloat64. The width of the original numeric type is not preserved.
Given a time.Time or time.Duration value, AnyValue returns a Value of kind KindTime or KindDuration. The monotonic time is not preserved.
For nil, or values of all other types, including named types whose underlying type is numeric, AnyValue returns a value of kind KindAny.
func ArrayValue ¶
func BytesValue ¶
func ComponentValue ¶
func DurationValue ¶
DurationValue returns a Value for a time.Duration.
func Float64Value ¶
Float64Value returns a Value for a floating-point number.
func KeywordValue ¶
func KeywordValue[T Keywordable](v T) Value
func LabelValue ¶
func StringValue ¶
StringValue returns a new Value for a string.
func (Value) Clone ¶
Clone returns a deep copy of the Value. For bytes, arrays, and components, this creates new copies of the underlying data. For other types (strings, primitives, immutable types), a shallow copy is sufficient.
func (Value) Component ¶
func (v Value) Component() *EntityComponent
func (Value) Duration ¶
Duration returns v's value as a time.Duration. It panics if v is not a time.Duration.
func (*Value) MarshalCBOR ¶
func (Value) MarshalJSON ¶
func (Value) MarshalYAML ¶
func (Value) String ¶
String returns Value's value as a string, formatted like fmt.Sprint. Unlike the methods Int64, Float64, and so on, which panic if v is of the wrong kind, String never panics.
func (Value) Uint64 ¶
Uint64 returns v's value as a uint64. It panics if v is not an unsigned integer.