@node-ts-cache/lru-redis-storage 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017 Himmet Avsar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,208 @@
1
+ # @node-ts-cache/lru-redis-storage
2
+
3
+ [![npm](https://img.shields.io/npm/v/@node-ts-cache/lru-redis-storage.svg)](https://www.npmjs.org/package/@node-ts-cache/lru-redis-storage)
4
+
5
+ Two-tier cache storage adapter for [@node-ts-cache/core](https://www.npmjs.com/package/@node-ts-cache/core) combining local LRU cache with remote Redis fallback.
6
+
7
+ ## Features
8
+
9
+ - **Two-tier architecture**: Fast local LRU cache + shared Redis backend
10
+ - Local cache for hot data (sub-millisecond access)
11
+ - Redis fallback for cache misses
12
+ - Automatic population of local cache from Redis hits
13
+ - Reduced Redis round-trips
14
+ - Ideal for high-traffic, distributed applications
15
+
16
+ ## How It Works
17
+
18
+ ```
19
+ ┌─────────────────────────────────────────────────────────┐
20
+ │ Application │
21
+ │ │ │
22
+ │ ▼ │
23
+ │ ┌───────────────────────┐ │
24
+ │ │ LRUWithRedisStorage │ │
25
+ │ └───────────┬───────────┘ │
26
+ │ │ │
27
+ │ ┌─────────────┴─────────────┐ │
28
+ │ ▼ ▼ │
29
+ │ ┌─────────────────┐ ┌─────────────────┐ │
30
+ │ │ Local LRU │ miss │ Redis │ │
31
+ │ │ (in-memory) │ ──────> │ (remote) │ │
32
+ │ │ ~0.01ms │ <────── │ ~1-5ms │ │
33
+ │ └─────────────────┘ hit └─────────────────┘ │
34
+ │ (populate) │
35
+ └─────────────────────────────────────────────────────────┘
36
+ ```
37
+
38
+ 1. **Get**: Check local LRU first. On miss, check Redis. If found in Redis, populate local LRU.
39
+ 2. **Set**: Write to both local LRU and Redis.
40
+ 3. **Clear**: Clear local LRU cache.
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ npm install @node-ts-cache/core @node-ts-cache/lru-redis-storage ioredis
46
+ ```
47
+
48
+ ## Usage
49
+
50
+ ### Basic Usage
51
+
52
+ ```typescript
53
+ import { Cache, ExpirationStrategy } from '@node-ts-cache/core';
54
+ import { LRUWithRedisStorage } from '@node-ts-cache/lru-redis-storage';
55
+ import Redis from 'ioredis';
56
+
57
+ const redisClient = new Redis({
58
+ host: 'localhost',
59
+ port: 6379
60
+ });
61
+
62
+ const storage = new LRUWithRedisStorage(
63
+ { max: 1000 }, // LRU options: max 1000 items locally
64
+ () => redisClient // Redis client factory
65
+ );
66
+
67
+ const strategy = new ExpirationStrategy(storage);
68
+
69
+ class UserService {
70
+ @Cache(strategy, { ttl: 300 })
71
+ async getUser(id: string): Promise<User> {
72
+ return await db.users.findById(id);
73
+ }
74
+ }
75
+ ```
76
+
77
+ ### With TTL
78
+
79
+ ```typescript
80
+ const storage = new LRUWithRedisStorage(
81
+ {
82
+ max: 500,
83
+ ttl: 60 // Local and Redis cache TTL: 1 minute (in seconds)
84
+ },
85
+ () => redisClient
86
+ );
87
+ ```
88
+
89
+ ### Direct API Usage
90
+
91
+ ```typescript
92
+ const storage = new LRUWithRedisStorage({ max: 100 }, () => redisClient);
93
+ const strategy = new ExpirationStrategy(storage);
94
+
95
+ // Store (writes to both LRU and Redis)
96
+ await strategy.setItem('user:123', { name: 'John' }, { ttl: 60 });
97
+
98
+ // First get - might hit Redis if not in LRU
99
+ const user1 = await strategy.getItem<User>('user:123');
100
+
101
+ // Second get - hits local LRU (fast!)
102
+ const user2 = await strategy.getItem<User>('user:123');
103
+
104
+ // Clear local LRU cache
105
+ await strategy.clear();
106
+ ```
107
+
108
+ ## Constructor
109
+
110
+ ```typescript
111
+ new LRUWithRedisStorage(
112
+ options: LRUWithRedisStorageOptions,
113
+ redis: () => Redis.Redis
114
+ )
115
+ ```
116
+
117
+ | Parameter | Type | Description |
118
+ | --------- | ---------------------------- | ----------------------------------------- |
119
+ | `options` | `LRUWithRedisStorageOptions` | Options for local LRU cache and TTL |
120
+ | `redis` | `() => Redis.Redis` | Factory function returning ioredis client |
121
+
122
+ ### Options
123
+
124
+ | Option | Type | Default | Description |
125
+ | ------ | -------- | ------- | -------------------------------------------------- |
126
+ | `max` | `number` | `500` | Maximum items in local LRU cache |
127
+ | `ttl` | `number` | `86400` | Time to live in **seconds** (for both LRU & Redis) |
128
+
129
+ ## Interface
130
+
131
+ ```typescript
132
+ interface IAsynchronousCacheType {
133
+ getItem<T>(key: string): Promise<T | undefined>;
134
+ setItem(key: string, content: any, options?: any): Promise<void>;
135
+ clear(): Promise<void>;
136
+ }
137
+ ```
138
+
139
+ ## Use Cases
140
+
141
+ ### High-Traffic APIs
142
+
143
+ ```typescript
144
+ class ProductAPI {
145
+ @Cache(strategy, { ttl: 60 })
146
+ async getProduct(id: string): Promise<Product> {
147
+ // Hot products served from local LRU (~0.01ms)
148
+ // Cold products fetched from Redis (~1-5ms)
149
+ // Very cold products hit database
150
+ return await db.products.findById(id);
151
+ }
152
+ }
153
+ ```
154
+
155
+ ### Distributed Systems
156
+
157
+ Multiple application instances share the same Redis cache while maintaining their own local LRU:
158
+
159
+ ```
160
+ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
161
+ │ Instance 1 │ │ Instance 2 │ │ Instance 3 │
162
+ │ ┌────────┐ │ │ ┌────────┐ │ │ ┌────────┐ │
163
+ │ │ LRU │ │ │ │ LRU │ │ │ │ LRU │ │
164
+ │ └────┬───┘ │ │ └────┬───┘ │ │ └────┬───┘ │
165
+ └───────┼──────┘ └───────┼──────┘ └───────┼──────┘
166
+ │ │ │
167
+ └────────────────────┼────────────────────┘
168
+
169
+ ┌──────┴──────┐
170
+ │ Redis │
171
+ │ (shared) │
172
+ └─────────────┘
173
+ ```
174
+
175
+ ### Session Caching
176
+
177
+ ```typescript
178
+ class SessionService {
179
+ @Cache(strategy, { ttl: 1800 }) // 30 minutes
180
+ async getSession(token: string): Promise<Session> {
181
+ // Active sessions stay in local LRU
182
+ // Inactive sessions fall back to Redis
183
+ return await db.sessions.findByToken(token);
184
+ }
185
+ }
186
+ ```
187
+
188
+ ## Performance Considerations
189
+
190
+ - **Local LRU hit**: ~0.01ms (in-process memory access)
191
+ - **Redis hit**: ~1-5ms (network round-trip)
192
+ - **Set local `max`** based on your memory budget and access patterns
193
+ - **Shorter TTL** = fresher data but more Redis hits
194
+ - **Longer TTL** = better performance but potentially stale data
195
+
196
+ ## Dependencies
197
+
198
+ - `lru-cache` ^10.0.0
199
+ - `ioredis` ^5.3.2
200
+
201
+ ## Requirements
202
+
203
+ - Node.js >= 18.0.0
204
+ - Redis server
205
+
206
+ ## License
207
+
208
+ MIT
@@ -0,0 +1,21 @@
1
+ import { IAsynchronousCacheType } from '@node-ts-cache/core';
2
+ import * as Redis from 'ioredis';
3
+ export interface LRUWithRedisStorageOptions {
4
+ /** Maximum number of items in local LRU cache */
5
+ max?: number;
6
+ /** Time to live in seconds for both local and Redis cache */
7
+ ttl?: number;
8
+ }
9
+ export declare class LRUWithRedisStorage implements IAsynchronousCacheType {
10
+ private redis;
11
+ private myCache;
12
+ /** ttl in seconds! */
13
+ private options;
14
+ constructor(options: LRUWithRedisStorageOptions, redis: () => Redis.Redis);
15
+ getItem<T>(key: string): Promise<T | undefined>;
16
+ /** ttl in seconds! */
17
+ setItem<T = unknown>(key: string, content: T | undefined, options?: {
18
+ ttl?: number;
19
+ }): Promise<void>;
20
+ clear(): Promise<void>;
21
+ }
@@ -0,0 +1,52 @@
1
+ import { LRUCache } from 'lru-cache';
2
+ export class LRUWithRedisStorage {
3
+ constructor(options, redis) {
4
+ this.redis = redis;
5
+ this.options = {
6
+ max: 500,
7
+ ttl: 86400,
8
+ ...options
9
+ };
10
+ this.myCache = new LRUCache({
11
+ max: this.options.max,
12
+ ttl: this.options.ttl * 1000 // convert to ms
13
+ });
14
+ }
15
+ async getItem(key) {
16
+ // check local cache
17
+ let localCache = this.myCache.get(key);
18
+ if (localCache === undefined) {
19
+ // check central cache
20
+ const redisValue = await this.redis().get(key);
21
+ if (redisValue !== null) {
22
+ try {
23
+ localCache = JSON.parse(redisValue);
24
+ }
25
+ catch (err) {
26
+ console.error('lru redis cache failed parsing data', err);
27
+ localCache = undefined;
28
+ }
29
+ // if found on central cache, copy it to a local cache
30
+ if (localCache !== undefined) {
31
+ this.myCache.set(key, localCache);
32
+ }
33
+ }
34
+ }
35
+ return localCache;
36
+ }
37
+ /** ttl in seconds! */
38
+ async setItem(key, content, options) {
39
+ this.myCache.set(key, content);
40
+ const ttl = options?.ttl || this.options.ttl;
41
+ if (ttl) {
42
+ await this.redis().setex(key, ttl, JSON.stringify(content));
43
+ }
44
+ else {
45
+ await this.redis().set(key, JSON.stringify(content));
46
+ }
47
+ }
48
+ async clear() {
49
+ this.myCache.clear();
50
+ }
51
+ }
52
+ //# sourceMappingURL=LRUWithRedisStorage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LRUWithRedisStorage.js","sourceRoot":"","sources":["../src/LRUWithRedisStorage.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAUrC,MAAM,OAAO,mBAAmB;IAQ/B,YAAY,OAAmC,EAAU,KAAwB;QAAxB,UAAK,GAAL,KAAK,CAAmB;QAChF,IAAI,CAAC,OAAO,GAAG;YACd,GAAG,EAAE,GAAG;YACR,GAAG,EAAE,KAAK;YACV,GAAG,OAAO;SACV,CAAC;QACF,IAAI,CAAC,OAAO,GAAG,IAAI,QAAQ,CAAC;YAC3B,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG;YACrB,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,gBAAgB;SAC7C,CAAC,CAAC;IACJ,CAAC;IAEM,KAAK,CAAC,OAAO,CAAI,GAAW;QAClC,oBAAoB;QACpB,IAAI,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAEvC,IAAI,UAAU,KAAK,SAAS,EAAE;YAC7B,sBAAsB;YACtB,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAE/C,IAAI,UAAU,KAAK,IAAI,EAAE;gBACxB,IAAI;oBACH,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;iBACpC;gBAAC,OAAO,GAAG,EAAE;oBACb,OAAO,CAAC,KAAK,CAAC,qCAAqC,EAAE,GAAG,CAAC,CAAC;oBAC1D,UAAU,GAAG,SAAS,CAAC;iBACvB;gBACD,sDAAsD;gBACtD,IAAI,UAAU,KAAK,SAAS,EAAE;oBAC7B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;iBAClC;aACD;SACD;QAED,OAAO,UAA2B,CAAC;IACpC,CAAC;IAED,sBAAsB;IACf,KAAK,CAAC,OAAO,CACnB,GAAW,EACX,OAAsB,EACtB,OAA0B;QAE1B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAC/B,MAAM,GAAG,GAAG,OAAO,EAAE,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC;QAC7C,IAAI,GAAG,EAAE;YACR,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;SAC5D;aAAM;YACN,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;SACrD;IACF,CAAC;IAEM,KAAK,CAAC,KAAK;QACjB,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;CACD"}
@@ -0,0 +1,2 @@
1
+ import { LRUWithRedisStorage } from './LRUWithRedisStorage.js';
2
+ export default LRUWithRedisStorage;
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import { LRUWithRedisStorage } from './LRUWithRedisStorage.js';
2
+ export default LRUWithRedisStorage;
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAE/D,eAAe,mBAAmB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@node-ts-cache/lru-redis-storage",
3
+ "version": "1.0.0",
4
+ "description": "Simple and extensible caching module supporting decorators",
5
+ "keywords": [
6
+ "node",
7
+ "nodejs",
8
+ "cache",
9
+ "typescript",
10
+ "ts",
11
+ "caching",
12
+ "memcache",
13
+ "memory-cache",
14
+ "redis-cache",
15
+ "redis",
16
+ "file-cache",
17
+ "node-cache",
18
+ "ts-cache"
19
+ ],
20
+ "homepage": "https://github.com/simllll/node-ts-cache/tree/master/storages/lru-redis#readme",
21
+ "bugs": {
22
+ "url": "https://github.com/simllll/node-ts-cache/issues"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/simllll/node-ts-cache.git"
27
+ },
28
+ "license": "MIT",
29
+ "author": "Simon Tretter <s.tretter@gmail.com>",
30
+ "type": "module",
31
+ "main": "dist/index.js",
32
+ "types": "dist/index.d.ts",
33
+ "files": [
34
+ "dist"
35
+ ],
36
+ "dependencies": {
37
+ "ioredis": "^5.3.2",
38
+ "lru-cache": "^10.0.0",
39
+ "@node-ts-cache/core": "1.0.0"
40
+ },
41
+ "devDependencies": {
42
+ "ioredis-mock": "^8.9.0"
43
+ },
44
+ "engines": {
45
+ "node": ">=18.0.0"
46
+ },
47
+ "publishConfig": {
48
+ "access": "public"
49
+ },
50
+ "gitHead": "c938eba762060f940a34bc192bec03bc76ea4017",
51
+ "scripts": {
52
+ "build": "tsc -p .",
53
+ "clean": "git clean -fdx src",
54
+ "dev": "tsc -p . -w",
55
+ "test": "mocha --loader=ts-node/esm test/**/*.test.ts"
56
+ }
57
+ }