Documentation
¶
Index ¶
- Constants
- Variables
- func OptionalInt(v int) *int
- type ArchivedContentItem
- type ArchivedSnapshot
- type ArchivedSnapshotInput
- type ContentChange
- type ContentExport
- type ContentFields
- type ContentItem
- type ContentItemMatch
- type ContentSnapshot
- type ContentStore
- type ContentType
- type ContentTypeSchema
- type DeploymentRole
- type Deps
- type Engine
- func (e *Engine) Archive(ctx context.Context, match ContentItemMatch) (*ContentItem, error)
- func (e *Engine) Delete(ctx context.Context, match ContentItemMatch) (bool, error)
- func (e *Engine) ExportContent(ctx context.Context) (*ContentExport, error)
- func (e *Engine) GetArchivedSnapshot(ctx context.Context, input ArchivedSnapshotInput) (*ArchivedSnapshot, error)
- func (e *Engine) GetItem(ctx context.Context, match ContentItemMatch) (*ContentItem, error)
- func (e *Engine) GetSchemas(_ context.Context) (*SchemasResult, error)
- func (e *Engine) GetSnapshot(ctx context.Context, view SnapshotView, input SnapshotInput) (*Snapshot, error)
- func (e *Engine) Publish(ctx context.Context, match ContentItemMatch) (*ContentItem, error)
- func (e *Engine) ReplaceContent(ctx context.Context, export ContentExport) (*ReplaceContentResult, error)
- func (e *Engine) SaveDraft(ctx context.Context, input SaveDraftInput) (*ContentItem, error)
- func (e *Engine) SetFieldValue(ctx context.Context, input SetFieldValueInput) (*ContentItem, error)
- func (e *Engine) Unarchive(ctx context.Context, match ContentItemMatch) (*ContentItem, error)
- func (e *Engine) Unpublish(ctx context.Context, match ContentItemMatch) (*ContentItem, error)
- type Error
- type FieldDefinition
- type FieldEditingFormat
- type FieldID
- type FieldType
- type FieldUpdate
- type FieldValue
- type Kind
- type LocaleConfig
- type LocalizedFieldValues
- type Notifier
- type OwnerRef
- type Persistor
- func (p *Persistor) Close() error
- func (p *Persistor) Delete(ctx context.Context, id string) error
- func (p *Persistor) ExportAll(ctx context.Context) ([]*ContentItem, error)
- func (p *Persistor) FindAll(ctx context.Context, types []ContentType, ownerRef *OwnerRef) ([]*ContentItem, error)
- func (p *Persistor) FindArchived(ctx context.Context, types []ContentType) ([]*ContentItem, error)
- func (p *Persistor) FindPublished(ctx context.Context, types []ContentType, ownerRef *OwnerRef) ([]*ContentItem, error)
- func (p *Persistor) GetByID(ctx context.Context, id string) (*ContentItem, error)
- func (p *Persistor) GetByKey(ctx context.Context, key string) (*ContentItem, error)
- func (p *Persistor) ReplaceAll(ctx context.Context, items []*ContentItem) error
- func (p *Persistor) SetField(ctx context.Context, in FieldUpdate) (*ContentItem, error)
- func (p *Persistor) Upsert(ctx context.Context, item *ContentItem, expectedDraftRevision *int64) error
- type Registry
- type ReplaceContentResult
- type SaveDraftInput
- type SchemasResult
- type SetFieldValueInput
- type Snapshot
- type SnapshotInput
- type SnapshotItem
- type SnapshotView
- type Store
Constants ¶
const ( OpSaveDraft = "saveDraft" OpPublish = "publish" OpUnpublish = "unpublish" OpArchive = "archive" OpUnarchive = "unarchive" OpDelete = "delete" OpContentReset = "contentReset" )
Content-change operations reported to a Notifier.
const ( StateDraft = "draft" StateChanged = "changed" StatePublished = "published" StateArchived = "archived" )
Derived publishing states (never stored as an enum; see ContentItem.State).
const ContentExportFormatVersion = 1
Variables ¶
var ( // ErrNotFound is returned by a Store when a content item does not exist. ErrNotFound = errors.New("content item not found") // ErrConflict is returned when a conditional write precondition does not match. ErrConflict = errors.New("content item write conflict") )
Functions ¶
func OptionalInt ¶
OptionalInt returns a pointer to v, for optional schema length hints.
Types ¶
type ArchivedContentItem ¶
type ArchivedContentItem struct {
ID string `json:"id"`
Key string `json:"key,omitempty"`
Type ContentType `json:"type"`
OwnerRef *OwnerRef `json:"ownerRef,omitempty"`
Fields ContentFields `json:"fields"`
ArchivedAt time.Time `json:"archivedAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
ArchivedContentItem is one item in an ArchivedSnapshot. It exposes the draft (last working) fields and ArchivedAt; it deliberately omits a derived State field, which would always be "archived".
type ArchivedSnapshot ¶
type ArchivedSnapshot struct {
Revision int64 `json:"revision"`
CreatedAt time.Time `json:"createdAt"`
Items []ArchivedContentItem `json:"items"`
}
ArchivedSnapshot is the bulk read of archived content, for editorial views that let editors find, restore (unarchive), or hard-delete archived items — which are otherwise excluded from both consumer snapshot views. It carries the working (draft) fields and when each item was archived.
type ArchivedSnapshotInput ¶
type ArchivedSnapshotInput struct {
Types []ContentType `json:"types,omitempty"`
}
ArchivedSnapshotInput narrows the archived snapshot to the given content types (all when empty).
type ContentChange ¶
type ContentChange struct {
Type ContentType
OwnerRef *OwnerRef
Op string
}
ContentChange describes a successful content write so consuming applications can invalidate the configured published or preview representation.
type ContentExport ¶
type ContentExport struct {
FormatVersion int `json:"formatVersion"`
CreatedAt time.Time `json:"createdAt"`
ItemCount int `json:"itemCount"`
Checksum string `json:"checksum"`
Items []*ContentItem `json:"items"`
}
ContentExport is a complete, transportable snapshot of Content Store data. It includes archived content and both draft and published representations.
type ContentFields ¶
type ContentFields map[string]LocalizedFieldValues
ContentFields maps fieldID -> locale code -> value.
type ContentItem ¶
type ContentItem struct {
ID string `json:"id" bson:"_id"`
Key string `json:"key,omitempty" bson:"key,omitempty"`
Type ContentType `json:"type" bson:"type"`
OwnerRef *OwnerRef `json:"ownerRef,omitempty" bson:"ownerRef,omitempty"`
Draft *ContentSnapshot `json:"draft,omitempty" bson:"draft,omitempty"`
Published *ContentSnapshot `json:"published,omitempty" bson:"published,omitempty"`
ArchivedAt *time.Time `json:"archivedAt,omitempty" bson:"archivedAt,omitempty"`
CreatedAt time.Time `json:"createdAt" bson:"createdAt"`
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"`
}
ContentItem is one stored content piece, persisted as a single MongoDB document (ID is the Mongo _id). Draft and Published snapshots live in the same document.
func (*ContentItem) State ¶
func (i *ContentItem) State() string
State derives the publishing state. Archived takes precedence; a live item always has a Draft, so Published == nil means draft-only.
type ContentItemMatch ¶
ContentItemMatch selects an item by ID and/or Key.
type ContentSnapshot ¶
type ContentSnapshot struct {
Revision int64 `json:"revision" bson:"revision"`
SchemaVersion string `json:"schemaVersion" bson:"schemaVersion"`
SchemaHash string `json:"schemaHash" bson:"schemaHash"`
Fields ContentFields `json:"fields" bson:"fields"`
CreatedAt time.Time `json:"createdAt" bson:"createdAt"`
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"`
}
ContentSnapshot is one revision of an item's fields (draft or published).
type ContentStore ¶
type ContentStore interface {
SaveDraft(ctx context.Context, input SaveDraftInput) (*ContentItem, error)
SetFieldValue(ctx context.Context, input SetFieldValueInput) (*ContentItem, error)
Publish(ctx context.Context, match ContentItemMatch) (*ContentItem, error)
Unpublish(ctx context.Context, match ContentItemMatch) (*ContentItem, error)
Archive(ctx context.Context, match ContentItemMatch) (*ContentItem, error)
Unarchive(ctx context.Context, match ContentItemMatch) (*ContentItem, error)
Delete(ctx context.Context, match ContentItemMatch) (bool, error)
GetItem(ctx context.Context, match ContentItemMatch) (*ContentItem, error)
GetSnapshot(ctx context.Context, view SnapshotView, input SnapshotInput) (*Snapshot, error)
GetArchivedSnapshot(ctx context.Context, input ArchivedSnapshotInput) (*ArchivedSnapshot, error)
GetSchemas(ctx context.Context) (*SchemasResult, error)
ExportContent(ctx context.Context) (*ContentExport, error)
ReplaceContent(ctx context.Context, export ContentExport) (*ReplaceContentResult, error)
}
ContentStore is the generic, transport-neutral interface for interacting with content. *Engine implements it; a transport binding (e.g. gotsrpc M2M) wraps it and maps the returned *Error to its own error model.
type ContentType ¶
type ContentType string
ContentType identifies a content schema (e.g. "seoOverride").
type ContentTypeSchema ¶
type ContentTypeSchema struct {
Type ContentType `json:"type"`
Version string `json:"version"`
Hash string `json:"hash"`
Fields []FieldDefinition `json:"fields"`
// SuppressChangeEvents disables content-change notifications for this type
// (writes never call the injected Notifier). Default
// false = notify. It is a behavioural policy, not structural shape, and is
// deliberately excluded from schemaHash so toggling it never perturbs snapshot
// hashes. Use it for types with no downstream published-change consumer (e.g. an
// internal, never-published draft type) so their deletes stay quiet.
SuppressChangeEvents bool `json:"suppressChangeEvents,omitempty"`
}
ContentTypeSchema is the authoritative (Go-defined) schema for a content type.
func NewSchema ¶
func NewSchema(t ContentType, version string, fields []FieldDefinition) ContentTypeSchema
NewSchema builds a schema and computes its structural hash. Consuming applications use it to define their content types before registering them.
type DeploymentRole ¶
type DeploymentRole string
DeploymentRole is the immutable safety role of one Content Store instance.
const ( DeploymentRoleCanonical DeploymentRole = "canonical" DeploymentRoleSandbox DeploymentRole = "sandbox" )
func ParseDeploymentRole ¶
func ParseDeploymentRole(value string) (DeploymentRole, error)
ParseDeploymentRole validates a configured deployment role.
type Deps ¶
type Deps struct {
Store Store
Schemas *Registry
Locales LocaleConfig
NewID func() string
Notify Notifier
Role DeploymentRole
}
Deps are the injected collaborators an Engine needs. Everything project- or platform-specific (persistence, schemas, locales, ID generation, change notification) is supplied here so the core stays dependency-free.
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine is the content store's business logic over an injected Store, schema Registry, and LocaleConfig. It returns typed *Error values (never a transport error type).
func (*Engine) Archive ¶
func (e *Engine) Archive(ctx context.Context, match ContentItemMatch) (*ContentItem, error)
Archive hides an item while keeping both snapshots.
func (*Engine) Delete ¶
Delete hard-deletes an item. Deleting a missing item is idempotent (returns false).
func (*Engine) ExportContent ¶
func (e *Engine) ExportContent(ctx context.Context) (*ContentExport, error)
ExportContent returns every stored document, including archived items and both lifecycle snapshots, in a deterministic checksummed envelope.
func (*Engine) GetArchivedSnapshot ¶
func (e *Engine) GetArchivedSnapshot(ctx context.Context, input ArchivedSnapshotInput) (*ArchivedSnapshot, error)
GetArchivedSnapshot returns all archived content, optionally narrowed by type — the counterpart to the consumer snapshot views, which both exclude archived items. It carries each item's working (draft) fields and ArchivedAt, for editorial views that restore or hard-delete archived content.
func (*Engine) GetItem ¶
func (e *Engine) GetItem(ctx context.Context, match ContentItemMatch) (*ContentItem, error)
GetItem returns a single item by match.
func (*Engine) GetSchemas ¶
func (e *Engine) GetSchemas(_ context.Context) (*SchemasResult, error)
GetSchemas exposes the registered content schemas for tooling and editors.
func (*Engine) GetSnapshot ¶
func (e *Engine) GetSnapshot(ctx context.Context, view SnapshotView, input SnapshotInput) (*Snapshot, error)
GetSnapshot returns the compact consumer snapshot selected by view. Published reads never expose working drafts; preview reads expose the latest draft and derived lifecycle state.
func (*Engine) Publish ¶
func (e *Engine) Publish(ctx context.Context, match ContentItemMatch) (*ContentItem, error)
Publish copies the current draft snapshot into the published snapshot.
func (*Engine) ReplaceContent ¶
func (e *Engine) ReplaceContent(ctx context.Context, export ContentExport) (*ReplaceContentResult, error)
ReplaceContent atomically replaces all data on a sandbox instance. Canonical instances reject the operation before inspecting or writing the payload.
func (*Engine) SaveDraft ¶
func (e *Engine) SaveDraft(ctx context.Context, input SaveDraftInput) (*ContentItem, error)
SaveDraft creates or updates an item's draft snapshot. It does not publish.
func (*Engine) SetFieldValue ¶
func (e *Engine) SetFieldValue(ctx context.Context, input SetFieldValueInput) (*ContentItem, error)
SetFieldValue merges a single field/locale value into the item's draft, leaving other fields untouched. It creates the item when the match resolves to nothing and Type is given. A nil value clears that locale. This is the partial-write counterpart to SaveDraft (which replaces the whole field set) and backs per-field editing.
func (*Engine) Unarchive ¶
func (e *Engine) Unarchive(ctx context.Context, match ContentItemMatch) (*ContentItem, error)
Unarchive restores an archived item to Draft. It drops the published snapshot: a previously-published item is NOT re-published automatically (symmetric to Contentful, where archived entries are never published). The working draft is preserved, so no content is lost; re-publish to make it live again.
func (*Engine) Unpublish ¶
func (e *Engine) Unpublish(ctx context.Context, match ContentItemMatch) (*ContentItem, error)
Unpublish drops the published snapshot, returning the item to Draft while keeping the working draft. Unlike Unarchive it leaves ArchivedAt untouched, so it is the plain "take it off the live site" counterpart to Publish. An item that is not published is left alone (and emits nothing) rather than rejected, so bulk callers can apply it to a mixed selection.
type Error ¶
Error is the typed error returned by the engine. Kind lets callers map it to their transport's error model; Msg is a caller-safe message; Err wraps an underlying cause (nil for validation/state errors).
type FieldDefinition ¶
type FieldDefinition struct {
ID FieldID `json:"id"`
Type FieldType `json:"type"`
EditingFormat FieldEditingFormat `json:"editingFormat,omitempty"`
Localized bool `json:"localized"`
Required bool `json:"required"`
ReadOnly bool `json:"readOnly"`
Label string `json:"label,omitempty"`
Description string `json:"description,omitempty"`
ContentHint string `json:"contentHint,omitempty"`
FormatHint string `json:"formatHint,omitempty"`
SuggestedMinLength *int `json:"suggestedMinLength,omitempty"`
SuggestedMaxLength *int `json:"suggestedMaxLength,omitempty"`
}
FieldDefinition describes one field. ReadOnly marks system-generated fields editors may not author (defaults to editable).
type FieldEditingFormat ¶
type FieldEditingFormat string
FieldEditingFormat describes the value representation expected by an editor. It is separate from FieldType so plain text and Markdown can both use text storage while remaining distinguishable to clients.
const ( FieldEditingFormatPlainText FieldEditingFormat = "plainText" FieldEditingFormatContentfulRichText FieldEditingFormat = "contentfulRichText" FieldEditingFormatMarkdown FieldEditingFormat = "markdown" )
type FieldUpdate ¶
type FieldUpdate struct {
Match ContentItemMatch
Type ContentType
OwnerRef *OwnerRef
FieldID string
Locale string
Value FieldValue
SchemaVersion string
SchemaHash string
Now time.Time
NewID string
}
FieldUpdate is a single-field, single-locale draft write. It is applied as a targeted, atomic update (see Persistor.SetField) so concurrent writers editing different fields do not clobber each other and a first-time write creates the item exactly once.
type FieldValue ¶
type FieldValue = any
FieldValue is a single field's value for one locale: a string for symbol/text, an integer-compatible number, or a JSON object for richtext/json.
type Kind ¶
type Kind int
Kind classifies an engine error so a transport binding can map it to its own error model (e.g. an HTTP status or a gotsrpc ServiceError type) without the core depending on that model.
const ( // KindInternal is an unexpected/technical failure (storage, serialization). KindInternal Kind = iota // KindBadRequest is a caller/validation error (bad input, unknown type/field). KindBadRequest // KindNotFound is a missing item addressed by an id/key match. KindNotFound // KindNotAcceptable is a state precondition failure (e.g. nothing to publish, // or a required field missing on publish). KindNotAcceptable // KindConflict is an optimistic-concurrency or create-only precondition failure. KindConflict // KindForbidden is an operation disallowed by the immutable deployment role. KindForbidden )
type LocaleConfig ¶
LocaleConfig declares the locales the engine validates against. It is injected so the core does not depend on any project's locale package.
type LocalizedFieldValues ¶
type LocalizedFieldValues map[string]FieldValue
LocalizedFieldValues maps locale code -> value. Non-localized fields carry only the default-locale key.
type Notifier ¶
type Notifier func(ContentChange)
Notifier is a best-effort hook invoked on content changes. It must not fail the originating write; implementations handle their own errors. A nil Notifier disables emission.
type OwnerRef ¶
type OwnerRef struct {
Source string `json:"source" bson:"source"`
Type string `json:"type" bson:"type"`
ID string `json:"id" bson:"id"`
}
OwnerRef identifies the primary external entity this content is associated with. It is ownership/reconciliation metadata, not a generic reference system.
type Persistor ¶
type Persistor struct {
// contains filtered or unexported fields
}
Persistor is the MongoDB-backed Store. It depends only on the mongo driver.
func NewMongoStore ¶
NewMongoStore connects to MongoDB (database taken from the connection string) and ensures the content_items indexes, returning a ready Store.
func (*Persistor) Delete ¶
Delete hard-deletes the item by ID. Deleting a missing item is not an error.
func (*Persistor) ExportAll ¶
func (p *Persistor) ExportAll(ctx context.Context) ([]*ContentItem, error)
ExportAll reads every content document from one MongoDB snapshot transaction.
func (*Persistor) FindAll ¶
func (p *Persistor) FindAll(ctx context.Context, types []ContentType, ownerRef *OwnerRef) ([]*ContentItem, error)
FindAll returns non-archived items (draft, changed, and published), optionally narrowed by type and exact owner. It backs the editing-oriented draft snapshot; archived items are excluded.
func (*Persistor) FindArchived ¶
func (p *Persistor) FindArchived(ctx context.Context, types []ContentType) ([]*ContentItem, error)
FindArchived returns all archived items, optionally narrowed to the given types (all when empty). It is the counterpart to FindPublished/FindAll, which both exclude archived items.
func (*Persistor) FindPublished ¶
func (p *Persistor) FindPublished(ctx context.Context, types []ContentType, ownerRef *OwnerRef) ([]*ContentItem, error)
FindPublished returns all published, non-archived items, optionally narrowed by type and exact owner.
func (*Persistor) ReplaceAll ¶
func (p *Persistor) ReplaceAll(ctx context.Context, items []*ContentItem) error
ReplaceAll builds and verifies a temporary collection, then atomically renames it over content_items. Any failure before the rename leaves the live collection untouched.
func (*Persistor) SetField ¶
func (p *Persistor) SetField(ctx context.Context, in FieldUpdate) (*ContentItem, error)
SetField atomically merges one field/locale into the item's draft, creating the item (keyed by Match.Key) when absent. Unlike Upsert it does not read-modify-write the whole document: the merge is a server-side $set on draft.fields.<fieldID>.<locale>, so a burst of concurrent per-field writes all land instead of clobbering each other. A nil value clears that locale.
func (*Persistor) Upsert ¶
func (p *Persistor) Upsert(ctx context.Context, item *ContentItem, expectedDraftRevision *int64) error
Upsert inserts or replaces the item's document (keyed by _id). A nil expected revision preserves unconditional upsert behavior; zero is create-only; a positive value replaces only when the stored draft revision matches. It uses a full document replacement so fields cleared through omitempty are actually removed.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry holds the authoritative Go-defined content schemas. It is populated by the consuming application at startup (no package-level global), so the core carries no project-specific content types.
func (*Registry) Register ¶
func (r *Registry) Register(s ContentTypeSchema)
Register adds (or replaces) a schema. It computes the structural Hash when the schema does not already carry one.
type ReplaceContentResult ¶
type ReplaceContentResult struct {
ItemCount int `json:"itemCount"`
Checksum string `json:"checksum"`
}
ReplaceContentResult identifies the snapshot installed in a sandbox.
type SaveDraftInput ¶
type SaveDraftInput struct {
Match ContentItemMatch `json:"match"`
Type ContentType `json:"type,omitempty"`
OwnerRef *OwnerRef `json:"ownerRef,omitempty"`
Fields ContentFields `json:"fields"`
ExpectedRevision *int64 `json:"expectedRevision,omitempty"`
}
SaveDraftInput is the request for SaveDraft. Type is required only when creating. ExpectedRevision is optional: nil preserves unconditional save behavior, zero creates only when no item exists, and a positive value updates only that draft revision.
type SchemasResult ¶
type SchemasResult struct {
Schemas []ContentTypeSchema `json:"schemas"`
}
SchemasResult exposes the registered content schemas.
type SetFieldValueInput ¶
type SetFieldValueInput struct {
Match ContentItemMatch `json:"match"`
Type ContentType `json:"type,omitempty"`
OwnerRef *OwnerRef `json:"ownerRef,omitempty"`
FieldID string `json:"fieldID"`
Locale string `json:"locale"`
Value FieldValue `json:"value"`
}
SetFieldValueInput sets a single field's value for one locale, merging into the item's draft (other fields are left untouched). Type/OwnerRef are used only when the match resolves to nothing and the item must be created. A nil Value clears that locale.
type Snapshot ¶
type Snapshot struct {
// Revision fingerprints the returned items using their latest timestamp. It
// is not monotonic and can move backwards when the newest item is deleted;
// consumers must not use it as a conditional-reload cursor.
Revision int64 `json:"revision"`
CreatedAt time.Time `json:"createdAt"`
Items []SnapshotItem `json:"items"`
}
Snapshot is the common bulk-read shape for published and preview consumers. The selected view changes visibility and fields, not the response contract.
type SnapshotInput ¶
type SnapshotInput struct {
Types []ContentType `json:"types,omitempty"`
OwnerRef *OwnerRef `json:"ownerRef,omitempty"`
}
SnapshotInput narrows a consumer snapshot by content type and, optionally, exact owner. Empty filters return every item visible in the configured view.
type SnapshotItem ¶
type SnapshotItem struct {
ID string `json:"id"`
Key string `json:"key,omitempty"`
Type ContentType `json:"type"`
OwnerRef *OwnerRef `json:"ownerRef,omitempty"`
State string `json:"state"`
Fields ContentFields `json:"fields"`
UpdatedAt time.Time `json:"updatedAt"`
}
SnapshotItem is one compact consumer-facing item. UpdatedAt is the time at which the returned representation last changed: publish time for the published view and item update time for the preview view.
type SnapshotView ¶
type SnapshotView string
SnapshotView selects which persisted representation is visible through the consumer snapshot API.
const ( SnapshotViewPublished SnapshotView = "published" SnapshotViewPreview SnapshotView = "preview" )
func (SnapshotView) Valid ¶
func (v SnapshotView) Valid() bool
Valid reports whether v identifies a supported consumer snapshot view.
type Store ¶
type Store interface {
GetByID(ctx context.Context, id string) (*ContentItem, error)
GetByKey(ctx context.Context, key string) (*ContentItem, error)
Upsert(ctx context.Context, item *ContentItem, expectedDraftRevision *int64) error
SetField(ctx context.Context, in FieldUpdate) (*ContentItem, error)
Delete(ctx context.Context, id string) error
FindPublished(ctx context.Context, types []ContentType, ownerRef *OwnerRef) ([]*ContentItem, error)
FindAll(ctx context.Context, types []ContentType, ownerRef *OwnerRef) ([]*ContentItem, error)
FindArchived(ctx context.Context, types []ContentType) ([]*ContentItem, error)
ExportAll(ctx context.Context) ([]*ContentItem, error)
ReplaceAll(ctx context.Context, items []*ContentItem) error
}
Store is the persistence contract the engine depends on. *Persistor is the MongoDB-backed implementation; tests use a fake.