Skip to content

Search Service

The SearchService is the central orchestrator that aggregates search results from all registered ProviderAdapters. It provides a uniform API to the UI layer, handling provider selection, error aggregation, and result normalisation.

Core API

/**
 * Initialise the service with a list of adapters.
 */
init(adapters: ProviderAdapter[]): Promise<void>;

/**
 * Perform a search across every enabled provider.
 * @param query Free‑text query entered by the user.
 * @returns An array of {@link SearchResult} objects, merged from all providers.
 */
searchAll(query: string): Promise<SearchResult[]>;

/**
 * Search a specific provider.
 * @param providerId Identifier of the provider (e.g. "vrt-max").
 * @param query Search term.
 */
searchProvider(providerId: string, query: string): Promise<SearchResult[]>;

How It Works

  1. Adapter registration – each concrete ProviderAdapter calls SearchService.register(adapter) during its module initialisation. The service stores the adapter instance in an internal map keyed by the provider ID.
  2. Parallel executionsearchAll fires adapter.search(query) for every registered adapter concurrently using Promise.allSettled.
  3. Result normalisation – each adapter returns its own SearchResult type. The service maps these into the shared SearchResult interface defined in packages/core/src/providers/types.ts.
  4. Error handling – if an individual provider fails, its error is captured and attached to the final result set under a providerErrors field. The UI can display per‑provider warnings without breaking the entire search.
interface AggregatedSearchResult {
  results: SearchResult[];
  providerErrors: Record<string, string>; // providerId → error message
}

Example Usage (React Hook)

import { useEffect, useState } from "react";
import { SearchService } from "@/core/services/SearchService";

export function useSearch(query: string) {
  const [data, setData] = useState<AggregatedSearchResult>({
    results: [],
    providerErrors: {},
  });
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    if (!query) return;
    setLoading(true);
    SearchService.searchAll(query)
      .then(res => setData(res))
      .finally(() => setLoading(false));
  }, [query]);

  return { data, loading };
}

Error Handling Strategy

  • Network failures – captured and stored in providerErrors. The UI can show a non‑intrusive toast.
  • Authentication errors – if a provider reports missing or expired credentials, the service triggers a vault.lock() and surfaces a “login required” message for that provider.
  • Partial successessearchAll returns the successful results along with any errors, never aborting the whole operation.

When to Extend

Add new behaviour when a provider introduces a fundamentally different result shape (e.g., live‑stream calendars). Create a transformation function in the adapter so the service continues to return the canonical SearchResult.


For a deeper dive, see the implementation in packages/core/src/services/SearchService.ts.