@devbro/neko-cache 0.1.3 → 0.1.5

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/index.js CHANGED
@@ -43,24 +43,67 @@ module.exports = __toCommonJS(index_exports);
43
43
  // src/cache.mts
44
44
  var import_crypto = require("crypto");
45
45
  var Cache = class {
46
+ /**
47
+ * Creates a new Cache instance.
48
+ * @param provider - The cache provider implementation to use
49
+ */
46
50
  constructor(provider) {
47
51
  this.provider = provider;
48
52
  }
49
53
  static {
50
54
  __name(this, "Cache");
51
55
  }
56
+ /**
57
+ * Retrieves a value from the cache.
58
+ * @template T - The expected type of the cached value
59
+ * @param key - The cache key (can be string, number, object, or array)
60
+ * @returns The cached value or undefined if not found or expired
61
+ */
52
62
  async get(key) {
53
63
  return this.provider.get(this.generateKey(key));
54
64
  }
65
+ /**
66
+ * Stores a value in the cache.
67
+ * @param key - The cache key (can be string, number, object, or array)
68
+ * @param value - The value to cache
69
+ * @param ttl - Time to live in seconds (optional)
70
+ */
55
71
  async put(key, value, ttl) {
56
72
  return this.provider.put(this.generateKey(key), value, ttl);
57
73
  }
74
+ /**
75
+ * Deletes a value from the cache.
76
+ * @param key - The cache key to delete
77
+ */
58
78
  async delete(key) {
59
79
  return this.provider.delete(this.generateKey(key));
60
80
  }
81
+ /**
82
+ * Checks if a key exists in the cache.
83
+ * @param key - The cache key to check
84
+ * @returns True if the key exists and has not expired, false otherwise
85
+ */
61
86
  async has(key) {
62
87
  return this.provider.has(this.generateKey(key));
63
88
  }
89
+ /**
90
+ * Increments a numeric value in the cache atomically.
91
+ * @param key - The cache key to increment
92
+ * @param amount - The amount to increment by (default: 1)
93
+ * @returns The new value after incrementing
94
+ */
95
+ async increment(key, amount = 1) {
96
+ return this.provider.increment(this.generateKey(key), amount);
97
+ }
98
+ /**
99
+ * Gets a value from cache or executes callback and caches the result.
100
+ * This is useful for caching expensive operations.
101
+ * @template T - The expected type of the value
102
+ * @param key - The cache key
103
+ * @param callback - Function to execute if cache miss occurs
104
+ * @param options - Cache options including TTL (default: 3600 seconds)
105
+ * @returns The cached value or the result of the callback
106
+ */
64
107
  async remember(key, callback, options = {}) {
65
108
  options.ttl = options.ttl ?? 3600;
66
109
  const cached = await this.get(key);
@@ -72,11 +115,16 @@ var Cache = class {
72
115
  return result;
73
116
  }
74
117
  /**
75
- * Generates a cache key by serializing and concatenating the provided parts.
76
- * @param parts
77
- * @returns
118
+ * Generates a cache key by serializing and hashing complex keys.
119
+ * Simple string keys under 250 characters are returned as-is.
120
+ * Complex keys (objects, arrays, long strings) are MD5 hashed.
121
+ * @param key - The key to generate a cache key from
122
+ * @returns A string suitable for use as a cache key
78
123
  */
79
124
  generateKey(key) {
125
+ if (typeof key === "string" && key.length <= 250) {
126
+ return key;
127
+ }
80
128
  return (0, import_crypto.createHash)("md5").update(JSON.stringify(key)).digest("hex");
81
129
  }
82
130
  };
@@ -85,6 +133,10 @@ var Cache = class {
85
133
  var import_redis = require("redis");
86
134
  var RedisCacheProvider = class {
87
135
  // default TTL in seconds
136
+ /**
137
+ * Creates a new RedisCacheProvider instance.
138
+ * @param config - Redis client configuration options
139
+ */
88
140
  constructor(config) {
89
141
  this.config = config;
90
142
  this.client = this.createRedisClient();
@@ -95,15 +147,27 @@ var RedisCacheProvider = class {
95
147
  }
96
148
  client;
97
149
  defaultTTL = 3600;
150
+ /**
151
+ * Creates a Redis client with the provided configuration.
152
+ * @returns A Redis client instance
153
+ */
98
154
  createRedisClient() {
99
155
  let rc = (0, import_redis.createClient)(this.config);
100
156
  return rc;
101
157
  }
158
+ /**
159
+ * Ensures the Redis client is connected before performing operations.
160
+ */
102
161
  async ensureConnection() {
103
162
  if (!this.client.isOpen) {
104
163
  await this.client.connect();
105
164
  }
106
165
  }
166
+ /**
167
+ * Retrieves a value from the cache.
168
+ * @param key - The cache key
169
+ * @returns The cached value or undefined if not found
170
+ */
107
171
  async get(key) {
108
172
  await this.ensureConnection();
109
173
  let rc = this.client.get(key);
@@ -114,6 +178,12 @@ var RedisCacheProvider = class {
114
178
  return JSON.parse(value);
115
179
  });
116
180
  }
181
+ /**
182
+ * Stores a value in the cache.
183
+ * @param key - The cache key
184
+ * @param value - The value to cache
185
+ * @param ttl - Time to live in seconds (optional)
186
+ */
117
187
  async put(key, value, ttl) {
118
188
  await this.ensureConnection();
119
189
  const serializedValue = JSON.stringify(value);
@@ -124,15 +194,34 @@ var RedisCacheProvider = class {
124
194
  await this.client.set(key, serializedValue);
125
195
  }
126
196
  }
197
+ /**
198
+ * Deletes a value from the cache.
199
+ * @param key - The cache key to delete
200
+ */
127
201
  async delete(key) {
128
202
  await this.ensureConnection();
129
203
  await this.client.del(key);
130
204
  }
205
+ /**
206
+ * Checks if a key exists in the cache.
207
+ * @param key - The cache key to check
208
+ * @returns True if the key exists, false otherwise
209
+ */
131
210
  async has(key) {
132
211
  await this.ensureConnection();
133
212
  const result = await this.client.exists(key);
134
213
  return result === 1;
135
214
  }
215
+ /**
216
+ * Increments a numeric value in the cache atomically using Redis INCRBY.
217
+ * @param key - The cache key to increment
218
+ * @param amount - The amount to increment by (default: 1)
219
+ * @returns The new value after incrementing
220
+ */
221
+ async increment(key, amount = 1) {
222
+ await this.ensureConnection();
223
+ return await this.client.incrBy(key, amount);
224
+ }
136
225
  };
137
226
 
138
227
  // src/providers/FileCacheProvider.mts
@@ -148,11 +237,18 @@ var FileCacheProvider = class {
148
237
  cleanupInterval: 300 * 1e3
149
238
  };
150
239
  cleanupTimer;
240
+ /**
241
+ * Creates a new FileCacheProvider instance.
242
+ * @param config - Configuration options for the cache
243
+ */
151
244
  constructor(config = {}) {
152
245
  this.config = { ...this.config, ...config };
153
246
  this.ensureCacheDirectory();
154
247
  this.startCleanupTimer();
155
248
  }
249
+ /**
250
+ * Ensures the cache directory exists, creating it if necessary.
251
+ */
156
252
  async ensureCacheDirectory() {
157
253
  try {
158
254
  await fs.access(this.config.cacheDirectory);
@@ -160,6 +256,9 @@ var FileCacheProvider = class {
160
256
  await fs.mkdir(this.config.cacheDirectory, { recursive: true });
161
257
  }
162
258
  }
259
+ /**
260
+ * Starts the automatic cleanup timer for expired entries.
261
+ */
163
262
  startCleanupTimer() {
164
263
  if (this.config.cleanupInterval > 0) {
165
264
  this.cleanupTimer = setInterval(() => {
@@ -167,16 +266,27 @@ var FileCacheProvider = class {
167
266
  }, this.config.cleanupInterval);
168
267
  }
169
268
  }
269
+ /**
270
+ * Stops the automatic cleanup timer.
271
+ */
170
272
  stopCleanupTimer() {
171
273
  if (this.cleanupTimer) {
172
274
  clearInterval(this.cleanupTimer);
173
275
  this.cleanupTimer = void 0;
174
276
  }
175
277
  }
278
+ /**
279
+ * Generates a safe file path for the given cache key.
280
+ * @param key - The cache key
281
+ * @returns The full file path for the cache entry
282
+ */
176
283
  getFilePath(key) {
177
284
  const safeKey = key.replace(/[^a-z0-9]/gi, "_");
178
285
  return path.join(this.config.cacheDirectory, `${safeKey}.json`);
179
286
  }
287
+ /**
288
+ * Removes all expired cache entries from the cache directory.
289
+ */
180
290
  async cleanupExpiredEntries() {
181
291
  try {
182
292
  const files = await fs.readdir(this.config.cacheDirectory);
@@ -199,6 +309,11 @@ var FileCacheProvider = class {
199
309
  } catch (error) {
200
310
  }
201
311
  }
312
+ /**
313
+ * Retrieves a value from the cache.
314
+ * @param key - The cache key
315
+ * @returns The cached value or undefined if not found or expired
316
+ */
202
317
  async get(key) {
203
318
  const filePath = this.getFilePath(key);
204
319
  try {
@@ -214,6 +329,12 @@ var FileCacheProvider = class {
214
329
  return void 0;
215
330
  }
216
331
  }
332
+ /**
333
+ * Stores a value in the cache.
334
+ * @param key - The cache key
335
+ * @param value - The value to cache
336
+ * @param ttl - Time to live in seconds (optional)
337
+ */
217
338
  async put(key, value, ttl) {
218
339
  const filePath = this.getFilePath(key);
219
340
  const now = Date.now();
@@ -226,6 +347,10 @@ var FileCacheProvider = class {
226
347
  this.ensureCacheDirectory();
227
348
  await fs.writeFile(filePath, JSON.stringify(item), "utf-8");
228
349
  }
350
+ /**
351
+ * Deletes a value from the cache.
352
+ * @param key - The cache key to delete
353
+ */
229
354
  async delete(key) {
230
355
  const filePath = this.getFilePath(key);
231
356
  try {
@@ -233,10 +358,71 @@ var FileCacheProvider = class {
233
358
  } catch {
234
359
  }
235
360
  }
361
+ /**
362
+ * Checks if a key exists in the cache and has not expired.
363
+ * @param key - The cache key to check
364
+ * @returns True if the key exists and is not expired, false otherwise
365
+ */
236
366
  async has(key) {
237
367
  const value = await this.get(key);
238
368
  return value !== void 0;
239
369
  }
370
+ /**
371
+ * Increments a numeric value in the cache atomically using file-based locking.
372
+ * If the key doesn't exist or is expired, it starts from 0.
373
+ * @param key - The cache key to increment
374
+ * @param amount - The amount to increment by (default: 1)
375
+ * @returns The new value after incrementing
376
+ * @throws Error if lock cannot be acquired after maximum retries
377
+ */
378
+ async increment(key, amount = 1) {
379
+ const filePath = this.getFilePath(key);
380
+ const lockPath = `${filePath}.lock`;
381
+ let lockAcquired = false;
382
+ let retries = 0;
383
+ const maxRetries = 50;
384
+ while (!lockAcquired && retries < maxRetries) {
385
+ try {
386
+ await fs.writeFile(lockPath, "", { flag: "wx" });
387
+ lockAcquired = true;
388
+ } catch {
389
+ await new Promise((resolve) => setTimeout(resolve, 10));
390
+ retries++;
391
+ }
392
+ }
393
+ if (!lockAcquired) {
394
+ throw new Error("Failed to acquire lock for increment operation");
395
+ }
396
+ try {
397
+ let currentValue = 0;
398
+ let item;
399
+ try {
400
+ const content = await fs.readFile(filePath, "utf-8");
401
+ const parsedItem = JSON.parse(content);
402
+ if (parsedItem.expiresAt && parsedItem.expiresAt < Date.now()) {
403
+ item = void 0;
404
+ } else {
405
+ item = parsedItem;
406
+ currentValue = typeof parsedItem.value === "number" ? parsedItem.value : 0;
407
+ }
408
+ } catch {
409
+ }
410
+ const newValue = currentValue + amount;
411
+ const now = Date.now();
412
+ const newItem = {
413
+ value: newValue,
414
+ createdAt: item?.createdAt ?? now,
415
+ expiresAt: item?.expiresAt
416
+ };
417
+ await fs.writeFile(filePath, JSON.stringify(newItem), "utf-8");
418
+ return newValue;
419
+ } finally {
420
+ try {
421
+ await fs.unlink(lockPath);
422
+ } catch {
423
+ }
424
+ }
425
+ }
240
426
  };
241
427
 
242
428
  // src/providers/MemoryCacheProvider.mts
@@ -252,10 +438,17 @@ var MemoryCacheProvider = class {
252
438
  // 10 minutes
253
439
  };
254
440
  cleanupTimer;
441
+ /**
442
+ * Creates a new MemoryCacheProvider instance.
443
+ * @param config - Configuration options for the cache
444
+ */
255
445
  constructor(config = {}) {
256
446
  this.config = { ...this.config, ...config };
257
447
  this.startCleanupTimer();
258
448
  }
449
+ /**
450
+ * Starts the automatic cleanup timer for expired entries.
451
+ */
259
452
  startCleanupTimer() {
260
453
  if (this.config.cleanupInterval > 0) {
261
454
  this.cleanupTimer = setInterval(() => {
@@ -263,12 +456,18 @@ var MemoryCacheProvider = class {
263
456
  }, this.config.cleanupInterval * 1e3);
264
457
  }
265
458
  }
459
+ /**
460
+ * Stops the automatic cleanup timer.
461
+ */
266
462
  stopCleanupTimer() {
267
463
  if (this.cleanupTimer) {
268
464
  clearInterval(this.cleanupTimer);
269
465
  this.cleanupTimer = void 0;
270
466
  }
271
467
  }
468
+ /**
469
+ * Removes all expired entries from the cache.
470
+ */
272
471
  cleanupExpiredEntries() {
273
472
  const now = Date.now();
274
473
  for (const [key, item] of this.cache.entries()) {
@@ -277,6 +476,9 @@ var MemoryCacheProvider = class {
277
476
  }
278
477
  }
279
478
  }
479
+ /**
480
+ * Evicts the least recently used item if cache size exceeds maximum.
481
+ */
280
482
  evictLRU() {
281
483
  if (this.cache.size <= this.config.maxSize) {
282
484
  return;
@@ -293,6 +495,11 @@ var MemoryCacheProvider = class {
293
495
  this.cache.delete(oldestKey);
294
496
  }
295
497
  }
498
+ /**
499
+ * Retrieves a value from the cache.
500
+ * @param key - The cache key
501
+ * @returns The cached value or undefined if not found or expired
502
+ */
296
503
  async get(key) {
297
504
  const item = this.cache.get(key);
298
505
  if (!item) {
@@ -305,6 +512,12 @@ var MemoryCacheProvider = class {
305
512
  item.lastAccessed = Date.now();
306
513
  return item.value;
307
514
  }
515
+ /**
516
+ * Stores a value in the cache.
517
+ * @param key - The cache key
518
+ * @param value - The value to cache
519
+ * @param ttl - Time to live in seconds (optional)
520
+ */
308
521
  async put(key, value, ttl) {
309
522
  const now = Date.now();
310
523
  const effectiveTTL = ttl ?? this.config.defaultTTL;
@@ -317,9 +530,18 @@ var MemoryCacheProvider = class {
317
530
  this.cache.set(key, item);
318
531
  this.evictLRU();
319
532
  }
533
+ /**
534
+ * Deletes a value from the cache.
535
+ * @param key - The cache key to delete
536
+ */
320
537
  async delete(key) {
321
538
  this.cache.delete(key);
322
539
  }
540
+ /**
541
+ * Checks if a key exists in the cache and has not expired.
542
+ * @param key - The cache key to check
543
+ * @returns True if the key exists and is not expired, false otherwise
544
+ */
323
545
  async has(key) {
324
546
  const item = this.cache.get(key);
325
547
  if (!item) {
@@ -331,12 +553,44 @@ var MemoryCacheProvider = class {
331
553
  }
332
554
  return true;
333
555
  }
556
+ /**
557
+ * Increments a numeric value in the cache atomically.
558
+ * If the key doesn't exist or is expired, it starts from 0.
559
+ * @param key - The cache key to increment
560
+ * @param amount - The amount to increment by (default: 1)
561
+ * @returns The new value after incrementing
562
+ */
563
+ async increment(key, amount = 1) {
564
+ const item = this.cache.get(key);
565
+ const now = Date.now();
566
+ let currentValue = 0;
567
+ if (item) {
568
+ if (item.expiresAt && item.expiresAt < now) {
569
+ this.cache.delete(key);
570
+ } else {
571
+ currentValue = typeof item.value === "number" ? item.value : 0;
572
+ }
573
+ }
574
+ const newValue = currentValue + amount;
575
+ const newItem = {
576
+ value: newValue,
577
+ createdAt: item?.createdAt ?? now,
578
+ lastAccessed: now,
579
+ expiresAt: item?.expiresAt
580
+ };
581
+ this.cache.set(key, newItem);
582
+ return newValue;
583
+ }
334
584
  };
335
585
 
336
586
  // src/providers/MemcacheCacheProvider.mts
337
587
  var import_memcached = __toESM(require("memcached"), 1);
338
588
  var MemcacheCacheProvider = class {
339
589
  // default TTL in seconds
590
+ /**
591
+ * Creates a new MemcacheCacheProvider instance.
592
+ * @param config - Memcached configuration options
593
+ */
340
594
  constructor(config = {}) {
341
595
  this.config = config;
342
596
  this.client = new import_memcached.default(config.location || "localhost:11211", config.options || {});
@@ -346,6 +600,11 @@ var MemcacheCacheProvider = class {
346
600
  }
347
601
  client;
348
602
  defaultTTL = 3600;
603
+ /**
604
+ * Retrieves a value from the cache.
605
+ * @param key - The cache key
606
+ * @returns The cached value or undefined if not found
607
+ */
349
608
  async get(key) {
350
609
  return new Promise((resolve, reject) => {
351
610
  this.client.get(key, (err, data) => {
@@ -365,6 +624,12 @@ var MemcacheCacheProvider = class {
365
624
  });
366
625
  });
367
626
  }
627
+ /**
628
+ * Stores a value in the cache.
629
+ * @param key - The cache key
630
+ * @param value - The value to cache
631
+ * @param ttl - Time to live in seconds (optional)
632
+ */
368
633
  async put(key, value, ttl) {
369
634
  return new Promise((resolve, reject) => {
370
635
  const serializedValue = JSON.stringify(value);
@@ -378,6 +643,10 @@ var MemcacheCacheProvider = class {
378
643
  });
379
644
  });
380
645
  }
646
+ /**
647
+ * Deletes a value from the cache.
648
+ * @param key - The cache key to delete
649
+ */
381
650
  async delete(key) {
382
651
  return new Promise((resolve, reject) => {
383
652
  this.client.del(key, (err) => {
@@ -389,6 +658,11 @@ var MemcacheCacheProvider = class {
389
658
  });
390
659
  });
391
660
  }
661
+ /**
662
+ * Checks if a key exists in the cache.
663
+ * @param key - The cache key to check
664
+ * @returns True if the key exists, false otherwise
665
+ */
392
666
  async has(key) {
393
667
  return new Promise((resolve, reject) => {
394
668
  this.client.get(key, (err, data) => {
@@ -400,26 +674,87 @@ var MemcacheCacheProvider = class {
400
674
  });
401
675
  });
402
676
  }
677
+ /**
678
+ * Increments a numeric value in the cache atomically using Memcached's native increment.
679
+ * If the key doesn't exist, it is initialized with the increment amount.
680
+ * @param key - The cache key to increment
681
+ * @param amount - The amount to increment by (default: 1)
682
+ * @returns The new value after incrementing
683
+ */
684
+ async increment(key, amount = 1) {
685
+ return new Promise((resolve, reject) => {
686
+ this.client.incr(key, amount, (err, result) => {
687
+ if (err) {
688
+ reject(err);
689
+ return;
690
+ }
691
+ if (result === false) {
692
+ this.client.set(key, amount.toString(), this.defaultTTL, (setErr) => {
693
+ if (setErr) {
694
+ reject(setErr);
695
+ return;
696
+ }
697
+ resolve(amount);
698
+ });
699
+ } else {
700
+ resolve(result);
701
+ }
702
+ });
703
+ });
704
+ }
403
705
  };
404
706
 
405
707
  // src/providers/DisabledCacheProvider.mts
406
708
  var DisabledCacheProvider = class {
709
+ /**
710
+ * Creates a new DisabledCacheProvider instance.
711
+ * @param config - Configuration object (currently unused)
712
+ */
407
713
  constructor(config = {}) {
408
714
  this.config = config;
409
715
  }
410
716
  static {
411
717
  __name(this, "DisabledCacheProvider");
412
718
  }
719
+ /**
720
+ * Always returns undefined (no caching).
721
+ * @param key - The cache key
722
+ * @returns Always undefined
723
+ */
413
724
  async get(key) {
414
725
  return void 0;
415
726
  }
727
+ /**
728
+ * Does nothing (no caching).
729
+ * @param key - The cache key
730
+ * @param value - The value to cache
731
+ * @param ttl - Time to live in seconds
732
+ */
416
733
  async put(key, value, ttl) {
417
734
  }
735
+ /**
736
+ * Does nothing (no caching).
737
+ * @param key - The cache key to delete
738
+ */
418
739
  async delete(key) {
419
740
  }
741
+ /**
742
+ * Always returns false (no caching).
743
+ * @param key - The cache key to check
744
+ * @returns Always false
745
+ */
420
746
  async has(key) {
421
747
  return false;
422
748
  }
749
+ /**
750
+ * Returns the increment amount as if starting from 0 (no caching).
751
+ * @param key - The cache key to increment
752
+ * @param amount - The amount to increment by (default: 1)
753
+ * @returns The increment amount
754
+ */
755
+ async increment(key, amount = 1) {
756
+ return amount;
757
+ }
423
758
  };
424
759
  // Annotate the CommonJS export names for ESM import in node:
425
760
  0 && (module.exports = {