type Doc> = { id: string; fields: Record; meta?: TMeta; }; interface Adapter { fromRow(row: R): D; toRow?(doc: D, original?: R): R; } declare const identityAdapter: Adapter; type DeltaOp = { op: 'add'; row: TRow; } | { op: 'update'; row: TRow; } | { op: 'remove'; id: string; }; type DeltaPayload = { version: string | number; changes: DeltaOp[]; }; type BootstrapPayload = { version: string | number; rows: TRow[]; }; type SyncConfig = { bootstrap?: () => Promise>; fetchDelta?: (sinceVersion: string | number) => Promise>; pollIntervalMs?: number; subscribe?: (onChange: (delta: DeltaPayload) => void) => () => void; }; interface Signal { id: string; compute(doc: TDoc, ctx: TCtx): number; meta?: { range?: [number, number]; description?: string; }; } type CombinerKind = 'product' | 'weighted_sum' | 'log_sum'; type Combiner = CombinerKind | ((scores: Record, weights: Record) => number); type ScoreExplanation = { docId: string; total: number; contributions: Array<{ signalId: string; value: number; weight: number; }>; }; type IdentityProvider = () => Record | undefined; declare function nfkc(s: string): string; declare function clamp(n: number, lo: number, hi: number): number; declare function combine(kind: Combiner, scores: Record, weights: Record): number; type Posting = { docId: string; fieldFreqs: Record; }; /** Tokeniser signature — feeds the inverted index. */ type Extract = (text: string) => string[]; declare class SearchStore { postings: Map; docs: Map; fieldStats: Map; totalDocs: number; features: Map; private docFieldLengths; add(doc: Doc, extract: Extract): void; remove(docId: string): void; candidates(queryFeats: string[]): Set; postingsFor(feature: string): Posting[] | undefined; avgFieldLen(field: string): number; } interface StorageAdapter { open(name: string): Promise; get(key: string): Promise; set(key: string, value: unknown): Promise; delete(key: string): Promise; iterate(prefix: string): AsyncIterable<[string, unknown]>; close(): Promise; } type StorageKind = 'memory' | 'indexeddb' | 'localstorage' | StorageAdapter; type SearchContext = { query: string; queryFeatures: string[]; now: number; userId?: string; customMeta?: Record; /** Internal — injected by Search.query for scorers that need store access (e.g. bm25Field). */ _store?: SearchStore; }; type SearchScorer = Signal & { /** * Optional gate: if returns false, the doc is filtered out entirely * (e.g. inventoryFilter for out-of-stock items). */ gate?(doc: Doc, ctx: SearchContext): boolean; }; type SearchOpts = { id?: string; adapter?: Adapter; storage?: StorageKind; sync?: SyncConfig; /** * Optional host-supplied tokeniser. Same function is used to index docs * AND to parse queries — symmetry is what makes BM25 matching work. * * Default: a Unicode-aware universal tokeniser (NFKC normalise + * whitespace split for whitespace scripts; codepoint + bigram for CJK, * Thai, Lao, Khmer, Burmese; lowercase throughout). Handles 200+ * languages out of the box with no per-locale packs. * * Plug in your own when you have domain-specific tokenisation rules * (e.g. medical term splitter, product SKU boundaries, ICD codes). */ extractFeatures?: (text: string) => string[]; scorers: SearchScorer[]; combiner?: Combiner; weights?: Record; topK?: number; scoreThreshold?: number; }; type SearchResult = { doc: TDoc; score: number; explanation?: ScoreExplanation; }; type QueryOpts = { topK?: number; explain?: boolean; scoreThreshold?: number; customMeta?: Record; }; type FieldWeightConfig = { weight: number; saturation?: number; }; type BM25FieldOpts = { weights: Record; k1?: number; b?: number; }; /** * Per-field BM25 with saturation. Reads the store from ctx._store (injected by Search). */ declare function bm25Field(opts: BM25FieldOpts): SearchScorer; type RecencyOpts = { field: string; halfLifeDays?: number; }; declare function recencyDecay$1(opts: RecencyOpts): SearchScorer; type NewProductBoostOpts = { field: string; withinDays?: number; multiplier?: number; }; declare function newProductBoost(opts: NewProductBoostOpts): SearchScorer; type BestsellerBoostOpts = { field: string; method?: 'log_normalize' | 'rank_inverse'; maxBoost?: number; }; declare function bestsellerBoost(opts: BestsellerBoostOpts): SearchScorer; declare function promoBoost$1(opts?: { field?: string; }): SearchScorer; declare function inventoryFilter(opts?: { field?: string; min?: number; }): SearchScorer; declare function activeFilter(opts?: { field?: string; }): SearchScorer; declare const builtin$1: { bm25Field: typeof bm25Field; recencyDecay: typeof recencyDecay$1; newProductBoost: typeof newProductBoost; bestsellerBoost: typeof bestsellerBoost; promoBoost: typeof promoBoost$1; inventoryFilter: typeof inventoryFilter; activeFilter: typeof activeFilter; }; declare class Search { private id; private store; private adapter; private storage; private scorers; private weights; private extract; private combiner; private topK; private scoreThreshold; private opts; private opened; private lowConfHook?; private pollTimer?; private unsubscribe?; constructor(opts: SearchOpts); init(): Promise; addDoc(row: TRow): Promise; addDocs(rows: TRow[]): Promise; removeDoc(id: string): Promise; updateDoc(row: TRow): Promise; query(text: string, opts?: QueryOpts): Promise; explain(text: string, docId: string): { docId: string; total: number; gated: boolean; contributions: never[]; } | { docId: string; total: number; contributions: { signalId: string; value: number; weight: number; }[]; gated?: undefined; } | undefined; onLowConfidence(fn: (q: string, c: SearchResult[]) => Promise): void; snapshot(): Promise<{ docs: Doc[]; }>; restore(snap: { docs: Doc[]; }): Promise; dispose(): Promise; getDoc(id: string): Doc | undefined; allDocs(): Iterable; getFeatures(id: string): string[] | undefined; _store(): SearchStore; private persist; private startSync; private applyDeltaPayload; } declare function createSearch(opts: SearchOpts): Search; type index$2_QueryOpts = QueryOpts; type index$2_Search = Search; declare const index$2_Search: typeof Search; type index$2_SearchContext = SearchContext; type index$2_SearchOpts = SearchOpts; type index$2_SearchResult = SearchResult; type index$2_SearchScorer = SearchScorer; declare const index$2_createSearch: typeof createSearch; declare namespace index$2 { export { type index$2_QueryOpts as QueryOpts, index$2_Search as Search, type index$2_SearchContext as SearchContext, type index$2_SearchOpts as SearchOpts, type index$2_SearchResult as SearchResult, type index$2_SearchScorer as SearchScorer, builtin$1 as builtin, index$2_createSearch as createSearch }; } /** * Structural interface a catalog implementation must satisfy to feed * `Recommend`. The shipped `Search` class implements this naturally, so * the most common wiring is still * * const cat = createSearch({...}); * const rec = createRecommend({ catalog: cat, ... }); * * but the interface lets hosts plug their own catalog backend in — * e.g. a remote-paged vector store, a graph-based retriever, or a * raw in-memory list with no BM25 layer at all. Whatever holds your * docs and can yield their features for similarity matching works. */ interface RecommendCatalog { /** Look up a single doc by id. */ getDoc(id: string): Doc | undefined; /** Yield every doc the catalog knows about. Order doesn't matter — * `Recommend` scores then re-sorts. */ allDocs(): Iterable; /** Return the feature-vector / token list for the doc with this id, * or `undefined` if absent. Used by `contentSimilarity` and the * preference-match scorer to compute Jaccard-style overlap. */ getFeatures(id: string): string[] | undefined; } type Preference = { productId: string; response: 'yes' | 'no' | 'dismiss'; ts: number; context?: Record; }; type RecommendContext = { userId?: string; source?: 'similarTo' | 'forCustomer' | 'goesWith'; sourceProductId?: string | string[]; now: number; prefs?: Preference[]; customMeta?: Record; }; type RecommendScorer = Signal & { gate?(doc: Doc, ctx: RecommendContext): boolean; }; type DiversityOpts = { /** meta field to diversify on (e.g. 'category'). */ field: string; /** 0..1 — higher = more diversity. Pure MMR lambda is (1 - this). */ weight?: number; /** how many candidates to consider in MMR pool before slicing topK. */ poolSize?: number; }; type RecommendOpts = { id?: string; /** * Doc source. Any object with `getDoc` / `allDocs` / `getFeatures` works * — the shipped `Search` class satisfies this out of the box, but hosts * can pass a custom backend (vector store, graph retriever, etc.). */ catalog: RecommendCatalog; customerId?: string; storage?: StorageKind; signals: RecommendScorer[]; weights?: Record; combiner?: Combiner; topK?: number; diversity?: DiversityOpts; prefsSync?: { bootstrap?: () => Promise; pushChange?: (p: Preference) => Promise; }; }; declare class Recommend { private id; private catalog; private customerId?; private storage; private signals; private weights; private combiner; private topK; private prefs; private opened; private opts; constructor(opts: RecommendOpts); init(): Promise; recordPreference(p: Omit & { ts?: number; }): Promise; similarTo(productId: string, opts?: { topK?: number; }): Promise>; forCustomer(opts?: { topK?: number; }): Promise>; goesWith(productIds: string[], opts?: { topK?: number; }): Promise>; explain(productId: string, ctx?: Partial): { docId: string; total: number; gated: boolean; contributions: never[]; } | { docId: string; total: number; contributions: { signalId: string; value: number; weight: number; }[]; gated?: undefined; } | undefined; private rank; dispose(): Promise; } declare function contentSimilarity(opts: { source: RecommendCatalog; }): RecommendScorer; declare function recencyDecay(opts?: { field?: string; halfLifeDays?: number; }): RecommendScorer; declare function coldStartBayesian(opts?: { priorWeight?: number; priorRate?: number; yesField?: string; eventsField?: string; }): RecommendScorer; declare function saturation(opts: { field: string; cap?: number; }): RecommendScorer; declare function promoBoost(opts?: { field?: string; }): RecommendScorer; declare function preferenceMatch(opts?: { catalog: RecommendCatalog; yesBoost?: number; noBoost?: number; }): RecommendScorer; declare function diversityPenalty(opts: { field: string; topNPenalty?: number; }): RecommendScorer; declare const builtin: { contentSimilarity: typeof contentSimilarity; recencyDecay: typeof recencyDecay; coldStartBayesian: typeof coldStartBayesian; saturation: typeof saturation; promoBoost: typeof promoBoost; preferenceMatch: typeof preferenceMatch; diversityPenalty: typeof diversityPenalty; }; declare function createRecommend(opts: RecommendOpts): Recommend; type index$1_DiversityOpts = DiversityOpts; type index$1_Preference = Preference; type index$1_Recommend = Recommend; declare const index$1_Recommend: typeof Recommend; type index$1_RecommendCatalog = RecommendCatalog; type index$1_RecommendContext = RecommendContext; type index$1_RecommendOpts = RecommendOpts; type index$1_RecommendScorer = RecommendScorer; declare const index$1_builtin: typeof builtin; declare const index$1_createRecommend: typeof createRecommend; declare namespace index$1 { export { type index$1_DiversityOpts as DiversityOpts, type index$1_Preference as Preference, index$1_Recommend as Recommend, type index$1_RecommendCatalog as RecommendCatalog, type index$1_RecommendContext as RecommendContext, type index$1_RecommendOpts as RecommendOpts, type index$1_RecommendScorer as RecommendScorer, index$1_builtin as builtin, index$1_createRecommend as createRecommend }; } /** * Toolbox-internal shared types (Doc / Adapter / Combiner / sync payloads). * No language or scoring logic — those live in `dddk/utils/text` so the * agent layer can use them without depending on toolbox. */ type index_Adapter = Adapter; type index_BootstrapPayload = BootstrapPayload; type index_Combiner = Combiner; type index_CombinerKind = CombinerKind; type index_DeltaOp = DeltaOp; type index_DeltaPayload = DeltaPayload; type index_Doc> = Doc; type index_IdentityProvider = IdentityProvider; type index_ScoreExplanation = ScoreExplanation; type index_Signal = Signal; type index_SyncConfig = SyncConfig; declare const index_clamp: typeof clamp; declare const index_combine: typeof combine; declare const index_identityAdapter: typeof identityAdapter; declare const index_nfkc: typeof nfkc; declare namespace index { export { type index_Adapter as Adapter, type index_BootstrapPayload as BootstrapPayload, type index_Combiner as Combiner, type index_CombinerKind as CombinerKind, type index_DeltaOp as DeltaOp, type index_DeltaPayload as DeltaPayload, type index_Doc as Doc, type index_IdentityProvider as IdentityProvider, type index_ScoreExplanation as ScoreExplanation, type index_Signal as Signal, type index_SyncConfig as SyncConfig, index_clamp as clamp, index_combine as combine, index_identityAdapter as identityAdapter, index_nfkc as nfkc }; } export { index as common, index$1 as recommend, index$2 as search };