Help us shape the future of Go! Take the 2026 Go Developer Survey

entitystore

package
v0.135.0 Latest Latest
Warning

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

Go to latest
Published: Oct 28, 2026 License: MIT Imports: 35 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrConflict = errors.New("entities: conflict")
)
View Source
var (
	ErrForbidden = errors.New("entities: forbidden")
)
View Source
var (
	// ErrLimit is a person owning as many entities of a kind as it allows
	// (kinds.EntityKind.MaxPerOwner).
	ErrLimit = errors.New("entities: limit reached")
)
View Source
var (
	ErrNotFound = errors.New("entities: not found")
)

Functions

This section is empty.

Types

type CardOptions

type CardOptions struct {
	// IncludeInternal is a trusted management read; As stores require staff.
	IncludeInternal bool
	// Language resolves names and paths as ListOptions.Language does.
	Language string
	// Covers fills covers' and avatars' renditions in one call; chips leave
	// them out.
	Covers bool
	// NamesOnly reads names and paths without counts or automatic covers,
	// which are derived from every content of each entity.
	NamesOnly bool
}

CardOptions resolve Cards.

type Folders

type Folders interface {
	RetireTx(context.Context, pgx.Tx, []entities.Ref) error
	RetirementStartedTx(context.Context, pgx.Tx, entities.Ref) (bool, error)
	RetirementComplete(context.Context, entities.Ref) (bool, error)
	PurgeRetired(context.Context, int) (int, error)
}

Folders records durable retirement and reports its actual storage outcome. PurgeRetired is invoked only by the existing content-deletion sweep.

type ListOptions

type ListOptions struct {
	Kind string
	// Query matches a name, alias or slug for management selectors.
	Query string
	// IncludeInternal is a trusted management read; As stores require staff.
	IncludeInternal bool
	// Deleted lists deleted entities instead of active ones; with IncludeInternal only.
	Deleted bool
	// IDs lists these entities (at most MaxListIDs) in every state, merged
	// tombstones included; with IncludeInternal only and without Deleted.
	IDs []entities.EntityID
	// Parent keeps the children of one entity (sub-series of a series).
	Parent entities.EntityID
	// Scope keeps the entities scoped by one entity (characters of a series).
	Scope entities.EntityID
	Slug  string
	// Letter keeps entities whose sort name starts with it (a-z), or with
	// anything else ("#").
	Letter string
	// Language resolves names, sort keys and slugs, scopes counts and
	// prefixes paths. Empty: names and paths in the first configured
	// language, counts over every language.
	Language string
	// ContentKind scopes counts to one content kind; empty sums them.
	ContentKind string
	Sort        entities.Sort
	// Cursor is the previous page's NextCursor under the same Sort.
	Cursor string
	// Page numbers pages from 1 instead of Cursor; the page then carries Total.
	Page int
	// Limit defaults to 50 and is capped at 200.
	Limit int
	// contains filtered or unexported fields
}

ListOptions filters, orders and pages List. Only active entities are listed.

type Options

type Options struct {
	Pool   *pgxpool.Pool
	Schema string
	Tenant string
	// Kinds is the site's content model; its EntityKinds govern every write.
	Kinds kinds.Config
	// ContentChanged runs whenever the effective entities of contents may have
	// changed (their facets' entity_ids are already re-derived), so the engine
	// can rebuild what else depends on them, such as search documents: in the
	// writing transaction, or for a large reach in the refresh job's batch
	// transactions. Optional.
	ContentChanged func(ctx context.Context, q Querier, contentIDs []string) error
	// NamesChanged runs in the writing transaction after the names or slugs
	// of entities changed, so the engine can queue the reindexing of content
	// that shows them (search documents carry entity names). Their facets are
	// unchanged; it must not lock content, and should write per entity, not
	// per item. Optional.
	NamesChanged func(ctx context.Context, q Querier, entityIDs []string) error
	// DocumentsChanged runs in the writing transaction after a write queued
	// search documents, so the engine can sync them at once. Optional.
	DocumentsChanged func(ctx context.Context, q Querier) error
	// EntitiesChanged records entity events in the writing transaction: the
	// catalog event log. Optional.
	EntitiesChanged func(ctx context.Context, q Querier, changes []entities.Change) error
	// Covers fills covers' and avatars' renditions on reads. Optional.
	Covers entities.CoverResolver
	// Gate reads the viewer's entitlement-backed entity membership.
	Gate *entitlement.Gate
	// Scopes provisions and retires the AuthKit scopes of auth_scope kinds,
	// and lists a person's. Optional: without it auth_scope is whatever staff
	// set.
	Scopes entities.Scopes
	// Folders retires a user-owned entity's media after DeletedRetention and
	// confirms physical erasure before marking it purged. Irreversible claims
	// refuse restoration: media/versions.Service.EntityFolders.
	// Optional.
	Folders Folders
	// DeletedRetention is how long a deleted user-owned entity keeps its media
	// for a restore; zero is DefaultDeletedRetention.
	DeletedRetention time.Duration
	// Owned deletes the content a deleted user-owned entity owns
	// (owner_entity) and restores it with the entity: the catalog. Optional.
	Owned entities.OwnedContent
	// Logger receives the HTTP API's failures. nil -> slog.Default().
	Logger *slog.Logger
}

Options configures one tenant's store.

type Querier

type Querier interface {
	Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error)
	Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error)
	QueryRow(ctx context.Context, sql string, args ...any) pgx.Row
}

Querier is a pgx pool, connection or transaction.

type Store

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

Store is one tenant's entities.

func New

func New(opts Options) (*Store, error)

New validates the options and returns the store.

func (*Store) AddEdges

func (s *Store) AddEdges(ctx context.Context, edges []entities.Edge) error

AddEdges adds edges between active entities of the tenant; an endpoint that is missing or not active is ErrNotFound.

func (*Store) AddMemberships

func (s *Store) AddMemberships(ctx context.Context, ms []entities.Membership) error

AddMemberships adds memberships, or updates the position and state of existing ones (same entity, content, version and role).

func (*Store) AddMembershipsTx

func (s *Store) AddMembershipsTx(ctx context.Context, tx pgx.Tx, in []entities.Membership) error

AddMembershipsTx adds associations in a caller-owned transaction, preserving every other shared and version-specific association. Authorize the mutation before opening tx; the same hierarchy-lock ordering as SetContentEntitiesTx applies.

func (*Store) AddNames

func (s *Store) AddNames(ctx context.Context, edits []entities.EntityNames) error

AddNames upserts names and aliases; a new canonical name demotes the previous one of its language to an alias.

func (*Store) As

func (s *Store) As(a auth.Authenticator, actor access.Actor) *Store

As returns the store acting as actor, judged by a. Its reads show actor's standing with entitlement-backed entities. Its writes: staff holding access.EntitiesWrite in a's root scope (and the engine's own client) write anything; anyone else writes only user-owned entities, under the creator rules of CreateEntities and UpdateEntities. Without As a store reads as an anonymous viewer and writes as the host, unchecked.

func (*Store) BuildDocuments

func (s *Store) BuildDocuments(ctx context.Context, kind, language string, refs []contentref.ContentRef) ([]search.KeywordDocument, error)

BuildDocuments builds entity documents: one per active entity and language, titled by that language's canonical name (falling back to English, then any language), with the language's aliases and every other name as aliases and overflow as keywords. Missing, merged and deleted entities yield no document. Internal classifications are never search documents.

func (*Store) Cards

func (s *Store) Cards(ctx context.Context, ids []entities.EntityID, o CardOptions) ([]entities.EntityCard, error)

Cards returns the active entities among ids as cards, resolved as List resolves them, in no particular order.

func (*Store) ChannelMetadata

func (s *Store) ChannelMetadata(ctx context.Context, id entities.EntityID) ([]entities.EntityID, error)

ChannelMetadata returns descriptive metadata on a reserved channel. This association never enters content facets or grants channel/post authority.

func (*Store) ChannelStorage

func (s *Store) ChannelStorage() *Store

ChannelStorage is the reserved persistence handle used by the channels module. It cannot read or mutate descriptive entities. Normal stores cannot read or mutate channels, even through raw IDs or management endpoints.

func (*Store) ContentAuthorEntities

func (s *Store) ContentAuthorEntities(ctx context.Context, a auth.Authenticator, actor access.Actor, editing bool) ([]entities.EntityID, error)

ContentAuthorEntities returns channels where the current credential may read or edit its own posts. This does not confer authority over other authors.

func (*Store) ContentEditorEntities

func (s *Store) ContentEditorEntities(ctx context.Context, a auth.Authenticator, actor access.Actor) ([]entities.EntityID, error)

ContentEditorEntities lists only this actor's owned or delegated content-writing entities. The scope directory is narrowed to this app and credential ceilings by heldScopes, as it is for Mine. It never enumerates catalog content.

func (*Store) CreateEntities

func (s *Store) CreateEntities(ctx context.Context, inputs []entities.EntityInput) ([]entities.Entity, error)

CreateEntities creates entities with their names and text in one transaction. A taken id or slug is ErrConflict; a kind, scope or parent the kinds config does not allow is ErrInvalid. Written as a person (As), each is of a user-owned kind and the person's own, within the kind's MaxPerOwner (ErrLimit), linked only to entities the person edits; anything else is ErrForbidden. An auth_scope kind's entity gets its AuthKit scope (Scopes) synchronously after commit; provider failures are returned for caller retry.

func (*Store) CreateEntitiesTx

func (s *Store) CreateEntitiesTx(ctx context.Context, tx pgx.Tx, in []entities.EntityInput) ([]entities.Entity, error)

CreateEntitiesTx is CreateEntities in a caller-owned transaction. A store bound with As retains that actor and all its ordinary authorization checks. Channels require an explicit FinishChannels call after the caller commits; this method never starts background work.

func (*Store) DeleteEntities

func (s *Store) DeleteEntities(ctx context.Context, ids []entities.EntityID) ([]entities.Entity, error)

DeleteEntities marks entities deleted: they keep their memberships and code but are effective nowhere, listed nowhere and lose their documents.

func (*Store) Detail

func (s *Store) Detail(ctx context.Context, id entities.EntityID, language string) (entities.EntityDetail, error)

Detail returns an active entity with its names, text, parent, scope, children, edges, counts and membership standing, resolved for language as List does. A merged entity answers with its survivor; a deleted one is ErrNotFound.

func (*Store) DocumentKinds

func (s *Store) DocumentKinds() []string

DocumentKinds are the entity kinds: the store is a worker.Source.

func (*Store) EffectiveEntities

func (s *Store) EffectiveEntities(ctx context.Context, q Querier, versionIDs []string) (map[string][]string, error)

EffectiveEntities returns, per version, the sorted ids of the active entities it belongs to through its content's memberships or its own, plus each one's active ancestors via parent_id. It runs on q, the caller's transaction, so memberships written in it count. Unknown versions are absent; a version in no entity maps to an empty list.

func (*Store) Entities

func (s *Store) Entities(ctx context.Context, ids []entities.EntityID) ([]entities.Entity, error)

Entities is a trusted raw lookup, including internal classifications and any state, in input order. Reader-facing callers use Cards or Detail instead.

func (*Store) FinishChannels

func (s *Store) FinishChannels(ctx context.Context, ids []entities.EntityID) error

FinishChannels completes the external effects of committed channel writes. It is safe to retry after a failed request, including a lost response from AuthKit. Calls run outside SQL transactions, so embedded and remote providers share the same contract. It adds no job, timer or separate retry record.

func (*Store) Handler

func (s *Store) Handler(a auth.Authenticator) (http.Handler, error)

Handler serves the entities API (routes): listings and entity pages, each with the viewer's standing with entitlement-backed entities; creator writes of user-owned entities (As); and staff writes for holders of access.EntitiesWrite in a's root scope. Errors are JSON {"error", "code"}: 400 for ErrInvalid, 403 for ErrForbidden, 404 for ErrNotFound, 409 for ErrConflict and ErrLimit (limit_reached), 503 when auth or billing is down and a sanitized 500 otherwise; the cause always reaches Options.Logger.

func (*Store) ImportDeletes

func (s *Store) ImportDeletes(ctx context.Context, q pgx.Tx, ids []entities.EntityID) error

ImportDeletes deletes the active entities among ids in q, as DeleteEntities does; deleted and merged ones stay as they are.

func (*Store) ImportEntities

func (s *Store) ImportEntities(ctx context.Context, q pgx.Tx, in []entities.ImportEntity, provision bool) error

ImportEntities writes entities in q: new ones are inserted, stored ones updated where the import changes them. Codes, search documents and the facets of the content a moved or deleted entity reaches follow. A merged entity is ErrConflict; a taken slug too. An active entity of an auth_scope kind keeps the AuthKit scope it names or has; one with neither is provisioned, by the caller after commit with provision (else ErrInvalid).

func (*Store) ImportMemberships

func (s *Store) ImportMemberships(ctx context.Context, q pgx.Tx, sets []entities.ContentEntities) error

ImportMemberships replaces, in q, each content's memberships in the entity kinds its set names (SetContentEntities); a set equal to what is stored writes nothing.

func (*Store) List

func (s *Store) List(ctx context.Context, opts ListOptions) (entities.EntityPage, error)

List pages active entities with their display name, code, path, cover, avatar, count and, for an entitlement-backed kind, the store viewer's membership (As).

func (*Store) ListDocuments

func (s *Store) ListDocuments(ctx context.Context, kind, language, cursor string, limit int) ([]contentref.ContentRef, string, bool, error)

ListDocuments pages the active entities of kind in id order.

func (*Store) LockHierarchyShared

func (s *Store) LockHierarchyShared(ctx context.Context, q Querier) error

LockHierarchyShared holds off hierarchy changes for the transaction's rest while letting other membership writers through. A membership writer takes it before any content or entity lock, so a hierarchy change either sees its membership or is seen by its facets.

func (*Store) LockNames

func (s *Store) LockNames(ctx context.Context, q Querier) error

LockNames serializes name resolution in this catalog, so concurrent writers that find no match never mint the same entity twice.

func (*Store) ManagementDetail

func (s *Store) ManagementDetail(ctx context.Context, id entities.EntityID, language string) (entities.EntityDetail, error)

ManagementDetail includes internal classifications and requires staff on an As-bound store. Public Detail remains public even for an administrator.

func (*Store) MatchName

func (s *Store) MatchName(ctx context.Context, q Querier, kind, scopeID, language, name string) ([]string, error)

MatchName returns at most two active entities of kind within scope whose canonical name or alias matches name, ignoring case, accents and word separators; in language only when it is set.

func (*Store) MatchNames

func (s *Store) MatchNames(ctx context.Context, m entities.MatchQuery) ([]entities.NameMatch, error)

MatchNames resolves names to entity ids, one NameMatch per name in order. It neither creates entities nor reserves names; merged and deleted entities never match.

func (*Store) Members

Members pages an entity's memberships in member order: by position (an ordered entity), then content, version (content first) and role. ErrNotFound for an entity the tenant lacks.

func (*Store) MembershipsOf

func (s *Store) MembershipsOf(ctx context.Context, contentIDs []string) (map[string][]entities.Membership, error)

MembershipsOf returns every membership of each content and its versions, by content id, ordered by entity and version (content first).

func (*Store) Merge

func (s *Store) Merge(ctx context.Context, merges []entities.Merge) ([]entities.MergeReport, error)

Merge folds each From into its Into in one transaction: memberships move (duplicates collapse, an accepted membership wins over a proposed one), names become aliases of Into, edges, children and scoped entities re-point, From becomes merged and its code redirects to Into's. Entities of a user-owned or entitlement-backed kind are never merged (ErrConflict).

func (*Store) Mine

func (s *Store) Mine(ctx context.Context, opts ListOptions) (entities.MyEntityPage, error)

Mine pages the active entities the store's viewer (As) owns, edits or writes content of through its AuthKit scope (Options.Scopes) or holds the key of (Options.Gate), filtered, ordered and paged as List.

func (*Store) PrepareStaffWrite

func (s *Store) PrepareStaffWrite(ctx context.Context) (bool, error)

PrepareStaffWrite resolves this As-bound store's staff permission before a caller holds a database transaction. The result is cached only on this actor-bound store, so subsequent transactional writes need no pool query. False is not an error: callers that only resolve existing taxonomy need not create any entities.

func (*Store) PurgeFolders

func (s *Store) PurgeFolders(ctx context.Context, limit int) (int, error)

PurgeFolders claims expired entity media, runs the shared retirement pass, and marks only physically completed folders. Domain rows remain retryable after a storage failure. The catalog deletion sweep is its sole scheduler.

func (*Store) RemoveEdges

func (s *Store) RemoveEdges(ctx context.Context, edges []entities.Edge) error

RemoveEdges deletes edges; unknown edges are ignored.

func (*Store) RemoveMemberships

func (s *Store) RemoveMemberships(ctx context.Context, ms []entities.Membership) error

RemoveMemberships deletes memberships matched by entity, content, version and role; unknown ones are ignored.

func (*Store) RemoveNames

func (s *Store) RemoveNames(ctx context.Context, edits []entities.EntityNames) error

RemoveNames deletes the given names, matched by language and normalized form; unknown names are ignored.

func (*Store) Reorder

func (s *Store) Reorder(ctx context.Context, orders []entities.Order) error

Reorder sets the member order of ordered entities: the listed members take positions 1..n in list order (every role of a member moves together), the others follow in their current order. A listed member the entity lacks is ErrNotFound.

func (*Store) SetChannelMetadata

func (s *Store) SetChannelMetadata(ctx context.Context, id entities.EntityID, ids []entities.EntityID) error

func (*Store) SetContentEntities

func (s *Store) SetContentEntities(ctx context.Context, sets []entities.ContentEntities) error

SetContentEntities replaces, per content, its content- and version-level memberships in the entity kinds the set names; memberships in other kinds are untouched. Positions omitted for existing memberships are kept.

func (*Store) SetContentEntitiesTx

func (s *Store) SetContentEntitiesTx(ctx context.Context, tx pgx.Tx, in []entities.ContentEntities) error

SetContentEntitiesTx is SetContentEntities in a caller-owned transaction. Like SetContentEntities, its caller must first authorize editing those contents. It takes LockHierarchyShared; a tx that already holds entity or content row locks must take that first, or it can deadlock with a hierarchy change.

func (*Store) SetNames

func (s *Store) SetNames(ctx context.Context, edits []entities.EntityNames) error

SetNames replaces every name and alias of each entity.

func (*Store) Tenant

func (s *Store) Tenant() string

Tenant returns the tenant this store is scoped to.

func (*Store) UpdateEntities

func (s *Store) UpdateEntities(ctx context.Context, updates []entities.EntityUpdate) ([]entities.Entity, error)

UpdateEntities applies each update in one transaction. A merged entity is never updated (ErrConflict). Deleting or restoring an entity, or moving it to another parent, re-derives the facets of the content it reaches, retires or provisions its AuthKit scope and hides or shows its media; deleting a user-owned entity deletes the content it owns, restoring it restores that content and, within the retention, its media (Options.Owned, Options.Folders). Billing product names are managed by the billing provider. Written as a person (As), each entity is a user-owned one the person owns or holds access.EntitySettingsManage in, its slug, links, owner and scope stay, and a restore stays within the kind's MaxPerOwner (ErrLimit); anything else is ErrForbidden.

Jump to

Keyboard shortcuts

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