{"version":3,"sources":["../src/client/SearchClient.ts","../src/engine/int8-codec.ts","../src/engine/fetch-json.ts","../src/engine/flat-engine.ts","../src/engine/shard-loader.ts","../src/engine/hnsw-engine.ts","../src/engine/hybrid-search.ts"],"sourcesContent":["/**\n * SearchClient — framework-agnostic wrapper around the search Web Worker.\n *\n * Works in any JavaScript environment that supports Web Workers.\n * Extend or wrap with framework adapters (see adapters/vue.ts, adapters/react.ts).\n *\n * @example\n * ```ts\n * const client = new SearchClient('https://example.com/data/prwp/manifest.json')\n *\n * client.on('index_ready', () => {\n *   client.search('climate finance', { topK: 10, mode: 'hybrid' })\n * })\n *\n * client.on('results', ({ data, stats }) => {\n *   console.log(data)   // SearchResult[]\n *   console.log(stats)  // SearchStats | null\n * })\n *\n * // Clean up when done\n * client.destroy()\n * ```\n */\n\nimport type { WorkerOutboundMessage, WorkerInboundMessage } from '../types/worker'\nimport type { CollectionManifest } from '../types/manifest'\nimport type { SearchOptions } from '../types/search'\n\n/** Injected at build time from package.json version; fallback for source builds */\ndeclare const __PACKAGE_VERSION__: string | undefined\nconst DEFAULT_CDN_WORKER_URL = `https://unpkg.com/@ai4data/search@${typeof __PACKAGE_VERSION__ !== 'undefined' ? __PACKAGE_VERSION__ : '0.0.0'}/dist/worker.mjs`\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport type SearchMode = 'semantic' | 'lexical' | 'hybrid'\n\nexport interface SearchClientOptions {\n  /** HuggingFace model ID to use for embeddings (default: avsolatorio/GIST-small-Embedding-v0) */\n  modelId?: string\n  /** If true, skip loading the embedding model (for testing BM25 fallback). */\n  skipModelLoad?: boolean\n  /** Delay (seconds) before loading the embedding model; index + BM25 load first (for testing). */\n  modelLoadDelaySeconds?: number\n  /** If true, do not load or build BM25 even when the manifest has bm25_corpus (semantic-only). */\n  skipBm25?: boolean\n  /**\n   * Base URL passed to `@xenova/transformers` as `env.remoteHost` (default Hub is `https://huggingface.co/`).\n   * Point this at a same-origin proxy that forwards to the Hub so embedding downloads comply with CSP.\n   * `modelId` remains a full repo id (e.g. `org/model`); the proxy should mirror Hub paths:\n   * `…/org/model/resolve/{revision}/…` (see the package `demo/proxy-server.mjs` route `/api/hf-proxy/`).\n   * If another stack requests `/api/resolve-cache/models/…/{revision}/file`, use a proxy that maps that to the Hub\n   * (the same demo server supports that pattern). Relative URLs are resolved with `new URL(…, location.href)`.\n   */\n  transformersRemoteHost?: string\n  /**\n   * Optional `env.remotePathTemplate` override. Omit unless your proxy does not mirror the Hub path layout.\n   */\n  transformersRemotePathTemplate?: string\n  /**\n   * Factory function that creates the Web Worker.\n   * Defaults to the bundled search worker created via `new URL()`.\n   * Override when you need a custom worker path (e.g. CDN, service worker proxy).\n   *\n   * @example\n   * ```ts\n   * // Vite / webpack 5 (recommended — bundler resolves the path)\n   * new SearchClient(url, {\n   *   workerFactory: () => new Worker(new URL('@ai4data/search/worker', import.meta.url), { type: 'module' })\n   * })\n   * ```\n   */\n  workerFactory?: () => Worker\n  /**\n   * URL to the worker script (e.g. from a CDN). Use this when loading the package from a CDN\n   * so the worker is created via fetch + blob and works cross-origin. Ignored if workerFactory is set.\n   *\n   * @example\n   * ```ts\n   * const client = await SearchClient.fromCDN(manifestUrl, {\n   *   workerUrl: 'https://esm.sh/@ai4data/search@0.1.0/worker'\n   * })\n   * ```\n   */\n  workerUrl?: string\n}\n\ntype MessageHandler<T extends WorkerOutboundMessage['type']> = (\n  msg: Extract<WorkerOutboundMessage, { type: T }>,\n) => void\n\n/**\n * Create a Worker from a cross-origin URL by fetching the script and instantiating\n * from a blob URL. Use this when loading the package from a CDN so the worker is\n * same-origin and browsers allow it.\n *\n * @param url - Full URL to the worker script (e.g. https://esm.sh/@ai4data/search@0.1.0/worker)\n * @returns Promise that resolves with the Worker instance\n */\n/**\n * Resolve a CDN worker URL to one that returns a single script (no import statements).\n * ESM.sh returns a wrapper with imports that fail when run from a blob: URL; unpkg/jsDelivr\n * serve the raw dist/worker.mjs which is self-contained.\n */\n/** Resolve relative HF proxy base against the page; ensure trailing slash for Xenova pathJoin. */\nfunction resolveTransformersRemoteHost(raw: string): string {\n  const base = globalThis.location?.href ?? 'http://localhost/'\n  const u = new URL(raw, base).href\n  return u.endsWith('/') ? u : `${u}/`\n}\n\nfunction getBundledWorkerUrl(url: string): string {\n  const esmMatch = url.match(/esm\\.sh\\/@ai4data\\/search@([^/]+)\\/worker/)\n  if (esmMatch) {\n    const version = esmMatch[1].split('?')[0]\n    return `https://unpkg.com/@ai4data/search@${version}/dist/worker.mjs`\n  }\n  const jdelivrMatch = url.match(/cdn\\.jsdelivr\\.net\\/npm\\/@ai4data\\/search@([^/]+)\\//)\n  if (jdelivrMatch) return url\n  return url\n}\n\nexport function createWorkerFromUrl(url: string): Promise<Worker> {\n  const fetchUrl = getBundledWorkerUrl(url)\n  return fetch(fetchUrl, { mode: 'cors' })\n    .then((r) => {\n      if (r.status === 404)\n        throw new Error(\n          `Worker not found (404). Version may not be published to npm yet. Publish with: npm publish --access public (from packages/ai4data/search). Or use a published version in workerUrl.`\n        )\n      if (!r.ok) throw new Error(`Failed to fetch worker: ${r.status} ${r.statusText}`)\n      return r.text()\n    })\n    .then((code) => {\n      const trimmed = code.trim()\n      if (trimmed.startsWith('<!') || trimmed.startsWith('<html'))\n        throw new Error('Worker URL returned HTML (likely 404 or error page). Check the worker URL and that the package is published.')\n      if (trimmed.length < 1000)\n        throw new Error(`Worker script too short (${trimmed.length} chars). Expected a bundled script. Check the worker URL.`)\n      // Reject wrapper scripts that would fail from blob (imports like /node/... don't resolve from blob:)\n      if (/^\\s*import\\s+/.test(trimmed))\n        throw new Error(\n          'Worker URL returned a wrapper with import statements; it cannot run from a blob. Use a CDN that serves the raw bundle (e.g. unpkg.com/@ai4data/search@VERSION/dist/worker.mjs).'\n        )\n      const blob = new Blob([code], { type: 'application/javascript' })\n      const blobUrl = URL.createObjectURL(blob)\n      try {\n        return new Worker(blobUrl, { type: 'module' })\n      } catch (e) {\n        URL.revokeObjectURL(blobUrl)\n        throw e instanceof Error ? e : new Error(String(e))\n      }\n    })\n}\n\n// ── SearchClient ──────────────────────────────────────────────────────────────\n\nexport class SearchClient {\n  // ── Public state (plain properties — no reactivity) ──\n\n  /** True once the index + BM25 corpus are loaded. Lexical search available. */\n  isIndexReady = false\n\n  /** True once the ONNX embedding model is ready. Semantic + hybrid search available. */\n  isModelReady = false\n\n  /** Latest progress/status message from the worker. */\n  loadingMessage = 'Initializing…'\n\n  /** True when the last search fell back to BM25 because the model wasn't ready. */\n  activeFallback = false\n\n  /** Parsed collection manifest, available after `index_ready`. */\n  manifest: CollectionManifest | null = null\n\n  // ── Private ──\n\n  private readonly worker: Worker\n  private readonly handlers = new Map<string, Set<(msg: WorkerOutboundMessage) => void>>()\n  private destroyed = false\n\n  // ── Constructor ──────────────────────────────────────────────────────────────\n\n  /**\n   * @param manifestUrl - Absolute or relative URL to the collection manifest JSON (any filename, e.g. `search_manifest.json`).\n   *   Relative URLs are resolved against `location.href`.\n   * @param opts        - Optional configuration.\n   */\n  constructor(manifestUrl: string, opts: SearchClientOptions = {}) {\n    if (opts.workerFactory) {\n      this.worker = opts.workerFactory()\n    } else if (opts.workerUrl) {\n      throw new Error(\n        'SearchClient: workerUrl is only supported with SearchClient.fromCDN(). Use fromCDN(manifestUrl, { workerUrl }) or pass workerFactory.'\n      )\n    } else {\n      this.worker = new Worker(new URL('./worker.mjs', import.meta.url), { type: 'module' })\n    }\n\n    this.worker.onmessage = (e: MessageEvent<WorkerOutboundMessage>) => {\n      this._handleMessage(e.data)\n    }\n\n    this.worker.onerror = (err: ErrorEvent) => {\n      const e = err as ErrorEvent & { error?: Error }\n      const msg =\n        e.error?.message ??\n        e.message ??\n        (e as unknown as { message?: string }).message ??\n        'Worker error (no details; check DevTools Console for the worker context or Network tab for failed requests)'\n      const filename = e.filename || ''\n      const lineno = e.lineno ?? ''\n      console.error('[SearchClient] Worker error:', msg, filename ? `at ${filename}` : '', lineno ? `:${lineno}` : '', e.error ? e.error.stack : '')\n      this.loadingMessage = `Search worker error: ${msg}`\n    }\n\n    // Resolve relative manifest URLs against current page origin\n    const resolvedUrl = new URL(\n      manifestUrl,\n      globalThis.location?.href ?? 'http://localhost/',\n    ).href\n\n    const initMsg: WorkerInboundMessage = {\n      type: 'init',\n      manifestUrl: resolvedUrl,\n      modelId: opts.modelId,\n      skipModelLoad: opts.skipModelLoad,\n      modelLoadDelaySeconds: opts.modelLoadDelaySeconds,\n      skipBm25: opts.skipBm25,\n      ...(opts.transformersRemoteHost !== undefined && {\n        transformersRemoteHost: resolveTransformersRemoteHost(opts.transformersRemoteHost),\n      }),\n      ...(opts.transformersRemotePathTemplate !== undefined && {\n        transformersRemotePathTemplate: opts.transformersRemotePathTemplate,\n      }),\n    }\n    this.worker.postMessage(initMsg)\n  }\n\n  // ── Event bus ─────────────────────────────────────────────────────────────────\n\n  /**\n   * Subscribe to a specific worker message type.\n   * Returns an unsubscribe function — call it to remove the handler.\n   *\n   * @example\n   * ```ts\n   * const off = client.on('results', ({ data }) => setResults(data))\n   * // later…\n   * off()\n   * ```\n   */\n  on<T extends WorkerOutboundMessage['type']>(\n    type: T,\n    handler: MessageHandler<T>,\n  ): () => void {\n    if (!this.handlers.has(type)) this.handlers.set(type, new Set())\n    const bucket = this.handlers.get(type)!\n    const h = handler as (msg: WorkerOutboundMessage) => void\n    bucket.add(h)\n    return () => bucket.delete(h)\n  }\n\n  // ── Actions ───────────────────────────────────────────────────────────────────\n\n  /**\n   * Submit a search query. No-op if the index is not yet ready.\n   *\n   * @param text - Natural-language query\n   * @param opts - Optional topK, ef, mode ('semantic' | 'lexical' | 'hybrid')\n   */\n  search(text: string, opts: SearchOptions & { mode?: SearchMode } = {}): void {\n    if (!this.isIndexReady) return\n    this.worker.postMessage({\n      type: 'search',\n      text,\n      topK: opts.topK ?? 20,\n      ef: opts.ef ?? 50,\n      ef_upper: opts.ef_upper ?? 2,\n      threshold: opts.threshold ?? 0.0,\n      mode: opts.mode ?? 'hybrid',\n    } satisfies WorkerInboundMessage)\n  }\n\n  /**\n   * Fetch the most-recent items from the index (useful for pre-search state).\n   */\n  getRecent(limit = 10): void {\n    this.worker.postMessage({ type: 'getRecent', limit } satisfies WorkerInboundMessage)\n  }\n\n  /**\n   * Fetch curated highlight items from `index/highlights.json` when the manifest\n   * sets `index.highlights`. Listen with `client.on('highlights', ({ data }) => …)`.\n   * If the file is missing or empty, `data` is `[]`.\n   */\n  getHighlights(limit = 10): void {\n    this.worker.postMessage({ type: 'getHighlights', limit } satisfies WorkerInboundMessage)\n  }\n\n  /**\n   * Ping the worker. Resolves when the worker responds with 'pong'.\n   */\n  ping(): Promise<void> {\n    return new Promise((resolve) => {\n      const off = this.on('pong', () => { off(); resolve() })\n      this.worker.postMessage({ type: 'ping' } satisfies WorkerInboundMessage)\n    })\n  }\n\n  /**\n   * Terminate the worker and clean up all event listeners.\n   * The client is unusable after this call.\n   */\n  destroy(): void {\n    if (this.destroyed) return\n    this.destroyed = true\n    this.worker.terminate()\n    this.handlers.clear()\n  }\n\n  /**\n   * Create a SearchClient when loading the package from a CDN. Fetches the worker\n   * script and creates the worker from a blob URL so it works cross-origin.\n   *\n   * @param manifestUrl - URL to the collection manifest.json\n   * @param opts - Options; workerUrl defaults to unpkg for this package version\n   * @returns Promise that resolves with the SearchClient\n   *\n   * @example\n   * ```ts\n   * const client = await SearchClient.fromCDN('https://example.com/data/manifest.json')\n   * client.on('results', ({ data }) => console.log(data))\n   * ```\n   */\n  static fromCDN(\n    manifestUrl: string,\n    opts: SearchClientOptions & { workerUrl?: string } = {},\n  ): Promise<SearchClient> {\n    const { workerUrl = DEFAULT_CDN_WORKER_URL, ...rest } = opts\n    return createWorkerFromUrl(workerUrl).then((worker) => {\n      return new SearchClient(manifestUrl, { ...rest, workerFactory: () => worker })\n    })\n  }\n\n  // ── Internal ──────────────────────────────────────────────────────────────────\n\n  private _handleMessage(msg: WorkerOutboundMessage): void {\n    // Update plain-property state\n    switch (msg.type) {\n      case 'progress':\n        this.loadingMessage = msg.message\n        break\n      case 'index_ready':\n        this.isIndexReady = true\n        break\n      case 'ready':\n        this.isModelReady = msg.modelLoaded !== false\n        this.manifest = msg.config\n        break\n      case 'results':\n        this.activeFallback = msg.fallback ?? false\n        break\n      case 'error':\n        this.isIndexReady = true  // exit loading state on error\n        this.loadingMessage = `Error: ${msg.message}`\n        console.error('[SearchClient] Worker error message:', msg.message)\n        break\n    }\n\n    // Dispatch to registered handlers\n    const bucket = this.handlers.get(msg.type)\n    if (bucket) {\n      for (const h of bucket) h(msg)\n    }\n  }\n}\n","/**\n * int8-codec.ts\n *\n * Quantization scheme: vectors are stored as Int8 values in [-127, 127].\n * Each vector is accompanied by a scalar `scale` such that the original\n * float value ≈ int8_value * scale.  Dot products are computed in mixed\n * precision (Float32 query × dequantized Int8 stored vector) to keep\n * both accuracy and memory efficiency.\n */\n\n/**\n * Compute the dot product between a Float32 query vector and a stored\n * Int8-quantized vector, dequantizing on the fly.\n *\n * @param queryF32   - L2-normalised query vector (Float32Array)\n * @param storedQV   - Int8-quantized stored vector\n * @param storedScale - Per-vector dequantization scale factor\n * @returns Approximate cosine similarity score\n */\nexport function dotProductMixed(\n  queryF32: Float32Array,\n  storedQV: Int8Array,\n  storedScale: number,\n): number {\n  let dot = 0.0;\n  const len = queryF32.length;\n  for (let i = 0; i < len; i++) {\n    dot += queryF32[i] * (storedQV[i] * storedScale);\n  }\n  return dot;\n}\n\n/**\n * Dequantize an Int8 vector back to Float32 using the stored scale.\n *\n * @param qv    - Int8-quantized vector\n * @param scale - Per-vector dequantization scale factor\n * @returns Reconstructed Float32 vector\n */\nexport function dequantize(qv: Int8Array, scale: number): Float32Array {\n  const out = new Float32Array(qv.length);\n  for (let i = 0; i < qv.length; i++) {\n    out[i] = qv[i] * scale;\n  }\n  return out;\n}\n\n/**\n * L2-normalise a Float32 vector in place.\n * Vectors whose norm is below 1e-9 are left unchanged to avoid division by zero.\n *\n * @param vec - Vector to normalise (mutated in place)\n * @returns The same (now normalised) vector\n */\nexport function l2NormalizeInPlace(vec: Float32Array): Float32Array {\n  let norm = 0.0;\n  for (let i = 0; i < vec.length; i++) norm += vec[i] * vec[i];\n  norm = Math.sqrt(norm);\n  if (norm < 1e-9) return vec;\n  for (let i = 0; i < vec.length; i++) vec[i] /= norm;\n  return vec;\n}\n\n/**\n * Convert a plain number array (or an existing Int8Array) to an Int8Array.\n * Values outside [-128, 127] are silently truncated by the typed-array constructor.\n *\n * @param arr - Source values\n * @returns An Int8Array view / copy of the input\n */\nexport function toInt8Array(arr: number[] | Int8Array): Int8Array {\n  return new Int8Array(arr);\n}\n","/**\n * fetch-json.ts\n *\n * Utility for fetching JSON (plain or gzip-compressed) with optional\n * Cache Storage read/write so repeat cold-starts skip the network.\n */\n\nexport interface FetchJsonOptions {\n  /**\n   * When provided, the response is read from (and written to) a named\n   * Cache Storage bucket.  Pass `null` to disable caching entirely.\n   */\n  cacheName?: string | null\n}\n\n/**\n * Fetch a JSON resource, transparently handling gzip-compressed responses.\n *\n * Caching behaviour:\n *  1. If `cacheName` is set and the Cache API is available, attempt a cache hit.\n *  2. On a miss, fetch from the network.\n *  3. Decompress if the URL ends with `.gz` and the server did not already\n *     decompress it (i.e. `Content-Encoding` is absent or non-gzip).\n *  4. Write the parsed object back to Cache Storage for future requests.\n *\n * @param url  - Absolute or relative URL to fetch\n * @param opts - Optional caching configuration\n * @returns Parsed JSON payload cast to `T`\n * @throws {Error} On non-2xx HTTP responses\n */\nexport async function fetchJson<T = unknown>(\n  url: string,\n  opts?: FetchJsonOptions,\n): Promise<T> {\n  const cacheName = opts?.cacheName ?? null;\n  const isGz = url.endsWith('.gz');\n\n  // --- cache read ---\n  if (cacheName && typeof caches !== 'undefined') {\n    try {\n      const cache = await caches.open(cacheName);\n      const cached = await cache.match(url);\n      if (cached) {\n        return cached.json() as Promise<T>;\n      }\n    } catch (_) {\n      // Cache API unavailable or denied — fall through to network fetch\n    }\n  }\n\n  // --- network fetch ---\n  const resp = await fetch(url);\n  if (!resp.ok) {\n    throw new Error(`fetchJson: HTTP ${resp.status}: ${url}`);\n  }\n\n  let data: T;\n\n  if (isGz) {\n    const encoding = resp.headers.get('Content-Encoding');\n    if (encoding === 'gzip' || encoding === 'x-gzip') {\n      // The server already decompressed it for us\n      data = (await resp.json()) as T;\n    } else {\n      // Decompress manually in the browser via DecompressionStream\n      const stream = resp.body!.pipeThrough(new DecompressionStream('gzip'));\n      data = (await new Response(stream).json()) as T;\n    }\n  } else {\n    data = (await resp.json()) as T;\n  }\n\n  // --- cache write ---\n  if (cacheName && typeof caches !== 'undefined') {\n    try {\n      const cache = await caches.open(cacheName);\n      cache.put(\n        url,\n        new Response(JSON.stringify(data), {\n          headers: { 'Content-Type': 'application/json' },\n        }),\n      );\n    } catch (_) {\n      // Best-effort — ignore write failures\n    }\n  }\n\n  return data;\n}\n","/**\n * flat-engine.ts\n *\n * Brute-force (flat) search engine backed by an Int8-quantized index.\n * Suitable for collections up to ~50 k documents where exact nearest-neighbour\n * search is fast enough without an ANN index structure.\n */\n\nimport type { FlatItem, SearchEngine, SearchOptions, SearchResult, SearchStats } from '../types/search'\nimport { dotProductMixed, toInt8Array } from './int8-codec'\nimport { fetchJson } from './fetch-json'\n\n/** Shape of the JSON file loaded by `FlatEngine.load()` */\ninterface FlatIndexFile {\n  dim: number\n  items: FlatItem[]\n}\n\n/** Internal representation — `qv` is always an `Int8Array` after loading. Defined\n * explicitly (not via `Omit<FlatItem, 'qv'>`) to avoid `[key: string]: unknown`\n * index-signature narrowing that makes named properties return `unknown`. */\ninterface LoadedFlatItem {\n  id: string | number\n  idno?: string\n  title: string\n  text: string\n  scale: number\n  qv: Int8Array\n  type?: string\n  [key: string]: unknown\n}\n\n/**\n * Brute-force semantic search engine.\n *\n * Usage:\n * ```ts\n * const engine = new FlatEngine()\n * await engine.load('/data/flat/embeddings.int8.json')\n * const results = engine.search(queryVec, { topK: 10 })\n * ```\n */\nexport class FlatEngine implements SearchEngine {\n  /** Internal item list with Int8-converted vectors */\n  private items: LoadedFlatItem[]\n\n  /** True once `load()` has completed successfully */\n  readonly ready: boolean = false\n\n  /** Statistics from the most recent `search()` call, or `null` before first search */\n  lastStats: SearchStats | null\n\n  constructor() {\n    this.items = []\n    this.lastStats = null\n  }\n\n  /**\n   * Fetch and parse the flat index file, converting all `qv` arrays to `Int8Array`.\n   *\n   * @param url - URL of the `embeddings.int8.json` index file\n   * @returns The raw item list from the JSON (before Int8 conversion)\n   */\n  async load(url: string): Promise<FlatItem[]> {\n    const data = await fetchJson<FlatIndexFile>(url)\n    this.items = data.items.map(item => ({\n      ...item,\n      qv: toInt8Array(item.qv as number[]),\n    }))\n    ;(this as { ready: boolean }).ready = true\n    return data.items\n  }\n\n  /**\n   * Run a brute-force cosine-similarity search over all loaded items.\n   *\n   * @param queryVec - L2-normalised query embedding (Float32Array)\n   * @param opts     - Optional search parameters\n   * @returns Top-K results sorted by descending score\n   * @throws {Error} If called before `load()` has completed\n   */\n  search(queryVec: Float32Array, opts?: SearchOptions): SearchResult[] {\n    if (!this.ready) throw new Error('FlatEngine: not loaded yet')\n\n    const topK = opts?.topK ?? 20\n    const threshold = opts?.threshold ?? 0.0\n\n    const t0 = Date.now()\n    const scores = new Float32Array(this.items.length)\n\n    for (let i = 0; i < this.items.length; i++) {\n      const item = this.items[i]\n      scores[i] = dotProductMixed(queryVec, item.qv, item.scale)\n    }\n\n    // Collect candidate indices above threshold\n    const candidates: number[] = []\n    for (let i = 0; i < scores.length; i++) {\n      if (scores[i] >= threshold) candidates.push(i)\n    }\n\n    candidates.sort((a, b) => scores[b] - scores[a])\n\n    const results = candidates.slice(0, topK).map(i => {\n      const item = this.items[i]\n      // Include all preview fields except internal-only ones\n      const extra: Record<string, unknown> = {}\n      for (const [k, v] of Object.entries(item)) {\n        if (!['id', 'scale', 'qv', 'title', 'text'].includes(k)) {\n          extra[k] = v\n        }\n      }\n      return {\n        id: item.id,\n        score: scores[i],\n        title: item.title,\n        text: item.text,\n        ...extra,\n      } as SearchResult\n    })\n\n    this.lastStats = {\n      latencyMs: Date.now() - t0,\n      shardsLoaded: 0,\n      totalCachedShards: 0,\n    }\n\n    return results\n  }\n}\n","/**\n * shard-loader.ts\n *\n * Loads layer-0 shard files on demand, deduplicates in-flight requests,\n * and maintains a bounded in-memory cache to limit worker heap growth.\n */\n\nimport type { Shard } from '../types/search'\nimport { fetchJson } from './fetch-json'\n\n/**\n * Loads, deduplicates, and caches HNSW layer-0 shard files.\n *\n * Shards are stored in files named `shard_NNN<suffix>` (e.g. `shard_007.json`)\n * under a common base URL.  The loader combines three levels of caching:\n *\n *  1. In-memory `Map` — fastest; survives across queries within the same worker.\n *  2. In-flight deduplication — a second caller for the same shard awaits the\n *     already-running fetch rather than issuing a duplicate request.\n *  3. Cache Storage (via `fetchJson`) — survives page reloads.\n */\nexport class ShardLoader {\n  /** Base URL for shard files (always ends with `/`) */\n  readonly baseUrl: string\n  /** Cache Storage bucket name passed through to `fetchJson` */\n  readonly cacheName: string\n  /** File extension appended to each shard filename (e.g. `.json` or `.json.gz`) */\n  readonly shardSuffix: string\n\n  /** In-memory shard cache keyed by numeric shard ID */\n  memoryCache: Map<number, Shard>\n  /** Promises for shards currently being fetched, keyed by numeric shard ID */\n  inflight: Map<number, Promise<Shard>>\n  /** Insertion-order log used by `evict()` to expire the oldest entries first */\n  _insertOrder: number[]\n\n  constructor(\n    baseUrl: string,\n    cacheName = 'hnsw-shards-v1',\n    shardSuffix = '.json',\n  ) {\n    this.baseUrl = baseUrl.endsWith('/') ? baseUrl : baseUrl + '/'\n    this.cacheName = cacheName\n    this.shardSuffix = shardSuffix\n    this.memoryCache = new Map()\n    this.inflight = new Map()\n    this._insertOrder = []\n  }\n\n  /**\n   * Load a shard by ID, returning the in-memory copy if already cached,\n   * joining an in-flight fetch if one exists, or issuing a new network request.\n   *\n   * @param shardId - Numeric shard identifier\n   * @returns Parsed shard data\n   */\n  async load(shardId: number): Promise<Shard> {\n    if (this.memoryCache.has(shardId)) {\n      return this.memoryCache.get(shardId)!\n    }\n    if (this.inflight.has(shardId)) {\n      return this.inflight.get(shardId)!\n    }\n\n    const promise = this._fetchShard(shardId)\n    this.inflight.set(shardId, promise)\n\n    try {\n      const data = await promise\n      this.memoryCache.set(shardId, data)\n      this._insertOrder.push(shardId)\n      return data\n    } finally {\n      this.inflight.delete(shardId)\n    }\n  }\n\n  /**\n   * Kick off background loads for a set of shard IDs without awaiting them.\n   * Useful for prefetching neighbours that will likely be needed soon.\n   *\n   * @param shardIds - Shard IDs to prefetch\n   */\n  prefetch(shardIds: number[]): void {\n    for (const sid of shardIds) {\n      if (!this.memoryCache.has(sid) && !this.inflight.has(sid)) {\n        this.load(sid)\n      }\n    }\n  }\n\n  /**\n   * Evict the oldest in-memory shard entries to keep heap usage bounded.\n   *\n   * @param maxEntries - Maximum number of shards to keep (default: 200)\n   */\n  evict(maxEntries = 200): void {\n    while (this._insertOrder.length > maxEntries) {\n      const oldest = this._insertOrder.shift()!\n      this.memoryCache.delete(oldest)\n    }\n  }\n\n  /**\n   * Build the URL for a given shard ID.\n   *\n   * @param shardId - Numeric shard identifier\n   * @returns Full URL string\n   */\n  private _shardUrl(shardId: number): string {\n    return this.baseUrl + `shard_${String(shardId).padStart(3, '0')}${this.shardSuffix}`\n  }\n\n  /**\n   * Fetch and parse a shard file from the network (or Cache Storage).\n   *\n   * @param shardId - Numeric shard identifier\n   * @returns Parsed shard data\n   */\n  private _fetchShard(shardId: number): Promise<Shard> {\n    return fetchJson<Shard>(this._shardUrl(shardId), { cacheName: this.cacheName })\n  }\n}\n","/**\n * hnsw-engine.ts\n *\n * Approximate nearest-neighbour search using a pre-built HNSW index.\n * Upper layers (layers ≥ 1) are held entirely in memory; layer-0 is\n * loaded on demand from shard files via `ShardLoader`.\n */\n\nimport type { HNSWConfig, HNSWIndexConfig, UpperLayersData, CollectionManifest } from '../types/manifest'\nimport type {\n  NodeCacheEntry,\n  ScoredNode,\n  SearchEngine,\n  SearchResult,\n  SearchStats,\n  SearchOptions,\n} from '../types/search'\nimport { dotProductMixed, toInt8Array } from './int8-codec'\nimport { ShardLoader } from './shard-loader'\nimport { fetchJson } from './fetch-json'\n\n/** Options accepted by `HNSWEngine.init()` */\ninterface HNSWInitOptions {\n  /** Cache Storage bucket name forwarded to `ShardLoader` and `fetchJson` */\n  cacheName?: string\n  /** Parsed manifest; used to resolve index file paths and compressed flag */\n  manifest?: CollectionManifest | null\n}\n\n/**\n * Insert `item` into `arr` in ascending score order (binary search).\n * `arr[0]` is always the lowest-scoring element after insertion.\n *\n * @param arr  - Sorted array to insert into (mutated in place)\n * @param item - `[score, nodeId]` tuple to insert\n */\nfunction _sortedInsert(arr: ScoredNode[], item: ScoredNode): void {\n  const score = item[0]\n  let lo = 0\n  let hi = arr.length\n  while (lo < hi) {\n    const mid = (lo + hi) >>> 1\n    if (arr[mid][0] < score) lo = mid + 1\n    else hi = mid\n  }\n  arr.splice(lo, 0, item)\n}\n\n/**\n * HNSW approximate nearest-neighbour search engine.\n *\n * Typical usage:\n * ```ts\n * const engine = new HNSWEngine()\n * await engine.init('/data/prwp/')\n * const results = await engine.search(queryVec, { topK: 10, ef: 50 })\n * ```\n */\nexport class HNSWEngine implements SearchEngine {\n  /** Parsed `index/config.json` */\n  private config: HNSWConfig | null\n  /** Parsed `index/upper_layers.json` */\n  private upperLayers: UpperLayersData | null\n  /** Maps string node ID → shard ID */\n  private nodeToShard: Record<string, number> | null\n  /** Shard loader for layer-0 data */\n  private loader: ShardLoader | null\n  /** In-memory node cache (Int8 vectors, neighbours) */\n  private nodeCache: Map<number, NodeCacheEntry>\n\n  /** True once `init()` has completed successfully */\n  readonly ready: boolean = false\n\n  /** Statistics from the most recent `search()` call, or `null` before first search */\n  lastStats: SearchStats | null\n\n  constructor() {\n    this.config = null\n    this.upperLayers = null\n    this.nodeToShard = null\n    this.loader = null\n    this.nodeCache = new Map()\n    this.lastStats = null\n  }\n\n  /**\n   * Load all index metadata and populate the upper-layer node cache.\n   * This must be called (and awaited) before any call to `search()`.\n   *\n   * @param baseUrl - Base URL of the collection directory (e.g. `/data/prwp/`)\n   * @param opts    - Optional cache name and manifest\n   */\n  async init(baseUrl: string, opts?: HNSWInitOptions): Promise<void> {\n    const cacheName = opts?.cacheName ?? 'hnsw-shards-v1'\n    const manifest = opts?.manifest ?? null\n\n    const base = baseUrl.endsWith('/') ? baseUrl : baseUrl + '/'\n    const idx: Partial<HNSWIndexConfig> = manifest?.index ?? {}\n    const configPath = base + (idx.config ?? 'index/config.json')\n    const upperPath = base + (idx.upper_layers ?? 'index/upper_layers.json')\n    const nodePath = base + (idx.node_to_shard ?? 'index/node_to_shard.json')\n    const shardSuffix = manifest?.compressed ? '.json.gz' : '.json'\n\n    const [config, upperLayers, nodeToShard] = await Promise.all([\n      fetchJson<HNSWConfig>(configPath, { cacheName }),\n      fetchJson<UpperLayersData>(upperPath, { cacheName }),\n      fetchJson<Record<string, number>>(nodePath, { cacheName }),\n    ])\n\n    this.config = config\n    this.upperLayers = upperLayers\n    this.nodeToShard = nodeToShard\n    this.loader = new ShardLoader(base + 'index/layer0/', cacheName, shardSuffix)\n\n    // Pre-populate the node cache with upper-layer nodes\n    for (const [idStr, node] of Object.entries(upperLayers.nodes)) {\n      this.nodeCache.set(parseInt(idStr, 10), {\n        id: parseInt(idStr, 10),\n        scale: node.scale,\n        qv: toInt8Array(node.qv),\n        neighbors: [],\n        layers: node.layers,\n        max_layer: node.max_layer,\n      })\n    }\n\n    ;(this as { ready: boolean }).ready = true  // bypass readonly for post-init assignment\n  }\n\n  /**\n   * Search the HNSW index for the nearest neighbours of `queryVec`.\n   *\n   * @param queryVec - L2-normalised query embedding (Float32Array)\n   * @param opts     - Optional search parameters (`topK`, `ef`, `ef_upper`)\n   * @returns Top-K results sorted by descending score\n   * @throws {Error} If called before `init()` has completed\n   */\n  async search(queryVec: Float32Array, opts?: SearchOptions): Promise<SearchResult[]> {\n    if (!this.ready || !this.config || !this.upperLayers || !this.loader) {\n      throw new Error('HNSWEngine: not initialized. Call init() first.')\n    }\n\n    const ef = opts?.ef ?? 50\n    const ef_upper = opts?.ef_upper ?? 2\n    const topK = opts?.topK ?? 10\n\n    const t0 = Date.now()\n    const prevCacheSize = this.loader.memoryCache.size\n\n    // Greedy descent through upper layers to find good entry points for layer 0\n    let entryPoints: ScoredNode[] = [\n      [this._scoreUpperNode(queryVec, this.upperLayers.entry_node_id), this.upperLayers.entry_node_id],\n    ]\n\n    for (let layer = this.config.n_layers - 1; layer >= 1; layer--) {\n      entryPoints = this._beamDescentLayer(queryVec, entryPoints, layer, ef_upper)\n    }\n\n    const results = await this._beamSearchLayer0(queryVec, entryPoints, ef)\n\n    const shardsLoaded =\n      this.loader.memoryCache.size - prevCacheSize + (this.loader.inflight.size > 0 ? 1 : 0)\n\n    this.lastStats = {\n      latencyMs: Date.now() - t0,\n      shardsLoaded: Math.max(0, shardsLoaded),\n      totalCachedShards: this.loader.memoryCache.size,\n    }\n\n    this.loader.evict(300)\n    return results.slice(0, topK)\n  }\n\n  /**\n   * Single-layer greedy beam descent for layers ≥ 1 (upper layers).\n   * All nodes at these layers are already in `nodeCache`.\n   *\n   * @param queryVec    - L2-normalised query vector\n   * @param entryPoints - Current best candidates as `[score, nodeId]` tuples\n   * @param layer       - Layer index to traverse\n   * @param ef_upper    - Beam width (number of candidates to keep)\n   * @returns Updated candidate list for the next layer\n   */\n  private _beamDescentLayer(\n    queryVec: Float32Array,\n    entryPoints: ScoredNode[],\n    layer: number,\n    ef_upper: number,\n  ): ScoredNode[] {\n    const layerStr = String(layer)\n    const seen = new Set<number>()\n    const W: ScoredNode[] = []\n\n    for (const [, nodeId] of entryPoints) {\n      if (seen.has(nodeId)) continue\n      seen.add(nodeId)\n      const score = this._scoreUpperNode(queryVec, nodeId)\n      _sortedInsert(W, [score, nodeId])\n      if (W.length > ef_upper) W.shift()\n\n      const node = this.nodeCache.get(nodeId)\n      if (!node) continue\n\n      const neighbors = node.layers?.[layerStr] ?? []\n      for (const nid of neighbors) {\n        if (seen.has(nid)) continue\n        seen.add(nid)\n        const s = this._scoreUpperNode(queryVec, nid)\n        _sortedInsert(W, [s, nid])\n        if (W.length > ef_upper) W.shift()\n      }\n    }\n\n    return W\n  }\n\n  /**\n   * Score a node that is present in `nodeCache` (upper-layer or already loaded layer-0).\n   *\n   * @param queryVec - L2-normalised query vector\n   * @param nodeId   - Node to score\n   * @returns Approximate dot-product similarity, or `-Infinity` if node is absent\n   */\n  private _scoreUpperNode(queryVec: Float32Array, nodeId: number): number {\n    const node = this.nodeCache.get(nodeId)\n    if (!node) return -Infinity\n    return dotProductMixed(queryVec, node.qv, node.scale)\n  }\n\n  /**\n   * Layer-0 beam search.  Loads shard files on demand as the search frontier expands.\n   *\n   * @param queryVec    - L2-normalised query vector\n   * @param entryPoints - Entry candidates from upper-layer descent\n   * @param ef          - Beam width (number of candidates to maintain in `W`)\n   * @returns All candidates in `W` sorted by descending score as `SearchResult` objects\n   */\n  private async _beamSearchLayer0(\n    queryVec: Float32Array,\n    entryPoints: ScoredNode[],\n    ef: number,\n  ): Promise<SearchResult[]> {\n    const visited = new Set<number>()\n    let W: ScoredNode[] = []\n    let C: ScoredNode[] = []\n\n    for (const [, nodeId] of entryPoints) {\n      if (visited.has(nodeId)) continue\n      visited.add(nodeId)\n      const node = await this._getLayer0Node(nodeId)\n      if (!node) continue\n      const s = dotProductMixed(queryVec, node.qv, node.scale)\n      _sortedInsert(W, [s, nodeId])\n      _sortedInsert(C, [s, nodeId])\n    }\n\n    if (W.length > ef) W = W.slice(-ef)\n    if (C.length > ef) C = C.slice(-ef)\n\n    while (C.length > 0) {\n      const [cScore, cId] = C.pop()!\n      const worstInW = W.length >= ef ? W[0][0] : -Infinity\n      if (cScore < worstInW) break\n\n      const cNode = await this._getLayer0Node(cId)\n      if (!cNode) continue\n\n      const unvisited = cNode.neighbors.filter(n => !visited.has(n))\n\n      // Batch-prefetch all shards needed for unvisited neighbours\n      const neededShards = new Set<number>()\n      for (const nid of unvisited) {\n        const sId = this.nodeToShard![String(nid)]\n        if (sId != null && !this.loader!.memoryCache.has(sId)) {\n          neededShards.add(sId)\n        }\n      }\n      if (neededShards.size > 0) {\n        await Promise.all([...neededShards].map(s => this.loader!.load(s)))\n      }\n\n      for (const nid of unvisited) {\n        visited.add(nid)\n        const nNode = await this._getLayer0Node(nid)\n        if (!nNode) continue\n        const score = dotProductMixed(queryVec, nNode.qv, nNode.scale)\n        const currentWorst = W.length >= ef ? W[0][0] : -Infinity\n        if (score > currentWorst || W.length < ef) {\n          _sortedInsert(C, [score, nid])\n          _sortedInsert(W, [score, nid])\n          if (W.length > ef) W.shift()\n        }\n      }\n    }\n\n    return W\n      .sort((a, b) => b[0] - a[0])\n      .map(([score, id]) => ({ id, score, title: '' }))\n  }\n\n  /**\n   * Retrieve a layer-0 node from cache, loading its shard file if necessary.\n   * Once loaded, the node entry in `nodeCache` is augmented with `neighbors`\n   * and `_l0loaded = true`.\n   *\n   * @param nodeId - Node to retrieve\n   * @returns Fully populated cache entry, or `null` if the node cannot be found\n   */\n  private async _getLayer0Node(nodeId: number): Promise<NodeCacheEntry | null> {\n    const cached = this.nodeCache.get(nodeId)\n    if (cached?._l0loaded) return cached\n\n    const shardId = this.nodeToShard![String(nodeId)]\n    if (shardId == null) return this.nodeCache.get(nodeId) ?? null\n\n    const shard = await this.loader!.load(shardId)\n\n    for (const n of shard.nodes) {\n      const existing = this.nodeCache.get(n.id)\n      const entry: NodeCacheEntry = {\n        ...(existing ?? {}),\n        id: n.id,\n        scale: n.scale,\n        qv: existing?.qv ?? toInt8Array(n.qv),\n        neighbors: n.neighbors,\n        _l0loaded: true,\n      }\n      this.nodeCache.set(n.id, entry)\n    }\n\n    return this.nodeCache.get(nodeId) ?? null\n  }\n}\n","/**\n * hybrid-search.ts\n *\n * Combines semantic (HNSW / flat) and lexical (BM25) search results using\n * min-max normalisation and a configurable linear blend.\n */\n\nimport type { SearchEngine, BM25Engine, SearchResult, SearchOptions } from '../types/search'\n\n/** Options accepted by `HybridSearch.search()` */\nexport interface HybridSearchOptions {\n  /** Number of results to return (default: 20) */\n  topK?: number\n  /** Weight applied to normalised semantic scores (default: 0.7) */\n  semanticWeight?: number\n  /** Weight applied to normalised BM25 scores (default: 0.3) */\n  lexicalWeight?: number\n  /** HNSW beam width forwarded to the semantic engine (default: 50) */\n  ef?: number\n  /** Search mode: `'semantic'`, `'lexical'`, or `'hybrid'` (default: `'hybrid'`) */\n  mode?: 'semantic' | 'lexical' | 'hybrid'\n}\n\n/** Extended result type used internally during score merging */\ninterface MergedResult extends SearchResult {\n  semanticScore: number\n  lexicalScore: number\n  rawSemanticScore: number\n}\n\n/**\n * Hybrid search combining a semantic vector engine and an optional BM25 engine.\n *\n * In `'hybrid'` mode both engines are queried in parallel; scores are\n * min-max normalised independently and then linearly blended.\n *\n * Example:\n * ```ts\n * const hybrid = new HybridSearch(hnswEngine, bm25Engine, id => titlesMap[id])\n * const results = await hybrid.search(queryVec, 'development finance', { topK: 10 })\n * ```\n */\nexport class HybridSearch {\n  private readonly semantic: SearchEngine\n  private readonly bm25: BM25Engine | null\n  private readonly idToMeta: ((id: number | string) => Partial<SearchResult>) | null\n\n  /**\n   * @param semanticEngine - Initialised `SearchEngine` (FlatEngine or HNSWEngine)\n   * @param bm25Engine     - Optional BM25 engine; pass `null` to disable lexical search\n   * @param idToMeta       - Optional callback to look up display metadata by document ID\n   */\n  constructor(\n    semanticEngine: SearchEngine,\n    bm25Engine: BM25Engine | null = null,\n    idToMeta: ((id: number | string) => Partial<SearchResult>) | null = null,\n  ) {\n    this.semantic = semanticEngine\n    this.bm25 = bm25Engine\n    this.idToMeta = idToMeta\n  }\n\n  /**\n   * Run a hybrid (or single-mode) search query.\n   *\n   * @param queryVec  - L2-normalised query embedding, or `null` for lexical-only mode\n   * @param queryText - Raw query string for BM25, or empty string for semantic-only mode\n   * @param opts      - Search options\n   * @returns Top-K results sorted by descending combined score\n   */\n  async search(\n    queryVec: Float32Array | null,\n    queryText: string,\n    opts?: HybridSearchOptions,\n  ): Promise<SearchResult[]> {\n    const topK = opts?.topK ?? 20\n    const semanticWeight = opts?.semanticWeight ?? 0.7\n    const lexicalWeight = opts?.lexicalWeight ?? 0.3\n    const ef = opts?.ef ?? 50\n    const mode = opts?.mode ?? 'hybrid'\n\n    const candidateK = topK * 3\n\n    const searchOpts: SearchOptions = { topK: candidateK, ef }\n\n    const [semanticResults, lexicalResults] = await Promise.all([\n      mode !== 'lexical' && queryVec && this.semantic\n        ? Promise.resolve(this.semantic.search(queryVec, searchOpts))\n        : Promise.resolve([]),\n      mode !== 'semantic' && this.bm25 && queryText\n        ? Promise.resolve(this._runBM25(queryText, candidateK))\n        : Promise.resolve([]),\n    ])\n\n    // Unwrap potential Promise from synchronous search implementations\n    const semResults = await semanticResults\n    const lexResults = await lexicalResults\n\n    if (mode === 'semantic') return this._formatResults(semResults, topK, 'semantic')\n    if (mode === 'lexical') return this._formatResults(lexResults, topK, 'lexical')\n\n    // --- Merge & blend ---\n    const scoreMap = new Map<string, MergedResult>()\n\n    if (semResults.length > 0) {\n      const maxSem = semResults[0].score || 1\n      const minSem = semResults[semResults.length - 1].score || 0\n      const rangeSem = maxSem - minSem || 1\n      for (const r of semResults) {\n        const normScore = (r.score - minSem) / rangeSem\n        scoreMap.set(String(r.id), {\n          ...r,\n          title: r.title,\n          semanticScore: normScore,\n          lexicalScore: 0,\n          rawSemanticScore: r.score,\n        })\n      }\n    }\n\n    if (lexResults.length > 0) {\n      const maxBm25 = lexResults[0].score || 1\n      const minBm25 = lexResults[lexResults.length - 1].score || 0\n      const rangeBm25 = maxBm25 - minBm25 || 1\n      for (const r of lexResults) {\n        const normScore = (r.score - minBm25) / rangeBm25\n        const idStr = String(r.id)\n        if (scoreMap.has(idStr)) {\n          scoreMap.get(idStr)!.lexicalScore = normScore\n        } else {\n          const meta = this.idToMeta ? this.idToMeta(r.id) : {}\n          scoreMap.set(idStr, {\n            ...r,\n            ...meta,\n            id: r.id,\n            title: meta.title ?? r.title ?? '',\n            text: meta.text ?? r.text ?? '',\n            semanticScore: 0,\n            lexicalScore: normScore,\n            rawSemanticScore: 0,\n          })\n        }\n      }\n    }\n\n    const merged = [...scoreMap.values()].map(r => ({\n      ...r,\n      score: semanticWeight * r.semanticScore + lexicalWeight * r.lexicalScore,\n    }))\n\n    merged.sort((a, b) => b.score - a.score)\n    return merged.slice(0, topK)\n  }\n\n  /**\n   * Run the BM25 engine and map raw `[docIdx, score]` tuples to `SearchResult` objects.\n   *\n   * @param queryText - Raw query string\n   * @param topK      - Maximum number of results to return\n   * @returns BM25 results as `SearchResult` objects (score order: descending)\n   */\n  private _runBM25(queryText: string, topK: number): SearchResult[] {\n    if (!this.bm25) return []\n    try {\n      const raw = this.bm25.search(queryText, topK)\n      return raw.map(([docIdx, score]) => {\n        const meta = this.idToMeta ? this.idToMeta(docIdx) : {}\n        return {\n          ...meta,\n          id: docIdx,\n          score,\n          title: meta.title ?? '',\n          text: meta.text ?? '',\n        } as SearchResult\n      })\n    } catch (e) {\n      console.warn('BM25 search error:', e)\n      return []\n    }\n  }\n\n  /**\n   * Format single-mode results, adding the appropriate `semanticScore` /\n   * `lexicalScore` fields expected by callers.\n   *\n   * @param results - Raw results from one engine\n   * @param topK    - Slice limit\n   * @param source  - Which engine produced the results\n   * @returns Results annotated with zeroed-out score fields for the unused engine\n   */\n  private _formatResults(\n    results: SearchResult[],\n    topK: number,\n    source: 'semantic' | 'lexical',\n  ): SearchResult[] {\n    return results.slice(0, topK).map(r => ({\n      ...r,\n      semanticScore: source === 'semantic' ? (r.score ?? 0) : 0,\n      lexicalScore: source === 'lexical' ? (r.score ?? 0) : 0,\n    }))\n  }\n}\n"],"mappings":"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","names":["DEFAULT_CDN_WORKER_URL","resolveTransformersRemoteHost","raw","base","u","getBundledWorkerUrl","url","esmMatch","createWorkerFromUrl","fetchUrl","r","code","trimmed","blob","blobUrl","e","SearchClient","_SearchClient","manifestUrl","opts","err","msg","filename","lineno","initMsg","type","handler","bucket","h","text","limit","resolve","off","workerUrl","rest","worker","dotProductMixed","queryF32","storedQV","storedScale","dot","len","i","dequantize","qv","scale","out","l2NormalizeInPlace","vec","norm","toInt8Array","arr","fetchJson","url","opts","cacheName","isGz","cached","resp","data","encoding","stream","FlatEngine","url","data","fetchJson","item","toInt8Array","queryVec","opts","topK","threshold","t0","scores","i","dotProductMixed","candidates","a","b","results","extra","k","v","ShardLoader","baseUrl","cacheName","shardSuffix","shardId","promise","data","shardIds","sid","maxEntries","oldest","fetchJson","_sortedInsert","arr","item","score","lo","hi","mid","HNSWEngine","baseUrl","opts","cacheName","manifest","base","idx","configPath","upperPath","nodePath","shardSuffix","config","upperLayers","nodeToShard","fetchJson","ShardLoader","idStr","node","toInt8Array","queryVec","ef","ef_upper","topK","t0","prevCacheSize","entryPoints","layer","results","shardsLoaded","layerStr","seen","W","nodeId","neighbors","nid","s","dotProductMixed","visited","C","cScore","cId","worstInW","cNode","unvisited","n","neededShards","sId","nNode","currentWorst","a","b","id","cached","shardId","shard","existing","entry","HybridSearch","semanticEngine","bm25Engine","idToMeta","queryVec","queryText","opts","topK","semanticWeight","lexicalWeight","ef","mode","candidateK","searchOpts","semanticResults","lexicalResults","semResults","lexResults","scoreMap","maxSem","minSem","rangeSem","r","normScore","maxBm25","minBm25","rangeBm25","idStr","meta","merged","a","b","docIdx","score","e","results","source"]}