Adapters

Prev Next

Adapters

Adapters exist so the core system never has to know where its data comes from.

Why Adapters Exist

The Command Center indexes data from many domains — navigation routes, actions, settings, content entities. Each domain has its own shape, its own quirks, its own source of truth.

Without adapters, the core would need to understand every domain. Every new data source would mean touching search, indexing, and rendering code. This couples the system to its inputs.

Adapters break that coupling. They sit between raw domain data and the unified index, translating one shape into another. The core system consumes IndexItem[] and nothing else. It does not know — and must not know — whether items came from a JSON file, an API response, or a hardcoded list.

The Contract

Every adapter is a function that returns IndexItem[]:

type IndexProvider = () => IndexItem[];

That is the entire contract. No base class, no interface beyond the return type, no lifecycle hooks.

What an adapter must do

  • Return IndexItem[] — the unified shape the index layer expects
  • Assign stable, deterministic id values — IDs must not change between builds for the same source data
  • Set type correctly — this determines how results are grouped and dispatched
  • Enrich keywords — raw titles are not enough for fuzzy search; adapters must add synonyms, parent context, and alternative phrasings

What an adapter must not do

  • Contain UI logic or rendering concerns
  • Perform side effects (network calls, state mutations, logging)
  • Know about the search library, scoring, or ranking
  • Reference other adapters or the engine directly

Keyword Enrichment

Adapters are the only place where keyword enrichment happens. This is deliberate.

  • The search engine scores against title, keywords, and description.
  • If an adapter returns an item with title: "Plans" and no keywords, the user can only find it by typing "plans".
  • But if the adapter adds ["plans", "monetization plans", "monetization", "pricing"], the item surfaces for any of those queries.

:::tip
The quality of search results is directly proportional to the quality of keyword enrichment in adapters. The engine does not compensate for sparse keyword lists — it scores what it receives.
:::

Rules for keywords:

  • Always include the lowercased title
  • Include parent context (e.g., "monetization plans" for a Plans page under Monetization)
  • Include the full breadcrumb path as a single keyword
  • Use a Set to avoid duplicates before returning the array

Registration

Adapters do not self-register. The composer (composer.ts) is the single file that wires adapters to the registry:

export function setupCommandCenter() {
  indexRegistry.register('routes', () => normalizeRoutesForFuseAdapter());
  // future adapters register here
}

Each adapter gets a string key. The key is used for debugging and provider identification — it has no runtime effect on search or execution.

This is intentional. Registration is explicit, auditable, and happens in one place. There is no auto-discovery, no decorator magic, no implicit registration.

Two Kinds of Adapters

The system has two adapter roles with different positions in the pipeline:

Role Position Input Output
Index adapter Before search Raw domain data IndexItem[]
Result adapter After search Library-specific search output CommandResult[]

Index adapters (like routes.adapter.ts) normalize domain data into the unified index. They run once at bootstrap.

The result adapter (result.adapter.ts) normalizes the search library's output into CommandResult[]. It runs on every search query. There is exactly one result adapter because there is exactly one search library. If the library changes, only this file changes.

Stable IDs

Every IndexItem needs an id that is:

  • Deterministic — the same source data always produces the same ID
  • Namespaced — IDs from different adapters cannot collide
  • Readable — the ID should hint at what it represents

The convention is <type>.<parent>.<child>:

nav.monetization.plans
nav.content.video
action.theme.toggle

IDs are not user-facing. They exist for deduplication, keying in React lists, and debugging. But stability matters — if IDs drift between builds, React will re-mount components unnecessarily and analytics will lose continuity.

Adding a New Adapter

  1. Create a file in adapters/ that exports a function returning IndexItem[]
  2. Ensure keyword enrichment covers the domain's vocabulary
  3. Register it in composer.ts with a unique key
  4. Run indexer.build() — the new items are now searchable

No core files change. No search config changes. No UI changes.