Skip to content

feat(search)!: implement inline references and class-less reference types#629

Merged
ddeboer merged 2 commits into
mainfrom
feat/inline-references
Jul 21, 2026
Merged

feat(search)!: implement inline references and class-less reference types#629
ddeboer merged 2 commits into
mainfrom
feat/inline-references

Conversation

@ddeboer

@ddeboer ddeboer commented Jul 19, 2026

Copy link
Copy Markdown
Member

Implements #608.

Realises the inline reference strategy that ADR 8 forward-declared and
ADR 11 decided:
reach a qualified or deep RDF value by inlining a reference and deriving from it,
never by growing the path grammar. Builds on #607 (the path/derive split and
internal fields).

What changed

  • Reference typesSearchType is now RootType | ReferenceType, told
    apart by one absence: a Root Type declares a class (indexed, keys the
    schema, earns a collection); a Reference Type declares none and is reached
    only through an inline reference. searchSchema partitions its arguments –
    root types key the class map, reference types go into a name index an inline
    ref.typeName resolves against. Because schema.values() yields only root
    types, no writer ever opens a run/collection for a reference type, and an
    indexed reference type fails to compile (the engine port and Typesense
    adapter are constrained to root types).

  • strategy: 'inline' – projection now carries a referent’s own projected
    fields. A referent is projected through its reference type as a nested
    SearchDocument (an array for an array reference), recursing to the depth
    the schema declares. Roles decide whether the nesting surfaces: a role-less
    inline reference is a reading device (internal, projected so a derive can
    flatten a value a path cannot address, then pruned before the writer); an
    output one is an API device that surfaces the nested type. So RDF depth
    and API shape stay independent.

  • Framing depthframeSubjects gains a depth parameter and follows the
    reference graph breadth-first (visiting each subject once, so a data cycle
    terminates). projectRoots derives the depth from the schema
    (inlineFramingDepth), floored at one hop, so the existing single-hop embed
    is preserved. Depth is a bounded property of the declaration, not a knob.

  • ValidationsearchSchema now checks that every inline ref.typeName
    resolves to a declared reference type and rejects inline cycles (the only way
    framing depth could be unbounded), at construction rather than index time.

Scope

idOnly stays a forward declaration; only inline is implemented here. The
Dataset Register migration (a separate repo, gated on #548) is out of scope –
this PR builds the LDE mechanism only.

Breaking

class is now optional on SearchType, and inline ref resolution is enforced
at searchSchema construction – a breaking @lde/search API change (feat!).

ddeboer added 2 commits July 19, 2026 20:00
…ypes

- Split SearchType into RootType (declares a class) and ReferenceType (no
  class). searchSchema partitions them, so only root types key the class map
  and earn a collection; reference types resolve an inline ref.typeName by name.
- Implement the inline strategy in projection: a referent is projected through
  its reference type as a nested Search Document (an array for an array
  reference), recursing to the depth the schema declares. A role-less inline
  reference is a reading device, pruned before the writer; an output one
  surfaces the nesting.
- frameSubjects gains a depth parameter and follows the reference graph
  breadth-first; projectRoots derives the depth from the schema, floored at one.
- searchSchema validates that every inline ref.typeName resolves to a declared
  reference type and rejects inline cycles, keeping framing depth bounded.
- Constrain the engine port and the Typesense adapter to root types, so an
  indexed reference type fails to compile.

BREAKING CHANGE: class is now optional on SearchType (a reference type declares
none); an inline ref.typeName is resolved and cycle-checked at searchSchema
construction.
…ne referents

- Prune internal (no-role) fields at every depth: a surfaced (output)
  inline reference now has its referent's internal helper fields removed
  before the writer, so the "a field without a role reaches neither the
  engine nor the API" invariant holds inside nested documents, not just
  at the root.
- Pruning runs as one post-order pass after all projection, so a derive
  at any depth still reads a helper field before it is removed.
- Mark ADR 11 Accepted.
@ddeboer
ddeboer merged commit 060a46d into main Jul 21, 2026
2 checks passed
@ddeboer
ddeboer deleted the feat/inline-references branch July 21, 2026 17:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant