From 7a25f154307ceb01d16141ae5345335fce4e9cb5 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 22 Jan 2026 10:15:16 +0000 Subject: [PATCH 1/6] docs(core): simplify README and add storage engines table - Reduce README from 764 to 182 lines - Add clear storage engines comparison table - Remove verbose interface definitions - Consolidate examples for brevity - Keep essential documentation for quick reference --- ts-cache/README.md | 738 +++++---------------------------------------- 1 file changed, 78 insertions(+), 660 deletions(-) diff --git a/ts-cache/README.md b/ts-cache/README.md index 3d5f349..d9ddf77 100644 --- a/ts-cache/README.md +++ b/ts-cache/README.md @@ -6,26 +6,6 @@ Simple and extensible caching module for TypeScript/Node.js with decorator support. -## Table of Contents - -- [Installation](#installation) -- [Quick Start](#quick-start) -- [Decorators](#decorators) - - [@Cache](#cache) - - [@SyncCache](#synccache) - - [@MultiCache](#multicache) -- [Direct API Usage](#direct-api-usage) -- [Strategies](#strategies) - - [ExpirationStrategy](#expirationstrategy) -- [Storages](#storages) - - [Built-in Storages](#built-in-storages) - - [Additional Storages](#additional-storages) -- [Custom Key Strategies](#custom-key-strategies) -- [Interface Definitions](#interface-definitions) -- [Advanced Usage](#advanced-usage) -- [Environment Variables](#environment-variables) -- [Testing](#testing) - ## Installation ```bash @@ -35,728 +15,166 @@ npm install @node-ts-cache/core ## Quick Start ```typescript -import { Cache, ExpirationStrategy, MemoryStorage } from '@node-ts-cache/core'; +import { Cache, ExpirationStrategy, MemoryStorage } from "@node-ts-cache/core"; -// Create a caching strategy with in-memory storage const cacheStrategy = new ExpirationStrategy(new MemoryStorage()); class UserService { - @Cache(cacheStrategy, { ttl: 60 }) - async getUser(id: string): Promise { - // Expensive operation - result will be cached for 60 seconds - return await database.findUser(id); - } + @Cache(cacheStrategy, { ttl: 60 }) + async getUser(id: string): Promise { + return await database.findUser(id); + } } ``` -## Decorators - -### @Cache +## Storage Engines -Caches async method responses. The cache key is generated from the class name, method name, and stringified arguments. +The core package includes `MemoryStorage` and `FsJsonStorage`. Additional storage backends are available as separate packages: -**Signature:** - -```typescript -@Cache(strategy: IAsynchronousCacheType | ISynchronousCacheType, options?: ExpirationOptions, keyStrategy?: IAsyncKeyStrategy) -``` +| Package | Storage Type | Sync/Async | Use Case | +|---------|-------------|------------|----------| +| `@node-ts-cache/core` | MemoryStorage | Sync | Development, simple caching | +| `@node-ts-cache/core` | FsJsonStorage | Async | Persistent local cache | +| `@node-ts-cache/node-cache-storage` | [node-cache](https://www.npmjs.com/package/node-cache) | Sync | Production single-instance with TTL | +| `@node-ts-cache/lru-storage` | [lru-cache](https://www.npmjs.com/package/lru-cache) | Sync | Memory-bounded with automatic eviction | +| `@node-ts-cache/redis-storage` | [redis](https://www.npmjs.com/package/redis) (v3.x) | Async | Shared cache (legacy) | +| `@node-ts-cache/ioredis-storage` | [ioredis](https://www.npmjs.com/package/ioredis) | Async | Shared cache with compression | +| `@node-ts-cache/lru-redis-storage` | LRU + Redis | Async | Two-tier: fast local + shared remote | -**Parameters:** - -- `strategy` - A caching strategy instance (e.g., `ExpirationStrategy`) -- `options` - Options passed to the strategy (see [ExpirationStrategy](#expirationstrategy)) -- `keyStrategy` - Optional custom key generation strategy +## Decorators -**Important:** `@Cache` always returns a Promise, even for synchronous methods, because cache operations may be asynchronous. +### @Cache -**Example:** +Caches async method results. Cache key is generated from class name, method name, and arguments. ```typescript -import { Cache, ExpirationStrategy, MemoryStorage } from '@node-ts-cache/core'; - -const strategy = new ExpirationStrategy(new MemoryStorage()); - class ProductService { - @Cache(strategy, { ttl: 300 }) - async getProduct(id: string): Promise { - console.log('Fetching product from database...'); - return await db.products.findById(id); - } - - @Cache(strategy, { ttl: 3600, isCachedForever: false }) - async getCategories(): Promise { - return await db.categories.findAll(); - } + @Cache(strategy, { ttl: 300 }) + async getProduct(id: string): Promise { + return await db.products.findById(id); + } } - -// Usage -const service = new ProductService(); - -// First call - hits database -const product1 = await service.getProduct('123'); - -// Second call with same args - returns cached result -const product2 = await service.getProduct('123'); - -// Different args - hits database again -const product3 = await service.getProduct('456'); ``` -### @SyncCache +**Note:** `@Cache` always returns a Promise since cache operations may be asynchronous. -Caches synchronous method responses without converting to Promises. Use this when your storage is synchronous (like `MemoryStorage` or `LRUStorage`). - -**Signature:** - -```typescript -@SyncCache(strategy: ISynchronousCacheType, options?: ExpirationOptions, keyStrategy?: ISyncKeyStrategy) -``` +### @SyncCache -**Example:** +Caches synchronous method results without converting to Promises. Use with synchronous storages like `MemoryStorage` or `LRUStorage`. ```typescript -import { SyncCache, ExpirationStrategy, MemoryStorage } from '@node-ts-cache/core'; - -const strategy = new ExpirationStrategy(new MemoryStorage()); - class ConfigService { - @SyncCache(strategy, { ttl: 60 }) - getConfig(key: string): ConfigValue { - // Expensive computation - return computeConfig(key); - } + @SyncCache(strategy, { ttl: 60 }) + getConfig(key: string): ConfigValue { + return computeConfig(key); + } } - -// Usage - returns value directly, not a Promise -const config = new ConfigService().getConfig('theme'); ``` ### @MultiCache -Enables multi-tier caching with batch operations. Useful for: - -- Caching array-based lookups efficiently -- Implementing local + remote cache tiers -- Reducing database queries for batch operations - -**Signature:** - -```typescript -@MultiCache( - strategies: Array, - parameterIndex: number, - cacheKeyFn?: (element: any) => string, - options?: ExpirationOptions -) -``` - -**Parameters:** - -- `strategies` - Array of cache strategies, checked in order (first = fastest, last = slowest) -- `parameterIndex` - Index of the array parameter in the method signature -- `cacheKeyFn` - Optional function to generate cache keys for each element -- `options` - Options passed to strategies - -**Example:** +Multi-tier caching with batch operations for array-based lookups. ```typescript -import { MultiCache, ExpirationStrategy } from '@node-ts-cache/core'; -import NodeCacheStorage from '@node-ts-cache/node-cache-storage'; -import RedisIOStorage from '@node-ts-cache/ioredis-storage'; - -// Local cache (fastest) -> Redis (shared) -> Database (slowest) -const localCache = new ExpirationStrategy(new NodeCacheStorage()); -const redisCache = new RedisIOStorage(() => redisClient, { maxAge: 3600 }); - class UserService { - @MultiCache([localCache, redisCache], 0, userId => `user:${userId}`, { ttl: 300 }) - async getUsersByIds(userIds: string[]): Promise { - // This only runs for IDs not found in any cache - // IMPORTANT: Return results in the same order as input IDs - return await db.users.findByIds(userIds); - } + @MultiCache([localCache, redisCache], 0, (id) => `user:${id}`, { ttl: 300 }) + async getUsersByIds(userIds: string[]): Promise { + return await db.users.findByIds(userIds); + } } - -// Usage -const service = new UserService(); - -// First call - checks local, then redis, then hits database -const users = await service.getUsersByIds(['1', '2', '3']); - -// Second call - user 1 & 2 from local cache, user 4 from database -const moreUsers = await service.getUsersByIds(['1', '2', '4']); ``` -**Return Value Requirements:** - -- Return an array with the same length and order as the input array -- Use `null` for entries that exist but are empty -- Use `undefined` for entries that should be re-queried next time - ## Direct API Usage -You can use the caching strategy directly without decorators: +Use the caching strategy directly without decorators: ```typescript -import { ExpirationStrategy, MemoryStorage } from '@node-ts-cache/core'; - const cache = new ExpirationStrategy(new MemoryStorage()); -class DataService { - async getData(key: string): Promise { - // Check cache first - const cached = await cache.getItem(key); - if (cached !== undefined) { - return cached; - } - - // Fetch fresh data - const data = await fetchData(key); - - // Store in cache - await cache.setItem(key, data, { ttl: 300 }); - - return data; - } - - async invalidate(key: string): Promise { - await cache.setItem(key, undefined); - } - - async clearAll(): Promise { - await cache.clear(); - } -} -``` - -## Strategies - -### ExpirationStrategy - -Time-based cache expiration strategy. Items are automatically invalidated after a specified TTL (Time To Live). - -**Constructor:** - -```typescript -new ExpirationStrategy(storage: IAsynchronousCacheType | ISynchronousCacheType) -``` - -**Options:** - -| Option | Type | Default | Description | -| ----------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------- | -| `ttl` | `number` | `60` | Time to live in **seconds** | -| `isLazy` | `boolean` | `true` | If `true`, items are deleted when accessed after expiration. If `false`, items are deleted automatically via `setTimeout` | -| `isCachedForever` | `boolean` | `false` | If `true`, items never expire (ignores `ttl`) | - -**Example:** - -```typescript -import { ExpirationStrategy, MemoryStorage } from '@node-ts-cache/core'; - -const storage = new MemoryStorage(); -const strategy = new ExpirationStrategy(storage); - -// Cache for 5 minutes with lazy expiration -await strategy.setItem('key1', 'value', { ttl: 300, isLazy: true }); - -// Cache forever -await strategy.setItem('key2', 'value', { isCachedForever: true }); - -// Cache for 10 seconds with eager expiration (auto-delete) -await strategy.setItem('key3', 'value', { ttl: 10, isLazy: false }); -``` - -**Lazy vs Eager Expiration:** - -- **Lazy (`isLazy: true`)**: Expired items remain in storage until accessed. Memory is freed on read. Better for large caches. -- **Eager (`isLazy: false`)**: Items are deleted via `setTimeout` after TTL. Frees memory automatically but uses timers. - -## Storages - -### Built-in Storages - -These storages are included in the core package: - -#### MemoryStorage - -Simple in-memory storage using a JavaScript object. Best for development and simple use cases. - -```typescript -import { MemoryStorage, ExpirationStrategy } from '@node-ts-cache/core'; - -const storage = new MemoryStorage(); -const strategy = new ExpirationStrategy(storage); -``` - -**Characteristics:** - -- Synchronous operations -- No external dependencies -- Data lost on process restart -- No size limits (can cause memory issues) - -#### FsJsonStorage - -File-based storage that persists cache to a JSON file. Useful for persistent local caching. - -```typescript -import { FsJsonStorage, ExpirationStrategy } from '@node-ts-cache/core'; - -const storage = new FsJsonStorage('/tmp/cache.json'); -const strategy = new ExpirationStrategy(storage); -``` - -**Characteristics:** - -- Asynchronous operations -- Survives process restarts -- Slower than memory storage -- Good for development/single-instance deployments - -### Additional Storages - -Install these separately based on your needs: - -#### NodeCacheStorage - -Wrapper for [node-cache](https://www.npmjs.com/package/node-cache) - a simple in-memory cache with TTL support. - -```bash -npm install @node-ts-cache/node-cache-storage -``` - -```typescript -import { ExpirationStrategy } from '@node-ts-cache/core'; -import NodeCacheStorage from '@node-ts-cache/node-cache-storage'; - -const storage = new NodeCacheStorage({ - stdTTL: 100, // Default TTL in seconds - checkperiod: 120, // Cleanup interval in seconds - maxKeys: 1000 // Maximum number of keys -}); -const strategy = new ExpirationStrategy(storage); -``` - -**Characteristics:** - -- Synchronous operations -- Supports multi-get/set operations -- Built-in TTL and cleanup -- Good for production single-instance apps - -#### LRUStorage +// Get item +const value = await cache.getItem("key"); -Wrapper for [lru-cache](https://www.npmjs.com/package/lru-cache) - Least Recently Used cache with automatic eviction. +// Set item with TTL +await cache.setItem("key", data, { ttl: 300 }); -```bash -npm install @node-ts-cache/lru-storage -``` - -```typescript -import { ExpirationStrategy } from '@node-ts-cache/core'; -import LRUStorage from '@node-ts-cache/lru-storage'; - -const storage = new LRUStorage({ - max: 500, // Maximum number of items - ttl: 300 // TTL in seconds -}); -const strategy = new ExpirationStrategy(storage); -``` - -**Characteristics:** - -- Synchronous operations -- Automatic eviction when max size reached -- Memory-safe with bounded size -- Supports multi-get/set operations - -**Note:** LRU cache has its own TTL (`ttl` in seconds). When using with `ExpirationStrategy`, both TTLs apply. Set LRU `ttl` higher than your strategy TTL or use `isCachedForever` in the strategy. - -#### RedisStorage - -Redis storage using the legacy `redis` package (v3.x). For new projects, consider using `RedisIOStorage` instead. - -```bash -npm install @node-ts-cache/redis-storage -``` +// Delete item +await cache.setItem("key", undefined); -```typescript -import { ExpirationStrategy } from '@node-ts-cache/core'; -import RedisStorage from '@node-ts-cache/redis-storage'; - -const storage = new RedisStorage({ - host: 'localhost', - port: 6379, - password: 'optional' -}); -const strategy = new ExpirationStrategy(storage); +// Clear all +await cache.clear(); ``` -**Characteristics:** - -- Asynchronous operations -- Uses legacy `redis` package with Bluebird promises -- Shared cache across multiple instances -- No compression support - -#### RedisIOStorage - -Modern Redis storage using [ioredis](https://github.com/redis/ioredis) with optional Snappy compression. - -```bash -npm install @node-ts-cache/ioredis-storage -``` +## ExpirationStrategy Options -```typescript -import { ExpirationStrategy } from '@node-ts-cache/core'; -import RedisIOStorage from '@node-ts-cache/ioredis-storage'; -import Redis from 'ioredis'; - -const redisClient = new Redis({ - host: 'localhost', - port: 6379 -}); - -// Basic usage -const storage = new RedisIOStorage( - () => redisClient, - { maxAge: 3600 } // TTL in seconds -); - -// With compression (reduces bandwidth, increases CPU usage) -const compressedStorage = new RedisIOStorage(() => redisClient, { maxAge: 3600, compress: true }); - -// With error handler (non-blocking writes) -storage.onError(error => { - console.error('Redis error:', error); -}); - -const strategy = new ExpirationStrategy(storage); -``` - -**Characteristics:** - -- Asynchronous operations -- Supports multi-get/set operations -- Optional Snappy compression -- Modern ioredis client -- Custom error handler support -- Can bypass ExpirationStrategy TTL (uses Redis native TTL) - -**Constructor Options:** | Option | Type | Default | Description | |--------|------|---------|-------------| -| `maxAge` | `number` | `86400` | TTL in seconds (used by Redis SETEX) | -| `compress` | `boolean` | `false` | Enable Snappy compression | - -#### LRUWithRedisStorage - -Two-tier caching: fast local LRU cache with Redis fallback. Provides the best of both worlds. - -```bash -npm install @node-ts-cache/lru-redis-storage -``` +| `ttl` | `number` | `60` | Time to live in seconds | +| `isLazy` | `boolean` | `true` | If `true`, delete on access after expiration. If `false`, delete via `setTimeout` | +| `isCachedForever` | `boolean` | `false` | If `true`, items never expire | ```typescript -import { ExpirationStrategy } from '@node-ts-cache/core'; -import LRUWithRedisStorage from '@node-ts-cache/lru-redis-storage'; -import Redis from 'ioredis'; +// Cache for 5 minutes with lazy expiration +await strategy.setItem("key", value, { ttl: 300, isLazy: true }); -const redisClient = new Redis(); +// Cache forever +await strategy.setItem("key", value, { isCachedForever: true }); -const storage = new LRUWithRedisStorage( - { max: 1000 }, // LRU options - () => redisClient // Redis client factory -); -const strategy = new ExpirationStrategy(storage); +// Cache with eager expiration (auto-delete after TTL) +await strategy.setItem("key", value, { ttl: 10, isLazy: false }); ``` -**Characteristics:** - -- Asynchronous operations -- Local LRU for hot data -- Redis fallback for cache misses -- Reduces Redis round-trips -- Good for high-traffic applications - ## Custom Key Strategies -By default, cache keys are generated as: `ClassName:methodName:JSON.stringify(args)` - -You can implement custom key strategies for different needs: - -### Synchronous Key Strategy +Override default key generation by implementing `ISyncKeyStrategy` or `IAsyncKeyStrategy`: ```typescript -import { Cache, ExpirationStrategy, MemoryStorage, ISyncKeyStrategy } from '@node-ts-cache/core'; - class CustomKeyStrategy implements ISyncKeyStrategy { - getKey(className: string, methodName: string, args: any[]): string | undefined { - // Return undefined to skip caching for this call - if (args[0] === 'skip') { - return undefined; - } - - // Custom key format - return `${className}::${methodName}::${args.join('-')}`; - } + getKey(className: string, methodName: string, args: any[]): string | undefined { + if (args[0] === "skip") return undefined; // Skip caching + return `${className}::${methodName}::${args.join("-")}`; + } } -const strategy = new ExpirationStrategy(new MemoryStorage()); -const keyStrategy = new CustomKeyStrategy(); - class MyService { - @Cache(strategy, { ttl: 60 }, keyStrategy) - async getData(id: string): Promise { - return fetchData(id); - } + @Cache(strategy, { ttl: 60 }, new CustomKeyStrategy()) + async getData(id: string): Promise { + return fetchData(id); + } } ``` -### Asynchronous Key Strategy - -For key generation that requires async operations (e.g., fetching user context): - -```typescript -import { Cache, ExpirationStrategy, MemoryStorage, IAsyncKeyStrategy } from '@node-ts-cache/core'; - -class AsyncKeyStrategy implements IAsyncKeyStrategy { - async getKey(className: string, methodName: string, args: any[]): Promise { - // Async operation to build key - const userId = await getCurrentUserId(); - return `${userId}:${className}:${methodName}:${JSON.stringify(args)}`; - } -} -``` - -## Interface Definitions - -### Storage Interfaces - -```typescript -/** - * Cache entry structure stored in backends - */ -interface ICacheEntry { - content: any; // The cached value - meta: any; // Metadata (e.g., TTL, createdAt) -} - -/** - * Asynchronous storage for single items - */ -interface IAsynchronousCacheType { - /** Retrieve an item by key. Returns undefined if not found. */ - getItem(key: string): Promise; - - /** Store an item. Pass undefined as content to delete. */ - setItem(key: string, content: C | undefined, options?: any): Promise; - - /** Clear all items from the cache. */ - clear(): Promise; -} - -/** - * Synchronous storage for single items - */ -interface ISynchronousCacheType { - /** Retrieve an item by key. Returns undefined if not found. */ - getItem(key: string): T | undefined; - - /** Store an item. Pass undefined as content to delete. */ - setItem(key: string, content: C | undefined, options?: any): void; - - /** Clear all items from the cache. */ - clear(): void; -} - -/** - * Asynchronous storage with batch operations - */ -interface IMultiIAsynchronousCacheType { - /** Retrieve multiple items by keys. */ - getItems(keys: string[]): Promise<{ [key: string]: T | undefined }>; - - /** Store multiple items at once. */ - setItems(values: { key: string; content: C | undefined }[], options?: any): Promise; - - /** Clear all items from the cache. */ - clear(): Promise; -} - -/** - * Synchronous storage with batch operations - */ -interface IMultiSynchronousCacheType { - /** Retrieve multiple items by keys. */ - getItems(keys: string[]): { [key: string]: T | undefined }; - - /** Store multiple items at once. */ - setItems(values: { key: string; content: C | undefined }[], options?: any): void; - - /** Clear all items from the cache. */ - clear(): void; -} -``` - -### Key Strategy Interfaces - -```typescript -/** - * Synchronous key generation strategy - */ -interface ISyncKeyStrategy { - /** - * Generate a cache key from method context - * @param className - Name of the class containing the method - * @param methodName - Name of the cached method - * @param args - Arguments passed to the method - * @returns Cache key string, or undefined to skip caching - */ - getKey(className: string, methodName: string, args: any[]): string | undefined; -} - -/** - * Asynchronous key generation strategy - */ -interface IAsyncKeyStrategy { - /** - * Generate a cache key from method context (can be async) - * @param className - Name of the class containing the method - * @param methodName - Name of the cached method - * @param args - Arguments passed to the method - * @returns Cache key string, or undefined to skip caching - */ - getKey( - className: string, - methodName: string, - args: any[] - ): Promise | string | undefined; -} -``` - -### ExpirationStrategy Options - -```typescript -interface ExpirationOptions { - /** Time to live in seconds (default: 60) */ - ttl?: number; - - /** If true, delete on access after expiration. If false, delete via setTimeout (default: true) */ - isLazy?: boolean; - - /** If true, cache forever ignoring TTL (default: false) */ - isCachedForever?: boolean; -} -``` - -## Advanced Usage +## Advanced Features ### Call Deduplication -The `@Cache` decorator automatically deduplicates concurrent calls with the same cache key. If multiple calls are made before the first one completes, they all receive the same result: +Concurrent calls with the same cache key share the same pending promise: ```typescript -class DataService { - @Cache(strategy, { ttl: 60 }) - async fetchData(id: string): Promise { - console.log('Fetching...'); // Only logged once - return await slowApiCall(id); - } -} - -const service = new DataService(); - -// All three calls share the same pending promise +// All three calls share one database request const [a, b, c] = await Promise.all([ - service.fetchData('123'), - service.fetchData('123'), - service.fetchData('123') + service.fetchData("123"), + service.fetchData("123"), + service.fetchData("123"), ]); -// "Fetching..." is logged only once, all three get the same result ``` -### Handling Undefined vs Null +### Null vs Undefined -The cache distinguishes between: - -- `undefined`: No value found in cache, or value should not be cached -- `null`: Explicit null value that is cached +- `undefined`: Cache miss or skip caching +- `null`: Cached value (e.g., "not found" result) ```typescript -class UserService { - @Cache(strategy, { ttl: 60 }) - async findUser(id: string): Promise { - const user = await db.findUser(id); - // Return null for non-existent users to cache the "not found" result - // Return undefined would cause re-fetching on every call - return user ?? null; - } +async findUser(id: string): Promise { + const user = await db.findUser(id); + return user ?? null; // Cache "not found" as null } ``` -### Error Handling - -Cache errors are logged but don't break the application flow. If caching fails, the method executes normally: - -```typescript -// Cache read/write failures are logged as warnings: -// "@node-ts-cache/core: reading cache failed [key] [error]" -// "@node-ts-cache/core: writing result to cache failed [key] [error]" - -// For RedisIOStorage, you can add a custom error handler: -storage.onError(error => { - metrics.incrementCacheError(); - logger.error('Cache error', error); -}); -``` - ## Environment Variables -| Variable | Description | -| ------------------------- | -------------------------------------------------------------------------- | -| `DISABLE_CACHE_DECORATOR` | Set to any value to disable all `@Cache` decorators (useful for debugging) | - -## Testing - -```bash -# Run all tests -npm test - -# Run tests in watch mode -npm run tdd - -# Run tests with debugger -npm run tdd-debug-brk -``` - -## API Reference - -### Exports - -```typescript -// Decorators -export { Cache } from './decorator/cache.decorator'; -export { SyncCache } from './decorator/synccache.decorator'; -export { MultiCache } from './decorator/multicache.decorator'; - -// Strategies -export { ExpirationStrategy } from './strategy/caching/expiration.strategy'; - -// Built-in Storages -export { MemoryStorage } from './storage/memory'; -export { FsJsonStorage } from './storage/fs'; - -// Interfaces -export { - IAsynchronousCacheType, - ISynchronousCacheType, - IMultiIAsynchronousCacheType, - IMultiSynchronousCacheType -} from './types/cache.types'; -export { ISyncKeyStrategy, IAsyncKeyStrategy } from './types/key.strategy.types'; -``` +| Variable | Description | +|----------|-------------| +| `DISABLE_CACHE_DECORATOR` | Set to any value to disable all `@Cache` decorators | ## License From bdfa74a2702d710b30b33e6bbb58377cf58ab880 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 22 Jan 2026 10:17:08 +0000 Subject: [PATCH 2/6] docs: fix redis version from v3.x to v4.x --- README.md | 4 ++-- ts-cache/README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0e40fd1..8256350 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ This is a monorepo containing the following packages: | Package | Version | Description | | ---------------------------------------------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------ | -| [@node-ts-cache/redis-storage](./storages/redis) | ![npm](https://img.shields.io/npm/v/@node-ts-cache/redis-storage.svg) | Redis storage using `redis` package (v3.x) | +| [@node-ts-cache/redis-storage](./storages/redis) | ![npm](https://img.shields.io/npm/v/@node-ts-cache/redis-storage.svg) | Redis storage using `redis` package (v4.x) | | [@node-ts-cache/ioredis-storage](./storages/redisio) | ![npm](https://img.shields.io/npm/v/@node-ts-cache/ioredis-storage.svg) | Redis storage using `ioredis` with compression support | | [@node-ts-cache/node-cache-storage](./storages/node-cache) | ![npm](https://img.shields.io/npm/v/@node-ts-cache/node-cache-storage.svg) | In-memory cache using `node-cache` | | [@node-ts-cache/lru-storage](./storages/lru) | ![npm](https://img.shields.io/npm/v/@node-ts-cache/lru-storage.svg) | LRU cache with automatic eviction | @@ -96,7 +96,7 @@ For detailed documentation, see the [main package README](./ts-cache/README.md). | **FsJsonStorage** | Async | Persistent local cache | File-based, survives restarts | | **NodeCacheStorage** | Sync | Production single-instance | TTL support, multi-ops | | **LRUStorage** | Sync | Memory-constrained apps | Auto-eviction, size limits | -| **RedisStorage** | Async | Distributed systems | Shared cache, legacy redis | +| **RedisStorage** | Async | Distributed systems | Shared cache, redis v4 | | **RedisIOStorage** | Async | Distributed systems | Compression, modern ioredis | | **LRUWithRedisStorage** | Async | High-performance distributed | Local + remote tiers | diff --git a/ts-cache/README.md b/ts-cache/README.md index d9ddf77..284a531 100644 --- a/ts-cache/README.md +++ b/ts-cache/README.md @@ -37,7 +37,7 @@ The core package includes `MemoryStorage` and `FsJsonStorage`. Additional storag | `@node-ts-cache/core` | FsJsonStorage | Async | Persistent local cache | | `@node-ts-cache/node-cache-storage` | [node-cache](https://www.npmjs.com/package/node-cache) | Sync | Production single-instance with TTL | | `@node-ts-cache/lru-storage` | [lru-cache](https://www.npmjs.com/package/lru-cache) | Sync | Memory-bounded with automatic eviction | -| `@node-ts-cache/redis-storage` | [redis](https://www.npmjs.com/package/redis) (v3.x) | Async | Shared cache (legacy) | +| `@node-ts-cache/redis-storage` | [redis](https://www.npmjs.com/package/redis) (v4.x) | Async | Shared cache | | `@node-ts-cache/ioredis-storage` | [ioredis](https://www.npmjs.com/package/ioredis) | Async | Shared cache with compression | | `@node-ts-cache/lru-redis-storage` | LRU + Redis | Async | Two-tier: fast local + shared remote | From 8ac83937d2c3ee6f9ce54e24c56e63031e1e80d7 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 22 Jan 2026 10:18:51 +0000 Subject: [PATCH 3/6] docs(core): add ADVANCED.md with detailed reference material Move interface definitions, detailed storage configuration examples, @MultiCache in-depth documentation, and error handling patterns to a separate file for advanced users and custom storage implementors. --- ts-cache/ADVANCED.md | 416 +++++++++++++++++++++++++++++++++++++++++++ ts-cache/README.md | 9 + 2 files changed, 425 insertions(+) create mode 100644 ts-cache/ADVANCED.md diff --git a/ts-cache/ADVANCED.md b/ts-cache/ADVANCED.md new file mode 100644 index 0000000..b9aba81 --- /dev/null +++ b/ts-cache/ADVANCED.md @@ -0,0 +1,416 @@ +# Advanced Usage Guide + +This document covers advanced topics for implementing custom storages, detailed storage configuration, and reference material. + +## Interface Definitions + +### Storage Interfaces + +Implement these interfaces to create custom storage backends. + +```typescript +/** + * Cache entry structure stored in backends + */ +interface ICacheEntry { + content: any; // The cached value + meta: any; // Metadata (e.g., TTL, createdAt) +} + +/** + * Asynchronous storage for single items + */ +interface IAsynchronousCacheType { + /** Retrieve an item by key. Returns undefined if not found. */ + getItem(key: string): Promise; + + /** Store an item. Pass undefined as content to delete. */ + setItem(key: string, content: C | undefined, options?: any): Promise; + + /** Clear all items from the cache. */ + clear(): Promise; +} + +/** + * Synchronous storage for single items + */ +interface ISynchronousCacheType { + /** Retrieve an item by key. Returns undefined if not found. */ + getItem(key: string): T | undefined; + + /** Store an item. Pass undefined as content to delete. */ + setItem(key: string, content: C | undefined, options?: any): void; + + /** Clear all items from the cache. */ + clear(): void; +} + +/** + * Asynchronous storage with batch operations (for @MultiCache) + */ +interface IMultiIAsynchronousCacheType { + /** Retrieve multiple items by keys. */ + getItems(keys: string[]): Promise<{ [key: string]: T | undefined }>; + + /** Store multiple items at once. */ + setItems(values: { key: string; content: C | undefined }[], options?: any): Promise; + + /** Clear all items from the cache. */ + clear(): Promise; +} + +/** + * Synchronous storage with batch operations (for @MultiCache) + */ +interface IMultiSynchronousCacheType { + /** Retrieve multiple items by keys. */ + getItems(keys: string[]): { [key: string]: T | undefined }; + + /** Store multiple items at once. */ + setItems(values: { key: string; content: C | undefined }[], options?: any): void; + + /** Clear all items from the cache. */ + clear(): void; +} +``` + +### Key Strategy Interfaces + +```typescript +/** + * Synchronous key generation strategy + */ +interface ISyncKeyStrategy { + getKey(className: string, methodName: string, args: any[]): string | undefined; +} + +/** + * Asynchronous key generation strategy + */ +interface IAsyncKeyStrategy { + getKey( + className: string, + methodName: string, + args: any[] + ): Promise | string | undefined; +} +``` + +## Detailed Storage Configuration + +### MemoryStorage + +Simple in-memory storage using a JavaScript object. + +```typescript +import { MemoryStorage, ExpirationStrategy } from "@node-ts-cache/core"; + +const storage = new MemoryStorage(); +const strategy = new ExpirationStrategy(storage); +``` + +**Characteristics:** + +- Synchronous operations +- No external dependencies +- Data lost on process restart +- No size limits (can cause memory issues) + +### FsJsonStorage + +File-based storage that persists cache to a JSON file. + +```typescript +import { FsJsonStorage, ExpirationStrategy } from "@node-ts-cache/core"; + +const storage = new FsJsonStorage("/tmp/cache.json"); +const strategy = new ExpirationStrategy(storage); +``` + +**Characteristics:** + +- Asynchronous operations +- Survives process restarts +- Slower than memory storage +- Good for development/single-instance deployments + +### NodeCacheStorage + +Wrapper for [node-cache](https://www.npmjs.com/package/node-cache). + +```bash +npm install @node-ts-cache/node-cache-storage +``` + +```typescript +import { ExpirationStrategy } from "@node-ts-cache/core"; +import NodeCacheStorage from "@node-ts-cache/node-cache-storage"; + +const storage = new NodeCacheStorage({ + stdTTL: 100, // Default TTL in seconds + checkperiod: 120, // Cleanup interval in seconds + maxKeys: 1000, // Maximum number of keys +}); +const strategy = new ExpirationStrategy(storage); +``` + +**Characteristics:** + +- Synchronous operations +- Supports multi-get/set operations +- Built-in TTL and cleanup +- Good for production single-instance apps + +### LRUStorage + +Wrapper for [lru-cache](https://www.npmjs.com/package/lru-cache). + +```bash +npm install @node-ts-cache/lru-storage +``` + +```typescript +import { ExpirationStrategy } from "@node-ts-cache/core"; +import LRUStorage from "@node-ts-cache/lru-storage"; + +const storage = new LRUStorage({ + max: 500, // Maximum number of items + ttl: 300, // TTL in seconds +}); +const strategy = new ExpirationStrategy(storage); +``` + +**Characteristics:** + +- Synchronous operations +- Automatic eviction when max size reached +- Memory-safe with bounded size +- Supports multi-get/set operations + +**Note:** LRU cache has its own TTL. When using with `ExpirationStrategy`, both TTLs apply. Set LRU `ttl` higher than your strategy TTL or use `isCachedForever` in the strategy. + +### RedisStorage + +Redis storage using the `redis` package (v4.x). + +```bash +npm install @node-ts-cache/redis-storage +``` + +```typescript +import { ExpirationStrategy } from "@node-ts-cache/core"; +import RedisStorage from "@node-ts-cache/redis-storage"; + +const storage = new RedisStorage({ + host: "localhost", + port: 6379, + password: "optional", +}); +const strategy = new ExpirationStrategy(storage); +``` + +**Characteristics:** + +- Asynchronous operations +- Shared cache across multiple instances +- No compression support + +### RedisIOStorage + +Modern Redis storage using [ioredis](https://github.com/redis/ioredis) with optional Snappy compression. + +```bash +npm install @node-ts-cache/ioredis-storage +``` + +```typescript +import { ExpirationStrategy } from "@node-ts-cache/core"; +import RedisIOStorage from "@node-ts-cache/ioredis-storage"; +import Redis from "ioredis"; + +const redisClient = new Redis({ + host: "localhost", + port: 6379, +}); + +// Basic usage +const storage = new RedisIOStorage( + () => redisClient, + { maxAge: 3600 } // TTL in seconds +); + +// With compression (reduces bandwidth, increases CPU usage) +const compressedStorage = new RedisIOStorage(() => redisClient, { + maxAge: 3600, + compress: true, +}); + +// With error handler (non-blocking writes) +storage.onError((error) => { + console.error("Redis error:", error); +}); + +const strategy = new ExpirationStrategy(storage); +``` + +**Constructor Options:** + +| Option | Type | Default | Description | +| ---------- | --------- | ------- | --------------------------------- | +| `maxAge` | `number` | `86400` | TTL in seconds (used by Redis) | +| `compress` | `boolean` | `false` | Enable Snappy compression | + +**Characteristics:** + +- Asynchronous operations +- Supports multi-get/set operations +- Optional Snappy compression +- Custom error handler support +- Can bypass ExpirationStrategy TTL (uses Redis native TTL) + +### LRUWithRedisStorage + +Two-tier caching: fast local LRU cache with Redis fallback. + +```bash +npm install @node-ts-cache/lru-redis-storage +``` + +```typescript +import { ExpirationStrategy } from "@node-ts-cache/core"; +import LRUWithRedisStorage from "@node-ts-cache/lru-redis-storage"; +import Redis from "ioredis"; + +const redisClient = new Redis(); + +const storage = new LRUWithRedisStorage( + { max: 1000 }, // LRU options + () => redisClient // Redis client factory +); +const strategy = new ExpirationStrategy(storage); +``` + +**Characteristics:** + +- Asynchronous operations +- Local LRU for hot data +- Redis fallback for cache misses +- Reduces Redis round-trips +- Good for high-traffic applications + +## @MultiCache Details + +### Signature + +```typescript +@MultiCache( + strategies: Array, + parameterIndex: number, + cacheKeyFn?: (element: any) => string, + options?: ExpirationOptions +) +``` + +### Parameters + +- `strategies` - Array of cache strategies, checked in order (first = fastest, last = slowest) +- `parameterIndex` - Index of the array parameter in the method signature +- `cacheKeyFn` - Optional function to generate cache keys for each element +- `options` - Options passed to strategies + +### Example + +```typescript +import { MultiCache, ExpirationStrategy } from "@node-ts-cache/core"; +import NodeCacheStorage from "@node-ts-cache/node-cache-storage"; +import RedisIOStorage from "@node-ts-cache/ioredis-storage"; + +// Local cache (fastest) -> Redis (shared) -> Database (slowest) +const localCache = new ExpirationStrategy(new NodeCacheStorage()); +const redisCache = new RedisIOStorage(() => redisClient, { maxAge: 3600 }); + +class UserService { + @MultiCache([localCache, redisCache], 0, (userId) => `user:${userId}`, { ttl: 300 }) + async getUsersByIds(userIds: string[]): Promise { + // This only runs for IDs not found in any cache + // IMPORTANT: Return results in the same order as input IDs + return await db.users.findByIds(userIds); + } +} + +// Usage +const service = new UserService(); + +// First call - checks local, then redis, then hits database +const users = await service.getUsersByIds(["1", "2", "3"]); + +// Second call - user 1 & 2 from local cache, user 4 from database +const moreUsers = await service.getUsersByIds(["1", "2", "4"]); +``` + +### Return Value Requirements + +- Return an array with the same length and order as the input array +- Use `null` for entries that exist but are empty +- Use `undefined` for entries that should be re-queried next time + +## Lazy vs Eager Expiration + +- **Lazy (`isLazy: true`)**: Expired items remain in storage until accessed. Memory is freed on read. Better for large caches. +- **Eager (`isLazy: false`)**: Items are deleted via `setTimeout` after TTL. Frees memory automatically but uses timers. + +## Error Handling + +Cache errors are logged but don't break the application flow. If caching fails, the method executes normally: + +```typescript +// Cache read/write failures are logged as warnings: +// "@node-ts-cache/core: reading cache failed [key] [error]" +// "@node-ts-cache/core: writing result to cache failed [key] [error]" + +// For RedisIOStorage, you can add a custom error handler: +storage.onError((error) => { + metrics.incrementCacheError(); + logger.error("Cache error", error); +}); +``` + +## Async Key Strategy Example + +For key generation that requires async operations (e.g., fetching user context): + +```typescript +import { Cache, ExpirationStrategy, MemoryStorage, IAsyncKeyStrategy } from "@node-ts-cache/core"; + +class AsyncKeyStrategy implements IAsyncKeyStrategy { + async getKey(className: string, methodName: string, args: any[]): Promise { + const userId = await getCurrentUserId(); + return `${userId}:${className}:${methodName}:${JSON.stringify(args)}`; + } +} +``` + +## API Exports + +```typescript +// Decorators +export { Cache } from "./decorator/cache.decorator"; +export { SyncCache } from "./decorator/synccache.decorator"; +export { MultiCache } from "./decorator/multicache.decorator"; + +// Strategies +export { ExpirationStrategy } from "./strategy/caching/expiration.strategy"; + +// Built-in Storages +export { MemoryStorage } from "./storage/memory"; +export { FsJsonStorage } from "./storage/fs"; + +// Interfaces +export { + IAsynchronousCacheType, + ISynchronousCacheType, + IMultiIAsynchronousCacheType, + IMultiSynchronousCacheType, +} from "./types/cache.types"; +export { ISyncKeyStrategy, IAsyncKeyStrategy } from "./types/key.strategy.types"; +``` diff --git a/ts-cache/README.md b/ts-cache/README.md index 284a531..9d4f2e8 100644 --- a/ts-cache/README.md +++ b/ts-cache/README.md @@ -176,6 +176,15 @@ async findUser(id: string): Promise { |----------|-------------| | `DISABLE_CACHE_DECORATOR` | Set to any value to disable all `@Cache` decorators | +## More Documentation + +See [ADVANCED.md](./ADVANCED.md) for: + +- Interface definitions for implementing custom storages +- Detailed storage configuration examples +- @MultiCache in-depth usage +- Error handling patterns + ## License MIT License From 8003c658534cacb51056b37943e1f00ac7322621 Mon Sep 17 00:00:00 2001 From: Simon Tretter Date: Thu, 22 Jan 2026 11:37:00 +0100 Subject: [PATCH 4/6] Simplify README and add storage engines table Updated README to simplify content and included a table for storage engines. --- .changeset/tiny-schools-lick.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tiny-schools-lick.md diff --git a/.changeset/tiny-schools-lick.md b/.changeset/tiny-schools-lick.md new file mode 100644 index 0000000..ab7512f --- /dev/null +++ b/.changeset/tiny-schools-lick.md @@ -0,0 +1,5 @@ +--- +"@node-ts-cache/core": patch +--- + +Simplify README and add storage engines table From 47770ecef24727373a214133681644bfe498af26 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 22 Jan 2026 10:38:32 +0000 Subject: [PATCH 5/6] style: apply prettier formatting --- ts-cache/ADVANCED.md | 200 +++++++++++++++++++++---------------------- ts-cache/README.md | 100 +++++++++++----------- 2 files changed, 150 insertions(+), 150 deletions(-) diff --git a/ts-cache/ADVANCED.md b/ts-cache/ADVANCED.md index b9aba81..4a02580 100644 --- a/ts-cache/ADVANCED.md +++ b/ts-cache/ADVANCED.md @@ -13,64 +13,64 @@ Implement these interfaces to create custom storage backends. * Cache entry structure stored in backends */ interface ICacheEntry { - content: any; // The cached value - meta: any; // Metadata (e.g., TTL, createdAt) + content: any; // The cached value + meta: any; // Metadata (e.g., TTL, createdAt) } /** * Asynchronous storage for single items */ interface IAsynchronousCacheType { - /** Retrieve an item by key. Returns undefined if not found. */ - getItem(key: string): Promise; + /** Retrieve an item by key. Returns undefined if not found. */ + getItem(key: string): Promise; - /** Store an item. Pass undefined as content to delete. */ - setItem(key: string, content: C | undefined, options?: any): Promise; + /** Store an item. Pass undefined as content to delete. */ + setItem(key: string, content: C | undefined, options?: any): Promise; - /** Clear all items from the cache. */ - clear(): Promise; + /** Clear all items from the cache. */ + clear(): Promise; } /** * Synchronous storage for single items */ interface ISynchronousCacheType { - /** Retrieve an item by key. Returns undefined if not found. */ - getItem(key: string): T | undefined; + /** Retrieve an item by key. Returns undefined if not found. */ + getItem(key: string): T | undefined; - /** Store an item. Pass undefined as content to delete. */ - setItem(key: string, content: C | undefined, options?: any): void; + /** Store an item. Pass undefined as content to delete. */ + setItem(key: string, content: C | undefined, options?: any): void; - /** Clear all items from the cache. */ - clear(): void; + /** Clear all items from the cache. */ + clear(): void; } /** * Asynchronous storage with batch operations (for @MultiCache) */ interface IMultiIAsynchronousCacheType { - /** Retrieve multiple items by keys. */ - getItems(keys: string[]): Promise<{ [key: string]: T | undefined }>; + /** Retrieve multiple items by keys. */ + getItems(keys: string[]): Promise<{ [key: string]: T | undefined }>; - /** Store multiple items at once. */ - setItems(values: { key: string; content: C | undefined }[], options?: any): Promise; + /** Store multiple items at once. */ + setItems(values: { key: string; content: C | undefined }[], options?: any): Promise; - /** Clear all items from the cache. */ - clear(): Promise; + /** Clear all items from the cache. */ + clear(): Promise; } /** * Synchronous storage with batch operations (for @MultiCache) */ interface IMultiSynchronousCacheType { - /** Retrieve multiple items by keys. */ - getItems(keys: string[]): { [key: string]: T | undefined }; + /** Retrieve multiple items by keys. */ + getItems(keys: string[]): { [key: string]: T | undefined }; - /** Store multiple items at once. */ - setItems(values: { key: string; content: C | undefined }[], options?: any): void; + /** Store multiple items at once. */ + setItems(values: { key: string; content: C | undefined }[], options?: any): void; - /** Clear all items from the cache. */ - clear(): void; + /** Clear all items from the cache. */ + clear(): void; } ``` @@ -81,18 +81,18 @@ interface IMultiSynchronousCacheType { * Synchronous key generation strategy */ interface ISyncKeyStrategy { - getKey(className: string, methodName: string, args: any[]): string | undefined; + getKey(className: string, methodName: string, args: any[]): string | undefined; } /** * Asynchronous key generation strategy */ interface IAsyncKeyStrategy { - getKey( - className: string, - methodName: string, - args: any[] - ): Promise | string | undefined; + getKey( + className: string, + methodName: string, + args: any[] + ): Promise | string | undefined; } ``` @@ -103,7 +103,7 @@ interface IAsyncKeyStrategy { Simple in-memory storage using a JavaScript object. ```typescript -import { MemoryStorage, ExpirationStrategy } from "@node-ts-cache/core"; +import { MemoryStorage, ExpirationStrategy } from '@node-ts-cache/core'; const storage = new MemoryStorage(); const strategy = new ExpirationStrategy(storage); @@ -121,9 +121,9 @@ const strategy = new ExpirationStrategy(storage); File-based storage that persists cache to a JSON file. ```typescript -import { FsJsonStorage, ExpirationStrategy } from "@node-ts-cache/core"; +import { FsJsonStorage, ExpirationStrategy } from '@node-ts-cache/core'; -const storage = new FsJsonStorage("/tmp/cache.json"); +const storage = new FsJsonStorage('/tmp/cache.json'); const strategy = new ExpirationStrategy(storage); ``` @@ -143,13 +143,13 @@ npm install @node-ts-cache/node-cache-storage ``` ```typescript -import { ExpirationStrategy } from "@node-ts-cache/core"; -import NodeCacheStorage from "@node-ts-cache/node-cache-storage"; +import { ExpirationStrategy } from '@node-ts-cache/core'; +import NodeCacheStorage from '@node-ts-cache/node-cache-storage'; const storage = new NodeCacheStorage({ - stdTTL: 100, // Default TTL in seconds - checkperiod: 120, // Cleanup interval in seconds - maxKeys: 1000, // Maximum number of keys + stdTTL: 100, // Default TTL in seconds + checkperiod: 120, // Cleanup interval in seconds + maxKeys: 1000 // Maximum number of keys }); const strategy = new ExpirationStrategy(storage); ``` @@ -170,12 +170,12 @@ npm install @node-ts-cache/lru-storage ``` ```typescript -import { ExpirationStrategy } from "@node-ts-cache/core"; -import LRUStorage from "@node-ts-cache/lru-storage"; +import { ExpirationStrategy } from '@node-ts-cache/core'; +import LRUStorage from '@node-ts-cache/lru-storage'; const storage = new LRUStorage({ - max: 500, // Maximum number of items - ttl: 300, // TTL in seconds + max: 500, // Maximum number of items + ttl: 300 // TTL in seconds }); const strategy = new ExpirationStrategy(storage); ``` @@ -198,13 +198,13 @@ npm install @node-ts-cache/redis-storage ``` ```typescript -import { ExpirationStrategy } from "@node-ts-cache/core"; -import RedisStorage from "@node-ts-cache/redis-storage"; +import { ExpirationStrategy } from '@node-ts-cache/core'; +import RedisStorage from '@node-ts-cache/redis-storage'; const storage = new RedisStorage({ - host: "localhost", - port: 6379, - password: "optional", + host: 'localhost', + port: 6379, + password: 'optional' }); const strategy = new ExpirationStrategy(storage); ``` @@ -224,30 +224,30 @@ npm install @node-ts-cache/ioredis-storage ``` ```typescript -import { ExpirationStrategy } from "@node-ts-cache/core"; -import RedisIOStorage from "@node-ts-cache/ioredis-storage"; -import Redis from "ioredis"; +import { ExpirationStrategy } from '@node-ts-cache/core'; +import RedisIOStorage from '@node-ts-cache/ioredis-storage'; +import Redis from 'ioredis'; const redisClient = new Redis({ - host: "localhost", - port: 6379, + host: 'localhost', + port: 6379 }); // Basic usage const storage = new RedisIOStorage( - () => redisClient, - { maxAge: 3600 } // TTL in seconds + () => redisClient, + { maxAge: 3600 } // TTL in seconds ); // With compression (reduces bandwidth, increases CPU usage) const compressedStorage = new RedisIOStorage(() => redisClient, { - maxAge: 3600, - compress: true, + maxAge: 3600, + compress: true }); // With error handler (non-blocking writes) -storage.onError((error) => { - console.error("Redis error:", error); +storage.onError(error => { + console.error('Redis error:', error); }); const strategy = new ExpirationStrategy(storage); @@ -255,10 +255,10 @@ const strategy = new ExpirationStrategy(storage); **Constructor Options:** -| Option | Type | Default | Description | -| ---------- | --------- | ------- | --------------------------------- | -| `maxAge` | `number` | `86400` | TTL in seconds (used by Redis) | -| `compress` | `boolean` | `false` | Enable Snappy compression | +| Option | Type | Default | Description | +| ---------- | --------- | ------- | ------------------------------ | +| `maxAge` | `number` | `86400` | TTL in seconds (used by Redis) | +| `compress` | `boolean` | `false` | Enable Snappy compression | **Characteristics:** @@ -277,15 +277,15 @@ npm install @node-ts-cache/lru-redis-storage ``` ```typescript -import { ExpirationStrategy } from "@node-ts-cache/core"; -import LRUWithRedisStorage from "@node-ts-cache/lru-redis-storage"; -import Redis from "ioredis"; +import { ExpirationStrategy } from '@node-ts-cache/core'; +import LRUWithRedisStorage from '@node-ts-cache/lru-redis-storage'; +import Redis from 'ioredis'; const redisClient = new Redis(); const storage = new LRUWithRedisStorage( - { max: 1000 }, // LRU options - () => redisClient // Redis client factory + { max: 1000 }, // LRU options + () => redisClient // Redis client factory ); const strategy = new ExpirationStrategy(storage); ``` @@ -321,31 +321,31 @@ const strategy = new ExpirationStrategy(storage); ### Example ```typescript -import { MultiCache, ExpirationStrategy } from "@node-ts-cache/core"; -import NodeCacheStorage from "@node-ts-cache/node-cache-storage"; -import RedisIOStorage from "@node-ts-cache/ioredis-storage"; +import { MultiCache, ExpirationStrategy } from '@node-ts-cache/core'; +import NodeCacheStorage from '@node-ts-cache/node-cache-storage'; +import RedisIOStorage from '@node-ts-cache/ioredis-storage'; // Local cache (fastest) -> Redis (shared) -> Database (slowest) const localCache = new ExpirationStrategy(new NodeCacheStorage()); const redisCache = new RedisIOStorage(() => redisClient, { maxAge: 3600 }); class UserService { - @MultiCache([localCache, redisCache], 0, (userId) => `user:${userId}`, { ttl: 300 }) - async getUsersByIds(userIds: string[]): Promise { - // This only runs for IDs not found in any cache - // IMPORTANT: Return results in the same order as input IDs - return await db.users.findByIds(userIds); - } + @MultiCache([localCache, redisCache], 0, userId => `user:${userId}`, { ttl: 300 }) + async getUsersByIds(userIds: string[]): Promise { + // This only runs for IDs not found in any cache + // IMPORTANT: Return results in the same order as input IDs + return await db.users.findByIds(userIds); + } } // Usage const service = new UserService(); // First call - checks local, then redis, then hits database -const users = await service.getUsersByIds(["1", "2", "3"]); +const users = await service.getUsersByIds(['1', '2', '3']); // Second call - user 1 & 2 from local cache, user 4 from database -const moreUsers = await service.getUsersByIds(["1", "2", "4"]); +const moreUsers = await service.getUsersByIds(['1', '2', '4']); ``` ### Return Value Requirements @@ -369,9 +369,9 @@ Cache errors are logged but don't break the application flow. If caching fails, // "@node-ts-cache/core: writing result to cache failed [key] [error]" // For RedisIOStorage, you can add a custom error handler: -storage.onError((error) => { - metrics.incrementCacheError(); - logger.error("Cache error", error); +storage.onError(error => { + metrics.incrementCacheError(); + logger.error('Cache error', error); }); ``` @@ -380,13 +380,13 @@ storage.onError((error) => { For key generation that requires async operations (e.g., fetching user context): ```typescript -import { Cache, ExpirationStrategy, MemoryStorage, IAsyncKeyStrategy } from "@node-ts-cache/core"; +import { Cache, ExpirationStrategy, MemoryStorage, IAsyncKeyStrategy } from '@node-ts-cache/core'; class AsyncKeyStrategy implements IAsyncKeyStrategy { - async getKey(className: string, methodName: string, args: any[]): Promise { - const userId = await getCurrentUserId(); - return `${userId}:${className}:${methodName}:${JSON.stringify(args)}`; - } + async getKey(className: string, methodName: string, args: any[]): Promise { + const userId = await getCurrentUserId(); + return `${userId}:${className}:${methodName}:${JSON.stringify(args)}`; + } } ``` @@ -394,23 +394,23 @@ class AsyncKeyStrategy implements IAsyncKeyStrategy { ```typescript // Decorators -export { Cache } from "./decorator/cache.decorator"; -export { SyncCache } from "./decorator/synccache.decorator"; -export { MultiCache } from "./decorator/multicache.decorator"; +export { Cache } from './decorator/cache.decorator'; +export { SyncCache } from './decorator/synccache.decorator'; +export { MultiCache } from './decorator/multicache.decorator'; // Strategies -export { ExpirationStrategy } from "./strategy/caching/expiration.strategy"; +export { ExpirationStrategy } from './strategy/caching/expiration.strategy'; // Built-in Storages -export { MemoryStorage } from "./storage/memory"; -export { FsJsonStorage } from "./storage/fs"; +export { MemoryStorage } from './storage/memory'; +export { FsJsonStorage } from './storage/fs'; // Interfaces export { - IAsynchronousCacheType, - ISynchronousCacheType, - IMultiIAsynchronousCacheType, - IMultiSynchronousCacheType, -} from "./types/cache.types"; -export { ISyncKeyStrategy, IAsyncKeyStrategy } from "./types/key.strategy.types"; + IAsynchronousCacheType, + ISynchronousCacheType, + IMultiIAsynchronousCacheType, + IMultiSynchronousCacheType +} from './types/cache.types'; +export { ISyncKeyStrategy, IAsyncKeyStrategy } from './types/key.strategy.types'; ``` diff --git a/ts-cache/README.md b/ts-cache/README.md index 9d4f2e8..e6a2023 100644 --- a/ts-cache/README.md +++ b/ts-cache/README.md @@ -15,15 +15,15 @@ npm install @node-ts-cache/core ## Quick Start ```typescript -import { Cache, ExpirationStrategy, MemoryStorage } from "@node-ts-cache/core"; +import { Cache, ExpirationStrategy, MemoryStorage } from '@node-ts-cache/core'; const cacheStrategy = new ExpirationStrategy(new MemoryStorage()); class UserService { - @Cache(cacheStrategy, { ttl: 60 }) - async getUser(id: string): Promise { - return await database.findUser(id); - } + @Cache(cacheStrategy, { ttl: 60 }) + async getUser(id: string): Promise { + return await database.findUser(id); + } } ``` @@ -31,15 +31,15 @@ class UserService { The core package includes `MemoryStorage` and `FsJsonStorage`. Additional storage backends are available as separate packages: -| Package | Storage Type | Sync/Async | Use Case | -|---------|-------------|------------|----------| -| `@node-ts-cache/core` | MemoryStorage | Sync | Development, simple caching | -| `@node-ts-cache/core` | FsJsonStorage | Async | Persistent local cache | -| `@node-ts-cache/node-cache-storage` | [node-cache](https://www.npmjs.com/package/node-cache) | Sync | Production single-instance with TTL | -| `@node-ts-cache/lru-storage` | [lru-cache](https://www.npmjs.com/package/lru-cache) | Sync | Memory-bounded with automatic eviction | -| `@node-ts-cache/redis-storage` | [redis](https://www.npmjs.com/package/redis) (v4.x) | Async | Shared cache | -| `@node-ts-cache/ioredis-storage` | [ioredis](https://www.npmjs.com/package/ioredis) | Async | Shared cache with compression | -| `@node-ts-cache/lru-redis-storage` | LRU + Redis | Async | Two-tier: fast local + shared remote | +| Package | Storage Type | Sync/Async | Use Case | +| ----------------------------------- | ------------------------------------------------------ | ---------- | -------------------------------------- | +| `@node-ts-cache/core` | MemoryStorage | Sync | Development, simple caching | +| `@node-ts-cache/core` | FsJsonStorage | Async | Persistent local cache | +| `@node-ts-cache/node-cache-storage` | [node-cache](https://www.npmjs.com/package/node-cache) | Sync | Production single-instance with TTL | +| `@node-ts-cache/lru-storage` | [lru-cache](https://www.npmjs.com/package/lru-cache) | Sync | Memory-bounded with automatic eviction | +| `@node-ts-cache/redis-storage` | [redis](https://www.npmjs.com/package/redis) (v4.x) | Async | Shared cache | +| `@node-ts-cache/ioredis-storage` | [ioredis](https://www.npmjs.com/package/ioredis) | Async | Shared cache with compression | +| `@node-ts-cache/lru-redis-storage` | LRU + Redis | Async | Two-tier: fast local + shared remote | ## Decorators @@ -49,10 +49,10 @@ Caches async method results. Cache key is generated from class name, method name ```typescript class ProductService { - @Cache(strategy, { ttl: 300 }) - async getProduct(id: string): Promise { - return await db.products.findById(id); - } + @Cache(strategy, { ttl: 300 }) + async getProduct(id: string): Promise { + return await db.products.findById(id); + } } ``` @@ -64,10 +64,10 @@ Caches synchronous method results without converting to Promises. Use with synch ```typescript class ConfigService { - @SyncCache(strategy, { ttl: 60 }) - getConfig(key: string): ConfigValue { - return computeConfig(key); - } + @SyncCache(strategy, { ttl: 60 }) + getConfig(key: string): ConfigValue { + return computeConfig(key); + } } ``` @@ -77,10 +77,10 @@ Multi-tier caching with batch operations for array-based lookups. ```typescript class UserService { - @MultiCache([localCache, redisCache], 0, (id) => `user:${id}`, { ttl: 300 }) - async getUsersByIds(userIds: string[]): Promise { - return await db.users.findByIds(userIds); - } + @MultiCache([localCache, redisCache], 0, id => `user:${id}`, { ttl: 300 }) + async getUsersByIds(userIds: string[]): Promise { + return await db.users.findByIds(userIds); + } } ``` @@ -92,13 +92,13 @@ Use the caching strategy directly without decorators: const cache = new ExpirationStrategy(new MemoryStorage()); // Get item -const value = await cache.getItem("key"); +const value = await cache.getItem('key'); // Set item with TTL -await cache.setItem("key", data, { ttl: 300 }); +await cache.setItem('key', data, { ttl: 300 }); // Delete item -await cache.setItem("key", undefined); +await cache.setItem('key', undefined); // Clear all await cache.clear(); @@ -106,21 +106,21 @@ await cache.clear(); ## ExpirationStrategy Options -| Option | Type | Default | Description | -|--------|------|---------|-------------| -| `ttl` | `number` | `60` | Time to live in seconds | -| `isLazy` | `boolean` | `true` | If `true`, delete on access after expiration. If `false`, delete via `setTimeout` | -| `isCachedForever` | `boolean` | `false` | If `true`, items never expire | +| Option | Type | Default | Description | +| ----------------- | --------- | ------- | --------------------------------------------------------------------------------- | +| `ttl` | `number` | `60` | Time to live in seconds | +| `isLazy` | `boolean` | `true` | If `true`, delete on access after expiration. If `false`, delete via `setTimeout` | +| `isCachedForever` | `boolean` | `false` | If `true`, items never expire | ```typescript // Cache for 5 minutes with lazy expiration -await strategy.setItem("key", value, { ttl: 300, isLazy: true }); +await strategy.setItem('key', value, { ttl: 300, isLazy: true }); // Cache forever -await strategy.setItem("key", value, { isCachedForever: true }); +await strategy.setItem('key', value, { isCachedForever: true }); // Cache with eager expiration (auto-delete after TTL) -await strategy.setItem("key", value, { ttl: 10, isLazy: false }); +await strategy.setItem('key', value, { ttl: 10, isLazy: false }); ``` ## Custom Key Strategies @@ -129,17 +129,17 @@ Override default key generation by implementing `ISyncKeyStrategy` or `IAsyncKey ```typescript class CustomKeyStrategy implements ISyncKeyStrategy { - getKey(className: string, methodName: string, args: any[]): string | undefined { - if (args[0] === "skip") return undefined; // Skip caching - return `${className}::${methodName}::${args.join("-")}`; - } + getKey(className: string, methodName: string, args: any[]): string | undefined { + if (args[0] === 'skip') return undefined; // Skip caching + return `${className}::${methodName}::${args.join('-')}`; + } } class MyService { - @Cache(strategy, { ttl: 60 }, new CustomKeyStrategy()) - async getData(id: string): Promise { - return fetchData(id); - } + @Cache(strategy, { ttl: 60 }, new CustomKeyStrategy()) + async getData(id: string): Promise { + return fetchData(id); + } } ``` @@ -152,9 +152,9 @@ Concurrent calls with the same cache key share the same pending promise: ```typescript // All three calls share one database request const [a, b, c] = await Promise.all([ - service.fetchData("123"), - service.fetchData("123"), - service.fetchData("123"), + service.fetchData('123'), + service.fetchData('123'), + service.fetchData('123') ]); ``` @@ -172,8 +172,8 @@ async findUser(id: string): Promise { ## Environment Variables -| Variable | Description | -|----------|-------------| +| Variable | Description | +| ------------------------- | --------------------------------------------------- | | `DISABLE_CACHE_DECORATOR` | Set to any value to disable all `@Cache` decorators | ## More Documentation From a7f9b2e3a000451ef40253c5b607fcf9a791e141 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 22 Jan 2026 10:39:23 +0000 Subject: [PATCH 6/6] docs: update CLAUDE.md to vitest and redis v4.x --- CLAUDE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index d176d14..01bdf78 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -61,7 +61,7 @@ Always run these checks before committing: │ └── test/ # Test files ├── storages/ # Storage adapter packages │ ├── lru/ # LRU cache storage -│ ├── redis/ # Redis storage (redis package v3.x) +│ ├── redis/ # Redis storage (redis package v4.x) │ ├── redisio/ # Redis storage (ioredis with compression) │ ├── node-cache/ # node-cache storage │ └── lru-redis/ # Two-tier LRU + Redis storage @@ -69,6 +69,6 @@ Always run these checks before committing: ## Testing Framework -- Uses Mocha with ts-node ESM loader +- Uses Vitest - Tests use Node's built-in `assert` module - Mock Redis instances using `redis-mock` and `ioredis-mock`