@devbro/neko-cache 0.1.2 → 0.1.4
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/CacheProviderInterface.d.mts +1 -0
- package/dist/cache.d.mts +13 -6
- package/dist/cache.mjs +19 -4
- package/dist/cache.mjs.map +1 -1
- package/dist/index.js +116 -4
- package/dist/index.js.map +1 -1
- package/dist/providers/DisabledCacheProvider.d.mts +1 -0
- package/dist/providers/DisabledCacheProvider.mjs +3 -0
- package/dist/providers/DisabledCacheProvider.mjs.map +1 -1
- package/dist/providers/FileCacheProvider.d.mts +1 -0
- package/dist/providers/FileCacheProvider.mjs +48 -0
- package/dist/providers/FileCacheProvider.mjs.map +1 -1
- package/dist/providers/MemcacheCacheProvider.d.mts +1 -0
- package/dist/providers/MemcacheCacheProvider.mjs +21 -0
- package/dist/providers/MemcacheCacheProvider.mjs.map +1 -1
- package/dist/providers/MemoryCacheProvider.d.mts +1 -0
- package/dist/providers/MemoryCacheProvider.mjs +21 -0
- package/dist/providers/MemoryCacheProvider.mjs.map +1 -1
- package/dist/providers/RedisCacheProvider.d.mts +1 -0
- package/dist/providers/RedisCacheProvider.mjs +4 -0
- package/dist/providers/RedisCacheProvider.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -5,6 +5,7 @@ interface CacheProviderInterface {
|
|
|
5
5
|
put(key: string, value: JSONObject | JSONValue, ttl?: number): Promise<void>;
|
|
6
6
|
delete(key: string): Promise<void>;
|
|
7
7
|
has(key: string): Promise<boolean>;
|
|
8
|
+
increment(key: string, amount?: number): Promise<number>;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
export type { CacheProviderInterface };
|
package/dist/cache.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { JSONValue } from '@devbro/neko-helper';
|
|
1
2
|
import { CacheProviderInterface } from './CacheProviderInterface.mjs';
|
|
2
|
-
import '@devbro/neko-helper';
|
|
3
3
|
|
|
4
4
|
type cacheOptions = {
|
|
5
5
|
ttl?: number;
|
|
@@ -7,11 +7,18 @@ type cacheOptions = {
|
|
|
7
7
|
declare class Cache {
|
|
8
8
|
private provider;
|
|
9
9
|
constructor(provider: CacheProviderInterface);
|
|
10
|
-
get<T>(key:
|
|
11
|
-
put(key:
|
|
12
|
-
delete(key:
|
|
13
|
-
has(key:
|
|
14
|
-
|
|
10
|
+
get<T>(key: JSONValue): Promise<T | undefined>;
|
|
11
|
+
put(key: JSONValue, value: any, ttl?: number): Promise<void>;
|
|
12
|
+
delete(key: JSONValue): Promise<void>;
|
|
13
|
+
has(key: JSONValue): Promise<boolean>;
|
|
14
|
+
increment(key: JSONValue, amount?: number): Promise<number>;
|
|
15
|
+
remember<T>(key: JSONValue, callback: () => Promise<T>, options?: cacheOptions): Promise<T>;
|
|
16
|
+
/**
|
|
17
|
+
* Generates a cache key by serializing and concatenating the provided parts.
|
|
18
|
+
* @param parts
|
|
19
|
+
* @returns
|
|
20
|
+
*/
|
|
21
|
+
generateKey(key: JSONValue): string;
|
|
15
22
|
}
|
|
16
23
|
|
|
17
24
|
export { Cache, type cacheOptions };
|
package/dist/cache.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
import { createHash } from "crypto";
|
|
3
4
|
class Cache {
|
|
4
5
|
constructor(provider) {
|
|
5
6
|
this.provider = provider;
|
|
@@ -8,16 +9,19 @@ class Cache {
|
|
|
8
9
|
__name(this, "Cache");
|
|
9
10
|
}
|
|
10
11
|
async get(key) {
|
|
11
|
-
return this.provider.get(key);
|
|
12
|
+
return this.provider.get(this.generateKey(key));
|
|
12
13
|
}
|
|
13
14
|
async put(key, value, ttl) {
|
|
14
|
-
return this.provider.put(key, value, ttl);
|
|
15
|
+
return this.provider.put(this.generateKey(key), value, ttl);
|
|
15
16
|
}
|
|
16
17
|
async delete(key) {
|
|
17
|
-
return this.provider.delete(key);
|
|
18
|
+
return this.provider.delete(this.generateKey(key));
|
|
18
19
|
}
|
|
19
20
|
async has(key) {
|
|
20
|
-
return this.provider.has(key);
|
|
21
|
+
return this.provider.has(this.generateKey(key));
|
|
22
|
+
}
|
|
23
|
+
async increment(key, amount = 1) {
|
|
24
|
+
return this.provider.increment(this.generateKey(key), amount);
|
|
21
25
|
}
|
|
22
26
|
async remember(key, callback, options = {}) {
|
|
23
27
|
options.ttl = options.ttl ?? 3600;
|
|
@@ -29,6 +33,17 @@ class Cache {
|
|
|
29
33
|
await this.put(key, result, options.ttl);
|
|
30
34
|
return result;
|
|
31
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Generates a cache key by serializing and concatenating the provided parts.
|
|
38
|
+
* @param parts
|
|
39
|
+
* @returns
|
|
40
|
+
*/
|
|
41
|
+
generateKey(key) {
|
|
42
|
+
if (typeof key === "string" && key.length <= 250) {
|
|
43
|
+
return key;
|
|
44
|
+
}
|
|
45
|
+
return createHash("md5").update(JSON.stringify(key)).digest("hex");
|
|
46
|
+
}
|
|
32
47
|
}
|
|
33
48
|
export {
|
|
34
49
|
Cache
|
package/dist/cache.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cache.mts"],"sourcesContent":["import { CacheProviderInterface } from './CacheProviderInterface.mjs';\n\nexport type cacheOptions = {\n ttl?: number;\n};\n\nexport class Cache {\n constructor(private provider: CacheProviderInterface) {}\n\n async get<T>(key:
|
|
1
|
+
{"version":3,"sources":["../src/cache.mts"],"sourcesContent":["import { JSONValue } from '@devbro/neko-helper';\nimport { CacheProviderInterface } from './CacheProviderInterface.mjs';\nimport { createHash } from 'crypto';\n\nexport type cacheOptions = {\n ttl?: number;\n};\n\nexport class Cache {\n constructor(private provider: CacheProviderInterface) {}\n\n async get<T>(key: JSONValue): Promise<T | undefined> {\n return this.provider.get(this.generateKey(key)) as Promise<T | undefined>;\n }\n\n async put(key: JSONValue, value: any, ttl?: number): Promise<void> {\n return this.provider.put(this.generateKey(key), value, ttl);\n }\n\n async delete(key: JSONValue): Promise<void> {\n return this.provider.delete(this.generateKey(key));\n }\n\n async has(key: JSONValue): Promise<boolean> {\n return this.provider.has(this.generateKey(key));\n }\n\n async increment(key: JSONValue, amount: number = 1): Promise<number> {\n return this.provider.increment(this.generateKey(key), amount);\n }\n\n async remember<T>(\n key: JSONValue,\n callback: () => Promise<T>,\n options: cacheOptions = {}\n ): Promise<T> {\n options.ttl = options.ttl ?? 3600; // default TTL 1 hour\n\n const cached = await this.get<T>(key);\n if (cached) {\n return cached;\n }\n\n const result = await callback();\n await this.put(key, result, options.ttl);\n return result;\n }\n\n /**\n * Generates a cache key by serializing and concatenating the provided parts.\n * @param parts\n * @returns\n */\n generateKey(key: JSONValue): string {\n if (typeof key === 'string' && key.length <= 250) {\n return key;\n }\n return createHash('md5').update(JSON.stringify(key)).digest('hex');\n }\n}\n"],"mappings":";;AAEA,SAAS,kBAAkB;AAMpB,MAAM,MAAM;AAAA,EACjB,YAAoB,UAAkC;AAAlC;AAAA,EAAmC;AAAA,EATzD,OAQmB;AAAA;AAAA;AAAA,EAGjB,MAAM,IAAO,KAAwC;AACnD,WAAO,KAAK,SAAS,IAAI,KAAK,YAAY,GAAG,CAAC;AAAA,EAChD;AAAA,EAEA,MAAM,IAAI,KAAgB,OAAY,KAA6B;AACjE,WAAO,KAAK,SAAS,IAAI,KAAK,YAAY,GAAG,GAAG,OAAO,GAAG;AAAA,EAC5D;AAAA,EAEA,MAAM,OAAO,KAA+B;AAC1C,WAAO,KAAK,SAAS,OAAO,KAAK,YAAY,GAAG,CAAC;AAAA,EACnD;AAAA,EAEA,MAAM,IAAI,KAAkC;AAC1C,WAAO,KAAK,SAAS,IAAI,KAAK,YAAY,GAAG,CAAC;AAAA,EAChD;AAAA,EAEA,MAAM,UAAU,KAAgB,SAAiB,GAAoB;AACnE,WAAO,KAAK,SAAS,UAAU,KAAK,YAAY,GAAG,GAAG,MAAM;AAAA,EAC9D;AAAA,EAEA,MAAM,SACJ,KACA,UACA,UAAwB,CAAC,GACb;AACZ,YAAQ,MAAM,QAAQ,OAAO;AAE7B,UAAM,SAAS,MAAM,KAAK,IAAO,GAAG;AACpC,QAAI,QAAQ;AACV,aAAO;AAAA,IACT;AAEA,UAAM,SAAS,MAAM,SAAS;AAC9B,UAAM,KAAK,IAAI,KAAK,QAAQ,QAAQ,GAAG;AACvC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,YAAY,KAAwB;AAClC,QAAI,OAAO,QAAQ,YAAY,IAAI,UAAU,KAAK;AAChD,aAAO;AAAA,IACT;AACA,WAAO,WAAW,KAAK,EAAE,OAAO,KAAK,UAAU,GAAG,CAAC,EAAE,OAAO,KAAK;AAAA,EACnE;AACF;","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -41,6 +41,7 @@ __export(index_exports, {
|
|
|
41
41
|
module.exports = __toCommonJS(index_exports);
|
|
42
42
|
|
|
43
43
|
// src/cache.mts
|
|
44
|
+
var import_crypto = require("crypto");
|
|
44
45
|
var Cache = class {
|
|
45
46
|
constructor(provider) {
|
|
46
47
|
this.provider = provider;
|
|
@@ -49,16 +50,19 @@ var Cache = class {
|
|
|
49
50
|
__name(this, "Cache");
|
|
50
51
|
}
|
|
51
52
|
async get(key) {
|
|
52
|
-
return this.provider.get(key);
|
|
53
|
+
return this.provider.get(this.generateKey(key));
|
|
53
54
|
}
|
|
54
55
|
async put(key, value, ttl) {
|
|
55
|
-
return this.provider.put(key, value, ttl);
|
|
56
|
+
return this.provider.put(this.generateKey(key), value, ttl);
|
|
56
57
|
}
|
|
57
58
|
async delete(key) {
|
|
58
|
-
return this.provider.delete(key);
|
|
59
|
+
return this.provider.delete(this.generateKey(key));
|
|
59
60
|
}
|
|
60
61
|
async has(key) {
|
|
61
|
-
return this.provider.has(key);
|
|
62
|
+
return this.provider.has(this.generateKey(key));
|
|
63
|
+
}
|
|
64
|
+
async increment(key, amount = 1) {
|
|
65
|
+
return this.provider.increment(this.generateKey(key), amount);
|
|
62
66
|
}
|
|
63
67
|
async remember(key, callback, options = {}) {
|
|
64
68
|
options.ttl = options.ttl ?? 3600;
|
|
@@ -70,6 +74,17 @@ var Cache = class {
|
|
|
70
74
|
await this.put(key, result, options.ttl);
|
|
71
75
|
return result;
|
|
72
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Generates a cache key by serializing and concatenating the provided parts.
|
|
79
|
+
* @param parts
|
|
80
|
+
* @returns
|
|
81
|
+
*/
|
|
82
|
+
generateKey(key) {
|
|
83
|
+
if (typeof key === "string" && key.length <= 250) {
|
|
84
|
+
return key;
|
|
85
|
+
}
|
|
86
|
+
return (0, import_crypto.createHash)("md5").update(JSON.stringify(key)).digest("hex");
|
|
87
|
+
}
|
|
73
88
|
};
|
|
74
89
|
|
|
75
90
|
// src/providers/RedisCacheProvider.mts
|
|
@@ -124,6 +139,10 @@ var RedisCacheProvider = class {
|
|
|
124
139
|
const result = await this.client.exists(key);
|
|
125
140
|
return result === 1;
|
|
126
141
|
}
|
|
142
|
+
async increment(key, amount = 1) {
|
|
143
|
+
await this.ensureConnection();
|
|
144
|
+
return await this.client.incrBy(key, amount);
|
|
145
|
+
}
|
|
127
146
|
};
|
|
128
147
|
|
|
129
148
|
// src/providers/FileCacheProvider.mts
|
|
@@ -228,6 +247,54 @@ var FileCacheProvider = class {
|
|
|
228
247
|
const value = await this.get(key);
|
|
229
248
|
return value !== void 0;
|
|
230
249
|
}
|
|
250
|
+
async increment(key, amount = 1) {
|
|
251
|
+
const filePath = this.getFilePath(key);
|
|
252
|
+
const lockPath = `${filePath}.lock`;
|
|
253
|
+
let lockAcquired = false;
|
|
254
|
+
let retries = 0;
|
|
255
|
+
const maxRetries = 50;
|
|
256
|
+
while (!lockAcquired && retries < maxRetries) {
|
|
257
|
+
try {
|
|
258
|
+
await fs.writeFile(lockPath, "", { flag: "wx" });
|
|
259
|
+
lockAcquired = true;
|
|
260
|
+
} catch {
|
|
261
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
262
|
+
retries++;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (!lockAcquired) {
|
|
266
|
+
throw new Error("Failed to acquire lock for increment operation");
|
|
267
|
+
}
|
|
268
|
+
try {
|
|
269
|
+
let currentValue = 0;
|
|
270
|
+
let item;
|
|
271
|
+
try {
|
|
272
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
273
|
+
const parsedItem = JSON.parse(content);
|
|
274
|
+
if (parsedItem.expiresAt && parsedItem.expiresAt < Date.now()) {
|
|
275
|
+
item = void 0;
|
|
276
|
+
} else {
|
|
277
|
+
item = parsedItem;
|
|
278
|
+
currentValue = typeof parsedItem.value === "number" ? parsedItem.value : 0;
|
|
279
|
+
}
|
|
280
|
+
} catch {
|
|
281
|
+
}
|
|
282
|
+
const newValue = currentValue + amount;
|
|
283
|
+
const now = Date.now();
|
|
284
|
+
const newItem = {
|
|
285
|
+
value: newValue,
|
|
286
|
+
createdAt: item?.createdAt ?? now,
|
|
287
|
+
expiresAt: item?.expiresAt
|
|
288
|
+
};
|
|
289
|
+
await fs.writeFile(filePath, JSON.stringify(newItem), "utf-8");
|
|
290
|
+
return newValue;
|
|
291
|
+
} finally {
|
|
292
|
+
try {
|
|
293
|
+
await fs.unlink(lockPath);
|
|
294
|
+
} catch {
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
231
298
|
};
|
|
232
299
|
|
|
233
300
|
// src/providers/MemoryCacheProvider.mts
|
|
@@ -322,6 +389,27 @@ var MemoryCacheProvider = class {
|
|
|
322
389
|
}
|
|
323
390
|
return true;
|
|
324
391
|
}
|
|
392
|
+
async increment(key, amount = 1) {
|
|
393
|
+
const item = this.cache.get(key);
|
|
394
|
+
const now = Date.now();
|
|
395
|
+
let currentValue = 0;
|
|
396
|
+
if (item) {
|
|
397
|
+
if (item.expiresAt && item.expiresAt < now) {
|
|
398
|
+
this.cache.delete(key);
|
|
399
|
+
} else {
|
|
400
|
+
currentValue = typeof item.value === "number" ? item.value : 0;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
const newValue = currentValue + amount;
|
|
404
|
+
const newItem = {
|
|
405
|
+
value: newValue,
|
|
406
|
+
createdAt: item?.createdAt ?? now,
|
|
407
|
+
lastAccessed: now,
|
|
408
|
+
expiresAt: item?.expiresAt
|
|
409
|
+
};
|
|
410
|
+
this.cache.set(key, newItem);
|
|
411
|
+
return newValue;
|
|
412
|
+
}
|
|
325
413
|
};
|
|
326
414
|
|
|
327
415
|
// src/providers/MemcacheCacheProvider.mts
|
|
@@ -391,6 +479,27 @@ var MemcacheCacheProvider = class {
|
|
|
391
479
|
});
|
|
392
480
|
});
|
|
393
481
|
}
|
|
482
|
+
async increment(key, amount = 1) {
|
|
483
|
+
return new Promise((resolve, reject) => {
|
|
484
|
+
this.client.incr(key, amount, (err, result) => {
|
|
485
|
+
if (err) {
|
|
486
|
+
reject(err);
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
if (result === false) {
|
|
490
|
+
this.client.set(key, amount.toString(), this.defaultTTL, (setErr) => {
|
|
491
|
+
if (setErr) {
|
|
492
|
+
reject(setErr);
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
resolve(amount);
|
|
496
|
+
});
|
|
497
|
+
} else {
|
|
498
|
+
resolve(result);
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
});
|
|
502
|
+
}
|
|
394
503
|
};
|
|
395
504
|
|
|
396
505
|
// src/providers/DisabledCacheProvider.mts
|
|
@@ -411,6 +520,9 @@ var DisabledCacheProvider = class {
|
|
|
411
520
|
async has(key) {
|
|
412
521
|
return false;
|
|
413
522
|
}
|
|
523
|
+
async increment(key, amount = 1) {
|
|
524
|
+
return amount;
|
|
525
|
+
}
|
|
414
526
|
};
|
|
415
527
|
// Annotate the CommonJS export names for ESM import in node:
|
|
416
528
|
0 && (module.exports = {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/cache.mts","../src/providers/RedisCacheProvider.mts","../src/providers/FileCacheProvider.mts","../src/providers/MemoryCacheProvider.mts","../src/providers/MemcacheCacheProvider.mts","../src/providers/DisabledCacheProvider.mts"],"sourcesContent":["export * from './cache.mjs';\nexport * from './CacheProviderInterface.mjs';\nexport * from './providers/index.mjs';\n","import { CacheProviderInterface } from './CacheProviderInterface.mjs';\n\nexport type cacheOptions = {\n ttl?: number;\n};\n\nexport class Cache {\n constructor(private provider: CacheProviderInterface) {}\n\n async get<T>(key: string): Promise<T | undefined> {\n return this.provider.get(key) as Promise<T | undefined>;\n }\n\n async put(key: string, value: any, ttl?: number): Promise<void> {\n return this.provider.put(key, value, ttl);\n }\n\n async delete(key: string): Promise<void> {\n return this.provider.delete(key);\n }\n\n async has(key: string): Promise<boolean> {\n return this.provider.has(key);\n }\n\n async remember<T>(\n key: string,\n callback: () => Promise<T>,\n options: cacheOptions = {}\n ): Promise<T> {\n options.ttl = options.ttl ?? 3600; // default TTL 1 hour\n\n const cached = await this.get<T>(key);\n if (cached) {\n return cached;\n }\n\n const result = await callback();\n await this.put(key, result, options.ttl);\n return result;\n }\n}\n","import { createClient, RedisClientOptions, RedisClientType } from 'redis';\nimport { CacheProviderInterface } from '../CacheProviderInterface.mjs';\nimport { JSONValue, JSONObject } from '@devbro/neko-helper';\n\nexport class RedisCacheProvider implements CacheProviderInterface {\n private client: RedisClientType;\n private defaultTTL: number = 3600; // default TTL in seconds\n\n constructor(private config: RedisClientOptions) {\n this.client = this.createRedisClient();\n this.client.connect();\n }\n\n private createRedisClient(): any {\n let rc = createClient(this.config);\n return rc;\n }\n\n private async ensureConnection(): Promise<void> {\n if (!this.client.isOpen) {\n await this.client.connect();\n }\n }\n\n async get(key: string): Promise<JSONValue | JSONObject | undefined> {\n await this.ensureConnection();\n let rc = this.client.get(key);\n return rc.then((value) => {\n if (value === null || value === undefined) {\n return undefined;\n }\n return JSON.parse(value);\n });\n }\n\n async put(key: string, value: JSONValue | JSONObject, ttl?: number): Promise<void> {\n await this.ensureConnection();\n const serializedValue = JSON.stringify(value);\n ttl = ttl ?? this.defaultTTL;\n if (ttl && ttl > 0) {\n await this.client.setEx(key, ttl, serializedValue);\n } else {\n await this.client.set(key, serializedValue);\n }\n }\n\n async delete(key: string): Promise<void> {\n await this.ensureConnection();\n await this.client.del(key);\n }\n\n async has(key: string): Promise<boolean> {\n await this.ensureConnection();\n const result = await this.client.exists(key);\n return result === 1;\n }\n}\n","import * as fs from 'fs/promises';\nimport * as path from 'path';\nimport { CacheProviderInterface } from '../CacheProviderInterface.mjs';\nimport { JSONObject, JSONValue } from '@devbro/neko-helper';\n\nexport interface FileCacheConfig {\n cacheDirectory?: string;\n defaultTTL?: number;\n cleanupInterval?: number;\n}\n\ninterface CacheItem {\n value: any;\n expiresAt?: number;\n createdAt: number;\n}\n\nexport class FileCacheProvider implements CacheProviderInterface {\n private config: FileCacheConfig = {\n cacheDirectory: path.join(process.cwd(), 'cache'),\n defaultTTL: 3600 * 1000,\n cleanupInterval: 300 * 1000,\n };\n\n private cleanupTimer?: NodeJS.Timeout;\n\n constructor(config: FileCacheConfig = {}) {\n this.config = { ...this.config, ...config };\n\n this.ensureCacheDirectory();\n this.startCleanupTimer();\n }\n\n private async ensureCacheDirectory(): Promise<void> {\n try {\n await fs.access(this.config.cacheDirectory!);\n } catch {\n await fs.mkdir(this.config.cacheDirectory!, { recursive: true });\n }\n }\n\n private startCleanupTimer(): void {\n if (this.config.cleanupInterval! > 0) {\n this.cleanupTimer = setInterval(() => {\n this.cleanupExpiredEntries().catch(console.error);\n }, this.config.cleanupInterval!);\n }\n }\n\n private stopCleanupTimer(): void {\n if (this.cleanupTimer) {\n clearInterval(this.cleanupTimer);\n this.cleanupTimer = undefined;\n }\n }\n\n private getFilePath(key: string): string {\n // Create a safe filename from the key\n const safeKey = key.replace(/[^a-z0-9]/gi, '_');\n return path.join(this.config.cacheDirectory!, `${safeKey}.json`);\n }\n\n private async cleanupExpiredEntries(): Promise<void> {\n try {\n const files = await fs.readdir(this.config.cacheDirectory!);\n const now = Date.now();\n\n for (const file of files) {\n if (file.endsWith('.json')) {\n const filePath = path.join(this.config.cacheDirectory!, file);\n try {\n const content = await fs.readFile(filePath, 'utf-8');\n const item: CacheItem = JSON.parse(content);\n\n if (item.expiresAt && item.expiresAt < now) {\n await fs.unlink(filePath);\n }\n } catch {\n // If file is corrupted, delete it\n await fs.unlink(filePath).catch(() => {});\n }\n }\n }\n } catch (error) {\n // Ignore cleanup errors\n }\n }\n\n async get(key: string): Promise<JSONObject | JSONValue | undefined> {\n const filePath = this.getFilePath(key);\n\n try {\n const content = await fs.readFile(filePath, 'utf-8');\n const item: CacheItem = JSON.parse(content);\n\n // Check if item has expired\n if (item.expiresAt && item.expiresAt < Date.now()) {\n await fs.unlink(filePath).catch(() => {});\n return undefined;\n }\n\n return item.value;\n } catch (error) {\n return undefined;\n }\n }\n\n async put(key: string, value: JSONObject | JSONValue, ttl?: number): Promise<void> {\n const filePath = this.getFilePath(key);\n const now = Date.now();\n const effectiveTTL = ttl ?? this.config.defaultTTL!;\n\n const item: CacheItem = {\n value,\n createdAt: now,\n expiresAt: effectiveTTL > 0 ? now + effectiveTTL * 1000 : undefined,\n };\n\n this.ensureCacheDirectory();\n await fs.writeFile(filePath, JSON.stringify(item), 'utf-8');\n }\n\n async delete(key: string): Promise<void> {\n const filePath = this.getFilePath(key);\n\n try {\n await fs.unlink(filePath);\n } catch {\n // File doesn't exist, that's fine\n }\n }\n\n async has(key: string): Promise<boolean> {\n const value = await this.get(key);\n return value !== undefined;\n }\n}\n","import { JSONObject, JSONValue } from '@devbro/neko-helper';\nimport { CacheProviderInterface } from '../CacheProviderInterface.mjs';\n\nexport interface MemoryCacheConfig {\n maxSize?: number;\n defaultTTL?: number;\n cleanupInterval?: number;\n}\n\ninterface CacheItem {\n value: any;\n expiresAt?: number;\n createdAt: number;\n lastAccessed: number;\n}\n\nexport class MemoryCacheProvider implements CacheProviderInterface {\n private cache = new Map<string, CacheItem>();\n private config: MemoryCacheConfig = {\n maxSize: 1000,\n defaultTTL: 3600,\n cleanupInterval: 600, // 10 minutes\n };\n\n private cleanupTimer?: NodeJS.Timeout;\n\n constructor(config: MemoryCacheConfig = {}) {\n this.config = { ...this.config, ...config };\n\n this.startCleanupTimer();\n }\n\n private startCleanupTimer(): void {\n if (this.config.cleanupInterval! > 0) {\n this.cleanupTimer = setInterval(() => {\n this.cleanupExpiredEntries();\n }, this.config.cleanupInterval! * 1000);\n }\n }\n\n private stopCleanupTimer(): void {\n if (this.cleanupTimer) {\n clearInterval(this.cleanupTimer);\n this.cleanupTimer = undefined;\n }\n }\n\n private cleanupExpiredEntries(): void {\n const now = Date.now();\n\n for (const [key, item] of this.cache.entries()) {\n if (item.expiresAt && item.expiresAt < now) {\n this.cache.delete(key);\n }\n }\n }\n\n private evictLRU(): void {\n if (this.cache.size <= this.config.maxSize!) {\n return;\n }\n\n // Find the least recently accessed item\n let oldestKey: string | null = null;\n let oldestTime = Date.now();\n\n for (const [key, item] of this.cache.entries()) {\n if (item.lastAccessed < oldestTime) {\n oldestTime = item.lastAccessed;\n oldestKey = key;\n }\n }\n\n if (oldestKey) {\n this.cache.delete(oldestKey);\n }\n }\n\n async get(key: string): Promise<JSONObject | JSONValue | undefined> {\n const item = this.cache.get(key);\n\n if (!item) {\n return undefined;\n }\n\n // Check if item has expired\n if (item.expiresAt && item.expiresAt < Date.now()) {\n this.cache.delete(key);\n return undefined;\n }\n\n // Update last accessed time for LRU\n item.lastAccessed = Date.now();\n\n return item.value;\n }\n\n async put(key: string, value: JSONObject | JSONValue, ttl?: number): Promise<void> {\n const now = Date.now();\n const effectiveTTL = ttl ?? this.config.defaultTTL!;\n\n const item: CacheItem = {\n value,\n createdAt: now,\n lastAccessed: now,\n expiresAt: effectiveTTL > 0 ? now + effectiveTTL * 1000 : undefined,\n };\n\n this.cache.set(key, item);\n\n // Evict items if we exceed maxSize\n this.evictLRU();\n }\n\n async delete(key: string): Promise<void> {\n this.cache.delete(key);\n }\n\n async has(key: string): Promise<boolean> {\n const item = this.cache.get(key);\n\n if (!item) {\n return false;\n }\n\n // Check if item has expired\n if (item.expiresAt && item.expiresAt < Date.now()) {\n this.cache.delete(key);\n return false;\n }\n\n return true;\n }\n}\n","import { CacheProviderInterface } from \"@/CacheProviderInterface.mjs\";\nimport { JSONValue, JSONObject } from \"@devbro/neko-helper\";\nimport Memcached from \"memcached\";\n\nexport interface MemcachedConfig {\n location?: Memcached.Location;\n options?: Memcached.options;\n}\n\nexport class MemcacheCacheProvider implements CacheProviderInterface {\n private client: Memcached;\n private defaultTTL: number = 3600; // default TTL in seconds\n\n constructor(private config: MemcachedConfig = {}) {\n this.client = new Memcached(config.location || 'localhost:11211', config.options || {});\n }\n\n async get(key: string): Promise<JSONValue | JSONObject | undefined> {\n return new Promise((resolve, reject) => {\n this.client.get(key, (err: Error | undefined, data: any) => {\n if (err) {\n reject(err);\n return;\n }\n \n if (data === undefined || data === null) {\n resolve(undefined);\n return;\n }\n\n try {\n // Memcached automatically handles JSON serialization/deserialization\n // but we need to ensure we return the correct type\n resolve(typeof data === 'string' ? JSON.parse(data) : data);\n } catch (parseErr) {\n // If parsing fails, return the raw value\n resolve(data);\n }\n });\n });\n }\n\n async put(key: string, value: JSONObject | JSONValue, ttl?: number): Promise<void> {\n return new Promise((resolve, reject) => {\n const serializedValue = JSON.stringify(value);\n const finalTTL = ttl ?? this.defaultTTL;\n\n this.client.set(key, serializedValue, finalTTL, (err: Error | undefined) => {\n if (err) {\n reject(err);\n return;\n }\n resolve();\n });\n });\n }\n\n async delete(key: string): Promise<void> {\n return new Promise((resolve, reject) => {\n this.client.del(key, (err: Error | undefined) => {\n if (err) {\n reject(err);\n return;\n }\n resolve();\n });\n });\n }\n\n async has(key: string): Promise<boolean> {\n return new Promise((resolve, reject) => {\n this.client.get(key, (err: Error | undefined, data: any) => {\n if (err) {\n reject(err);\n return;\n }\n resolve(data !== undefined && data !== null);\n });\n });\n }\n\n\n\n}","import { createClient, RedisClientOptions, RedisClientType } from 'redis';\nimport { CacheProviderInterface } from '../CacheProviderInterface.mjs';\nimport { JSONValue, JSONObject } from '@devbro/neko-helper';\n\nexport class DisabledCacheProvider implements CacheProviderInterface {\n constructor(private config = {}) {}\n\n async get(key: string): Promise<JSONValue | JSONObject | undefined> {\n return undefined;\n }\n\n async put(key: string, value: JSONValue | JSONObject, ttl?: number): Promise<void> {}\n\n async delete(key: string): Promise<void> {}\n\n async has(key: string): Promise<boolean> {\n return false;\n }\n}\n"],"mappings":"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","names":["Memcached"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/cache.mts","../src/providers/RedisCacheProvider.mts","../src/providers/FileCacheProvider.mts","../src/providers/MemoryCacheProvider.mts","../src/providers/MemcacheCacheProvider.mts","../src/providers/DisabledCacheProvider.mts"],"sourcesContent":["export * from './cache.mjs';\nexport * from './CacheProviderInterface.mjs';\nexport * from './providers/index.mjs';\n","import { JSONValue } from '@devbro/neko-helper';\nimport { CacheProviderInterface } from './CacheProviderInterface.mjs';\nimport { createHash } from 'crypto';\n\nexport type cacheOptions = {\n ttl?: number;\n};\n\nexport class Cache {\n constructor(private provider: CacheProviderInterface) {}\n\n async get<T>(key: JSONValue): Promise<T | undefined> {\n return this.provider.get(this.generateKey(key)) as Promise<T | undefined>;\n }\n\n async put(key: JSONValue, value: any, ttl?: number): Promise<void> {\n return this.provider.put(this.generateKey(key), value, ttl);\n }\n\n async delete(key: JSONValue): Promise<void> {\n return this.provider.delete(this.generateKey(key));\n }\n\n async has(key: JSONValue): Promise<boolean> {\n return this.provider.has(this.generateKey(key));\n }\n\n async increment(key: JSONValue, amount: number = 1): Promise<number> {\n return this.provider.increment(this.generateKey(key), amount);\n }\n\n async remember<T>(\n key: JSONValue,\n callback: () => Promise<T>,\n options: cacheOptions = {}\n ): Promise<T> {\n options.ttl = options.ttl ?? 3600; // default TTL 1 hour\n\n const cached = await this.get<T>(key);\n if (cached) {\n return cached;\n }\n\n const result = await callback();\n await this.put(key, result, options.ttl);\n return result;\n }\n\n /**\n * Generates a cache key by serializing and concatenating the provided parts.\n * @param parts\n * @returns\n */\n generateKey(key: JSONValue): string {\n if (typeof key === 'string' && key.length <= 250) {\n return key;\n }\n return createHash('md5').update(JSON.stringify(key)).digest('hex');\n }\n}\n","import { createClient, RedisClientOptions, RedisClientType } from 'redis';\nimport { CacheProviderInterface } from '../CacheProviderInterface.mjs';\nimport { JSONValue, JSONObject } from '@devbro/neko-helper';\n\nexport class RedisCacheProvider implements CacheProviderInterface {\n private client: RedisClientType;\n private defaultTTL: number = 3600; // default TTL in seconds\n\n constructor(private config: RedisClientOptions) {\n this.client = this.createRedisClient();\n this.client.connect();\n }\n\n private createRedisClient(): any {\n let rc = createClient(this.config);\n return rc;\n }\n\n private async ensureConnection(): Promise<void> {\n if (!this.client.isOpen) {\n await this.client.connect();\n }\n }\n\n async get(key: string): Promise<JSONValue | JSONObject | undefined> {\n await this.ensureConnection();\n let rc = this.client.get(key);\n return rc.then((value) => {\n if (value === null || value === undefined) {\n return undefined;\n }\n return JSON.parse(value);\n });\n }\n\n async put(key: string, value: JSONValue | JSONObject, ttl?: number): Promise<void> {\n await this.ensureConnection();\n const serializedValue = JSON.stringify(value);\n ttl = ttl ?? this.defaultTTL;\n if (ttl && ttl > 0) {\n await this.client.setEx(key, ttl, serializedValue);\n } else {\n await this.client.set(key, serializedValue);\n }\n }\n\n async delete(key: string): Promise<void> {\n await this.ensureConnection();\n await this.client.del(key);\n }\n\n async has(key: string): Promise<boolean> {\n await this.ensureConnection();\n const result = await this.client.exists(key);\n return result === 1;\n }\n\n async increment(key: string, amount: number = 1): Promise<number> {\n await this.ensureConnection();\n // Redis INCRBY is atomic\n return await this.client.incrBy(key, amount);\n }\n}\n","import * as fs from 'fs/promises';\nimport * as path from 'path';\nimport { CacheProviderInterface } from '../CacheProviderInterface.mjs';\nimport { JSONObject, JSONValue } from '@devbro/neko-helper';\n\nexport interface FileCacheConfig {\n cacheDirectory?: string;\n defaultTTL?: number;\n cleanupInterval?: number;\n}\n\ninterface CacheItem {\n value: any;\n expiresAt?: number;\n createdAt: number;\n}\n\nexport class FileCacheProvider implements CacheProviderInterface {\n private config: FileCacheConfig = {\n cacheDirectory: path.join(process.cwd(), 'cache'),\n defaultTTL: 3600 * 1000,\n cleanupInterval: 300 * 1000,\n };\n\n private cleanupTimer?: NodeJS.Timeout;\n\n constructor(config: FileCacheConfig = {}) {\n this.config = { ...this.config, ...config };\n\n this.ensureCacheDirectory();\n this.startCleanupTimer();\n }\n\n private async ensureCacheDirectory(): Promise<void> {\n try {\n await fs.access(this.config.cacheDirectory!);\n } catch {\n await fs.mkdir(this.config.cacheDirectory!, { recursive: true });\n }\n }\n\n private startCleanupTimer(): void {\n if (this.config.cleanupInterval! > 0) {\n this.cleanupTimer = setInterval(() => {\n this.cleanupExpiredEntries().catch(console.error);\n }, this.config.cleanupInterval!);\n }\n }\n\n private stopCleanupTimer(): void {\n if (this.cleanupTimer) {\n clearInterval(this.cleanupTimer);\n this.cleanupTimer = undefined;\n }\n }\n\n private getFilePath(key: string): string {\n // Create a safe filename from the key\n const safeKey = key.replace(/[^a-z0-9]/gi, '_');\n return path.join(this.config.cacheDirectory!, `${safeKey}.json`);\n }\n\n private async cleanupExpiredEntries(): Promise<void> {\n try {\n const files = await fs.readdir(this.config.cacheDirectory!);\n const now = Date.now();\n\n for (const file of files) {\n if (file.endsWith('.json')) {\n const filePath = path.join(this.config.cacheDirectory!, file);\n try {\n const content = await fs.readFile(filePath, 'utf-8');\n const item: CacheItem = JSON.parse(content);\n\n if (item.expiresAt && item.expiresAt < now) {\n await fs.unlink(filePath);\n }\n } catch {\n // If file is corrupted, delete it\n await fs.unlink(filePath).catch(() => {});\n }\n }\n }\n } catch (error) {\n // Ignore cleanup errors\n }\n }\n\n async get(key: string): Promise<JSONObject | JSONValue | undefined> {\n const filePath = this.getFilePath(key);\n\n try {\n const content = await fs.readFile(filePath, 'utf-8');\n const item: CacheItem = JSON.parse(content);\n\n // Check if item has expired\n if (item.expiresAt && item.expiresAt < Date.now()) {\n await fs.unlink(filePath).catch(() => {});\n return undefined;\n }\n\n return item.value;\n } catch (error) {\n return undefined;\n }\n }\n\n async put(key: string, value: JSONObject | JSONValue, ttl?: number): Promise<void> {\n const filePath = this.getFilePath(key);\n const now = Date.now();\n const effectiveTTL = ttl ?? this.config.defaultTTL!;\n\n const item: CacheItem = {\n value,\n createdAt: now,\n expiresAt: effectiveTTL > 0 ? now + effectiveTTL * 1000 : undefined,\n };\n\n this.ensureCacheDirectory();\n await fs.writeFile(filePath, JSON.stringify(item), 'utf-8');\n }\n\n async delete(key: string): Promise<void> {\n const filePath = this.getFilePath(key);\n\n try {\n await fs.unlink(filePath);\n } catch {\n // File doesn't exist, that's fine\n }\n }\n\n async has(key: string): Promise<boolean> {\n const value = await this.get(key);\n return value !== undefined;\n }\n\n async increment(key: string, amount: number = 1): Promise<number> {\n const filePath = this.getFilePath(key);\n\n // Use a lock file to ensure atomicity\n const lockPath = `${filePath}.lock`;\n\n // Simple file-based locking mechanism\n let lockAcquired = false;\n let retries = 0;\n const maxRetries = 50;\n\n while (!lockAcquired && retries < maxRetries) {\n try {\n // Try to create lock file exclusively\n await fs.writeFile(lockPath, '', { flag: 'wx' });\n lockAcquired = true;\n } catch {\n // Lock exists, wait a bit and retry\n await new Promise((resolve) => setTimeout(resolve, 10));\n retries++;\n }\n }\n\n if (!lockAcquired) {\n throw new Error('Failed to acquire lock for increment operation');\n }\n\n try {\n let currentValue = 0;\n let item: CacheItem | undefined;\n\n // Read current value\n try {\n const content = await fs.readFile(filePath, 'utf-8');\n const parsedItem = JSON.parse(content);\n\n // Check if item has expired\n if (parsedItem.expiresAt && parsedItem.expiresAt < Date.now()) {\n item = undefined;\n } else {\n item = parsedItem;\n currentValue = typeof parsedItem.value === 'number' ? parsedItem.value : 0;\n }\n } catch {\n // File doesn't exist or is corrupted, start from 0\n }\n\n // Calculate new value\n const newValue = currentValue + amount;\n\n // Write back with same TTL if it existed\n const now = Date.now();\n const newItem: CacheItem = {\n value: newValue,\n createdAt: item?.createdAt ?? now,\n expiresAt: item?.expiresAt,\n };\n\n await fs.writeFile(filePath, JSON.stringify(newItem), 'utf-8');\n\n return newValue;\n } finally {\n // Release lock\n try {\n await fs.unlink(lockPath);\n } catch {\n // Ignore errors when removing lock file\n }\n }\n }\n}\n","import { JSONObject, JSONValue } from '@devbro/neko-helper';\nimport { CacheProviderInterface } from '../CacheProviderInterface.mjs';\n\nexport interface MemoryCacheConfig {\n maxSize?: number;\n defaultTTL?: number;\n cleanupInterval?: number;\n}\n\ninterface CacheItem {\n value: any;\n expiresAt?: number;\n createdAt: number;\n lastAccessed: number;\n}\n\nexport class MemoryCacheProvider implements CacheProviderInterface {\n private cache = new Map<string, CacheItem>();\n private config: MemoryCacheConfig = {\n maxSize: 1000,\n defaultTTL: 3600,\n cleanupInterval: 600, // 10 minutes\n };\n\n private cleanupTimer?: NodeJS.Timeout;\n\n constructor(config: MemoryCacheConfig = {}) {\n this.config = { ...this.config, ...config };\n\n this.startCleanupTimer();\n }\n\n private startCleanupTimer(): void {\n if (this.config.cleanupInterval! > 0) {\n this.cleanupTimer = setInterval(() => {\n this.cleanupExpiredEntries();\n }, this.config.cleanupInterval! * 1000);\n }\n }\n\n private stopCleanupTimer(): void {\n if (this.cleanupTimer) {\n clearInterval(this.cleanupTimer);\n this.cleanupTimer = undefined;\n }\n }\n\n private cleanupExpiredEntries(): void {\n const now = Date.now();\n\n for (const [key, item] of this.cache.entries()) {\n if (item.expiresAt && item.expiresAt < now) {\n this.cache.delete(key);\n }\n }\n }\n\n private evictLRU(): void {\n if (this.cache.size <= this.config.maxSize!) {\n return;\n }\n\n // Find the least recently accessed item\n let oldestKey: string | null = null;\n let oldestTime = Date.now();\n\n for (const [key, item] of this.cache.entries()) {\n if (item.lastAccessed < oldestTime) {\n oldestTime = item.lastAccessed;\n oldestKey = key;\n }\n }\n\n if (oldestKey) {\n this.cache.delete(oldestKey);\n }\n }\n\n async get(key: string): Promise<JSONObject | JSONValue | undefined> {\n const item = this.cache.get(key);\n\n if (!item) {\n return undefined;\n }\n\n // Check if item has expired\n if (item.expiresAt && item.expiresAt < Date.now()) {\n this.cache.delete(key);\n return undefined;\n }\n\n // Update last accessed time for LRU\n item.lastAccessed = Date.now();\n\n return item.value;\n }\n\n async put(key: string, value: JSONObject | JSONValue, ttl?: number): Promise<void> {\n const now = Date.now();\n const effectiveTTL = ttl ?? this.config.defaultTTL!;\n\n const item: CacheItem = {\n value,\n createdAt: now,\n lastAccessed: now,\n expiresAt: effectiveTTL > 0 ? now + effectiveTTL * 1000 : undefined,\n };\n\n this.cache.set(key, item);\n\n // Evict items if we exceed maxSize\n this.evictLRU();\n }\n\n async delete(key: string): Promise<void> {\n this.cache.delete(key);\n }\n\n async has(key: string): Promise<boolean> {\n const item = this.cache.get(key);\n\n if (!item) {\n return false;\n }\n\n // Check if item has expired\n if (item.expiresAt && item.expiresAt < Date.now()) {\n this.cache.delete(key);\n return false;\n }\n\n return true;\n }\n\n async increment(key: string, amount: number = 1): Promise<number> {\n const item = this.cache.get(key);\n const now = Date.now();\n\n let currentValue = 0;\n\n // Check if item exists and is not expired\n if (item) {\n if (item.expiresAt && item.expiresAt < now) {\n this.cache.delete(key);\n } else {\n // Get current value, ensure it's a number\n currentValue = typeof item.value === 'number' ? item.value : 0;\n }\n }\n\n // Calculate new value\n const newValue = currentValue + amount;\n\n // Store the new value with the same TTL if it existed\n const newItem: CacheItem = {\n value: newValue,\n createdAt: item?.createdAt ?? now,\n lastAccessed: now,\n expiresAt: item?.expiresAt,\n };\n\n this.cache.set(key, newItem);\n\n return newValue;\n }\n}\n","import { CacheProviderInterface } from '@/CacheProviderInterface.mjs';\nimport { JSONValue, JSONObject } from '@devbro/neko-helper';\nimport Memcached from 'memcached';\n\nexport interface MemcachedConfig {\n location?: Memcached.Location;\n options?: Memcached.options;\n}\n\nexport class MemcacheCacheProvider implements CacheProviderInterface {\n private client: Memcached;\n private defaultTTL: number = 3600; // default TTL in seconds\n\n constructor(private config: MemcachedConfig = {}) {\n this.client = new Memcached(config.location || 'localhost:11211', config.options || {});\n }\n\n async get(key: string): Promise<JSONValue | JSONObject | undefined> {\n return new Promise((resolve, reject) => {\n this.client.get(key, (err: Error | undefined, data: any) => {\n if (err) {\n reject(err);\n return;\n }\n\n if (data === undefined || data === null) {\n resolve(undefined);\n return;\n }\n\n try {\n // Memcached automatically handles JSON serialization/deserialization\n // but we need to ensure we return the correct type\n resolve(typeof data === 'string' ? JSON.parse(data) : data);\n } catch (parseErr) {\n // If parsing fails, return the raw value\n resolve(data);\n }\n });\n });\n }\n\n async put(key: string, value: JSONObject | JSONValue, ttl?: number): Promise<void> {\n return new Promise((resolve, reject) => {\n const serializedValue = JSON.stringify(value);\n const finalTTL = ttl ?? this.defaultTTL;\n\n this.client.set(key, serializedValue, finalTTL, (err: Error | undefined) => {\n if (err) {\n reject(err);\n return;\n }\n resolve();\n });\n });\n }\n\n async delete(key: string): Promise<void> {\n return new Promise((resolve, reject) => {\n this.client.del(key, (err: Error | undefined) => {\n if (err) {\n reject(err);\n return;\n }\n resolve();\n });\n });\n }\n\n async has(key: string): Promise<boolean> {\n return new Promise((resolve, reject) => {\n this.client.get(key, (err: Error | undefined, data: any) => {\n if (err) {\n reject(err);\n return;\n }\n resolve(data !== undefined && data !== null);\n });\n });\n }\n\n async increment(key: string, amount: number = 1): Promise<number> {\n return new Promise((resolve, reject) => {\n // Memcached incr are atomic operations\n this.client.incr(key, amount, (err: any, result: number | boolean) => {\n if (err) {\n reject(err);\n return;\n }\n\n // If key doesn't exist, result will be false\n if (result === false) {\n // Initialize the key with the amount value\n this.client.set(key, amount.toString(), this.defaultTTL, (setErr: Error | undefined) => {\n if (setErr) {\n reject(setErr);\n return;\n }\n resolve(amount);\n });\n } else {\n resolve(result as number);\n }\n });\n });\n }\n}\n","import { createClient, RedisClientOptions, RedisClientType } from 'redis';\nimport { CacheProviderInterface } from '../CacheProviderInterface.mjs';\nimport { JSONValue, JSONObject } from '@devbro/neko-helper';\n\nexport class DisabledCacheProvider implements CacheProviderInterface {\n constructor(private config = {}) {}\n\n async get(key: string): Promise<JSONValue | JSONObject | undefined> {\n return undefined;\n }\n\n async put(key: string, value: JSONValue | JSONObject, ttl?: number): Promise<void> {}\n\n async delete(key: string): Promise<void> {}\n\n async has(key: string): Promise<boolean> {\n return false;\n }\n\n async increment(key: string, amount: number = 1): Promise<number> {\n // Disabled cache always returns the increment amount as if starting from 0\n return amount;\n }\n}\n"],"mappings":"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","names":["Memcached"]}
|
|
@@ -8,6 +8,7 @@ declare class DisabledCacheProvider implements CacheProviderInterface {
|
|
|
8
8
|
put(key: string, value: JSONValue | JSONObject, ttl?: number): Promise<void>;
|
|
9
9
|
delete(key: string): Promise<void>;
|
|
10
10
|
has(key: string): Promise<boolean>;
|
|
11
|
+
increment(key: string, amount?: number): Promise<number>;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export { DisabledCacheProvider };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/providers/DisabledCacheProvider.mts"],"sourcesContent":["import { createClient, RedisClientOptions, RedisClientType } from 'redis';\nimport { CacheProviderInterface } from '../CacheProviderInterface.mjs';\nimport { JSONValue, JSONObject } from '@devbro/neko-helper';\n\nexport class DisabledCacheProvider implements CacheProviderInterface {\n constructor(private config = {}) {}\n\n async get(key: string): Promise<JSONValue | JSONObject | undefined> {\n return undefined;\n }\n\n async put(key: string, value: JSONValue | JSONObject, ttl?: number): Promise<void> {}\n\n async delete(key: string): Promise<void> {}\n\n async has(key: string): Promise<boolean> {\n return false;\n }\n}\n"],"mappings":";;AAIO,MAAM,sBAAwD;AAAA,EACnE,YAAoB,SAAS,CAAC,GAAG;AAAb;AAAA,EAAc;AAAA,EALpC,OAIqE;AAAA;AAAA;AAAA,EAGnE,MAAM,IAAI,KAA0D;AAClE,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,IAAI,KAAa,OAA+B,KAA6B;AAAA,EAAC;AAAA,EAEpF,MAAM,OAAO,KAA4B;AAAA,EAAC;AAAA,EAE1C,MAAM,IAAI,KAA+B;AACvC,WAAO;AAAA,EACT;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/providers/DisabledCacheProvider.mts"],"sourcesContent":["import { createClient, RedisClientOptions, RedisClientType } from 'redis';\nimport { CacheProviderInterface } from '../CacheProviderInterface.mjs';\nimport { JSONValue, JSONObject } from '@devbro/neko-helper';\n\nexport class DisabledCacheProvider implements CacheProviderInterface {\n constructor(private config = {}) {}\n\n async get(key: string): Promise<JSONValue | JSONObject | undefined> {\n return undefined;\n }\n\n async put(key: string, value: JSONValue | JSONObject, ttl?: number): Promise<void> {}\n\n async delete(key: string): Promise<void> {}\n\n async has(key: string): Promise<boolean> {\n return false;\n }\n\n async increment(key: string, amount: number = 1): Promise<number> {\n // Disabled cache always returns the increment amount as if starting from 0\n return amount;\n }\n}\n"],"mappings":";;AAIO,MAAM,sBAAwD;AAAA,EACnE,YAAoB,SAAS,CAAC,GAAG;AAAb;AAAA,EAAc;AAAA,EALpC,OAIqE;AAAA;AAAA;AAAA,EAGnE,MAAM,IAAI,KAA0D;AAClE,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,IAAI,KAAa,OAA+B,KAA6B;AAAA,EAAC;AAAA,EAEpF,MAAM,OAAO,KAA4B;AAAA,EAAC;AAAA,EAE1C,MAAM,IAAI,KAA+B;AACvC,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,UAAU,KAAa,SAAiB,GAAoB;AAEhE,WAAO;AAAA,EACT;AACF;","names":[]}
|
|
@@ -19,6 +19,7 @@ declare class FileCacheProvider implements CacheProviderInterface {
|
|
|
19
19
|
put(key: string, value: JSONObject | JSONValue, ttl?: number): Promise<void>;
|
|
20
20
|
delete(key: string): Promise<void>;
|
|
21
21
|
has(key: string): Promise<boolean>;
|
|
22
|
+
increment(key: string, amount?: number): Promise<number>;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export { type FileCacheConfig, FileCacheProvider };
|
|
@@ -101,6 +101,54 @@ class FileCacheProvider {
|
|
|
101
101
|
const value = await this.get(key);
|
|
102
102
|
return value !== void 0;
|
|
103
103
|
}
|
|
104
|
+
async increment(key, amount = 1) {
|
|
105
|
+
const filePath = this.getFilePath(key);
|
|
106
|
+
const lockPath = `${filePath}.lock`;
|
|
107
|
+
let lockAcquired = false;
|
|
108
|
+
let retries = 0;
|
|
109
|
+
const maxRetries = 50;
|
|
110
|
+
while (!lockAcquired && retries < maxRetries) {
|
|
111
|
+
try {
|
|
112
|
+
await fs.writeFile(lockPath, "", { flag: "wx" });
|
|
113
|
+
lockAcquired = true;
|
|
114
|
+
} catch {
|
|
115
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
116
|
+
retries++;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (!lockAcquired) {
|
|
120
|
+
throw new Error("Failed to acquire lock for increment operation");
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
let currentValue = 0;
|
|
124
|
+
let item;
|
|
125
|
+
try {
|
|
126
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
127
|
+
const parsedItem = JSON.parse(content);
|
|
128
|
+
if (parsedItem.expiresAt && parsedItem.expiresAt < Date.now()) {
|
|
129
|
+
item = void 0;
|
|
130
|
+
} else {
|
|
131
|
+
item = parsedItem;
|
|
132
|
+
currentValue = typeof parsedItem.value === "number" ? parsedItem.value : 0;
|
|
133
|
+
}
|
|
134
|
+
} catch {
|
|
135
|
+
}
|
|
136
|
+
const newValue = currentValue + amount;
|
|
137
|
+
const now = Date.now();
|
|
138
|
+
const newItem = {
|
|
139
|
+
value: newValue,
|
|
140
|
+
createdAt: item?.createdAt ?? now,
|
|
141
|
+
expiresAt: item?.expiresAt
|
|
142
|
+
};
|
|
143
|
+
await fs.writeFile(filePath, JSON.stringify(newItem), "utf-8");
|
|
144
|
+
return newValue;
|
|
145
|
+
} finally {
|
|
146
|
+
try {
|
|
147
|
+
await fs.unlink(lockPath);
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
104
152
|
}
|
|
105
153
|
export {
|
|
106
154
|
FileCacheProvider
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/providers/FileCacheProvider.mts"],"sourcesContent":["import * as fs from 'fs/promises';\nimport * as path from 'path';\nimport { CacheProviderInterface } from '../CacheProviderInterface.mjs';\nimport { JSONObject, JSONValue } from '@devbro/neko-helper';\n\nexport interface FileCacheConfig {\n cacheDirectory?: string;\n defaultTTL?: number;\n cleanupInterval?: number;\n}\n\ninterface CacheItem {\n value: any;\n expiresAt?: number;\n createdAt: number;\n}\n\nexport class FileCacheProvider implements CacheProviderInterface {\n private config: FileCacheConfig = {\n cacheDirectory: path.join(process.cwd(), 'cache'),\n defaultTTL: 3600 * 1000,\n cleanupInterval: 300 * 1000,\n };\n\n private cleanupTimer?: NodeJS.Timeout;\n\n constructor(config: FileCacheConfig = {}) {\n this.config = { ...this.config, ...config };\n\n this.ensureCacheDirectory();\n this.startCleanupTimer();\n }\n\n private async ensureCacheDirectory(): Promise<void> {\n try {\n await fs.access(this.config.cacheDirectory!);\n } catch {\n await fs.mkdir(this.config.cacheDirectory!, { recursive: true });\n }\n }\n\n private startCleanupTimer(): void {\n if (this.config.cleanupInterval! > 0) {\n this.cleanupTimer = setInterval(() => {\n this.cleanupExpiredEntries().catch(console.error);\n }, this.config.cleanupInterval!);\n }\n }\n\n private stopCleanupTimer(): void {\n if (this.cleanupTimer) {\n clearInterval(this.cleanupTimer);\n this.cleanupTimer = undefined;\n }\n }\n\n private getFilePath(key: string): string {\n // Create a safe filename from the key\n const safeKey = key.replace(/[^a-z0-9]/gi, '_');\n return path.join(this.config.cacheDirectory!, `${safeKey}.json`);\n }\n\n private async cleanupExpiredEntries(): Promise<void> {\n try {\n const files = await fs.readdir(this.config.cacheDirectory!);\n const now = Date.now();\n\n for (const file of files) {\n if (file.endsWith('.json')) {\n const filePath = path.join(this.config.cacheDirectory!, file);\n try {\n const content = await fs.readFile(filePath, 'utf-8');\n const item: CacheItem = JSON.parse(content);\n\n if (item.expiresAt && item.expiresAt < now) {\n await fs.unlink(filePath);\n }\n } catch {\n // If file is corrupted, delete it\n await fs.unlink(filePath).catch(() => {});\n }\n }\n }\n } catch (error) {\n // Ignore cleanup errors\n }\n }\n\n async get(key: string): Promise<JSONObject | JSONValue | undefined> {\n const filePath = this.getFilePath(key);\n\n try {\n const content = await fs.readFile(filePath, 'utf-8');\n const item: CacheItem = JSON.parse(content);\n\n // Check if item has expired\n if (item.expiresAt && item.expiresAt < Date.now()) {\n await fs.unlink(filePath).catch(() => {});\n return undefined;\n }\n\n return item.value;\n } catch (error) {\n return undefined;\n }\n }\n\n async put(key: string, value: JSONObject | JSONValue, ttl?: number): Promise<void> {\n const filePath = this.getFilePath(key);\n const now = Date.now();\n const effectiveTTL = ttl ?? this.config.defaultTTL!;\n\n const item: CacheItem = {\n value,\n createdAt: now,\n expiresAt: effectiveTTL > 0 ? now + effectiveTTL * 1000 : undefined,\n };\n\n this.ensureCacheDirectory();\n await fs.writeFile(filePath, JSON.stringify(item), 'utf-8');\n }\n\n async delete(key: string): Promise<void> {\n const filePath = this.getFilePath(key);\n\n try {\n await fs.unlink(filePath);\n } catch {\n // File doesn't exist, that's fine\n }\n }\n\n async has(key: string): Promise<boolean> {\n const value = await this.get(key);\n return value !== undefined;\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/providers/FileCacheProvider.mts"],"sourcesContent":["import * as fs from 'fs/promises';\nimport * as path from 'path';\nimport { CacheProviderInterface } from '../CacheProviderInterface.mjs';\nimport { JSONObject, JSONValue } from '@devbro/neko-helper';\n\nexport interface FileCacheConfig {\n cacheDirectory?: string;\n defaultTTL?: number;\n cleanupInterval?: number;\n}\n\ninterface CacheItem {\n value: any;\n expiresAt?: number;\n createdAt: number;\n}\n\nexport class FileCacheProvider implements CacheProviderInterface {\n private config: FileCacheConfig = {\n cacheDirectory: path.join(process.cwd(), 'cache'),\n defaultTTL: 3600 * 1000,\n cleanupInterval: 300 * 1000,\n };\n\n private cleanupTimer?: NodeJS.Timeout;\n\n constructor(config: FileCacheConfig = {}) {\n this.config = { ...this.config, ...config };\n\n this.ensureCacheDirectory();\n this.startCleanupTimer();\n }\n\n private async ensureCacheDirectory(): Promise<void> {\n try {\n await fs.access(this.config.cacheDirectory!);\n } catch {\n await fs.mkdir(this.config.cacheDirectory!, { recursive: true });\n }\n }\n\n private startCleanupTimer(): void {\n if (this.config.cleanupInterval! > 0) {\n this.cleanupTimer = setInterval(() => {\n this.cleanupExpiredEntries().catch(console.error);\n }, this.config.cleanupInterval!);\n }\n }\n\n private stopCleanupTimer(): void {\n if (this.cleanupTimer) {\n clearInterval(this.cleanupTimer);\n this.cleanupTimer = undefined;\n }\n }\n\n private getFilePath(key: string): string {\n // Create a safe filename from the key\n const safeKey = key.replace(/[^a-z0-9]/gi, '_');\n return path.join(this.config.cacheDirectory!, `${safeKey}.json`);\n }\n\n private async cleanupExpiredEntries(): Promise<void> {\n try {\n const files = await fs.readdir(this.config.cacheDirectory!);\n const now = Date.now();\n\n for (const file of files) {\n if (file.endsWith('.json')) {\n const filePath = path.join(this.config.cacheDirectory!, file);\n try {\n const content = await fs.readFile(filePath, 'utf-8');\n const item: CacheItem = JSON.parse(content);\n\n if (item.expiresAt && item.expiresAt < now) {\n await fs.unlink(filePath);\n }\n } catch {\n // If file is corrupted, delete it\n await fs.unlink(filePath).catch(() => {});\n }\n }\n }\n } catch (error) {\n // Ignore cleanup errors\n }\n }\n\n async get(key: string): Promise<JSONObject | JSONValue | undefined> {\n const filePath = this.getFilePath(key);\n\n try {\n const content = await fs.readFile(filePath, 'utf-8');\n const item: CacheItem = JSON.parse(content);\n\n // Check if item has expired\n if (item.expiresAt && item.expiresAt < Date.now()) {\n await fs.unlink(filePath).catch(() => {});\n return undefined;\n }\n\n return item.value;\n } catch (error) {\n return undefined;\n }\n }\n\n async put(key: string, value: JSONObject | JSONValue, ttl?: number): Promise<void> {\n const filePath = this.getFilePath(key);\n const now = Date.now();\n const effectiveTTL = ttl ?? this.config.defaultTTL!;\n\n const item: CacheItem = {\n value,\n createdAt: now,\n expiresAt: effectiveTTL > 0 ? now + effectiveTTL * 1000 : undefined,\n };\n\n this.ensureCacheDirectory();\n await fs.writeFile(filePath, JSON.stringify(item), 'utf-8');\n }\n\n async delete(key: string): Promise<void> {\n const filePath = this.getFilePath(key);\n\n try {\n await fs.unlink(filePath);\n } catch {\n // File doesn't exist, that's fine\n }\n }\n\n async has(key: string): Promise<boolean> {\n const value = await this.get(key);\n return value !== undefined;\n }\n\n async increment(key: string, amount: number = 1): Promise<number> {\n const filePath = this.getFilePath(key);\n\n // Use a lock file to ensure atomicity\n const lockPath = `${filePath}.lock`;\n\n // Simple file-based locking mechanism\n let lockAcquired = false;\n let retries = 0;\n const maxRetries = 50;\n\n while (!lockAcquired && retries < maxRetries) {\n try {\n // Try to create lock file exclusively\n await fs.writeFile(lockPath, '', { flag: 'wx' });\n lockAcquired = true;\n } catch {\n // Lock exists, wait a bit and retry\n await new Promise((resolve) => setTimeout(resolve, 10));\n retries++;\n }\n }\n\n if (!lockAcquired) {\n throw new Error('Failed to acquire lock for increment operation');\n }\n\n try {\n let currentValue = 0;\n let item: CacheItem | undefined;\n\n // Read current value\n try {\n const content = await fs.readFile(filePath, 'utf-8');\n const parsedItem = JSON.parse(content);\n\n // Check if item has expired\n if (parsedItem.expiresAt && parsedItem.expiresAt < Date.now()) {\n item = undefined;\n } else {\n item = parsedItem;\n currentValue = typeof parsedItem.value === 'number' ? parsedItem.value : 0;\n }\n } catch {\n // File doesn't exist or is corrupted, start from 0\n }\n\n // Calculate new value\n const newValue = currentValue + amount;\n\n // Write back with same TTL if it existed\n const now = Date.now();\n const newItem: CacheItem = {\n value: newValue,\n createdAt: item?.createdAt ?? now,\n expiresAt: item?.expiresAt,\n };\n\n await fs.writeFile(filePath, JSON.stringify(newItem), 'utf-8');\n\n return newValue;\n } finally {\n // Release lock\n try {\n await fs.unlink(lockPath);\n } catch {\n // Ignore errors when removing lock file\n }\n }\n }\n}\n"],"mappings":";;AAAA,YAAY,QAAQ;AACpB,YAAY,UAAU;AAgBf,MAAM,kBAAoD;AAAA,EAjBjE,OAiBiE;AAAA;AAAA;AAAA,EACvD,SAA0B;AAAA,IAChC,gBAAgB,KAAK,KAAK,QAAQ,IAAI,GAAG,OAAO;AAAA,IAChD,YAAY,OAAO;AAAA,IACnB,iBAAiB,MAAM;AAAA,EACzB;AAAA,EAEQ;AAAA,EAER,YAAY,SAA0B,CAAC,GAAG;AACxC,SAAK,SAAS,EAAE,GAAG,KAAK,QAAQ,GAAG,OAAO;AAE1C,SAAK,qBAAqB;AAC1B,SAAK,kBAAkB;AAAA,EACzB;AAAA,EAEA,MAAc,uBAAsC;AAClD,QAAI;AACF,YAAM,GAAG,OAAO,KAAK,OAAO,cAAe;AAAA,IAC7C,QAAQ;AACN,YAAM,GAAG,MAAM,KAAK,OAAO,gBAAiB,EAAE,WAAW,KAAK,CAAC;AAAA,IACjE;AAAA,EACF;AAAA,EAEQ,oBAA0B;AAChC,QAAI,KAAK,OAAO,kBAAmB,GAAG;AACpC,WAAK,eAAe,YAAY,MAAM;AACpC,aAAK,sBAAsB,EAAE,MAAM,QAAQ,KAAK;AAAA,MAClD,GAAG,KAAK,OAAO,eAAgB;AAAA,IACjC;AAAA,EACF;AAAA,EAEQ,mBAAyB;AAC/B,QAAI,KAAK,cAAc;AACrB,oBAAc,KAAK,YAAY;AAC/B,WAAK,eAAe;AAAA,IACtB;AAAA,EACF;AAAA,EAEQ,YAAY,KAAqB;AAEvC,UAAM,UAAU,IAAI,QAAQ,eAAe,GAAG;AAC9C,WAAO,KAAK,KAAK,KAAK,OAAO,gBAAiB,GAAG,OAAO,OAAO;AAAA,EACjE;AAAA,EAEA,MAAc,wBAAuC;AACnD,QAAI;AACF,YAAM,QAAQ,MAAM,GAAG,QAAQ,KAAK,OAAO,cAAe;AAC1D,YAAM,MAAM,KAAK,IAAI;AAErB,iBAAW,QAAQ,OAAO;AACxB,YAAI,KAAK,SAAS,OAAO,GAAG;AAC1B,gBAAM,WAAW,KAAK,KAAK,KAAK,OAAO,gBAAiB,IAAI;AAC5D,cAAI;AACF,kBAAM,UAAU,MAAM,GAAG,SAAS,UAAU,OAAO;AACnD,kBAAM,OAAkB,KAAK,MAAM,OAAO;AAE1C,gBAAI,KAAK,aAAa,KAAK,YAAY,KAAK;AAC1C,oBAAM,GAAG,OAAO,QAAQ;AAAA,YAC1B;AAAA,UACF,QAAQ;AAEN,kBAAM,GAAG,OAAO,QAAQ,EAAE,MAAM,MAAM;AAAA,YAAC,CAAC;AAAA,UAC1C;AAAA,QACF;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AAAA,IAEhB;AAAA,EACF;AAAA,EAEA,MAAM,IAAI,KAA0D;AAClE,UAAM,WAAW,KAAK,YAAY,GAAG;AAErC,QAAI;AACF,YAAM,UAAU,MAAM,GAAG,SAAS,UAAU,OAAO;AACnD,YAAM,OAAkB,KAAK,MAAM,OAAO;AAG1C,UAAI,KAAK,aAAa,KAAK,YAAY,KAAK,IAAI,GAAG;AACjD,cAAM,GAAG,OAAO,QAAQ,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AACxC,eAAO;AAAA,MACT;AAEA,aAAO,KAAK;AAAA,IACd,SAAS,OAAO;AACd,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,IAAI,KAAa,OAA+B,KAA6B;AACjF,UAAM,WAAW,KAAK,YAAY,GAAG;AACrC,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,eAAe,OAAO,KAAK,OAAO;AAExC,UAAM,OAAkB;AAAA,MACtB;AAAA,MACA,WAAW;AAAA,MACX,WAAW,eAAe,IAAI,MAAM,eAAe,MAAO;AAAA,IAC5D;AAEA,SAAK,qBAAqB;AAC1B,UAAM,GAAG,UAAU,UAAU,KAAK,UAAU,IAAI,GAAG,OAAO;AAAA,EAC5D;AAAA,EAEA,MAAM,OAAO,KAA4B;AACvC,UAAM,WAAW,KAAK,YAAY,GAAG;AAErC,QAAI;AACF,YAAM,GAAG,OAAO,QAAQ;AAAA,IAC1B,QAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEA,MAAM,IAAI,KAA+B;AACvC,UAAM,QAAQ,MAAM,KAAK,IAAI,GAAG;AAChC,WAAO,UAAU;AAAA,EACnB;AAAA,EAEA,MAAM,UAAU,KAAa,SAAiB,GAAoB;AAChE,UAAM,WAAW,KAAK,YAAY,GAAG;AAGrC,UAAM,WAAW,GAAG,QAAQ;AAG5B,QAAI,eAAe;AACnB,QAAI,UAAU;AACd,UAAM,aAAa;AAEnB,WAAO,CAAC,gBAAgB,UAAU,YAAY;AAC5C,UAAI;AAEF,cAAM,GAAG,UAAU,UAAU,IAAI,EAAE,MAAM,KAAK,CAAC;AAC/C,uBAAe;AAAA,MACjB,QAAQ;AAEN,cAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AACtD;AAAA,MACF;AAAA,IACF;AAEA,QAAI,CAAC,cAAc;AACjB,YAAM,IAAI,MAAM,gDAAgD;AAAA,IAClE;AAEA,QAAI;AACF,UAAI,eAAe;AACnB,UAAI;AAGJ,UAAI;AACF,cAAM,UAAU,MAAM,GAAG,SAAS,UAAU,OAAO;AACnD,cAAM,aAAa,KAAK,MAAM,OAAO;AAGrC,YAAI,WAAW,aAAa,WAAW,YAAY,KAAK,IAAI,GAAG;AAC7D,iBAAO;AAAA,QACT,OAAO;AACL,iBAAO;AACP,yBAAe,OAAO,WAAW,UAAU,WAAW,WAAW,QAAQ;AAAA,QAC3E;AAAA,MACF,QAAQ;AAAA,MAER;AAGA,YAAM,WAAW,eAAe;AAGhC,YAAM,MAAM,KAAK,IAAI;AACrB,YAAM,UAAqB;AAAA,QACzB,OAAO;AAAA,QACP,WAAW,MAAM,aAAa;AAAA,QAC9B,WAAW,MAAM;AAAA,MACnB;AAEA,YAAM,GAAG,UAAU,UAAU,KAAK,UAAU,OAAO,GAAG,OAAO;AAE7D,aAAO;AAAA,IACT,UAAE;AAEA,UAAI;AACF,cAAM,GAAG,OAAO,QAAQ;AAAA,MAC1B,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
|
@@ -15,6 +15,7 @@ declare class MemcacheCacheProvider implements CacheProviderInterface {
|
|
|
15
15
|
put(key: string, value: JSONObject | JSONValue, ttl?: number): Promise<void>;
|
|
16
16
|
delete(key: string): Promise<void>;
|
|
17
17
|
has(key: string): Promise<boolean>;
|
|
18
|
+
increment(key: string, amount?: number): Promise<number>;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export { MemcacheCacheProvider, type MemcachedConfig };
|
|
@@ -66,6 +66,27 @@ class MemcacheCacheProvider {
|
|
|
66
66
|
});
|
|
67
67
|
});
|
|
68
68
|
}
|
|
69
|
+
async increment(key, amount = 1) {
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
this.client.incr(key, amount, (err, result) => {
|
|
72
|
+
if (err) {
|
|
73
|
+
reject(err);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (result === false) {
|
|
77
|
+
this.client.set(key, amount.toString(), this.defaultTTL, (setErr) => {
|
|
78
|
+
if (setErr) {
|
|
79
|
+
reject(setErr);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
resolve(amount);
|
|
83
|
+
});
|
|
84
|
+
} else {
|
|
85
|
+
resolve(result);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
}
|
|
69
90
|
}
|
|
70
91
|
export {
|
|
71
92
|
MemcacheCacheProvider
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/providers/MemcacheCacheProvider.mts"],"sourcesContent":["import { CacheProviderInterface } from
|
|
1
|
+
{"version":3,"sources":["../../src/providers/MemcacheCacheProvider.mts"],"sourcesContent":["import { CacheProviderInterface } from '@/CacheProviderInterface.mjs';\nimport { JSONValue, JSONObject } from '@devbro/neko-helper';\nimport Memcached from 'memcached';\n\nexport interface MemcachedConfig {\n location?: Memcached.Location;\n options?: Memcached.options;\n}\n\nexport class MemcacheCacheProvider implements CacheProviderInterface {\n private client: Memcached;\n private defaultTTL: number = 3600; // default TTL in seconds\n\n constructor(private config: MemcachedConfig = {}) {\n this.client = new Memcached(config.location || 'localhost:11211', config.options || {});\n }\n\n async get(key: string): Promise<JSONValue | JSONObject | undefined> {\n return new Promise((resolve, reject) => {\n this.client.get(key, (err: Error | undefined, data: any) => {\n if (err) {\n reject(err);\n return;\n }\n\n if (data === undefined || data === null) {\n resolve(undefined);\n return;\n }\n\n try {\n // Memcached automatically handles JSON serialization/deserialization\n // but we need to ensure we return the correct type\n resolve(typeof data === 'string' ? JSON.parse(data) : data);\n } catch (parseErr) {\n // If parsing fails, return the raw value\n resolve(data);\n }\n });\n });\n }\n\n async put(key: string, value: JSONObject | JSONValue, ttl?: number): Promise<void> {\n return new Promise((resolve, reject) => {\n const serializedValue = JSON.stringify(value);\n const finalTTL = ttl ?? this.defaultTTL;\n\n this.client.set(key, serializedValue, finalTTL, (err: Error | undefined) => {\n if (err) {\n reject(err);\n return;\n }\n resolve();\n });\n });\n }\n\n async delete(key: string): Promise<void> {\n return new Promise((resolve, reject) => {\n this.client.del(key, (err: Error | undefined) => {\n if (err) {\n reject(err);\n return;\n }\n resolve();\n });\n });\n }\n\n async has(key: string): Promise<boolean> {\n return new Promise((resolve, reject) => {\n this.client.get(key, (err: Error | undefined, data: any) => {\n if (err) {\n reject(err);\n return;\n }\n resolve(data !== undefined && data !== null);\n });\n });\n }\n\n async increment(key: string, amount: number = 1): Promise<number> {\n return new Promise((resolve, reject) => {\n // Memcached incr are atomic operations\n this.client.incr(key, amount, (err: any, result: number | boolean) => {\n if (err) {\n reject(err);\n return;\n }\n\n // If key doesn't exist, result will be false\n if (result === false) {\n // Initialize the key with the amount value\n this.client.set(key, amount.toString(), this.defaultTTL, (setErr: Error | undefined) => {\n if (setErr) {\n reject(setErr);\n return;\n }\n resolve(amount);\n });\n } else {\n resolve(result as number);\n }\n });\n });\n }\n}\n"],"mappings":";;AAEA,OAAO,eAAe;AAOf,MAAM,sBAAwD;AAAA;AAAA,EAInE,YAAoB,SAA0B,CAAC,GAAG;AAA9B;AAClB,SAAK,SAAS,IAAI,UAAU,OAAO,YAAY,mBAAmB,OAAO,WAAW,CAAC,CAAC;AAAA,EACxF;AAAA,EAfF,OASqE;AAAA;AAAA;AAAA,EAC3D;AAAA,EACA,aAAqB;AAAA,EAM7B,MAAM,IAAI,KAA0D;AAClE,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,WAAK,OAAO,IAAI,KAAK,CAAC,KAAwB,SAAc;AAC1D,YAAI,KAAK;AACP,iBAAO,GAAG;AACV;AAAA,QACF;AAEA,YAAI,SAAS,UAAa,SAAS,MAAM;AACvC,kBAAQ,MAAS;AACjB;AAAA,QACF;AAEA,YAAI;AAGF,kBAAQ,OAAO,SAAS,WAAW,KAAK,MAAM,IAAI,IAAI,IAAI;AAAA,QAC5D,SAAS,UAAU;AAEjB,kBAAQ,IAAI;AAAA,QACd;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,IAAI,KAAa,OAA+B,KAA6B;AACjF,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,YAAM,kBAAkB,KAAK,UAAU,KAAK;AAC5C,YAAM,WAAW,OAAO,KAAK;AAE7B,WAAK,OAAO,IAAI,KAAK,iBAAiB,UAAU,CAAC,QAA2B;AAC1E,YAAI,KAAK;AACP,iBAAO,GAAG;AACV;AAAA,QACF;AACA,gBAAQ;AAAA,MACV,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,OAAO,KAA4B;AACvC,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,WAAK,OAAO,IAAI,KAAK,CAAC,QAA2B;AAC/C,YAAI,KAAK;AACP,iBAAO,GAAG;AACV;AAAA,QACF;AACA,gBAAQ;AAAA,MACV,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,IAAI,KAA+B;AACvC,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,WAAK,OAAO,IAAI,KAAK,CAAC,KAAwB,SAAc;AAC1D,YAAI,KAAK;AACP,iBAAO,GAAG;AACV;AAAA,QACF;AACA,gBAAQ,SAAS,UAAa,SAAS,IAAI;AAAA,MAC7C,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,UAAU,KAAa,SAAiB,GAAoB;AAChE,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AAEtC,WAAK,OAAO,KAAK,KAAK,QAAQ,CAAC,KAAU,WAA6B;AACpE,YAAI,KAAK;AACP,iBAAO,GAAG;AACV;AAAA,QACF;AAGA,YAAI,WAAW,OAAO;AAEpB,eAAK,OAAO,IAAI,KAAK,OAAO,SAAS,GAAG,KAAK,YAAY,CAAC,WAA8B;AACtF,gBAAI,QAAQ;AACV,qBAAO,MAAM;AACb;AAAA,YACF;AACA,oBAAQ,MAAM;AAAA,UAChB,CAAC;AAAA,QACH,OAAO;AACL,kBAAQ,MAAgB;AAAA,QAC1B;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AACF;","names":[]}
|
|
@@ -19,6 +19,7 @@ declare class MemoryCacheProvider implements CacheProviderInterface {
|
|
|
19
19
|
put(key: string, value: JSONObject | JSONValue, ttl?: number): Promise<void>;
|
|
20
20
|
delete(key: string): Promise<void>;
|
|
21
21
|
has(key: string): Promise<boolean>;
|
|
22
|
+
increment(key: string, amount?: number): Promise<number>;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export { type MemoryCacheConfig, MemoryCacheProvider };
|
|
@@ -91,6 +91,27 @@ class MemoryCacheProvider {
|
|
|
91
91
|
}
|
|
92
92
|
return true;
|
|
93
93
|
}
|
|
94
|
+
async increment(key, amount = 1) {
|
|
95
|
+
const item = this.cache.get(key);
|
|
96
|
+
const now = Date.now();
|
|
97
|
+
let currentValue = 0;
|
|
98
|
+
if (item) {
|
|
99
|
+
if (item.expiresAt && item.expiresAt < now) {
|
|
100
|
+
this.cache.delete(key);
|
|
101
|
+
} else {
|
|
102
|
+
currentValue = typeof item.value === "number" ? item.value : 0;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const newValue = currentValue + amount;
|
|
106
|
+
const newItem = {
|
|
107
|
+
value: newValue,
|
|
108
|
+
createdAt: item?.createdAt ?? now,
|
|
109
|
+
lastAccessed: now,
|
|
110
|
+
expiresAt: item?.expiresAt
|
|
111
|
+
};
|
|
112
|
+
this.cache.set(key, newItem);
|
|
113
|
+
return newValue;
|
|
114
|
+
}
|
|
94
115
|
}
|
|
95
116
|
export {
|
|
96
117
|
MemoryCacheProvider
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/providers/MemoryCacheProvider.mts"],"sourcesContent":["import { JSONObject, JSONValue } from '@devbro/neko-helper';\nimport { CacheProviderInterface } from '../CacheProviderInterface.mjs';\n\nexport interface MemoryCacheConfig {\n maxSize?: number;\n defaultTTL?: number;\n cleanupInterval?: number;\n}\n\ninterface CacheItem {\n value: any;\n expiresAt?: number;\n createdAt: number;\n lastAccessed: number;\n}\n\nexport class MemoryCacheProvider implements CacheProviderInterface {\n private cache = new Map<string, CacheItem>();\n private config: MemoryCacheConfig = {\n maxSize: 1000,\n defaultTTL: 3600,\n cleanupInterval: 600, // 10 minutes\n };\n\n private cleanupTimer?: NodeJS.Timeout;\n\n constructor(config: MemoryCacheConfig = {}) {\n this.config = { ...this.config, ...config };\n\n this.startCleanupTimer();\n }\n\n private startCleanupTimer(): void {\n if (this.config.cleanupInterval! > 0) {\n this.cleanupTimer = setInterval(() => {\n this.cleanupExpiredEntries();\n }, this.config.cleanupInterval! * 1000);\n }\n }\n\n private stopCleanupTimer(): void {\n if (this.cleanupTimer) {\n clearInterval(this.cleanupTimer);\n this.cleanupTimer = undefined;\n }\n }\n\n private cleanupExpiredEntries(): void {\n const now = Date.now();\n\n for (const [key, item] of this.cache.entries()) {\n if (item.expiresAt && item.expiresAt < now) {\n this.cache.delete(key);\n }\n }\n }\n\n private evictLRU(): void {\n if (this.cache.size <= this.config.maxSize!) {\n return;\n }\n\n // Find the least recently accessed item\n let oldestKey: string | null = null;\n let oldestTime = Date.now();\n\n for (const [key, item] of this.cache.entries()) {\n if (item.lastAccessed < oldestTime) {\n oldestTime = item.lastAccessed;\n oldestKey = key;\n }\n }\n\n if (oldestKey) {\n this.cache.delete(oldestKey);\n }\n }\n\n async get(key: string): Promise<JSONObject | JSONValue | undefined> {\n const item = this.cache.get(key);\n\n if (!item) {\n return undefined;\n }\n\n // Check if item has expired\n if (item.expiresAt && item.expiresAt < Date.now()) {\n this.cache.delete(key);\n return undefined;\n }\n\n // Update last accessed time for LRU\n item.lastAccessed = Date.now();\n\n return item.value;\n }\n\n async put(key: string, value: JSONObject | JSONValue, ttl?: number): Promise<void> {\n const now = Date.now();\n const effectiveTTL = ttl ?? this.config.defaultTTL!;\n\n const item: CacheItem = {\n value,\n createdAt: now,\n lastAccessed: now,\n expiresAt: effectiveTTL > 0 ? now + effectiveTTL * 1000 : undefined,\n };\n\n this.cache.set(key, item);\n\n // Evict items if we exceed maxSize\n this.evictLRU();\n }\n\n async delete(key: string): Promise<void> {\n this.cache.delete(key);\n }\n\n async has(key: string): Promise<boolean> {\n const item = this.cache.get(key);\n\n if (!item) {\n return false;\n }\n\n // Check if item has expired\n if (item.expiresAt && item.expiresAt < Date.now()) {\n this.cache.delete(key);\n return false;\n }\n\n return true;\n }\n}\n"],"mappings":";;AAgBO,MAAM,oBAAsD;AAAA,EAhBnE,OAgBmE;AAAA;AAAA;AAAA,EACzD,QAAQ,oBAAI,IAAuB;AAAA,EACnC,SAA4B;AAAA,IAClC,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,iBAAiB;AAAA;AAAA,EACnB;AAAA,EAEQ;AAAA,EAER,YAAY,SAA4B,CAAC,GAAG;AAC1C,SAAK,SAAS,EAAE,GAAG,KAAK,QAAQ,GAAG,OAAO;AAE1C,SAAK,kBAAkB;AAAA,EACzB;AAAA,EAEQ,oBAA0B;AAChC,QAAI,KAAK,OAAO,kBAAmB,GAAG;AACpC,WAAK,eAAe,YAAY,MAAM;AACpC,aAAK,sBAAsB;AAAA,MAC7B,GAAG,KAAK,OAAO,kBAAmB,GAAI;AAAA,IACxC;AAAA,EACF;AAAA,EAEQ,mBAAyB;AAC/B,QAAI,KAAK,cAAc;AACrB,oBAAc,KAAK,YAAY;AAC/B,WAAK,eAAe;AAAA,IACtB;AAAA,EACF;AAAA,EAEQ,wBAA8B;AACpC,UAAM,MAAM,KAAK,IAAI;AAErB,eAAW,CAAC,KAAK,IAAI,KAAK,KAAK,MAAM,QAAQ,GAAG;AAC9C,UAAI,KAAK,aAAa,KAAK,YAAY,KAAK;AAC1C,aAAK,MAAM,OAAO,GAAG;AAAA,MACvB;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,WAAiB;AACvB,QAAI,KAAK,MAAM,QAAQ,KAAK,OAAO,SAAU;AAC3C;AAAA,IACF;AAGA,QAAI,YAA2B;AAC/B,QAAI,aAAa,KAAK,IAAI;AAE1B,eAAW,CAAC,KAAK,IAAI,KAAK,KAAK,MAAM,QAAQ,GAAG;AAC9C,UAAI,KAAK,eAAe,YAAY;AAClC,qBAAa,KAAK;AAClB,oBAAY;AAAA,MACd;AAAA,IACF;AAEA,QAAI,WAAW;AACb,WAAK,MAAM,OAAO,SAAS;AAAA,IAC7B;AAAA,EACF;AAAA,EAEA,MAAM,IAAI,KAA0D;AAClE,UAAM,OAAO,KAAK,MAAM,IAAI,GAAG;AAE/B,QAAI,CAAC,MAAM;AACT,aAAO;AAAA,IACT;AAGA,QAAI,KAAK,aAAa,KAAK,YAAY,KAAK,IAAI,GAAG;AACjD,WAAK,MAAM,OAAO,GAAG;AACrB,aAAO;AAAA,IACT;AAGA,SAAK,eAAe,KAAK,IAAI;AAE7B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,IAAI,KAAa,OAA+B,KAA6B;AACjF,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,eAAe,OAAO,KAAK,OAAO;AAExC,UAAM,OAAkB;AAAA,MACtB;AAAA,MACA,WAAW;AAAA,MACX,cAAc;AAAA,MACd,WAAW,eAAe,IAAI,MAAM,eAAe,MAAO;AAAA,IAC5D;AAEA,SAAK,MAAM,IAAI,KAAK,IAAI;AAGxB,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,MAAM,OAAO,KAA4B;AACvC,SAAK,MAAM,OAAO,GAAG;AAAA,EACvB;AAAA,EAEA,MAAM,IAAI,KAA+B;AACvC,UAAM,OAAO,KAAK,MAAM,IAAI,GAAG;AAE/B,QAAI,CAAC,MAAM;AACT,aAAO;AAAA,IACT;AAGA,QAAI,KAAK,aAAa,KAAK,YAAY,KAAK,IAAI,GAAG;AACjD,WAAK,MAAM,OAAO,GAAG;AACrB,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/providers/MemoryCacheProvider.mts"],"sourcesContent":["import { JSONObject, JSONValue } from '@devbro/neko-helper';\nimport { CacheProviderInterface } from '../CacheProviderInterface.mjs';\n\nexport interface MemoryCacheConfig {\n maxSize?: number;\n defaultTTL?: number;\n cleanupInterval?: number;\n}\n\ninterface CacheItem {\n value: any;\n expiresAt?: number;\n createdAt: number;\n lastAccessed: number;\n}\n\nexport class MemoryCacheProvider implements CacheProviderInterface {\n private cache = new Map<string, CacheItem>();\n private config: MemoryCacheConfig = {\n maxSize: 1000,\n defaultTTL: 3600,\n cleanupInterval: 600, // 10 minutes\n };\n\n private cleanupTimer?: NodeJS.Timeout;\n\n constructor(config: MemoryCacheConfig = {}) {\n this.config = { ...this.config, ...config };\n\n this.startCleanupTimer();\n }\n\n private startCleanupTimer(): void {\n if (this.config.cleanupInterval! > 0) {\n this.cleanupTimer = setInterval(() => {\n this.cleanupExpiredEntries();\n }, this.config.cleanupInterval! * 1000);\n }\n }\n\n private stopCleanupTimer(): void {\n if (this.cleanupTimer) {\n clearInterval(this.cleanupTimer);\n this.cleanupTimer = undefined;\n }\n }\n\n private cleanupExpiredEntries(): void {\n const now = Date.now();\n\n for (const [key, item] of this.cache.entries()) {\n if (item.expiresAt && item.expiresAt < now) {\n this.cache.delete(key);\n }\n }\n }\n\n private evictLRU(): void {\n if (this.cache.size <= this.config.maxSize!) {\n return;\n }\n\n // Find the least recently accessed item\n let oldestKey: string | null = null;\n let oldestTime = Date.now();\n\n for (const [key, item] of this.cache.entries()) {\n if (item.lastAccessed < oldestTime) {\n oldestTime = item.lastAccessed;\n oldestKey = key;\n }\n }\n\n if (oldestKey) {\n this.cache.delete(oldestKey);\n }\n }\n\n async get(key: string): Promise<JSONObject | JSONValue | undefined> {\n const item = this.cache.get(key);\n\n if (!item) {\n return undefined;\n }\n\n // Check if item has expired\n if (item.expiresAt && item.expiresAt < Date.now()) {\n this.cache.delete(key);\n return undefined;\n }\n\n // Update last accessed time for LRU\n item.lastAccessed = Date.now();\n\n return item.value;\n }\n\n async put(key: string, value: JSONObject | JSONValue, ttl?: number): Promise<void> {\n const now = Date.now();\n const effectiveTTL = ttl ?? this.config.defaultTTL!;\n\n const item: CacheItem = {\n value,\n createdAt: now,\n lastAccessed: now,\n expiresAt: effectiveTTL > 0 ? now + effectiveTTL * 1000 : undefined,\n };\n\n this.cache.set(key, item);\n\n // Evict items if we exceed maxSize\n this.evictLRU();\n }\n\n async delete(key: string): Promise<void> {\n this.cache.delete(key);\n }\n\n async has(key: string): Promise<boolean> {\n const item = this.cache.get(key);\n\n if (!item) {\n return false;\n }\n\n // Check if item has expired\n if (item.expiresAt && item.expiresAt < Date.now()) {\n this.cache.delete(key);\n return false;\n }\n\n return true;\n }\n\n async increment(key: string, amount: number = 1): Promise<number> {\n const item = this.cache.get(key);\n const now = Date.now();\n\n let currentValue = 0;\n\n // Check if item exists and is not expired\n if (item) {\n if (item.expiresAt && item.expiresAt < now) {\n this.cache.delete(key);\n } else {\n // Get current value, ensure it's a number\n currentValue = typeof item.value === 'number' ? item.value : 0;\n }\n }\n\n // Calculate new value\n const newValue = currentValue + amount;\n\n // Store the new value with the same TTL if it existed\n const newItem: CacheItem = {\n value: newValue,\n createdAt: item?.createdAt ?? now,\n lastAccessed: now,\n expiresAt: item?.expiresAt,\n };\n\n this.cache.set(key, newItem);\n\n return newValue;\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -13,6 +13,7 @@ declare class RedisCacheProvider implements CacheProviderInterface {
|
|
|
13
13
|
put(key: string, value: JSONValue | JSONObject, ttl?: number): Promise<void>;
|
|
14
14
|
delete(key: string): Promise<void>;
|
|
15
15
|
has(key: string): Promise<boolean>;
|
|
16
|
+
increment(key: string, amount?: number): Promise<number>;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
export { RedisCacheProvider };
|
|
@@ -51,6 +51,10 @@ class RedisCacheProvider {
|
|
|
51
51
|
const result = await this.client.exists(key);
|
|
52
52
|
return result === 1;
|
|
53
53
|
}
|
|
54
|
+
async increment(key, amount = 1) {
|
|
55
|
+
await this.ensureConnection();
|
|
56
|
+
return await this.client.incrBy(key, amount);
|
|
57
|
+
}
|
|
54
58
|
}
|
|
55
59
|
export {
|
|
56
60
|
RedisCacheProvider
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/providers/RedisCacheProvider.mts"],"sourcesContent":["import { createClient, RedisClientOptions, RedisClientType } from 'redis';\nimport { CacheProviderInterface } from '../CacheProviderInterface.mjs';\nimport { JSONValue, JSONObject } from '@devbro/neko-helper';\n\nexport class RedisCacheProvider implements CacheProviderInterface {\n private client: RedisClientType;\n private defaultTTL: number = 3600; // default TTL in seconds\n\n constructor(private config: RedisClientOptions) {\n this.client = this.createRedisClient();\n this.client.connect();\n }\n\n private createRedisClient(): any {\n let rc = createClient(this.config);\n return rc;\n }\n\n private async ensureConnection(): Promise<void> {\n if (!this.client.isOpen) {\n await this.client.connect();\n }\n }\n\n async get(key: string): Promise<JSONValue | JSONObject | undefined> {\n await this.ensureConnection();\n let rc = this.client.get(key);\n return rc.then((value) => {\n if (value === null || value === undefined) {\n return undefined;\n }\n return JSON.parse(value);\n });\n }\n\n async put(key: string, value: JSONValue | JSONObject, ttl?: number): Promise<void> {\n await this.ensureConnection();\n const serializedValue = JSON.stringify(value);\n ttl = ttl ?? this.defaultTTL;\n if (ttl && ttl > 0) {\n await this.client.setEx(key, ttl, serializedValue);\n } else {\n await this.client.set(key, serializedValue);\n }\n }\n\n async delete(key: string): Promise<void> {\n await this.ensureConnection();\n await this.client.del(key);\n }\n\n async has(key: string): Promise<boolean> {\n await this.ensureConnection();\n const result = await this.client.exists(key);\n return result === 1;\n }\n}\n"],"mappings":";;AAAA,SAAS,oBAAyD;AAI3D,MAAM,mBAAqD;AAAA;AAAA,EAIhE,YAAoB,QAA4B;AAA5B;AAClB,SAAK,SAAS,KAAK,kBAAkB;AACrC,SAAK,OAAO,QAAQ;AAAA,EACtB;AAAA,EAXF,OAIkE;AAAA;AAAA;AAAA,EACxD;AAAA,EACA,aAAqB;AAAA,EAOrB,oBAAyB;AAC/B,QAAI,KAAK,aAAa,KAAK,MAAM;AACjC,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,mBAAkC;AAC9C,QAAI,CAAC,KAAK,OAAO,QAAQ;AACvB,YAAM,KAAK,OAAO,QAAQ;AAAA,IAC5B;AAAA,EACF;AAAA,EAEA,MAAM,IAAI,KAA0D;AAClE,UAAM,KAAK,iBAAiB;AAC5B,QAAI,KAAK,KAAK,OAAO,IAAI,GAAG;AAC5B,WAAO,GAAG,KAAK,CAAC,UAAU;AACxB,UAAI,UAAU,QAAQ,UAAU,QAAW;AACzC,eAAO;AAAA,MACT;AACA,aAAO,KAAK,MAAM,KAAK;AAAA,IACzB,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,IAAI,KAAa,OAA+B,KAA6B;AACjF,UAAM,KAAK,iBAAiB;AAC5B,UAAM,kBAAkB,KAAK,UAAU,KAAK;AAC5C,UAAM,OAAO,KAAK;AAClB,QAAI,OAAO,MAAM,GAAG;AAClB,YAAM,KAAK,OAAO,MAAM,KAAK,KAAK,eAAe;AAAA,IACnD,OAAO;AACL,YAAM,KAAK,OAAO,IAAI,KAAK,eAAe;AAAA,IAC5C;AAAA,EACF;AAAA,EAEA,MAAM,OAAO,KAA4B;AACvC,UAAM,KAAK,iBAAiB;AAC5B,UAAM,KAAK,OAAO,IAAI,GAAG;AAAA,EAC3B;AAAA,EAEA,MAAM,IAAI,KAA+B;AACvC,UAAM,KAAK,iBAAiB;AAC5B,UAAM,SAAS,MAAM,KAAK,OAAO,OAAO,GAAG;AAC3C,WAAO,WAAW;AAAA,EACpB;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/providers/RedisCacheProvider.mts"],"sourcesContent":["import { createClient, RedisClientOptions, RedisClientType } from 'redis';\nimport { CacheProviderInterface } from '../CacheProviderInterface.mjs';\nimport { JSONValue, JSONObject } from '@devbro/neko-helper';\n\nexport class RedisCacheProvider implements CacheProviderInterface {\n private client: RedisClientType;\n private defaultTTL: number = 3600; // default TTL in seconds\n\n constructor(private config: RedisClientOptions) {\n this.client = this.createRedisClient();\n this.client.connect();\n }\n\n private createRedisClient(): any {\n let rc = createClient(this.config);\n return rc;\n }\n\n private async ensureConnection(): Promise<void> {\n if (!this.client.isOpen) {\n await this.client.connect();\n }\n }\n\n async get(key: string): Promise<JSONValue | JSONObject | undefined> {\n await this.ensureConnection();\n let rc = this.client.get(key);\n return rc.then((value) => {\n if (value === null || value === undefined) {\n return undefined;\n }\n return JSON.parse(value);\n });\n }\n\n async put(key: string, value: JSONValue | JSONObject, ttl?: number): Promise<void> {\n await this.ensureConnection();\n const serializedValue = JSON.stringify(value);\n ttl = ttl ?? this.defaultTTL;\n if (ttl && ttl > 0) {\n await this.client.setEx(key, ttl, serializedValue);\n } else {\n await this.client.set(key, serializedValue);\n }\n }\n\n async delete(key: string): Promise<void> {\n await this.ensureConnection();\n await this.client.del(key);\n }\n\n async has(key: string): Promise<boolean> {\n await this.ensureConnection();\n const result = await this.client.exists(key);\n return result === 1;\n }\n\n async increment(key: string, amount: number = 1): Promise<number> {\n await this.ensureConnection();\n // Redis INCRBY is atomic\n return await this.client.incrBy(key, amount);\n }\n}\n"],"mappings":";;AAAA,SAAS,oBAAyD;AAI3D,MAAM,mBAAqD;AAAA;AAAA,EAIhE,YAAoB,QAA4B;AAA5B;AAClB,SAAK,SAAS,KAAK,kBAAkB;AACrC,SAAK,OAAO,QAAQ;AAAA,EACtB;AAAA,EAXF,OAIkE;AAAA;AAAA;AAAA,EACxD;AAAA,EACA,aAAqB;AAAA,EAOrB,oBAAyB;AAC/B,QAAI,KAAK,aAAa,KAAK,MAAM;AACjC,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,mBAAkC;AAC9C,QAAI,CAAC,KAAK,OAAO,QAAQ;AACvB,YAAM,KAAK,OAAO,QAAQ;AAAA,IAC5B;AAAA,EACF;AAAA,EAEA,MAAM,IAAI,KAA0D;AAClE,UAAM,KAAK,iBAAiB;AAC5B,QAAI,KAAK,KAAK,OAAO,IAAI,GAAG;AAC5B,WAAO,GAAG,KAAK,CAAC,UAAU;AACxB,UAAI,UAAU,QAAQ,UAAU,QAAW;AACzC,eAAO;AAAA,MACT;AACA,aAAO,KAAK,MAAM,KAAK;AAAA,IACzB,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,IAAI,KAAa,OAA+B,KAA6B;AACjF,UAAM,KAAK,iBAAiB;AAC5B,UAAM,kBAAkB,KAAK,UAAU,KAAK;AAC5C,UAAM,OAAO,KAAK;AAClB,QAAI,OAAO,MAAM,GAAG;AAClB,YAAM,KAAK,OAAO,MAAM,KAAK,KAAK,eAAe;AAAA,IACnD,OAAO;AACL,YAAM,KAAK,OAAO,IAAI,KAAK,eAAe;AAAA,IAC5C;AAAA,EACF;AAAA,EAEA,MAAM,OAAO,KAA4B;AACvC,UAAM,KAAK,iBAAiB;AAC5B,UAAM,KAAK,OAAO,IAAI,GAAG;AAAA,EAC3B;AAAA,EAEA,MAAM,IAAI,KAA+B;AACvC,UAAM,KAAK,iBAAiB;AAC5B,UAAM,SAAS,MAAM,KAAK,OAAO,OAAO,GAAG;AAC3C,WAAO,WAAW;AAAA,EACpB;AAAA,EAEA,MAAM,UAAU,KAAa,SAAiB,GAAoB;AAChE,UAAM,KAAK,iBAAiB;AAE5B,WAAO,MAAM,KAAK,OAAO,OAAO,KAAK,MAAM;AAAA,EAC7C;AACF;","names":[]}
|