@recoengine/core
v0.1.2
Published
Core of the recoengine recommendation engine: pipeline, ports, scoring maths and structural explainability. Zero dependencies; runs on Node, Bun, Deno and in the browser.
Downloads
496
Maintainers
Readme
@recoengine/core
The domain-agnostic core of the recoengine recommendation system. A composable recommendation pipeline — retrieval → filtering → feature extraction → scoring → normalization → ranking → diversification → explanation — with zero runtime dependencies. Runs on Node, Bun, Deno and in the browser.
npm i @recoengine/coreWant the core plus every standard plugin in one install? Use
recoengineinstead.
What it is
The core knows nothing about tracks, products, or articles. You supply the parts that are
domain-specific — where candidates come from, how your payload turns into numbers — and it
supplies the algorithmic machinery: a deterministic pipeline, a plugin/DI kernel, scoring
maths (min-max, z-score, rank, RRF, cosine/Jaccard similarity, decay curves, seeded RNG),
and explanations as part of the result, not a log line. Configuration is validated at
build(), so a missing feature or an impossible config fails before the first request, not
during it.
Usage
import {
createEngine, featureKey, itemId, rank, strategyId, userId,
type CandidateProvider, type FeatureExtractor, type ScoringStrategy,
} from '@recoengine/core'
interface Track { title: string; plays: number }
const POPULARITY = featureKey('popularity')
// 1. Where candidates come from — the only place allowed to touch your database.
const library: CandidateProvider<Track> = {
id: 'library', version: '1.0.0',
provide: async (_ctx, budget) => {
const rows = await db.tracks.findMany({ take: budget.maxItems })
return rows.map((r) => ({ id: itemId(r.id), type: 'track', payload: { title: r.title, plays: r.plays } }))
},
}
// 2. Domain knowledge → numbers. The only component that knows what a track is.
const popularity: FeatureExtractor<Track> = {
id: 'popularity-extractor', version: '1.0.0',
provides: [{ key: POPULARITY, kind: 'numeric', defaultValue: 0, description: 'plays', owner: 'popularity-extractor', ownerVersion: '1.0.0' }],
extract: async (set, out) => {
const col = out.columnMut(POPULARITY)
for (let row = 0; row < set.size; row++) col[row] = set.at(row).item.payload.plays
},
}
// 3. The maths. Reads a column of numbers, knows nothing about tracks.
const popular: ScoringStrategy = {
id: strategyId('popularity'), requires: [POPULARITY], normalizer: rank,
score: (view) => ({ strategyId: strategyId('popularity'), raw: view.items.column(POPULARITY), reasons: new Map() }),
}
const engine = createEngine<Track>()
.use(library)
.use(popularity)
.use(popular)
.configure({ limits: { maxCandidates: 5_000, maxLimit: 100, timeoutMs: 200 }, weights: { popularity: 1.0 } })
.build() // throws here if a feature is missing or the config does not hold together
const { recommendations, diagnostics } = await engine.recommend({
user: { id: userId('u1'), payload: {} },
history: { userId: userId('u1'), events: [] },
limit: 10,
explain: 'reasons',
})recommendations is a ranked list where each entry carries its score and an
explanation of how that score was reached; diagnostics reports per-stage timings, how
many candidates were retrieved/filtered, and any warnings — so even an empty feed explains
itself.
Standard plugins
You rarely write scoring maths by hand — the standard strategies, modifiers, diversifiers
and feature producers are published as separate packages that plug into this core via
.use(...):
@recoengine/strategies@recoengine/modifiers@recoengine/diversity@recoengine/features@recoengine/testing— fixtures & port contracts
Links
- Repository, architecture notes & runnable examples: https://github.com/waleron07/recommendationEngine
MIT
