Documentation
¶
Overview ¶
Package registry is fabriq's declarative schema registry. Entities are described once as EntitySpecs — relational shape via a grove-tagged model, fabric-only concerns (graph mapping, search mapping, subscription scopes, CRDT plane) layered on top — and everything else is derived: projection mappings, channel names, tenant-scoped store names, conformance checks.
The registry never generates DDL; grove migrations remain the schema authority and the registry-conformance test is the bridge.
Index ¶
- Constants
- Variables
- func ChannelName(tenantID string, scope Scope, id string) string
- func DocChannelName(tenantID, docID string) string
- func EventType(entity, verb string) string
- func GraphName(tenantID string) string
- func GraphNameVersioned(tenantID string, version int) string
- func SearchIndexAlias(tenantID, base string) string
- func SearchIndexVersioned(tenantID, base string, version int) string
- func StreamKey() string
- type Binding
- func (b *Binding) HasColumn(col string) bool
- func (b *Binding) IsDynamic() bool
- func (b *Binding) ModelType() reflect.Type
- func (b *Binding) NewModel() any
- func (b *Binding) Populate(model any, vals map[string]any) error
- func (b *Binding) Required() []string
- func (b *Binding) ValuesByColumn(model any) (map[string]any, error)
- type CRDTSpec
- type CacheSpec
- type ColumnType
- type DistillSpec
- type DynamicColumn
- type DynamicIndex
- type DynamicSchema
- type EdgeSpec
- type EmbedSpec
- type Entity
- type EntitySpec
- type GraphEdgeSpec
- type Kind
- type LiveSpec
- type Registry
- type Scope
- type SearchSpec
Constants ¶
const ( VerbCreated = "created" VerbUpdated = "updated" VerbDeleted = "deleted" )
Event verbs. EventType(entity, verb) is the only way event type strings are minted, so appliers and consumers can rely on the shape.
const ( ColumnID = "id" ColumnTenant = "tenant_id" ColumnVersion = "version" // ColumnScope is the optional secondary-scope column. It is nullable: a NULL // scope_id means the row is "shared" and visible in all scoped and unscoped // reads within the tenant. A non-NULL value restricts the row to that scope // (plus unscoped reads). Consumers that want secondary scoping must declare // this column in their entity model (grove tag: `db:"scope_id"`) and apply // migrations.ScopeAwareTenantPolicy to the table. ColumnScope = "scope_id" )
Structural column names used by fabriq adapters.
ColumnID, ColumnTenant, and ColumnVersion are REQUIRED on every fabriq-managed entity table: they make tenancy (RLS) and optimistic concurrency enforceable by construction.
ColumnScope is OPTIONAL. When a table carries it, consumers may partition rows within a tenant into a secondary scope (e.g. "project" within a "workspace") by adding the column and applying migrations.ScopeAwareTenantPolicy. Fabriq stamped-write paths detect presence of the column and fill it from the context scope; read paths enforce the soft filter automatically.
Variables ¶
var ByID = Scope{Name: "id"}
ByID scopes deltas to a single aggregate: changes:{tenant}:id:{aggID}.
var ByTenant = Scope{Name: "tenant"}
ByTenant scopes deltas to everything in the tenant.
Functions ¶
func ChannelName ¶
ChannelName derives a subscription channel. Channels are tenant-prefixed and only ever constructed here: changes:{tenant}:{scope}:{id}.
func DocChannelName ¶
DocChannelName derives the RAW document-sync channel for one document: doc:{tenant}:{docID}. Frames on it are never conflated.
func EventType ¶
EventType derives the canonical event type for an entity and verb, e.g. "asset.updated".
func GraphNameVersioned ¶
GraphNameVersioned derives a blue-green build target for rebuilds; the live pointer is tracked in projection_state.
func SearchIndexAlias ¶
SearchIndexAlias derives the stable per-tenant alias for a search index; reads and writes go through the alias, rebuilds swap it atomically.
func SearchIndexVersioned ¶
SearchIndexVersioned derives the concrete versioned index behind the alias.
Types ¶
type Binding ¶
type Binding struct {
Table string
Columns []string
PK string
TenantColumn string
VersionColumn string
// contains filtered or unexported fields
}
Binding is the compiled relational shape of an entity, derived from its grove-tagged model at registration time, or from a DynamicSchema.
func (*Binding) IsDynamic ¶
IsDynamic reports whether this binding describes a runtime-defined entity (declared via DynamicSchema rather than a Go Model).
func (*Binding) NewModel ¶
NewModel returns a pointer to a fresh zero value of the bound model type.
func (*Binding) Populate ¶
Populate sets the model's fields from column-keyed values (the reverse of ValuesByColumn). Numeric JSON widening (float64 -> int64 etc.) is converted; incompatible types error.
func (*Binding) Required ¶
Required returns the non-structural columns that must be provided on create/update: NOT NULL, no default, not auto-generated.
func (*Binding) ValuesByColumn ¶
ValuesByColumn extracts the model's field values keyed by column name. This is the canonical payload shape: event payloads, graph node props and search documents are all column-keyed. For dynamic entities model must be a map[string]any; unknown keys are dropped.
type CRDTSpec ¶
type CRDTSpec struct {
Engine string // engine reference, e.g. "grove-crdt"
SnapshotEvery int // compact after this many updates
QuietWindow time.Duration // idle window before materialization
}
CRDTSpec configures the document plane for KindDocument entities. The merge engine comes from grove's crdt packages — referenced, not reimplemented.
type CacheSpec ¶
CacheSpec opts an entity into the read-through row cache (P3). Nil (the zero value on EntitySpec) means caching is disabled for the entity. Scoped picks the cache partition: true => tenant+scope, false => tenant. TTL bounds each cached row (0 = no expiry; per-id eviction on write still applies).
type ColumnType ¶
type ColumnType int
ColumnType is the neutral column type set for dynamic entities; adapters map it to engine SQL types.
const ( ColText ColumnType = iota ColInt ColFloat ColBool ColTime ColJSON )
type DistillSpec ¶
type DistillSpec struct {
SourceFields []string
Text func(vals map[string]any) string
Scopes []string
Budget int
}
DistillSpec opts an entity into context distillation. Declarative metadata only — the distillation layer supplies the summarization model and the guard. SourceFields names the columns concatenated into the L0 source text; Text, when set, overrides SourceFields. Scopes names the declared scope names that form L1 backbone digest nodes. Budget is the L0 summary token budget (0 = config default).
type DynamicColumn ¶
type DynamicColumn struct {
Name string
Type ColumnType
NotNull bool
// Default is an optional SQL default EXPRESSION (e.g. "now()", "'pending'",
// "0"). It is interpolated verbatim into DDL and is intentionally NOT
// identifier-validated (it is an expression, not an identifier), so it must
// be a trusted, control-plane value — never a user-supplied string. Same
// trust level as hand-written migration SQL.
Default string
}
DynamicColumn is one domain column of a runtime-defined entity.
type DynamicIndex ¶
DynamicIndex is an optional secondary index on a dynamic entity.
type DynamicSchema ¶
type DynamicSchema struct {
Table string
Columns []DynamicColumn
Indexes []DynamicIndex
}
DynamicSchema describes an entity defined at runtime instead of by a Go Model. Mutually exclusive with EntitySpec.Model. fabriq injects the structural columns (id, tenant_id, version); declare only domain columns.
type EdgeSpec ¶
type EdgeSpec struct {
Field string // FK column on this entity's table
Rel string // relationship type, e.g. "LOCATED_AT"
Target string // registry name of the target entity
}
EdgeSpec maps a foreign-key column to a graph relationship.
type EmbedSpec ¶
EmbedSpec opts an entity into vector embedding. It is declarative metadata only — the agent layer supplies the embedding model. Fields names the columns whose values are concatenated into the embed text; Text, when set, overrides Fields and builds the text from column values.
type Entity ¶
type Entity struct {
Spec EntitySpec
Binding *Binding
}
Entity is a registered, compiled spec: the declarative EntitySpec plus its relational Binding.
type EntitySpec ¶
type EntitySpec struct {
Name string
Kind Kind
Model any
GraphNode string // graph label; empty = not projected to the graph
GraphEdge *GraphEdgeSpec // when set, the entity projects as a relationship
Edges []EdgeSpec
Search SearchSpec
Subscribe []Scope
CRDT *CRDTSpec
// Schema declares a runtime-defined ("dynamic") entity instead of Model.
// Exactly one of Model or Schema must be set.
Schema *DynamicSchema
// Validate, when set, runs after structural validation on every
// create/update/upsert with the column-keyed payload. Fabriq attaches
// no meaning to the values; consumers enforce their own invariants
// (enum membership, checksums, cross-field rules).
Validate func(vals map[string]any) error
// Live opts the entity into the maintained-result-set live query engine.
// Nil (the zero value) means live queries are disabled for this entity.
Live *LiveSpec
// Cache opts the entity into the read-through row cache. Nil = not cached.
Cache *CacheSpec
// Embed opts the entity into vector embedding (auto-indexing). Nil = not embedded.
Embed *EmbedSpec
// Distill opts the entity into context distillation: each row gets an
// L0 digest summary; declared Scopes form L1 backbone nodes. Nil = not
// distilled. The distillation layer supplies the Summarizer/Guard.
Distill *DistillSpec
}
EntitySpec declares one entity. Model must be a grove-tagged struct pointer such as (*domain.Asset)(nil); its table and columns are bound at registration.
type GraphEdgeSpec ¶
type GraphEdgeSpec struct {
TypeField string
SourceField string
TargetField string
SourceLabel string
TargetLabel string
PropFields []string
}
GraphEdgeSpec maps a reified-edge ENTITY (rows that ARE relationships) into the graph. Endpoints are matched by id under their identity labels; the rel type comes from a column value. General: reified relationships (membership, grant, subscription) are a common pattern, not specific to any domain.
type Kind ¶
type Kind int
Kind classifies how an entity is written.
const ( // KindAggregate entities are written exclusively through the command // plane: one transactional write, one versioned outbox event. KindAggregate Kind = iota // KindDocument entities are collaborative CRDT documents: updates land // in the append-only document plane and are periodically materialized // into an ordinary versioned domain event. The plane's implementation // is deferred; the seam exists from phase 1. KindDocument )
type LiveSpec ¶
type LiveSpec struct {
Filterable []string // columns allowed in Where (empty = all)
Sortable []string // columns allowed in Sort (empty = all)
MaxWindow int // cap on Limit (0 = engine default)
}
LiveSpec opts an entity into the live query engine (nil = disabled). Filterable/Sortable default to all columns when empty; columns are validated against the model at registration.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry holds all registered entities. Registration happens at startup; lookups are concurrent and read-only afterwards.
func (*Registry) GetByModelType ¶
GetByModelType returns the entity bound to the given model struct type; it powers hydration-target inference in TraverseAndHydrate.
func (*Registry) MustRegister ¶
func (r *Registry) MustRegister(spec EntitySpec)
MustRegister is Register that panics; for static wiring in domain packs.
func (*Registry) Register ¶
func (r *Registry) Register(spec EntitySpec) error
Register compiles and validates a spec. Cross-entity references (edge targets) are checked in Validate once all entities are registered.
type Scope ¶
type Scope struct {
// Name appears in channel names: changes:{tenant}:{name}:{id}.
Name string
// Field is the model column whose value provides the channel id for
// containing-scope channels (e.g. "site_id" for a by-site scope).
// Empty for the ByID and ByTenant builtins.
Field string
}
Scope names a subscription dimension. Channels are always resolved server-side from (tenant, scope, id) — clients never name channels.
type SearchSpec ¶
type SearchSpec struct {
Index string // logical index base name; tenant routing is derived
Fields []string // columns included in the indexed document
}
SearchSpec maps an entity into the search projection. The zero value (empty Index) means the entity is not indexed.