Skip to content

Object/global search across multiple types (layer a SearchResultItem interface over the concrete per-type API) #530

Description

@ddeboer

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Fields

    No fields configured for Feature.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions