Object / global search across multiple types
v1 search (the @lde/search* family + ADR 0003/0004) is single-type: one SearchSchema
(type) → one concrete GraphQL output type + one root field (e.g. datasets: DatasetSearchResult),
querying one engine collection. This issue tracks extending it to object/global search across
multiple entity types (Person, CreativeWork, Term, …) — the deferred substrate-B/C direction from
ADR 0003. Coming back to it later; capturing the design now.
GraphQL shape — layer, do not replace
Keep the concrete per-type API and add a global, interface-typed field:
type Query {
datasets(where: DatasetWhere, …): DatasetSearchResult! # unchanged (v1)
people(where: PersonWhere, …): PersonSearchResult! # added
search(types: [SearchableType!], …): SearchResult! # added; items: [SearchResultItem!]!
}
interface SearchResultItem { id: String! } # + shared fields
type Dataset implements SearchResultItem { … } # the same Dataset datasets() returns
type Person implements SearchResultItem { … }
type SearchResult { items: [SearchResultItem!]!; total: Int!; facets: [Facet!]! } # incl. a type facet
- Concrete per-type fields stay the ergonomic path: per-type
where/facets/orderBy, concrete
output, no fragments. This is what the single-type browser wants.
- Global
search returns the SearchResultItem interface (clients discriminate with
... on Person / __typename), narrowable via a types filter, with a type facet.
- Do not collapse v1 to an interface-only
search — it taxes single-type clients with inline
fragments and loses per-type filters. A type enum argument cannot switch the return type
(GraphQL output type cannot depend on an arg value), so separate fields are the way.
- Adding
implements SearchResultItem to Dataset later is non-breaking, so v1 ships
concrete-only; the interface is introduced together with the second type.
buildSearchSchema extends from single-schema to multi-schema: per-type output types +
per-type root fields + the SearchResultItem interface + the global search field.
UI — grid-only for global results
Render mixed-type global results as a uniform grid using only the shared SearchResultItem
fields (one card shape), since cross-type results cannot use type-specific rich layouts. Per-type
pages keep their richer, type-specific rendering.
Storage — two options
- (a) One shared collection with a
type discriminator and a union of (optional) fields →
most coherent native global ranking, single pagination, the type facet falls out. Lean here
for a true universal search box.
- (b) Per-type collections + Typesense
multi_search union → one globally-ranked merged list
with pagination, but cross-collection _text_match scores are not perfectly comparable
(different schemas/weights), so ranking is a merge rather than one native context. Better when
the type schemas diverge a lot.
Notes
- Field model is unchanged — each type is just another
SearchSchema; projectGraph is already
multi-shape (takes a list of schemas).
- Related deferred payoffs from ADR 0003: the
inline reference strategy, the framed-JSON-LD
materialised view, and vector/hybrid search.
- Record the decision as ADR 0005 when this is built.
Object / global search across multiple types
v1 search (the
@lde/search*family + ADR 0003/0004) is single-type: oneSearchSchema(
type) → one concrete GraphQL output type + one root field (e.g.datasets: DatasetSearchResult),querying one engine collection. This issue tracks extending it to object/global search across
multiple entity types (Person, CreativeWork, Term, …) — the deferred substrate-B/C direction from
ADR 0003. Coming back to it later; capturing the design now.
GraphQL shape — layer, do not replace
Keep the concrete per-type API and add a global, interface-typed field:
where/facets/orderBy, concreteoutput, no fragments. This is what the single-type browser wants.
searchreturns theSearchResultIteminterface (clients discriminate with... on Person/__typename), narrowable via atypesfilter, with a type facet.search— it taxes single-type clients with inlinefragments and loses per-type filters. A
typeenum argument cannot switch the return type(GraphQL output type cannot depend on an arg value), so separate fields are the way.
implements SearchResultItemtoDatasetlater is non-breaking, so v1 shipsconcrete-only; the interface is introduced together with the second type.
buildSearchSchemaextends from single-schema to multi-schema: per-type output types +per-type root fields + the
SearchResultIteminterface + the globalsearchfield.UI — grid-only for global results
Render mixed-type global results as a uniform grid using only the shared
SearchResultItemfields (one card shape), since cross-type results cannot use type-specific rich layouts. Per-type
pages keep their richer, type-specific rendering.
Storage — two options
typediscriminator and a union of (optional) fields →most coherent native global ranking, single pagination, the type facet falls out. Lean here
for a true universal search box.
multi_searchunion → one globally-ranked merged listwith pagination, but cross-collection
_text_matchscores are not perfectly comparable(different schemas/weights), so ranking is a merge rather than one native context. Better when
the type schemas diverge a lot.
Notes
SearchSchema;projectGraphis alreadymulti-shape (takes a list of schemas).
inlinereference strategy, the framed-JSON-LDmaterialised view, and vector/hybrid search.