Skip to content

SearchEngine

SearchEngine is the singleton that owns the provider registry, the session cache, and the query → session dispatch logic. There is exactly one instance per app session, created in App.vue and passed down via provide/inject.

Source: src/search/engine.ts


Creation

export function createSearchEngine(opts: SearchEngineOptions = {}): SearchEngine {
  const cache = new SessionCache<SearchSession>(opts.cacheMax ?? 30);
  const registry: Record<string, SearchProvider> = {
    rutracker: rutrackerProvider,
    soulseek: soulseekProvider,
  };
  // ...
}

SessionCache is an LRU cache keyed by "${normalizedQuery}|${enabledProviders}". This means searching for the same query with the same set of enabled providers returns the existing live session rather than starting a new one — useful when the user navigates back.


query()

async function query(
  rawQuery: string,
  enabled: Record<string, boolean>,  // { rutracker: true, soulseek: true }
  queryOpts: { skipResolver?: boolean } = {},
): Promise<SearchSession>

The full flow:

rawQuery (e.g. "нон стоп молли")
normalizeQuery → lowercase trim → cacheKey
  ↓ cache miss
resolveQuery(rawQuery)  [Tauri IPC → Rust resolver]
  ↓ ResolveResult | null
build providerQ:
  canonical == null            → providerQ = rawQuery
  canonical.title == ""        → providerQ = stripBrackets(artist)
  else                         → providerQ = "Artist Title" (brackets stripped)
new SearchSession(providerQ, { providers, pipeline, resolved, rawQuery })
cache.set(cacheKey, session)
return session

Bracket stripping

Before building providerQ, brackets are stripped from the canonical:

const stripBrackets = (s: unknown): string =>
  String(s ?? "")
    .replace(/\([^()]*\)/g, "")
    .replace(/\[[^\]]*\]/g, "")
    .replace(/\s{2,}/g, " ")
    .trim();

This prevents sending "1.Kla$ (Первый класс)" to providers — the parenthetical is a Genius annotation, not part of the actual search string.

skipResolver

When skipResolver: true, the cache lookup is also skipped and the raw query is used directly as providerQ. This is used for manual "literal search" mode in the UI.


Provider registry

Two providers are registered at startup:

const registry: Record<string, SearchProvider> = {
  rutracker: rutrackerProvider,
  soulseek:  soulseekProvider,
};

The enabled parameter (from the search store, driven by UI toggles) filters which providers actually run for a given query:

const enabledKinds = Object.keys(registry)
  .filter((k) => enabled?.[k])
  .sort();

Sorted to make cache keys deterministic regardless of object property order.


Cache

Sessions are cached by "${normalizedQuery}|${sortedEnabledProviders}". A cache hit returns the existing session — its generators may still be running, and the caller gets a live reactive view.

Cache size is 30 by default. LRU eviction removes the least-recently-used session when the cache is full.

clearCache() is called when the user explicitly clears search results or when settings change.