editor

package
v0.140.6 Latest Latest
Warning

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

Go to latest
Published: Jul 10, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package editor is the record-editing service surface: the JSON patch shape the API accepts, its validation against the editorial predicate whitelist, and the conversion to bibframe editorial patches. The typed WorkDoc mapper and operation lists layer on top in later tasks; this is the quad-level floor they compile down to.

Index

Constants

View Source
const ResourceItems = "items"

ResourceItems addresses every bf:Item in the grain at once (tasks/058). It is the only resource an op list can name that is not a single node: item ids are minted per grain, so a selection cannot name them, but "every copy shelved in Stacks" is exactly what a batch relocation means.

Variables

View Source
var DefaultPredicateAllowlist = []string{
	"http://id.loc.gov/ontologies/bibframe/",
	"http://id.loc.gov/ontologies/bflc/",
	"https://github.com/freeeve/libcat/ns#",
	"http://www.w3.org/2004/02/skos/core#",
	"http://www.w3.org/2000/01/rdf-schema#label",
}

DefaultPredicateAllowlist is the editorial predicate policy when a deployment configures none: BIBFRAME descriptive predicates, libcat's lcat: extension markers, and SKOS term descriptions. A fully open editorial graph would let one bad request poison projector output.

Functions

func ApplyOps

func ApplyOps(m *Mapper, grainNQ []byte, workID string, ops []Op, labels LabelResolver) ([]byte, error)

ApplyOps edits a grain through the profile mapper, translating field operations into the editorial write shapes of ARCHITECTURE §5:

  • Adding a value asserts it editorially (direct fields on the resource node; chained fields on a deterministic skolem IRI, since editorial statements cannot reference the feed's blank structure nodes).
  • Removing an editorial value retracts its quad.
  • Any edit that suppresses feed values (remove/set/clear touching them) claims the field with an lcat:overrides marker and re-asserts the surviving values editorially -- the tasks/042 semantics, so the feed stays untouched and revert is always possible.

Label companions (tasks/145): when labels is non-nil, every IRI the patch asserts on a prefLabel-annotated field also gets its vocabulary labels written into the grain's authority:<scheme> graph, so the grain stays self-describing (Duplicates compare, exports, the projection's label index) without a vocab lookup at read time -- feed parity with ingest.enrichmentQuads.

Returns the re-canonicalized grain.

Types

type Diff

type Diff struct {
	Added   []string `json:"added"`
	Removed []string `json:"removed"`
}

Diff describes the exact grain change a patch would make (the dry-run / diff-preview payload): canonical N-Quads lines added and removed.

func ComputeDiff

func ComputeDiff(grain []byte, p Patch) (Diff, []byte, error)

ComputeDiff applies the patch to a copy of the grain and reports the line-level delta between the canonical forms.

func DiffLines

func DiffLines(before, after []byte) Diff

DiffLines reports the canonical N-Quads lines added and removed between two grains -- the diff-preview payload for ops and patches alike.

func (Diff) Empty added in v0.100.0

func (d Diff) Empty() bool

Empty reports that the patch changes nothing. A save whose diff is empty must not write, republish, or audit: the grain store is content-addressed so the write is a no-op anyway, but the audit trail would gain a RECORD_EDIT row naming an etag identical to its predecessor's, indistinguishable from a real edit (tasks/249).

type FieldValue

type FieldValue struct {
	V    string `json:"v"`
	Lang string `json:"lang,omitempty"`
	// Datatype preserves a literal's explicit datatype IRI.
	Datatype string `json:"datatype,omitempty"`
	// IRI marks an entity-valued field (V is the IRI).
	IRI bool `json:"iri,omitempty"`
	// Prov is the named graph the value came from ("feed:overdrive",
	// "editorial:", "enrichment:locsh").
	Prov string `json:"prov"`
	// Overridden marks feed values shadowed by an lcat:overrides marker
	// (tasks/042).
	Overridden bool `json:"overridden,omitempty"`
	// Annotation is the field's display-only qualifier resolved from the
	// value's structure node (e.g. a heading's bf:source label, a
	// contribution's bf:role label). Its quads stay in passthrough; ToGrain
	// ignores it.
	Annotation string `json:"annotation,omitempty"`
	// Primary marks a chained value whose structure head is typed
	// bflc:PrimaryContribution (tasks/138) -- the author sorts before the
	// narrator. Display-only; ToGrain ignores it.
	Primary bool `json:"primary,omitempty"`
	// Node is the value's subject term in N-Quads syntax -- the resource
	// node for direct fields, the intermediate node for chained fields.
	// Reconstruction metadata; clients treat it as opaque.
	Node string `json:"node"`
}

FieldValue is one value of a profile field, with provenance for the editor's badges and the reconstruction metadata (Node) that keeps the grain <-> doc mapping lossless.

type LabelResolver

type LabelResolver func(iri string) (scheme string, labels map[string]string, ok bool)

LabelResolver resolves a controlled-term IRI to its vocabulary scheme and language-tagged prefLabels ("" key = untagged); ok is false when no loaded vocabulary knows the IRI. vocab.Index.LabelResolver adapts the index; nil disables label companions.

type Mapper

type Mapper struct {
	// WorkProfile shapes the Work fields; InstanceProfile the Instances'.
	WorkProfile     *profiles.Profile
	InstanceProfile *profiles.Profile
}

Mapper materializes grains through a profile pair.

func (*Mapper) ToDoc

func (m *Mapper) ToDoc(grainNQ []byte, workID string) (*WorkDoc, error)

ToDoc decomposes a grain into the typed document for one of its Works. Every quad is either claimed by exactly one profile field (rendered as a FieldValue) or preserved verbatim in Passthrough. Feed values whose (subject, predicate) carries an editorial lcat:overrides marker come back flagged Overridden -- shadowed in projection, shown to the editor for the hover-reveal / revert affordance.

func (*Mapper) ToGrain

func (m *Mapper) ToGrain(doc *WorkDoc) ([]byte, error)

ToGrain reassembles a document into canonical grain bytes: passthrough statements plus each field value rendered back onto its node. An unedited round-trip is byte-identical to the source grain.

type Op

type Op struct {
	// Resource is "work", an Instance id, or ResourceItems.
	Resource string `json:"resource"`
	// Path names the profile field, or an item field for ResourceItems.
	Path string `json:"path"`
	// Action: "add" one value, "remove" one matching value, "set" the whole
	// value set, "clear" every value.
	Action string    `json:"action"`
	Value  *OpValue  `json:"value,omitempty"`  // add / remove
	Values []OpValue `json:"values,omitempty"` // set
	// Where guards a ResourceItems op: only items whose current value at Path
	// is exactly this string are edited. Meaningless on the other resources.
	Where *string `json:"where,omitempty"`
}

Op is one field-level edit. The SPA emits ordered op lists; the same shape backs drafts, macros, and batch templates (the plan's everything-is-an-operation-list rule).

type OpValue

type OpValue struct {
	V    string `json:"v"`
	Lang string `json:"lang,omitempty"`
	IRI  bool   `json:"iri,omitempty"`
}

OpValue is one value in an operation.

type Patch

type Patch struct {
	Add    []Statement `json:"add,omitempty"`
	Remove []Statement `json:"remove,omitempty"`
}

Patch is the request body of a record edit: statements to add to and remove from the editorial graph.

func (Patch) BoundTo added in v0.92.0

func (p Patch) BoundTo(workID string) error

BoundTo checks that every Work-node subject in p names workID -- the single-record form of the tasks/240 rule. A patch aimed at one work has no business asserting statements about another work's node, and a grain that describes a Work it does not contain is invisible to every reader that resolves from the Work id inward.

Subjects that are not Work nodes (an Instance node, a skolem child, an absolute IRI) pass through: those are the shapes a single-record editorial patch legitimately mints, and unlike the batch case there is nothing to rebind them to.

func (Patch) RebindWork added in v0.92.0

func (p Patch) RebindWork(workID string) Patch

RebindWork returns p with every subject rebound to workID's Work node, so applying it to that work states something about that work. Call Rebindable first; a subject that is not a Work node is left alone here rather than silently rewritten.

func (Patch) Rebindable added in v0.92.0

func (p Patch) Rebindable() error

Rebindable reports whether p can be applied to a work other than the one its subjects name -- the question a batch edit has to answer before it writes anything (tasks/240).

A batch patch carries one literal subject. Applied verbatim to every selected work, it writes quads describing the first work into every other work's grain, and the dry run agrees with the corruption because it diffs the same verbatim patch. The subject has to be rebound per work, which is only meaningful for a Work node: an Instance id or a skolem child names a node in one grain and nothing at all in another. A grain-local object has the same problem. Both are refused rather than guessed at.

func (Patch) ToBibframe

func (p Patch) ToBibframe() bibframe.Patch

ToBibframe converts the wire patch to a bibframe editorial patch.

func (Patch) Validate

func (p Patch) Validate(allowlist []string) error

Validate checks the patch's shape against the allowlist (nil = default).

type ResourceDoc

type ResourceDoc struct {
	ID     string                  `json:"id"`
	Fields map[string][]FieldValue `json:"fields"`
}

ResourceDoc is one resource's field view.

type Statement

type Statement struct {
	S string `json:"s"`
	P string `json:"p"`
	O Term   `json:"o"`
}

Statement is the JSON wire form of one editorial statement. Subjects are IRIs (typically "#<id>Work" / "#<id>Instance" fragments); objects are IRIs or literals -- never blank nodes, per the editorial-graph constraint.

type Term

type Term struct {
	// Kind is "iri" or "literal".
	Kind  string `json:"kind"`
	Value string `json:"value"`
	Lang  string `json:"lang,omitempty"`
	// Datatype is the literal datatype IRI, when not a plain/lang string.
	Datatype string `json:"datatype,omitempty"`
}

Term is the JSON wire form of an RDF term.

type WorkDoc

type WorkDoc struct {
	WorkID    string        `json:"workId"`
	ProfileID string        `json:"profileId"`
	Work      ResourceDoc   `json:"work"`
	Instances []ResourceDoc `json:"instances"`
	// Passthrough holds the unclaimed statements as raw N-Quads lines.
	Passthrough []string `json:"passthrough"`
}

WorkDoc is the typed editing document one grain materializes into: the Work's profile fields, its Instances' fields, and a passthrough of every statement no field claims -- so doc -> grain reproduces the input byte-for-byte when nothing is edited.

Jump to

Keyboard shortcuts

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