Member Junction
    Preparing search index...

    Singleton search engine that orchestrates multi-source search with RRF fusion.

    Providers are discovered from the MJ: Search Providers entity. Each active provider's DriverClass is resolved via ClassFactory to create an instance, which is then initialized with the provider's config from the DB record.

    Usage:

    // Initialize once at server startup
    await SearchEngine.Instance.Config({}, contextUser);

    // Execute searches (unscoped — original behavior)
    const result = await SearchEngine.Instance.Search({
    Query: 'quarterly revenue',
    MaxResults: 20,
    MinScore: 0.1
    }, contextUser);

    // Scoped search against two scopes with multi-tenant context
    const scopedResult = await SearchEngine.Instance.Search({
    Query: 'refund policy',
    MaxResults: 20,
    ScopeIDs: ['hr-scope-id', 'legal-scope-id'],
    SearchContext: { PrimaryScopeRecordID: 'tenant-a' }
    }, contextUser);

    Hierarchy (View Summary)

    Index

    Constructors

    Accessors

    • get GlobalKey(): string

      Returns string

    Methods

    • Initialize the search engine by reading active SearchProvider records from SearchEngineBase (which caches them via BaseEngine) and instantiating each via ClassFactory.

      Safe to call multiple times (no-ops if already configured unless forceRefresh=true).

      Parameters

      • config: SearchEngineConfig = {}

        Engine configuration options

      • contextUser: UserInfo

        The user context for initialization

      • forceRefresh: boolean = false

        If true, re-initializes even if already configured

      Returns Promise<void>

    • Filter search results by entity-level and row-level security permissions.

      This is a safety net. Providers are expected to do per-provider permission push-down (Section 3.6 of plans/search-scopes-rag-plus.md). If this filter is removing more than a handful of results in practice, the responsible provider's push-down is incomplete and should be fixed.

      Groups results by entity for efficient permission checking:

      1. Unknown entities are excluded (fail closed).
      2. If the user lacks entity-level CanRead, all results for that entity are dropped.
      3. If the user is exempt from RLS, all results pass through.
      4. If RLS applies, a RunView validates which record IDs the user can read.

      Parameters

      Returns Promise<SearchResultItem[]>

    • The Global Object Store is a place to store global objects that need to be shared across the application. Depending on the execution environment, this could be the window object in a browser, or the global object in a node environment, or something else in other contexts. The key here is that in some cases static variables are not truly shared because it is possible that a given class might have copies of its code in multiple paths in a deployed application. This approach ensures that no matter how many code copies might exist, there is only one instance of the object in question by using the Global Object Store.

      Returns GlobalObjectStore

    • Public hook for callers (e.g. the GraphQL resolver) to emit a Status='Forbidden' SearchExecutionLog row when they reject a request before delegating to Search. Without this, forbidden invocations never reach the analytics dashboard — exactly the signal admins need to spot users / agents trying to access scopes they shouldn't.

      Parameters

      • input: {
            AIAgentID?: string;
            ContextUser: UserInfo;
            FailureReason: string;
            Query: string;
            ScopeIDs?: string[];
            StartTime: number;
        }

      Returns Promise<void>

    • Quick preview search optimized for autocomplete / typeahead. Uses preview mode (no enrichment), limited to 8 results by default. Only runs providers that have SupportsPreview=true.

      Parameters

      • query: string

        The search query text

      • maxResults: number = 8

        Maximum number of preview results (default: 8)

      • contextUser: UserInfo

        The user performing the search

      Returns Promise<SearchResult>

      Search result in preview mode

    • Streaming variant of Search. Yields events as each pipeline stage produces output so the caller can emit partials to the UI / agent before fusion + reranking complete.

      Phase 2C v1 semantics: runs the same internal pipeline as Search and emits synthetic events at each transition. This preserves all existing fusion / permission / dedup / enrich behavior — important because those steps have subtle correctness rules that we don't want to re-implement in a parallel code path. Per-provider partials are reconstructed from the final SourceCounts; a future refactor (Phase 2C v2) can split provider emission to true real-time concurrent emission once we measure that the synthetic phase is the actual bottleneck.

      Cancellation: the consumer can stop iterating at any point — the underlying Search() will run to completion but its result is discarded. AbortSignal-based mid-pipeline cancellation is a Phase 2C v2 concern.

      Event ordering:

      1. Zero or more provider events (one per non-empty source)
      2. Exactly one fused event
      3. Optional one reranked event (when a reranker is configured)
      4. Exactly one final event
      5. On error: a single error event in place of final.

      Parameters

      Returns AsyncIterable<SearchStreamEvent>

      for await (const ev of SearchEngine.Instance.streamSearch(params, user)) {
      switch (ev.phase) {
      case 'provider': scratchpad.append(`${ev.providerName}: ${ev.results.length} hits`); break;
      case 'final': scratchpad.commit(ev.results); break;
      case 'error': scratchpad.fail(ev.error); break;
      }
      }
    • Returns the singleton instance of the class. If the instance does not exist, it is created and stored in the Global Object Store. If className is provided it will be used as part of the key in the Global Object Store, otherwise the actual class name will be used. NOTE: the class name used by default is the lowest level of the object hierarchy, so if you have a class that extends another class, the lowest level class name will be used.

      Type Parameters

      Parameters

      • this: new () => T
      • OptionalclassName: string

      Returns T