koatty_store 1.8.0 → 1.9.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/dist/README.md CHANGED
@@ -1,2 +1,373 @@
1
1
  # koatty_store
2
- Cache store (memory or reids) for koatty.
2
+
3
+ [![Version npm](https://img.shields.io/npm/v/koatty_store.svg?style=flat-square)](https://www.npmjs.com/package/koatty_store)
4
+ [![npm Downloads](https://img.shields.io/npm/dm/koatty_store.svg?style=flat-square)](https://npmjs.org/package/koatty_store)
5
+
6
+ Cache store (memory or redis) for Koatty framework.
7
+
8
+ ## Features
9
+
10
+ - 🚀 **Dual Storage**: Support both in-memory and Redis storage
11
+ - 💾 **LRU Cache**: Built-in LRU cache with configurable size
12
+ - ⏰ **TTL Support**: Field-level TTL for hash operations
13
+ - 🔒 **Concurrency Safe**: Atomic operations with lock protection
14
+ - 📊 **Rich Data Types**: String, Hash, List, Set, Sorted Set
15
+ - 🔄 **Auto Reconnect**: Redis connection with retry mechanism
16
+ - 🎯 **Type Safe**: Full TypeScript support
17
+ - 📦 **Lightweight**: Minimal dependencies
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install koatty_store
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ ### Memory Store
28
+
29
+ ```typescript
30
+ import { CacheStore } from 'koatty_store';
31
+
32
+ // Create memory store
33
+ const store = new CacheStore({
34
+ type: 'memory',
35
+ keyPrefix: 'myapp:',
36
+ maxKeys: 1000
37
+ });
38
+
39
+ // String operations
40
+ await store.set('user:1', 'John', 60); // Expires in 60 seconds
41
+ const value = await store.get('user:1');
42
+
43
+ // Hash operations
44
+ await store.hset('user:info', 'name', 'John');
45
+ await store.hset('user:info', 'age', '25');
46
+ const name = await store.hget('user:info', 'name');
47
+
48
+ // List operations
49
+ await store.rpush('queue', 'task1');
50
+ await store.rpush('queue', 'task2');
51
+ const task = await store.lpop('queue');
52
+
53
+ // Close store
54
+ await store.close();
55
+ ```
56
+
57
+ ### Redis Store
58
+
59
+ ```typescript
60
+ import { CacheStore } from 'koatty_store';
61
+
62
+ // Create redis store
63
+ const store = new CacheStore({
64
+ type: 'redis',
65
+ host: '127.0.0.1',
66
+ port: 6379,
67
+ password: 'your-password',
68
+ db: 0,
69
+ keyPrefix: 'myapp:',
70
+ poolSize: 10
71
+ });
72
+
73
+ // Use same API as memory store
74
+ await store.set('key', 'value');
75
+ await store.close();
76
+ ```
77
+
78
+ ## Configuration
79
+
80
+ ### Memory Store Options
81
+
82
+ ```typescript
83
+ interface MemoryStoreOpt {
84
+ type: 'memory';
85
+ keyPrefix?: string; // Key prefix, default: 'Koatty'
86
+ db?: number; // Database index, default: 0
87
+ timeout?: number; // Default TTL in seconds, default: 600
88
+ maxKeys?: number; // LRU max keys, default: 1000
89
+ maxMemory?: number; // Max memory in bytes
90
+ evictionPolicy?: 'lru' | 'lfu' | 'random'; // Eviction policy, default: 'lru'
91
+ ttlCheckInterval?: number; // TTL check interval in ms, default: 60000
92
+ }
93
+ ```
94
+
95
+ ### Redis Store Options
96
+
97
+ ```typescript
98
+ interface RedisStoreOpt {
99
+ type: 'redis';
100
+ host: string; // Redis host
101
+ port: number; // Redis port
102
+ password?: string; // Redis password
103
+ db?: number; // Database index, default: 0
104
+ keyPrefix?: string; // Key prefix, default: 'Koatty'
105
+ timeout?: number; // Default TTL in seconds, default: 600
106
+ poolSize?: number; // Connection pool size, default: 10
107
+ connectTimeout?: number; // Connection timeout in ms, default: 500
108
+ }
109
+ ```
110
+
111
+ ## API Reference
112
+
113
+ ### String Operations
114
+
115
+ ```typescript
116
+ // Set a string value with optional TTL
117
+ await store.set(key: string, value: string | number, timeout?: number): Promise<string>
118
+
119
+ // Get a string value
120
+ await store.get(key: string): Promise<string | null>
121
+
122
+ // Delete a key
123
+ await store.del(key: string): Promise<number>
124
+
125
+ // Check if key exists
126
+ await store.exists(key: string): Promise<number>
127
+
128
+ // Get TTL of a key
129
+ await store.ttl(key: string): Promise<number>
130
+
131
+ // Set TTL for a key
132
+ await store.expire(key: string, timeout: number): Promise<number>
133
+
134
+ // Increment
135
+ await store.incr(key: string): Promise<number>
136
+ await store.incrby(key: string, increment: number): Promise<number>
137
+
138
+ // Decrement
139
+ await store.decr(key: string): Promise<number>
140
+ await store.decrby(key: string, decrement: number): Promise<number>
141
+ ```
142
+
143
+ ### Hash Operations
144
+
145
+ ```typescript
146
+ // Set hash field with optional TTL (field-level)
147
+ await store.hset(name: string, key: string, value: string | number, timeout?: number): Promise<number>
148
+
149
+ // Get hash field
150
+ await store.hget(name: string, key: string): Promise<string | null>
151
+
152
+ // Delete hash field
153
+ await store.hdel(name: string, key: string): Promise<number>
154
+
155
+ // Check if hash field exists
156
+ await store.hexists(name: string, key: string): Promise<number>
157
+
158
+ // Get all fields and values
159
+ await store.hgetall(name: string): Promise<any>
160
+
161
+ // Get all field names
162
+ await store.hkeys(name: string): Promise<string[]>
163
+
164
+ // Get all values
165
+ await store.hvals(name: string): Promise<any[]>
166
+
167
+ // Get hash length
168
+ await store.hlen(name: string): Promise<number>
169
+
170
+ // Increment hash field
171
+ await store.hincrby(name: string, key: string, increment: number): Promise<number>
172
+ ```
173
+
174
+ ### List Operations
175
+
176
+ ```typescript
177
+ // Push to right
178
+ await store.rpush(name: string, value: string | number): Promise<number>
179
+
180
+ // Push to left
181
+ await store.lpush(name: string, value: string | number): Promise<number>
182
+
183
+ // Pop from left
184
+ await store.lpop(name: string): Promise<string | null>
185
+
186
+ // Pop from right
187
+ await store.rpop(name: string): Promise<string | null>
188
+
189
+ // Get list length
190
+ await store.llen(name: string): Promise<number>
191
+
192
+ // Get range
193
+ await store.lrange(name: string, start: number, stop: number): Promise<any[]>
194
+ ```
195
+
196
+ ### Set Operations
197
+
198
+ ```typescript
199
+ // Add member with optional TTL
200
+ await store.sadd(name: string, value: string | number, timeout?: number): Promise<number>
201
+
202
+ // Remove member
203
+ await store.srem(name: string, key: string): Promise<number>
204
+
205
+ // Get set size
206
+ await store.scard(name: string): Promise<number>
207
+
208
+ // Check if member exists
209
+ await store.sismember(name: string, key: string): Promise<number>
210
+
211
+ // Get all members
212
+ await store.smembers(name: string): Promise<any[]>
213
+
214
+ // Pop random member
215
+ await store.spop(name: string): Promise<any>
216
+
217
+ // Move member between sets
218
+ await store.smove(source: string, destination: string, member: string): Promise<number>
219
+ ```
220
+
221
+ ## Advanced Features
222
+
223
+ ### Field-Level TTL for Hash
224
+
225
+ ```typescript
226
+ // Set hash field with TTL (expires in 60 seconds)
227
+ await store.hset('user:session', 'token', 'abc123', 60);
228
+
229
+ // Field will be automatically deleted after TTL expires
230
+ setTimeout(async () => {
231
+ const token = await store.hget('user:session', 'token');
232
+ console.log(token); // null
233
+ }, 61000);
234
+ ```
235
+
236
+ ### Singleton Pattern
237
+
238
+ ```typescript
239
+ // Get singleton instance
240
+ const store1 = CacheStore.getInstance({
241
+ type: 'memory',
242
+ keyPrefix: 'app:'
243
+ }, 'cache1');
244
+
245
+ const store2 = CacheStore.getInstance({
246
+ type: 'memory',
247
+ keyPrefix: 'app:'
248
+ }, 'cache1');
249
+
250
+ console.log(store1 === store2); // true
251
+
252
+ // Clear specific instance
253
+ await CacheStore.clearInstance('cache1');
254
+
255
+ // Clear all instances
256
+ await CacheStore.clearAllInstances();
257
+ ```
258
+
259
+ ### Concurrency Safe Operations
260
+
261
+ All atomic operations (incr, decr, incrby, decrby, hincrby) are protected with locks to ensure data consistency in concurrent scenarios.
262
+
263
+ ```typescript
264
+ // Safe concurrent increments
265
+ await Promise.all([
266
+ store.incr('counter'),
267
+ store.incr('counter'),
268
+ store.incr('counter')
269
+ ]);
270
+
271
+ const count = await store.get('counter');
272
+ console.log(count); // "3" - guaranteed consistency
273
+ ```
274
+
275
+ ## Best Practices
276
+
277
+ ### 1. Use Key Prefix
278
+
279
+ ```typescript
280
+ const store = new CacheStore({
281
+ type: 'memory',
282
+ keyPrefix: 'myapp:' // Prefix all keys
283
+ });
284
+ ```
285
+
286
+ ### 2. Set Appropriate TTL
287
+
288
+ ```typescript
289
+ // Short-lived data
290
+ await store.set('session:token', token, 3600); // 1 hour
291
+
292
+ // Long-lived data
293
+ await store.set('user:profile', profile, 86400); // 24 hours
294
+ ```
295
+
296
+ ### 3. Handle Errors Gracefully
297
+
298
+ ```typescript
299
+ try {
300
+ await store.set('key', 'value');
301
+ } catch (error) {
302
+ console.error('Cache operation failed:', error.message);
303
+ // Fallback to database or other source
304
+ }
305
+ ```
306
+
307
+ ### 4. Close Store on Exit
308
+
309
+ ```typescript
310
+ process.on('SIGINT', async () => {
311
+ await store.close();
312
+ process.exit(0);
313
+ });
314
+ ```
315
+
316
+ ### 5. Use Connection Pool for Redis
317
+
318
+ ```typescript
319
+ const store = new CacheStore({
320
+ type: 'redis',
321
+ host: '127.0.0.1',
322
+ port: 6379,
323
+ poolSize: 20 // Adjust based on load
324
+ });
325
+ ```
326
+
327
+ ## Testing
328
+
329
+ ```bash
330
+ # Run tests
331
+ npm test
332
+
333
+ # Run tests with coverage
334
+ npm test -- --coverage
335
+
336
+ # Run specific test
337
+ npm test -- test/memory.test.ts
338
+ ```
339
+
340
+ ## Performance
341
+
342
+ ### Memory Store
343
+ - **Operations**: ~1,000,000 ops/sec
344
+ - **LRU Eviction**: O(1)
345
+ - **TTL Check**: Background task, configurable interval
346
+
347
+ ### Redis Store
348
+ - **Operations**: Depends on Redis server
349
+ - **Connection Pool**: Configurable size
350
+ - **Auto Reconnect**: Exponential backoff strategy
351
+
352
+ ## Changelog
353
+
354
+ See [CHANGELOG.md](CHANGELOG.md) for release history.
355
+
356
+ ## License
357
+
358
+ BSD-3-Clause
359
+
360
+ ## Contributing
361
+
362
+ Contributions are welcome! Please read the [contributing guidelines](https://github.com/koatty/koatty_store/blob/master/CONTRIBUTING.md) first.
363
+
364
+ ## Support
365
+
366
+ - [GitHub Issues](https://github.com/koatty/koatty_store/issues)
367
+ - [Documentation](https://koatty.com)
368
+
369
+ ## Related Projects
370
+
371
+ - [koatty](https://github.com/koatty/koatty) - The Koatty framework
372
+ - [koatty_lib](https://github.com/koatty/koatty_lib) - Koatty utilities
373
+ - [koatty_logger](https://github.com/koatty/koatty_logger) - Koatty logger
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * @Author: richen
3
- * @Date: 2025-06-09 12:32:53
3
+ * @Date: 2025-11-02 08:37:37
4
4
  * @License: BSD (3-Clause)
5
5
  * @Copyright (c) - <richenlin(at)gmail.com>
6
6
  * @HomePage: https://koatty.org/
@@ -54,7 +54,7 @@ export declare class CacheStore implements CacheStoreInterface {
54
54
  * @static
55
55
  */
56
56
  static clearAllInstances(): Promise<void>;
57
- getConnection(): MemoryCache | Promise<Cluster | default_2>;
57
+ getConnection(): MemoryCache | Promise<default_2 | Cluster>;
58
58
  close(): Promise<void>;
59
59
  release(conn: any): Promise<void>;
60
60
  /**
@@ -292,38 +292,38 @@ declare interface CacheStoreInterface {
292
292
  getConnection(): any;
293
293
  close(): Promise<void>;
294
294
  release(conn: any): Promise<void>;
295
- get?(name: string): Promise<string | null>;
296
- set?(name: string, value: string | number, timeout?: number): Promise<string>;
297
- del?(name: string): Promise<number>;
298
- exists?(name: string): Promise<number>;
299
- ttl?(name: string): Promise<number>;
300
- expire?(name: string, timeout: number): Promise<number>;
301
- incr?(name: string): Promise<number>;
302
- decr?(name: string): Promise<number>;
303
- incrby?(name: string, increment: number): Promise<number>;
304
- decrby?(name: string, decrement: number): Promise<number>;
305
- hset?(name: string, key: string, value: string | number, timeout?: number): Promise<number>;
306
- hget?(name: string, key: string): Promise<string | null>;
307
- hdel?(name: string, key: string): Promise<number>;
308
- hexists?(name: string, key: string): Promise<number>;
309
- hgetall?(name: string): Promise<any>;
310
- hkeys?(name: string): Promise<string[]>;
311
- hvals?(name: string): Promise<any[]>;
312
- hlen?(name: string): Promise<number>;
313
- hincrby?(name: string, key: string, increment: number): Promise<number>;
314
- lpush?(name: string, value: string | number): Promise<number>;
315
- rpush?(name: string, value: string | number): Promise<number>;
316
- lpop?(name: string): Promise<string | null>;
317
- rpop?(name: string): Promise<string | null>;
318
- llen?(name: string): Promise<number>;
319
- lrange?(name: string, start: number, stop: number): Promise<any[]>;
320
- sadd?(name: string, value: string | number, timeout?: number): Promise<number>;
321
- srem?(name: string, key: string): Promise<number>;
322
- scard?(name: string): Promise<number>;
323
- sismember?(name: string, key: string): Promise<number>;
324
- smembers?(name: string): Promise<any[]>;
325
- spop?(name: string): Promise<any>;
326
- smove?(source: string, destination: string, member: string): Promise<number>;
295
+ get(name: string): Promise<string | null>;
296
+ set(name: string, value: string | number, timeout?: number): Promise<string>;
297
+ del(name: string): Promise<number>;
298
+ exists(name: string): Promise<number>;
299
+ ttl(name: string): Promise<number>;
300
+ expire(name: string, timeout: number): Promise<number>;
301
+ incr(name: string): Promise<number>;
302
+ decr(name: string): Promise<number>;
303
+ incrby(name: string, increment: number): Promise<number>;
304
+ decrby(name: string, decrement: number): Promise<number>;
305
+ hset(name: string, key: string, value: string | number, timeout?: number): Promise<number>;
306
+ hget(name: string, key: string): Promise<string | null>;
307
+ hdel(name: string, key: string): Promise<number>;
308
+ hexists(name: string, key: string): Promise<number>;
309
+ hgetall(name: string): Promise<any>;
310
+ hkeys(name: string): Promise<string[]>;
311
+ hvals(name: string): Promise<any[]>;
312
+ hlen(name: string): Promise<number>;
313
+ hincrby(name: string, key: string, increment: number): Promise<number>;
314
+ lpush(name: string, value: string | number): Promise<number>;
315
+ rpush(name: string, value: string | number): Promise<number>;
316
+ lpop(name: string): Promise<string | null>;
317
+ rpop(name: string): Promise<string | null>;
318
+ llen(name: string): Promise<number>;
319
+ lrange(name: string, start: number, stop: number): Promise<any[]>;
320
+ sadd(name: string, value: string | number, timeout?: number): Promise<number>;
321
+ srem(name: string, key: string): Promise<number>;
322
+ scard(name: string): Promise<number>;
323
+ sismember(name: string, key: string): Promise<number>;
324
+ smembers(name: string): Promise<any[]>;
325
+ spop(name: string): Promise<any>;
326
+ smove(source: string, destination: string, member: string): Promise<number>;
327
327
  }
328
328
 
329
329
  declare type CallbackFunction<T = any> = (err: Error | null, result?: T) => void;
@@ -352,6 +352,10 @@ declare class MemoryCache extends EventEmitter {
352
352
  /**
353
353
  * 停止TTL检查
354
354
  */
355
+ /**
356
+ * 清理所有资源
357
+ * @private
358
+ */
355
359
  /**
356
360
  *
357
361
  *
@@ -470,7 +474,7 @@ declare class MemoryCache extends EventEmitter {
470
474
  * @returns {*}
471
475
  * @memberof MemoryCache
472
476
  */
473
- incr(key: string, callback?: CallbackFunction): any;
477
+ incr(key: string, callback?: CallbackFunction): Promise<any>;
474
478
  /**
475
479
  *
476
480
  *
@@ -480,7 +484,7 @@ declare class MemoryCache extends EventEmitter {
480
484
  * @returns {*}
481
485
  * @memberof MemoryCache
482
486
  */
483
- incrby(key: string, amount: number, callback?: CallbackFunction): any;
487
+ incrby(key: string, amount: number, callback?: CallbackFunction): Promise<any>;
484
488
  /**
485
489
  *
486
490
  *
@@ -489,7 +493,7 @@ declare class MemoryCache extends EventEmitter {
489
493
  * @returns {*}
490
494
  * @memberof MemoryCache
491
495
  */
492
- decr(key: string, callback?: CallbackFunction): any;
496
+ decr(key: string, callback?: CallbackFunction): Promise<any>;
493
497
  /**
494
498
  *
495
499
  *
@@ -499,8 +503,8 @@ declare class MemoryCache extends EventEmitter {
499
503
  * @returns {*}
500
504
  * @memberof MemoryCache
501
505
  */
502
- decrby(key: string, amount: number, callback?: CallbackFunction): any;
503
- hset(key: string, field: string, value: string | number, callback?: CallbackFunction): any;
506
+ decrby(key: string, amount: number, callback?: CallbackFunction): Promise<any>;
507
+ hset(key: string, field: string, value: string | number, timeout?: number, callback?: CallbackFunction): any;
504
508
  /**
505
509
  *
506
510
  *
@@ -549,7 +553,7 @@ declare class MemoryCache extends EventEmitter {
549
553
  * @returns {*}
550
554
  * @memberof MemoryCache
551
555
  */
552
- hincrby(key: string, field: string, value: any, callback?: CallbackFunction): any;
556
+ hincrby(key: string, field: string, value: any, callback?: CallbackFunction): Promise<any>;
553
557
  /**
554
558
  *
555
559
  *
@@ -989,9 +993,8 @@ declare interface MemoryCacheOptions {
989
993
  maxAge?: number;
990
994
  }
991
995
 
992
- declare class MemoryStore implements CacheStoreInterface {
993
- client: any;
994
- pool: any;
996
+ declare class MemoryStore {
997
+ client: MemoryCache;
995
998
  options: MemoryStoreOpt;
996
999
  /**
997
1000
  * Creates an instance of MemoryStore.
@@ -1061,7 +1064,7 @@ declare interface MemoryStoreOpt {
1061
1064
  * @export
1062
1065
  * @class RedisStore
1063
1066
  */
1064
- declare class RedisStore implements CacheStoreInterface {
1067
+ declare class RedisStore {
1065
1068
  options: RedisStoreOpt;
1066
1069
  pool: genericPool.Pool<Redis | Cluster>;
1067
1070
  client: Redis | Cluster;
@@ -1089,7 +1092,7 @@ declare class RedisStore implements CacheStoreInterface {
1089
1092
  * @returns {*}
1090
1093
  * @memberof RedisStore
1091
1094
  */
1092
- getConnection(): Promise<Cluster | Redis>;
1095
+ getConnection(): Promise<Redis | Cluster>;
1093
1096
  /**
1094
1097
  * close connection with proper cleanup
1095
1098
  *