type Doc> = { id: string; fields: Record; meta?: TMeta; }; 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); 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; /** * 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; export { type DiversityOpts, type Preference, Recommend, type RecommendCatalog, type RecommendContext, type RecommendOpts, type RecommendScorer, builtin, createRecommend };