@fizm/nano-recommender
v1.3.0
Published
Zero-dependency collaborative filtering recommendation engine built for performance
Maintainers
Readme
Table of Contents
- Why nano-recommender
- Features
- Installation
- Quick Start
- Packaging Support
- Recommendation Strategies
- Performance
- API Reference
- Architecture
- Contributing
- License
Why nano-recommender
The library is a lightweight, zero-dependency, in-memory recommendation engine built to run efficiently in Node.js and browser environments.
It is designed for use-cases requiring rapid collaborative filtering and fallback recommendations without the overhead of heavy native dependencies, external databases, or machine learning pipelines.
Design Pillars
- Zero Runtime Dependencies: Avoids dependency bloat. Relies entirely on native JavaScript and TypeScript features.
- Sparse Matrix Optimization: Ratings are stored in memory using sparse user-item maps and item-user indices, minimizing memory overhead and avoiding dense matrix allocations.
- Symmetric Similarity Cache: Pairwise similarities are computed lazily on demand and cached symmetrically, reducing subsequent query times to O(1) lookups.
- Dual Packaging: Ships with full ESM and CommonJS support alongside native TypeScript typings out of the box.
- Tree-shakable Exports: Algorithms and core utility classes are exported individually, allowing modern bundlers to remove unused code.
Features
| Feature | Supported | Description | | :------------------------------------------ | :-------: | :------------------------------------------------------------- | | Item-Based Collaborative Filtering | Yes | Recommends items based on item-item similarity matrices | | User-Based Collaborative Filtering | Yes | Recommends items based on user-user similarity matrices | | K-Nearest Neighbors (KNN) Limit | Yes | Limits calculations to top K nearest neighbors for performance | | Similarity Intersection Threshold | Yes | Avoids statistical coincidences by enforcing minimum overlap | | Custom Similarity Metrics | Yes | Built-in Cosine, Jaccard, and Pearson correlation coefficients | | Custom Filtering & Blacklisting | Yes | Filters recommendations dynamically via callback or blacklist | | Real-Time Incremental Updates | Yes | Live interaction updates with selective cache invalidation | | Popularity Fallback Engine | Yes | Handles cold-start users using view, rate, and purchase counts | | Time-Decay Weighting | Yes | Automatically decays older interaction ratings exponentially | | Dynamic Interaction Weighting | Yes | Scales rating scores based on interaction types at load time | | LRU Similarity Cache | Yes | Prevents memory bloat with configurable LRU eviction limits | | Sparse Storage Engine | Yes | Operates entirely in memory with sparse indices | | TypeScript Ready | Yes | Written in strict TypeScript with full declaration files |
Installation
Install via npm:
npm install @fizm/nano-recommenderInstall via pnpm:
pnpm add @fizm/nano-recommenderInstall via yarn:
yarn add @fizm/nano-recommenderQuick Start
The following is a complete, compilable TypeScript example showing how to load a dataset and generate recommendations.
import { NanoRecommender } from "@fizm/nano-recommender";
// 1. Initialize the engine
const recommender = new NanoRecommender({
defaultStrategy: "item-based",
defaultSimilarityThreshold: 0.0,
});
// 2. Load interaction datasets
recommender.load([
{ userId: "u1", itemId: "i1", rating: 5.0, type: "rate" },
{ userId: "u1", itemId: "i2", rating: 3.0, type: "rate" },
{ userId: "u2", itemId: "i1", rating: 4.0, type: "rate" },
{ userId: "u2", itemId: "i2", rating: 3.0, type: "rate" },
{ userId: "u2", itemId: "i3", rating: 2.0, type: "rate" },
{ userId: "u3", itemId: "i2", rating: 4.0, type: "rate" },
{ userId: "u3", itemId: "i3", rating: 5.0, type: "rate" },
]);
// 3. Generate recommendations for a user
const recommendations = recommender.recommend("u1", {
limit: 2,
strategy: "item-based",
});
// Output: [{ itemId: "i3", score: 3.5 }]
console.log(recommendations);Packaging Support
The package supports both ESM and CommonJS formats.
ESM Import (Default)
import { NanoRecommender, cosineSimilarity, pearsonCorrelation } from "@fizm/nano-recommender";CommonJS Require
const { NanoRecommender, cosineSimilarity, pearsonCorrelation } = require("@fizm/nano-recommender");Recommendation Strategies
1. Item-Based Collaborative Filtering (Default)
Finds items similar to those the user has already rated. It supports custom similarity functions (e.g. Cosine, Pearson) over sparse item vectors and computes predicted ratings using a weighted average.
import { pearsonCorrelation } from "@fizm/nano-recommender";
const recs = recommender.recommendItemBased("user_id", {
limit: 10,
similarityThreshold: 0.1,
excludeInteracted: true,
similarityFunction: pearsonCorrelation,
});2. User-Based Collaborative Filtering
Finds users similar to the target user and recommends items they liked. It supports custom similarity functions (e.g. Cosine, Jaccard, Pearson).
import { jaccardSimilarity } from "@fizm/nano-recommender";
const recs = recommender.recommendUserBased("user_id", {
limit: 10,
similarityThreshold: 0.2,
similarityFunction: jaccardSimilarity,
});3. Popularity & Cold Start Fallbacks
If a user has no interaction history (a cold-start user), recommend() automatically falls back to popularity recommendations. You can configure which interaction type counts are evaluated.
const recs = recommender.recommend("new_user_id", {
fallbackStrategy: "most-purchased", // 'most-rated' | 'most-viewed' | 'most-purchased' | 'none'
});4. Time-Decay Weighting
To prevent recommendations from getting stale, you can configure an exponential decay half-life in days. Interactions will automatically have their ratings scaled down based on how old they are relative to the latest interaction in the dataset (or a custom reference time).
const recommender = new NanoRecommender({
decayHalfLifeDays: 30, // 30-day half-life (interactions 30 days old decay to 50% weight)
});
recommender.load([
{ userId: "u1", itemId: "i1", rating: 5.0, timestamp: "2026-06-12T00:00:00Z" },
{ userId: "u1", itemId: "i2", rating: 5.0, timestamp: "2026-05-13T00:00:00Z" }, // ~30 days old -> scaled to 2.5
]);You can also supply a custom reference time:
recommender.load(dataset, { referenceTime: new Date("2026-06-12T00:00:00Z") });5. Recommendation Filtering & Blacklisting
You can dynamically filter recommendations based on custom criteria (e.g. stock availability, age rating) or supply an explicit list of item IDs to exclude (blacklist):
const recs = recommender.recommend("user_id", {
strategy: "item-based",
// Exclude explicit list of item IDs (blacklist)
excludeItemIds: ["item_out_of_stock_1", "item_out_of_stock_2"],
// Custom filter callback (keep item if it returns true)
filter: (itemId) => {
const isAdultOnly = checkAdultCategory(itemId);
const isUserMinor = checkUserIsMinor("user_id");
return !(isAdultOnly && isUserMinor);
}
});6. Similarity Intersection Threshold
To avoid statistical anomalies in sparse datasets (such as a similarity score of 1.0 between two entities sharing only a single rated item), you can enforce a minimum intersection threshold. Similarity computations will immediately exit and return 0.0 for any pairs sharing fewer than this number of common interactions:
const recs = recommender.recommend("user_id", {
minIntersectionSize: 3, // Requires at least 3 shared ratings to compute similarity
});7. K-Nearest Neighbors (KNN) Limit
To maximize prediction accuracy and reduce computational complexity under dense vectors, you can limit similarity scoring to the top K nearest neighbors (similar items in Item-Based CF, or similar users in User-Based CF):
const recs = recommender.recommend("user_id", {
k: 20, // Only compute score using the top 20 nearest neighbors
});Performance
The benchmark suite was run on synthetic datasets generated with 10 interactions per user, measuring loading speed, memory footprint, and query latency (average and P95).
Loading & Memory Footprint
| Scale | Users | Items | Interactions | Load Time | Load Rate (Ops/sec) | Heap Delta (Loaded) | Heap Delta (Cached) | | :--------- | :-----: | :---: | :----------: | :-------: | :-----------------: | :-----------------: | :-----------------: | | Small | 1,000 | 100 | 10,000 | 5.17 ms | 1,933,712 | 2.12 MB | 2.14 MB | | Medium | 10,000 | 1,000 | 100,000 | 41.81 ms | 2,391,641 | 20.10 MB | 158.90 MB | | Large | 100,000 | 5,000 | 1,000,000 | 708.87 ms | 1,410,706 | 200.68 MB | 1,508.78 MB |
Recommendation Latency (Item-Based)
| Scale | Cache-Miss Avg | Cache-Miss P95 | Cache-Hit Avg | Cache-Hit P95 | Speedup Factor | | :--------- | :------------: | :------------: | :-----------: | :-----------: | :------------: | | Small | 0.80 ms | 2.49 ms | 0.55 ms | 0.79 ms | 1.5x | | Medium | 28.60 ms | 53.69 ms | 6.70 ms | 7.61 ms | 4.3x | | Large | 539.49 ms | 632.14 ms | 54.23 ms | 68.19 ms | 9.9x |
API Reference
class NanoRecommender
constructor(config?: NanoRecommenderConfig)
Instantiates the recommendation engine facade.
| Parameter | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| defaultStrategy | "item-based" \| "user-based" | "item-based" | The default strategy to use in the recommend() method. |
| defaultSimilarityThreshold | number | 0.0 | The default similarity threshold score between entities. |
| defaultMinIntersectionSize | number | 1 | The default minimum number of shared items/users required to compute similarity. |
| defaultK | number | undefined | The default neighborhood limit (K) to use in recommendation calculations. |
| defaultFallbackStrategy | "most-rated" \| "most-viewed" \| "most-purchased" \| "none" | "most-rated" | The default fallback strategy for cold start users. |
| interactionWeights | Record<string, number> | undefined | Optional mapping of interaction types to positive rating multipliers. |
| decayHalfLifeDays | number | undefined | Optional half-life in days for exponential time-decay weighting. |
| maxSimilarityCacheSize | number | undefined | Optional capacity limit for similarity cache (LRU eviction). |
load(interactions: Interaction[], options?: { referenceTime?: number | string | Date }): void
Clears existing interactions and loads a new batch dataset. Automatically applies weights from interactionWeights and decays ratings based on decayHalfLifeDays relative to options.referenceTime (defaults to max timestamp or Date.now()). Invalidates (clears) similarity caches.
addInteraction(interaction: Interaction): void
Adds or updates a single user-item interaction in real-time. Automatically applies weights from interactionWeights and decays the rating based on decayHalfLifeDays relative to the engine's last reference time. Updates the sparse matrix and selectively invalidates only the similarity cache entries associated with the affected user and item, maintaining high retrieval performance for other queries.
recommend(userId: string, options?: RecommendationOptions): Recommendation[]
Generates recommendation array for a user. Automatically delegates to the selected strategy, falling back to popularity engine if the user has no history.
| Option | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| strategy | "item-based" \| "user-based" | defaultStrategy | The recommendation strategy to use. |
| limit | number | 10 | Maximum number of recommendations to return. |
| similarityThreshold | number | defaultSimilarityThreshold | Minimum similarity score required between entities. |
| minIntersectionSize | number | defaultMinIntersectionSize | Minimum number of shared items/users required to compute similarity. |
| k | number | defaultK | Limit the similarity calculation to the top K nearest neighbors. |
| excludeInteracted | boolean | true | Whether to exclude items the user has already rated/interacted with. |
| fallbackStrategy | "most-rated" \| "most-viewed" \| "most-purchased" \| "none" | defaultFallbackStrategy | Fallback strategy for cold start users. |
| excludeItemIds | string[] | undefined | Optional array of item IDs to blacklist/exclude. |
| filter | (itemId: string) => boolean | undefined | Optional custom callback to dynamically filter item recommendations. |
recommendItemBased(userId: string, options?: ItemBasedRecommendationOptions): Recommendation[]
Directly triggers Item-Based Collaborative Filtering. Accepts all filtering options (excludeItemIds, filter).
recommendUserBased(userId: string, options?: UserBasedRecommendationOptions): Recommendation[]
Directly triggers User-Based Collaborative Filtering. Accepts all filtering options (excludeItemIds, filter).
clear(): void
Pushes the engine to a clean state. Clears sparse matrix storage and deletes similarity cache instances.
stats(): RecommenderStats
Returns descriptive summary statistics (userCount, itemCount, interactionCount).
export(): RecommenderState
Exports the entire internal state of the recommender engine (including sparse matrix, item index, and popularity metrics) to a JSON-serializable object.
import(state: RecommenderState): void
Restores the recommender engine state from a serialized state object. Automatically invalidates internal similarity caches. Throws a ValidationError if the version or structure is invalid.
Core Interfaces
interface Interaction
Represents a single user-item interaction event.
| Property | Type | Required | Description |
| :--- | :--- | :---: | :--- |
| userId | string | Yes | Unique identifier of the user. |
| itemId | string | Yes | Unique identifier of the item. |
| rating | number | Yes | Numeric rating, weight, or score for the interaction. |
| type | string | No | Type of interaction (e.g. 'view', 'rate', 'purchase'). Used for weighting and fallback popularity strategy. |
| timestamp | number \| string \| Date | No | Optional timestamp of when the interaction occurred. Used for exponential time-decay. |
interface Recommendation
Represents a single item recommendation result.
| Property | Type | Description |
| :--- | :--- | :--- |
| itemId | string | Unique identifier of the recommended item. |
| score | number | Calculated recommendation score (higher scores represent better/stronger recommendations). |
interface RecommenderState
Represents the complete serialized state of the engine.
| Property | Type | Description |
| :--- | :--- | :--- |
| version | string | Serialization schema version (currently "1"). |
| matrix | SerializedMatrixState | The serialized sparse matrix and item popularity indices. |
Similarity Functions
The library exports the following built-in similarity algorithms that satisfy the SimilarityFunction interface:
cosineSimilarity: Computes standard Cosine Similarity between two sparse vectors.jaccardSimilarity: Computes Jaccard Similarity coefficient based on the overlap of rated item sets (ignores rating values).pearsonCorrelation: Computes Pearson Correlation Coefficient by mean-centering the vectors before calculating cosine similarity, normalizing user rating scale bias.
Architecture
The project maintains a clean structural modularity:
src/
├── core/
│ ├── cache.ts # Symmetric cache
│ └── matrix.ts # Sparse rating matrix
├── algorithms/
│ ├── math.ts # Sparse vector operations
│ ├── similarity.ts # Similarity definitions
│ ├── cosine.ts # Cosine similarity
│ ├── jaccard.ts # Jaccard similarity
│ ├── pearson.ts # Pearson correlation
│ ├── item-based.ts # Item-based collaborative filtering
│ ├── user-based.ts # User-based collaborative filtering
│ └── popularity.ts # Popularity ranking indices
├── errors/
│ └── index.ts # Custom domain exceptions
├── types/
│ └── index.ts # TS type definitions
├── utils/
│ └── matrix-utils.ts# Common array transformations
└── recommender.ts # Main public facadeContributing
- Clone the repository:
git clone https://github.com/Fizm00/Lightweight-Recommendation-Engine.git - Install dependencies:
npm install - Run tests to verify setup:
npm test - Run benchmarks:
npm run benchmark
License
MIT License. See LICENSE for details.
