/** * Collection-level search / retrieval (`search` / `retrieve` / `similarTo` + * the lexical-index build/flush/warm machinery). * * This is the retrieval surface: a pure client-side lexical scan * (`search`), the persisted-or-in-memory inverted-index lifecycle * (`flushIndex` / `warmIndex` / `buildRetrievalDocs` + the dict-label / blob- * filename resolvers that feed it), and the lexical / semantic / hybrid * `retrieve` fan-out plus raw-vector kNN (`similarTo`). None of it touches the * write path — it reads the live eager cache and the encrypted `_vec` / `_ftindex` * side-cars. * * Every function takes a small {@link SearchContext} instead of `this`, * mirroring the `record-keys/` siblings. * * The `cache` is the SAME `Map` reference `Collection` owns (passed by * reference, never copied) so the index always builds over the live working * set. {@link buildPersistedIndexCallbacks} is special: it is invoked from the * `Collection` constructor *before* `this.codec` exists, so it takes a context * THUNK and resolves the context lazily at each callback invocation. * * Internal service — not exported as a `@noy-db/hub/*` subpath. */ import type { NoydbStore } from '../../kernel/types.js'; import type { RecordCodec } from '../../kernel/enclave/index.js'; import { type I18nTextDescriptor } from '../../via/i18n/core.js'; import { type DictKeyDescriptor, type StaticDictDescriptor, type DictionaryHandle } from '../../via/i18n/dictionary.js'; import type { BlobSet } from '../../with-shape/blobs/blob-set.js'; import type { BlobFieldsConfig } from '../../with-shape/blobs/blob-compaction.js'; import { type VectorSet, type EmbeddingDescriptor } from '../embeddings/index.js'; import { type SearchOptions, type SearchResult } from './index.js'; import type { IndexStore } from './index-store.js'; import type { PersistedIndexCallbacks } from './persisted-index-store.js'; import type { IndexDoc } from './inverted-index.js'; import type { RetrieveOptions, RetrieveHit } from './retrieve-types.js'; /** Everything the moving search/retrieval methods touched on `this.*`. */ export interface SearchContext { /** Collection name — error context + `_ftindex` row key. */ readonly name: string; /** Vault namespace the side-cars (`_vec`, `_ftindex`) live under. */ readonly vault: string; /** The ciphertext store. */ readonly adapter: NoydbStore; /** The record codec — decrypts the `_vec` / `_ftindex` side-car bodies. */ readonly codec: RecordCodec; /** The eager working-set cache (SHARED `Map` reference, never copied). */ readonly cache: Map; /** True in lazy mode — search/retrieve/warm require eager mode. */ readonly lazy: boolean; /** Declared text-index fields, or undefined. */ readonly textIndexes: readonly string[] | undefined; /** Declared i18n fields, or undefined. */ readonly i18nFields: Record | undefined; /** Declared dictionary-key fields, or undefined. */ readonly dictKeyFields: Record | undefined; /** Declared blob fields, or undefined. */ readonly blobFields: BlobFieldsConfig | undefined; /** Dictionary handle resolver (dynamic dict-key labels), or undefined. */ readonly getDictionary: ((name: string) => Promise) | undefined; /** The lexical inverted-index store (in-memory or persisted), or undefined. */ readonly searchIndexStore: IndexStore | undefined; /** The vector set for semantic retrieval, or undefined. */ readonly vectorSet: VectorSet | undefined; /** The embeddings descriptor, or undefined. */ readonly embeddings: EmbeddingDescriptor | undefined; /** Hydrate the eager cache before reading it. */ ensureHydrated(): Promise; /** Open the blob facade for a record (used to list indexed blob filenames). */ blob(id: string): BlobSet; } /** * Client-side lexical scan over the live eager cache. Eager mode only; * nothing searchable is written to the store. */ export declare function search(ctx: SearchContext, field: string, query: string, opts?: SearchOptions): Promise[]>; /** L1 — build IndexDoc[] for the configured text fields over the live cache. */ export declare function buildRetrievalDocs(ctx: SearchContext, labelMaps: Map>>, blobFilenames: Map>, only?: readonly string[]): IndexDoc[]; /** L1 — true iff any configured text index is also a blob field (gates ALL slot I/O). */ export declare function hasIndexedBlobFields(ctx: SearchContext, only?: readonly string[]): boolean; /** L1.5 — force-persist the lexical index now (e.g. on save/idle). Persists only when textIndexPersist is enabled; a no-op otherwise. */ export declare function flushIndex(ctx: SearchContext): Promise; /** * L1.5 — build the PersistedIndexCallbacks bridge: crypto lives here * (collection has getDEK / encryptJsonString / decryptJsonString / adapter), * the index store itself is crypto-free. * * Fingerprint encoding: body-wrap approach — save(json, fp) stores * JSON.stringify({ fp, idx: json }) as the encrypted body so the standard * EncryptedEnvelope shape is never extended. load() decrypts and JSON.parses * the wrapper back out. * * Cache shape: ctx.cache stores { record, version } — currentFingerprint() * iterates over e.version. * * NOTE: this factory is invoked from the `Collection` constructor *before* * `this.codec` is assigned, so it takes a context THUNK and resolves the * context lazily inside each callback — by the time a callback actually runs, * the codec and cache are fully wired. */ export declare function buildPersistedIndexCallbacks(provideCtx: () => SearchContext): PersistedIndexCallbacks; /** L1 — pre-build the lexical index (e.g. on open) so the first retrieve() pays no build scan. */ export declare function warmIndex(ctx: SearchContext): Promise; /** Retrieval. mode: 'lexical' (default) | 'semantic' (L2) | 'hybrid' (L3). */ export declare function retrieve(ctx: SearchContext, query: string, opts?: RetrieveOptions): Promise[]>; /** L2 — raw-vector kNN over the encrypted vector set (decrypted in the trusted tier). * Snippet is '' for vector hits in v1 (semantic match isn't span-located). */ export declare function similarTo(ctx: SearchContext, vector: Float32Array, opts?: { k?: number; minScore?: number; includeRecord?: boolean; }): Promise[]>; /** * L2 — the embedding write-hook: derive a record's embedding vector at * write and persist it as the encrypted `_vec` side-car. Routed through the * search strategy so it runs only when `withSearch()` is opted in — computing * vectors that no gated `similarTo` / semantic `retrieve` could query would be * dead weight, so the compute is paired with the search capability. The caller * (`Collection.put`) only invokes this when `embeddings` is declared. */ export declare function embedOnWrite(ctx: SearchContext, id: string, record: T, version: number): Promise; /** * Force-re-derive every eligible tier-0 record's `_vec` sidecar once (#788). * Opt-in bulk repair for the #726 re-namespace: legacy bare-id rows are * unreachable to `buildVectorLoad` (it only recognises `/` * keys) and otherwise self-heal only when a record is next `put()` — this * lets an adopter recover recall immediately instead of waiting on writes. * * **Skips elevated records** (`liveRecordIsElevated`) rather than refusing * the whole walk — the OPPOSITE of `_applyCutoverTransform`'s * `assertCutoverTierSafe` refuse-whole-batch precedent. An elevated record is * SUPPOSED to have no `_vec` (`syncTierSearch` purges it on elevate); * re-embedding one here would write searchable plaintext-derived data above * tier 0. Load-bearing: never write a `_vec` row for an elevated record. * * Tombstones, delete markers, and a raw `get` racing a delete between `list` * and `get` all decrypt to `null` (`decryptRecord` already folds * tombstone/delete-marker into that null) and are skipped the same way. * Unconditional re-derive, no already-migrated check — safe to re-run after * a partial failure (each id is independently idempotent). */ export declare function rebuildEmbeddings(ctx: SearchContext): Promise<{ rebuilt: number; skipped: number; }>; /** * Sync the collection's SEARCH artifacts after a tier move (#721). Both the * lexical `_ftindex` blob and the `_vec/` embedding are encrypted under * the tier-0 DEK and hold the record's derived plaintext (full field text / * a text-invertible vector), so leaving them means elevation never hid what * the record was searchable by — the `forget()` precedent, unapplied to * elevate. `null` → the record left tier 0: purge its `_vec` sidecar (mirrors * `Collection._purgeVector`), and invalidate the `_ftindex` blob (mirrors * `Collection._purgeSearchIndex`: deletes the persisted blob when persisted, * else drops the in-memory index) so the next `retrieve()` rebuilds from the * elevated-free `ctx.cache`. A record → it is tier-0 again: re-embed it via * {@link embedOnWrite}, then invalidate `_ftindex` so the rebuild includes it * again. No-op fast when the collection has neither a lexical index nor a * vector set. */ export declare function syncTierSearch(ctx: SearchContext, id: string, record: T | null, version?: number): Promise;