@devbro/neko-cache 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -0
- package/dist/CacheProviderInterface.d.mts +10 -0
- package/dist/CacheProviderInterface.mjs +1 -0
- package/dist/CacheProviderInterface.mjs.map +1 -0
- package/dist/cache.d.mts +17 -0
- package/dist/cache.mjs +37 -0
- package/dist/cache.mjs.map +1 -0
- package/dist/index.d.mts +10 -0
- package/dist/index.js +424 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +4 -0
- package/dist/index.mjs.map +1 -0
- package/dist/providers/DisabledCacheProvider.d.mts +13 -0
- package/dist/providers/DisabledCacheProvider.mjs +24 -0
- package/dist/providers/DisabledCacheProvider.mjs.map +1 -0
- package/dist/providers/FileCacheProvider.d.mts +24 -0
- package/dist/providers/FileCacheProvider.mjs +107 -0
- package/dist/providers/FileCacheProvider.mjs.map +1 -0
- package/dist/providers/MemcacheCacheProvider.d.mts +20 -0
- package/dist/providers/MemcacheCacheProvider.mjs +73 -0
- package/dist/providers/MemcacheCacheProvider.mjs.map +1 -0
- package/dist/providers/MemoryCacheProvider.d.mts +24 -0
- package/dist/providers/MemoryCacheProvider.mjs +98 -0
- package/dist/providers/MemoryCacheProvider.mjs.map +1 -0
- package/dist/providers/RedisCacheProvider.d.mts +18 -0
- package/dist/providers/RedisCacheProvider.mjs +58 -0
- package/dist/providers/RedisCacheProvider.mjs.map +1 -0
- package/dist/providers/index.d.mts +9 -0
- package/dist/providers/index.mjs +6 -0
- package/dist/providers/index.mjs.map +1 -0
- package/package.json +66 -0
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# @devbro/neko-scheduler
|
|
2
|
+
|
|
3
|
+
customizable cron/scheduling solution with support for context.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { Scheduler } from '@devbro/neko-scheduler';
|
|
7
|
+
|
|
8
|
+
const scheduler = new Scheduler();
|
|
9
|
+
|
|
10
|
+
//if you want to add a wrapper around all your schedules.
|
|
11
|
+
scheduler.setContextWrapper(async (tickFunction: Function) => {
|
|
12
|
+
// ???
|
|
13
|
+
await tickFunction();
|
|
14
|
+
// ???
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
scheduler.setErrorHandler((err: any) => {
|
|
18
|
+
console.log('scheduler error', err);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
scheduler
|
|
22
|
+
.call(() => {
|
|
23
|
+
console.log('Hello World');
|
|
24
|
+
})
|
|
25
|
+
.setName('test1')
|
|
26
|
+
.setCronTime('*/5 * * * *')
|
|
27
|
+
.setTimezone('UTC');
|
|
28
|
+
|
|
29
|
+
scheduler.start();
|
|
30
|
+
scheduler.stop();
|
|
31
|
+
```
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { JSONValue, JSONObject } from '@devbro/neko-helper';
|
|
2
|
+
|
|
3
|
+
interface CacheProviderInterface {
|
|
4
|
+
get(key: string): Promise<JSONValue | JSONObject | undefined>;
|
|
5
|
+
put(key: string, value: JSONObject | JSONValue, ttl?: number): Promise<void>;
|
|
6
|
+
delete(key: string): Promise<void>;
|
|
7
|
+
has(key: string): Promise<boolean>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type { CacheProviderInterface };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=CacheProviderInterface.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/cache.d.mts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { CacheProviderInterface } from './CacheProviderInterface.mjs';
|
|
2
|
+
import '@devbro/neko-helper';
|
|
3
|
+
|
|
4
|
+
type cacheOptions = {
|
|
5
|
+
ttl?: number;
|
|
6
|
+
};
|
|
7
|
+
declare class Cache {
|
|
8
|
+
private provider;
|
|
9
|
+
constructor(provider: CacheProviderInterface);
|
|
10
|
+
get<T>(key: string): Promise<T | undefined>;
|
|
11
|
+
put(key: string, value: any, ttl?: number): Promise<void>;
|
|
12
|
+
delete(key: string): Promise<void>;
|
|
13
|
+
has(key: string): Promise<boolean>;
|
|
14
|
+
remember<T>(key: string, callback: () => Promise<T>, options?: cacheOptions): Promise<T>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export { Cache, type cacheOptions };
|
package/dist/cache.mjs
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
class Cache {
|
|
4
|
+
static {
|
|
5
|
+
__name(this, "Cache");
|
|
6
|
+
}
|
|
7
|
+
provider;
|
|
8
|
+
constructor(provider) {
|
|
9
|
+
this.provider = provider;
|
|
10
|
+
}
|
|
11
|
+
async get(key) {
|
|
12
|
+
return this.provider.get(key);
|
|
13
|
+
}
|
|
14
|
+
async put(key, value, ttl) {
|
|
15
|
+
return this.provider.put(key, value, ttl);
|
|
16
|
+
}
|
|
17
|
+
async delete(key) {
|
|
18
|
+
return this.provider.delete(key);
|
|
19
|
+
}
|
|
20
|
+
async has(key) {
|
|
21
|
+
return this.provider.has(key);
|
|
22
|
+
}
|
|
23
|
+
async remember(key, callback, options = {}) {
|
|
24
|
+
options.ttl = options.ttl ?? 3600;
|
|
25
|
+
const cached = await this.get(key);
|
|
26
|
+
if (cached) {
|
|
27
|
+
return cached;
|
|
28
|
+
}
|
|
29
|
+
const result = await callback();
|
|
30
|
+
await this.put(key, result, options.ttl);
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export {
|
|
35
|
+
Cache
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=cache.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cache.ts"],"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: 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"],"mappings":";;AAMO,MAAMA,MAAAA;EAAb,OAAaA;;;;EACX,YAAoBC,UAAkC;SAAlCA,WAAAA;EAAmC;EAEvD,MAAMC,IAAOC,KAAqC;AAChD,WAAO,KAAKF,SAASC,IAAIC,GAAAA;EAC3B;EAEA,MAAMC,IAAID,KAAaE,OAAYC,KAA6B;AAC9D,WAAO,KAAKL,SAASG,IAAID,KAAKE,OAAOC,GAAAA;EACvC;EAEA,MAAMC,OAAOJ,KAA4B;AACvC,WAAO,KAAKF,SAASM,OAAOJ,GAAAA;EAC9B;EAEA,MAAMK,IAAIL,KAA+B;AACvC,WAAO,KAAKF,SAASO,IAAIL,GAAAA;EAC3B;EAEA,MAAMM,SACJN,KACAO,UACAC,UAAwB,CAAC,GACb;AACZA,YAAQL,MAAMK,QAAQL,OAAO;AAE7B,UAAMM,SAAS,MAAM,KAAKV,IAAOC,GAAAA;AACjC,QAAIS,QAAQ;AACV,aAAOA;IACT;AAEA,UAAMC,SAAS,MAAMH,SAAAA;AACrB,UAAM,KAAKN,IAAID,KAAKU,QAAQF,QAAQL,GAAG;AACvC,WAAOO;EACT;AACF;","names":["Cache","provider","get","key","put","value","ttl","delete","has","remember","callback","options","cached","result"]}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { Cache, cacheOptions } from './cache.mjs';
|
|
2
|
+
export { CacheProviderInterface } from './CacheProviderInterface.mjs';
|
|
3
|
+
export { RedisCacheProvider } from './providers/RedisCacheProvider.mjs';
|
|
4
|
+
export { FileCacheConfig, FileCacheProvider } from './providers/FileCacheProvider.mjs';
|
|
5
|
+
export { MemoryCacheConfig, MemoryCacheProvider } from './providers/MemoryCacheProvider.mjs';
|
|
6
|
+
export { MemcacheCacheProvider, MemcachedConfig } from './providers/MemcacheCacheProvider.mjs';
|
|
7
|
+
export { DisabledCacheProvider } from './providers/DisabledCacheProvider.mjs';
|
|
8
|
+
import '@devbro/neko-helper';
|
|
9
|
+
import 'redis';
|
|
10
|
+
import 'memcached';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
|
|
31
|
+
// src/index.ts
|
|
32
|
+
var index_exports = {};
|
|
33
|
+
__export(index_exports, {
|
|
34
|
+
Cache: () => Cache,
|
|
35
|
+
DisabledCacheProvider: () => DisabledCacheProvider,
|
|
36
|
+
FileCacheProvider: () => FileCacheProvider,
|
|
37
|
+
MemcacheCacheProvider: () => MemcacheCacheProvider,
|
|
38
|
+
MemoryCacheProvider: () => MemoryCacheProvider,
|
|
39
|
+
RedisCacheProvider: () => RedisCacheProvider
|
|
40
|
+
});
|
|
41
|
+
module.exports = __toCommonJS(index_exports);
|
|
42
|
+
|
|
43
|
+
// src/cache.ts
|
|
44
|
+
var Cache = class {
|
|
45
|
+
static {
|
|
46
|
+
__name(this, "Cache");
|
|
47
|
+
}
|
|
48
|
+
provider;
|
|
49
|
+
constructor(provider) {
|
|
50
|
+
this.provider = provider;
|
|
51
|
+
}
|
|
52
|
+
async get(key) {
|
|
53
|
+
return this.provider.get(key);
|
|
54
|
+
}
|
|
55
|
+
async put(key, value, ttl) {
|
|
56
|
+
return this.provider.put(key, value, ttl);
|
|
57
|
+
}
|
|
58
|
+
async delete(key) {
|
|
59
|
+
return this.provider.delete(key);
|
|
60
|
+
}
|
|
61
|
+
async has(key) {
|
|
62
|
+
return this.provider.has(key);
|
|
63
|
+
}
|
|
64
|
+
async remember(key, callback, options = {}) {
|
|
65
|
+
options.ttl = options.ttl ?? 3600;
|
|
66
|
+
const cached = await this.get(key);
|
|
67
|
+
if (cached) {
|
|
68
|
+
return cached;
|
|
69
|
+
}
|
|
70
|
+
const result = await callback();
|
|
71
|
+
await this.put(key, result, options.ttl);
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// src/providers/RedisCacheProvider.mts
|
|
77
|
+
var import_redis = require("redis");
|
|
78
|
+
var RedisCacheProvider = class {
|
|
79
|
+
// default TTL in seconds
|
|
80
|
+
constructor(config) {
|
|
81
|
+
this.config = config;
|
|
82
|
+
this.client = this.createRedisClient();
|
|
83
|
+
this.client.connect();
|
|
84
|
+
}
|
|
85
|
+
static {
|
|
86
|
+
__name(this, "RedisCacheProvider");
|
|
87
|
+
}
|
|
88
|
+
client;
|
|
89
|
+
defaultTTL = 3600;
|
|
90
|
+
createRedisClient() {
|
|
91
|
+
let rc = (0, import_redis.createClient)(this.config);
|
|
92
|
+
return rc;
|
|
93
|
+
}
|
|
94
|
+
async ensureConnection() {
|
|
95
|
+
if (!this.client.isOpen) {
|
|
96
|
+
await this.client.connect();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async get(key) {
|
|
100
|
+
await this.ensureConnection();
|
|
101
|
+
let rc = this.client.get(key);
|
|
102
|
+
return rc.then((value) => {
|
|
103
|
+
if (value === null || value === void 0) {
|
|
104
|
+
return void 0;
|
|
105
|
+
}
|
|
106
|
+
return JSON.parse(value);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
async put(key, value, ttl) {
|
|
110
|
+
await this.ensureConnection();
|
|
111
|
+
const serializedValue = JSON.stringify(value);
|
|
112
|
+
ttl = ttl ?? this.defaultTTL;
|
|
113
|
+
if (ttl && ttl > 0) {
|
|
114
|
+
await this.client.setEx(key, ttl, serializedValue);
|
|
115
|
+
} else {
|
|
116
|
+
await this.client.set(key, serializedValue);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
async delete(key) {
|
|
120
|
+
await this.ensureConnection();
|
|
121
|
+
await this.client.del(key);
|
|
122
|
+
}
|
|
123
|
+
async has(key) {
|
|
124
|
+
await this.ensureConnection();
|
|
125
|
+
const result = await this.client.exists(key);
|
|
126
|
+
return result === 1;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// src/providers/FileCacheProvider.mts
|
|
131
|
+
var fs = __toESM(require("fs/promises"), 1);
|
|
132
|
+
var path = __toESM(require("path"), 1);
|
|
133
|
+
var FileCacheProvider = class {
|
|
134
|
+
static {
|
|
135
|
+
__name(this, "FileCacheProvider");
|
|
136
|
+
}
|
|
137
|
+
config = {
|
|
138
|
+
cacheDirectory: path.join(process.cwd(), "cache"),
|
|
139
|
+
defaultTTL: 3600 * 1e3,
|
|
140
|
+
cleanupInterval: 300 * 1e3
|
|
141
|
+
};
|
|
142
|
+
cleanupTimer;
|
|
143
|
+
constructor(config = {}) {
|
|
144
|
+
this.config = { ...this.config, ...config };
|
|
145
|
+
this.ensureCacheDirectory();
|
|
146
|
+
this.startCleanupTimer();
|
|
147
|
+
}
|
|
148
|
+
async ensureCacheDirectory() {
|
|
149
|
+
try {
|
|
150
|
+
await fs.access(this.config.cacheDirectory);
|
|
151
|
+
} catch {
|
|
152
|
+
await fs.mkdir(this.config.cacheDirectory, { recursive: true });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
startCleanupTimer() {
|
|
156
|
+
if (this.config.cleanupInterval > 0) {
|
|
157
|
+
this.cleanupTimer = setInterval(() => {
|
|
158
|
+
this.cleanupExpiredEntries().catch(console.error);
|
|
159
|
+
}, this.config.cleanupInterval);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
stopCleanupTimer() {
|
|
163
|
+
if (this.cleanupTimer) {
|
|
164
|
+
clearInterval(this.cleanupTimer);
|
|
165
|
+
this.cleanupTimer = void 0;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
getFilePath(key) {
|
|
169
|
+
const safeKey = key.replace(/[^a-z0-9]/gi, "_");
|
|
170
|
+
return path.join(this.config.cacheDirectory, `${safeKey}.json`);
|
|
171
|
+
}
|
|
172
|
+
async cleanupExpiredEntries() {
|
|
173
|
+
try {
|
|
174
|
+
const files = await fs.readdir(this.config.cacheDirectory);
|
|
175
|
+
const now = Date.now();
|
|
176
|
+
for (const file of files) {
|
|
177
|
+
if (file.endsWith(".json")) {
|
|
178
|
+
const filePath = path.join(this.config.cacheDirectory, file);
|
|
179
|
+
try {
|
|
180
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
181
|
+
const item = JSON.parse(content);
|
|
182
|
+
if (item.expiresAt && item.expiresAt < now) {
|
|
183
|
+
await fs.unlink(filePath);
|
|
184
|
+
}
|
|
185
|
+
} catch {
|
|
186
|
+
await fs.unlink(filePath).catch(() => {
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
} catch (error) {
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async get(key) {
|
|
195
|
+
const filePath = this.getFilePath(key);
|
|
196
|
+
try {
|
|
197
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
198
|
+
const item = JSON.parse(content);
|
|
199
|
+
if (item.expiresAt && item.expiresAt < Date.now()) {
|
|
200
|
+
await fs.unlink(filePath).catch(() => {
|
|
201
|
+
});
|
|
202
|
+
return void 0;
|
|
203
|
+
}
|
|
204
|
+
return item.value;
|
|
205
|
+
} catch (error) {
|
|
206
|
+
return void 0;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
async put(key, value, ttl) {
|
|
210
|
+
const filePath = this.getFilePath(key);
|
|
211
|
+
const now = Date.now();
|
|
212
|
+
const effectiveTTL = ttl ?? this.config.defaultTTL;
|
|
213
|
+
const item = {
|
|
214
|
+
value,
|
|
215
|
+
createdAt: now,
|
|
216
|
+
expiresAt: effectiveTTL > 0 ? now + effectiveTTL * 1e3 : void 0
|
|
217
|
+
};
|
|
218
|
+
await fs.writeFile(filePath, JSON.stringify(item), "utf-8");
|
|
219
|
+
}
|
|
220
|
+
async delete(key) {
|
|
221
|
+
const filePath = this.getFilePath(key);
|
|
222
|
+
try {
|
|
223
|
+
await fs.unlink(filePath);
|
|
224
|
+
} catch {
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
async has(key) {
|
|
228
|
+
const value = await this.get(key);
|
|
229
|
+
return value !== void 0;
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// src/providers/MemoryCacheProvider.mts
|
|
234
|
+
var MemoryCacheProvider = class {
|
|
235
|
+
static {
|
|
236
|
+
__name(this, "MemoryCacheProvider");
|
|
237
|
+
}
|
|
238
|
+
cache = /* @__PURE__ */ new Map();
|
|
239
|
+
config = {
|
|
240
|
+
maxSize: 1e3,
|
|
241
|
+
defaultTTL: 3600,
|
|
242
|
+
cleanupInterval: 600
|
|
243
|
+
// 10 minutes
|
|
244
|
+
};
|
|
245
|
+
cleanupTimer;
|
|
246
|
+
constructor(config = {}) {
|
|
247
|
+
this.config = { ...this.config, ...config };
|
|
248
|
+
this.startCleanupTimer();
|
|
249
|
+
}
|
|
250
|
+
startCleanupTimer() {
|
|
251
|
+
if (this.config.cleanupInterval > 0) {
|
|
252
|
+
this.cleanupTimer = setInterval(() => {
|
|
253
|
+
this.cleanupExpiredEntries();
|
|
254
|
+
}, this.config.cleanupInterval * 1e3);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
stopCleanupTimer() {
|
|
258
|
+
if (this.cleanupTimer) {
|
|
259
|
+
clearInterval(this.cleanupTimer);
|
|
260
|
+
this.cleanupTimer = void 0;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
cleanupExpiredEntries() {
|
|
264
|
+
const now = Date.now();
|
|
265
|
+
for (const [key, item] of this.cache.entries()) {
|
|
266
|
+
if (item.expiresAt && item.expiresAt < now) {
|
|
267
|
+
this.cache.delete(key);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
evictLRU() {
|
|
272
|
+
if (this.cache.size <= this.config.maxSize) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
let oldestKey = null;
|
|
276
|
+
let oldestTime = Date.now();
|
|
277
|
+
for (const [key, item] of this.cache.entries()) {
|
|
278
|
+
if (item.lastAccessed < oldestTime) {
|
|
279
|
+
oldestTime = item.lastAccessed;
|
|
280
|
+
oldestKey = key;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (oldestKey) {
|
|
284
|
+
this.cache.delete(oldestKey);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
async get(key) {
|
|
288
|
+
const item = this.cache.get(key);
|
|
289
|
+
if (!item) {
|
|
290
|
+
return void 0;
|
|
291
|
+
}
|
|
292
|
+
if (item.expiresAt && item.expiresAt < Date.now()) {
|
|
293
|
+
this.cache.delete(key);
|
|
294
|
+
return void 0;
|
|
295
|
+
}
|
|
296
|
+
item.lastAccessed = Date.now();
|
|
297
|
+
return item.value;
|
|
298
|
+
}
|
|
299
|
+
async put(key, value, ttl) {
|
|
300
|
+
const now = Date.now();
|
|
301
|
+
const effectiveTTL = ttl ?? this.config.defaultTTL;
|
|
302
|
+
const item = {
|
|
303
|
+
value,
|
|
304
|
+
createdAt: now,
|
|
305
|
+
lastAccessed: now,
|
|
306
|
+
expiresAt: effectiveTTL > 0 ? now + effectiveTTL * 1e3 : void 0
|
|
307
|
+
};
|
|
308
|
+
this.cache.set(key, item);
|
|
309
|
+
this.evictLRU();
|
|
310
|
+
}
|
|
311
|
+
async delete(key) {
|
|
312
|
+
this.cache.delete(key);
|
|
313
|
+
}
|
|
314
|
+
async has(key) {
|
|
315
|
+
const item = this.cache.get(key);
|
|
316
|
+
if (!item) {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
if (item.expiresAt && item.expiresAt < Date.now()) {
|
|
320
|
+
this.cache.delete(key);
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
return true;
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
// src/providers/MemcacheCacheProvider.mts
|
|
328
|
+
var import_memcached = __toESM(require("memcached"), 1);
|
|
329
|
+
var MemcacheCacheProvider = class {
|
|
330
|
+
// default TTL in seconds
|
|
331
|
+
constructor(config = {}) {
|
|
332
|
+
this.config = config;
|
|
333
|
+
this.client = new import_memcached.default(config.location || "localhost:11211", config.options || {});
|
|
334
|
+
}
|
|
335
|
+
static {
|
|
336
|
+
__name(this, "MemcacheCacheProvider");
|
|
337
|
+
}
|
|
338
|
+
client;
|
|
339
|
+
defaultTTL = 3600;
|
|
340
|
+
async get(key) {
|
|
341
|
+
return new Promise((resolve, reject) => {
|
|
342
|
+
this.client.get(key, (err, data) => {
|
|
343
|
+
if (err) {
|
|
344
|
+
reject(err);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
if (data === void 0 || data === null) {
|
|
348
|
+
resolve(void 0);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
try {
|
|
352
|
+
resolve(typeof data === "string" ? JSON.parse(data) : data);
|
|
353
|
+
} catch (parseErr) {
|
|
354
|
+
resolve(data);
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
async put(key, value, ttl) {
|
|
360
|
+
return new Promise((resolve, reject) => {
|
|
361
|
+
const serializedValue = JSON.stringify(value);
|
|
362
|
+
const finalTTL = ttl ?? this.defaultTTL;
|
|
363
|
+
this.client.set(key, serializedValue, finalTTL, (err) => {
|
|
364
|
+
if (err) {
|
|
365
|
+
reject(err);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
resolve();
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
async delete(key) {
|
|
373
|
+
return new Promise((resolve, reject) => {
|
|
374
|
+
this.client.del(key, (err) => {
|
|
375
|
+
if (err) {
|
|
376
|
+
reject(err);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
resolve();
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
async has(key) {
|
|
384
|
+
return new Promise((resolve, reject) => {
|
|
385
|
+
this.client.get(key, (err, data) => {
|
|
386
|
+
if (err) {
|
|
387
|
+
reject(err);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
resolve(data !== void 0 && data !== null);
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
// src/providers/DisabledCacheProvider.mts
|
|
397
|
+
var DisabledCacheProvider = class {
|
|
398
|
+
constructor(config = {}) {
|
|
399
|
+
this.config = config;
|
|
400
|
+
}
|
|
401
|
+
static {
|
|
402
|
+
__name(this, "DisabledCacheProvider");
|
|
403
|
+
}
|
|
404
|
+
async get(key) {
|
|
405
|
+
return void 0;
|
|
406
|
+
}
|
|
407
|
+
async put(key, value, ttl) {
|
|
408
|
+
}
|
|
409
|
+
async delete(key) {
|
|
410
|
+
}
|
|
411
|
+
async has(key) {
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
416
|
+
0 && (module.exports = {
|
|
417
|
+
Cache,
|
|
418
|
+
DisabledCacheProvider,
|
|
419
|
+
FileCacheProvider,
|
|
420
|
+
MemcacheCacheProvider,
|
|
421
|
+
MemoryCacheProvider,
|
|
422
|
+
RedisCacheProvider
|
|
423
|
+
});
|
|
424
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/cache.ts","../src/providers/RedisCacheProvider.mts","../src/providers/FileCacheProvider.mts","../src/providers/MemoryCacheProvider.mts","../src/providers/MemcacheCacheProvider.mts","../src/providers/DisabledCacheProvider.mts"],"sourcesContent":["export * from './cache';\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 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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;;;;;;ACMO,IAAMA,QAAN,MAAMA;EAAb,OAAaA;;;;EACX,YAAoBC,UAAkC;SAAlCA,WAAAA;EAAmC;EAEvD,MAAMC,IAAOC,KAAqC;AAChD,WAAO,KAAKF,SAASC,IAAIC,GAAAA;EAC3B;EAEA,MAAMC,IAAID,KAAaE,OAAYC,KAA6B;AAC9D,WAAO,KAAKL,SAASG,IAAID,KAAKE,OAAOC,GAAAA;EACvC;EAEA,MAAMC,OAAOJ,KAA4B;AACvC,WAAO,KAAKF,SAASM,OAAOJ,GAAAA;EAC9B;EAEA,MAAMK,IAAIL,KAA+B;AACvC,WAAO,KAAKF,SAASO,IAAIL,GAAAA;EAC3B;EAEA,MAAMM,SACJN,KACAO,UACAC,UAAwB,CAAC,GACb;AACZA,YAAQL,MAAMK,QAAQL,OAAO;AAE7B,UAAMM,SAAS,MAAM,KAAKV,IAAOC,GAAAA;AACjC,QAAIS,QAAQ;AACV,aAAOA;IACT;AAEA,UAAMC,SAAS,MAAMH,SAAAA;AACrB,UAAM,KAAKN,IAAID,KAAKU,QAAQF,QAAQL,GAAG;AACvC,WAAOO;EACT;AACF;;;ACzCA,mBAAkE;AAI3D,IAAM,qBAAN,MAA2D;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,SAAK,2BAAa,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;;;ACxDA,SAAoB;AACpB,WAAsB;AAgBf,IAAM,oBAAN,MAA0D;AAAA,EAjBjE,OAiBiE;AAAA;AAAA;AAAA,EACvD,SAA0B;AAAA,IAChC,gBAAqB,UAAK,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,YAAS,UAAO,KAAK,OAAO,cAAe;AAAA,IAC7C,QAAQ;AACN,YAAS,SAAM,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,WAAY,UAAK,KAAK,OAAO,gBAAiB,GAAG,OAAO,OAAO;AAAA,EACjE;AAAA,EAEA,MAAc,wBAAuC;AACnD,QAAI;AACF,YAAM,QAAQ,MAAS,WAAQ,KAAK,OAAO,cAAe;AAC1D,YAAM,MAAM,KAAK,IAAI;AAErB,iBAAW,QAAQ,OAAO;AACxB,YAAI,KAAK,SAAS,OAAO,GAAG;AAC1B,gBAAM,WAAgB,UAAK,KAAK,OAAO,gBAAiB,IAAI;AAC5D,cAAI;AACF,kBAAM,UAAU,MAAS,YAAS,UAAU,OAAO;AACnD,kBAAM,OAAkB,KAAK,MAAM,OAAO;AAE1C,gBAAI,KAAK,aAAa,KAAK,YAAY,KAAK;AAC1C,oBAAS,UAAO,QAAQ;AAAA,YAC1B;AAAA,UACF,QAAQ;AAEN,kBAAS,UAAO,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,MAAS,YAAS,UAAU,OAAO;AACnD,YAAM,OAAkB,KAAK,MAAM,OAAO;AAG1C,UAAI,KAAK,aAAa,KAAK,YAAY,KAAK,IAAI,GAAG;AACjD,cAAS,UAAO,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,UAAS,aAAU,UAAU,KAAK,UAAU,IAAI,GAAG,OAAO;AAAA,EAC5D;AAAA,EAEA,MAAM,OAAO,KAA4B;AACvC,UAAM,WAAW,KAAK,YAAY,GAAG;AAErC,QAAI;AACF,YAAS,UAAO,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;AACF;;;ACvHO,IAAM,sBAAN,MAA4D;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;;;ACnIA,uBAAsB;AAOf,IAAM,wBAAN,MAA8D;AAAA;AAAA,EAInE,YAAoB,SAA0B,CAAC,GAAG;AAA9B;AAClB,SAAK,SAAS,IAAI,iBAAAC,QAAU,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;AAIF;;;AC/EO,IAAM,wBAAN,MAA8D;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":["Cache","provider","get","key","put","value","ttl","delete","has","remember","callback","options","cached","result","Memcached"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from './cache';\nexport * from './CacheProviderInterface.mjs';\nexport * from './providers/index.mjs';\n"],"mappings":"AAAA,cAAc;AACd,cAAc;AACd,cAAc;","names":[]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { CacheProviderInterface } from '../CacheProviderInterface.mjs';
|
|
2
|
+
import { JSONValue, JSONObject } from '@devbro/neko-helper';
|
|
3
|
+
|
|
4
|
+
declare class DisabledCacheProvider implements CacheProviderInterface {
|
|
5
|
+
private config;
|
|
6
|
+
constructor(config?: {});
|
|
7
|
+
get(key: string): Promise<JSONValue | JSONObject | undefined>;
|
|
8
|
+
put(key: string, value: JSONValue | JSONObject, ttl?: number): Promise<void>;
|
|
9
|
+
delete(key: string): Promise<void>;
|
|
10
|
+
has(key: string): Promise<boolean>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { DisabledCacheProvider };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
class DisabledCacheProvider {
|
|
4
|
+
constructor(config = {}) {
|
|
5
|
+
this.config = config;
|
|
6
|
+
}
|
|
7
|
+
static {
|
|
8
|
+
__name(this, "DisabledCacheProvider");
|
|
9
|
+
}
|
|
10
|
+
async get(key) {
|
|
11
|
+
return void 0;
|
|
12
|
+
}
|
|
13
|
+
async put(key, value, ttl) {
|
|
14
|
+
}
|
|
15
|
+
async delete(key) {
|
|
16
|
+
}
|
|
17
|
+
async has(key) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export {
|
|
22
|
+
DisabledCacheProvider
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=DisabledCacheProvider.mjs.map
|
|
@@ -0,0 +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":[]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { CacheProviderInterface } from '../CacheProviderInterface.mjs';
|
|
2
|
+
import { JSONObject, JSONValue } from '@devbro/neko-helper';
|
|
3
|
+
|
|
4
|
+
interface FileCacheConfig {
|
|
5
|
+
cacheDirectory?: string;
|
|
6
|
+
defaultTTL?: number;
|
|
7
|
+
cleanupInterval?: number;
|
|
8
|
+
}
|
|
9
|
+
declare class FileCacheProvider implements CacheProviderInterface {
|
|
10
|
+
private config;
|
|
11
|
+
private cleanupTimer?;
|
|
12
|
+
constructor(config?: FileCacheConfig);
|
|
13
|
+
private ensureCacheDirectory;
|
|
14
|
+
private startCleanupTimer;
|
|
15
|
+
private stopCleanupTimer;
|
|
16
|
+
private getFilePath;
|
|
17
|
+
private cleanupExpiredEntries;
|
|
18
|
+
get(key: string): Promise<JSONObject | JSONValue | undefined>;
|
|
19
|
+
put(key: string, value: JSONObject | JSONValue, ttl?: number): Promise<void>;
|
|
20
|
+
delete(key: string): Promise<void>;
|
|
21
|
+
has(key: string): Promise<boolean>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { type FileCacheConfig, FileCacheProvider };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
import * as fs from "fs/promises";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
class FileCacheProvider {
|
|
6
|
+
static {
|
|
7
|
+
__name(this, "FileCacheProvider");
|
|
8
|
+
}
|
|
9
|
+
config = {
|
|
10
|
+
cacheDirectory: path.join(process.cwd(), "cache"),
|
|
11
|
+
defaultTTL: 3600 * 1e3,
|
|
12
|
+
cleanupInterval: 300 * 1e3
|
|
13
|
+
};
|
|
14
|
+
cleanupTimer;
|
|
15
|
+
constructor(config = {}) {
|
|
16
|
+
this.config = { ...this.config, ...config };
|
|
17
|
+
this.ensureCacheDirectory();
|
|
18
|
+
this.startCleanupTimer();
|
|
19
|
+
}
|
|
20
|
+
async ensureCacheDirectory() {
|
|
21
|
+
try {
|
|
22
|
+
await fs.access(this.config.cacheDirectory);
|
|
23
|
+
} catch {
|
|
24
|
+
await fs.mkdir(this.config.cacheDirectory, { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
startCleanupTimer() {
|
|
28
|
+
if (this.config.cleanupInterval > 0) {
|
|
29
|
+
this.cleanupTimer = setInterval(() => {
|
|
30
|
+
this.cleanupExpiredEntries().catch(console.error);
|
|
31
|
+
}, this.config.cleanupInterval);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
stopCleanupTimer() {
|
|
35
|
+
if (this.cleanupTimer) {
|
|
36
|
+
clearInterval(this.cleanupTimer);
|
|
37
|
+
this.cleanupTimer = void 0;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
getFilePath(key) {
|
|
41
|
+
const safeKey = key.replace(/[^a-z0-9]/gi, "_");
|
|
42
|
+
return path.join(this.config.cacheDirectory, `${safeKey}.json`);
|
|
43
|
+
}
|
|
44
|
+
async cleanupExpiredEntries() {
|
|
45
|
+
try {
|
|
46
|
+
const files = await fs.readdir(this.config.cacheDirectory);
|
|
47
|
+
const now = Date.now();
|
|
48
|
+
for (const file of files) {
|
|
49
|
+
if (file.endsWith(".json")) {
|
|
50
|
+
const filePath = path.join(this.config.cacheDirectory, file);
|
|
51
|
+
try {
|
|
52
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
53
|
+
const item = JSON.parse(content);
|
|
54
|
+
if (item.expiresAt && item.expiresAt < now) {
|
|
55
|
+
await fs.unlink(filePath);
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
await fs.unlink(filePath).catch(() => {
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
} catch (error) {
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async get(key) {
|
|
67
|
+
const filePath = this.getFilePath(key);
|
|
68
|
+
try {
|
|
69
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
70
|
+
const item = JSON.parse(content);
|
|
71
|
+
if (item.expiresAt && item.expiresAt < Date.now()) {
|
|
72
|
+
await fs.unlink(filePath).catch(() => {
|
|
73
|
+
});
|
|
74
|
+
return void 0;
|
|
75
|
+
}
|
|
76
|
+
return item.value;
|
|
77
|
+
} catch (error) {
|
|
78
|
+
return void 0;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async put(key, value, ttl) {
|
|
82
|
+
const filePath = this.getFilePath(key);
|
|
83
|
+
const now = Date.now();
|
|
84
|
+
const effectiveTTL = ttl ?? this.config.defaultTTL;
|
|
85
|
+
const item = {
|
|
86
|
+
value,
|
|
87
|
+
createdAt: now,
|
|
88
|
+
expiresAt: effectiveTTL > 0 ? now + effectiveTTL * 1e3 : void 0
|
|
89
|
+
};
|
|
90
|
+
await fs.writeFile(filePath, JSON.stringify(item), "utf-8");
|
|
91
|
+
}
|
|
92
|
+
async delete(key) {
|
|
93
|
+
const filePath = this.getFilePath(key);
|
|
94
|
+
try {
|
|
95
|
+
await fs.unlink(filePath);
|
|
96
|
+
} catch {
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async has(key) {
|
|
100
|
+
const value = await this.get(key);
|
|
101
|
+
return value !== void 0;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
export {
|
|
105
|
+
FileCacheProvider
|
|
106
|
+
};
|
|
107
|
+
//# sourceMappingURL=FileCacheProvider.mjs.map
|
|
@@ -0,0 +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 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":[]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { CacheProviderInterface } from '../CacheProviderInterface.mjs';
|
|
2
|
+
import { JSONValue, JSONObject } from '@devbro/neko-helper';
|
|
3
|
+
import Memcached from 'memcached';
|
|
4
|
+
|
|
5
|
+
interface MemcachedConfig {
|
|
6
|
+
location?: Memcached.Location;
|
|
7
|
+
options?: Memcached.options;
|
|
8
|
+
}
|
|
9
|
+
declare class MemcacheCacheProvider implements CacheProviderInterface {
|
|
10
|
+
private config;
|
|
11
|
+
private client;
|
|
12
|
+
private defaultTTL;
|
|
13
|
+
constructor(config?: MemcachedConfig);
|
|
14
|
+
get(key: string): Promise<JSONValue | JSONObject | undefined>;
|
|
15
|
+
put(key: string, value: JSONObject | JSONValue, ttl?: number): Promise<void>;
|
|
16
|
+
delete(key: string): Promise<void>;
|
|
17
|
+
has(key: string): Promise<boolean>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { MemcacheCacheProvider, type MemcachedConfig };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
import Memcached from "memcached";
|
|
4
|
+
class MemcacheCacheProvider {
|
|
5
|
+
// default TTL in seconds
|
|
6
|
+
constructor(config = {}) {
|
|
7
|
+
this.config = config;
|
|
8
|
+
this.client = new Memcached(config.location || "localhost:11211", config.options || {});
|
|
9
|
+
}
|
|
10
|
+
static {
|
|
11
|
+
__name(this, "MemcacheCacheProvider");
|
|
12
|
+
}
|
|
13
|
+
client;
|
|
14
|
+
defaultTTL = 3600;
|
|
15
|
+
async get(key) {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
this.client.get(key, (err, data) => {
|
|
18
|
+
if (err) {
|
|
19
|
+
reject(err);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (data === void 0 || data === null) {
|
|
23
|
+
resolve(void 0);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
resolve(typeof data === "string" ? JSON.parse(data) : data);
|
|
28
|
+
} catch (parseErr) {
|
|
29
|
+
resolve(data);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
async put(key, value, ttl) {
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
const serializedValue = JSON.stringify(value);
|
|
37
|
+
const finalTTL = ttl ?? this.defaultTTL;
|
|
38
|
+
this.client.set(key, serializedValue, finalTTL, (err) => {
|
|
39
|
+
if (err) {
|
|
40
|
+
reject(err);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
resolve();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async delete(key) {
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
this.client.del(key, (err) => {
|
|
50
|
+
if (err) {
|
|
51
|
+
reject(err);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
resolve();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
async has(key) {
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
this.client.get(key, (err, data) => {
|
|
61
|
+
if (err) {
|
|
62
|
+
reject(err);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
resolve(data !== void 0 && data !== null);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export {
|
|
71
|
+
MemcacheCacheProvider
|
|
72
|
+
};
|
|
73
|
+
//# sourceMappingURL=MemcacheCacheProvider.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
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\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;AAIF;","names":[]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { JSONObject, JSONValue } from '@devbro/neko-helper';
|
|
2
|
+
import { CacheProviderInterface } from '../CacheProviderInterface.mjs';
|
|
3
|
+
|
|
4
|
+
interface MemoryCacheConfig {
|
|
5
|
+
maxSize?: number;
|
|
6
|
+
defaultTTL?: number;
|
|
7
|
+
cleanupInterval?: number;
|
|
8
|
+
}
|
|
9
|
+
declare class MemoryCacheProvider implements CacheProviderInterface {
|
|
10
|
+
private cache;
|
|
11
|
+
private config;
|
|
12
|
+
private cleanupTimer?;
|
|
13
|
+
constructor(config?: MemoryCacheConfig);
|
|
14
|
+
private startCleanupTimer;
|
|
15
|
+
private stopCleanupTimer;
|
|
16
|
+
private cleanupExpiredEntries;
|
|
17
|
+
private evictLRU;
|
|
18
|
+
get(key: string): Promise<JSONObject | JSONValue | undefined>;
|
|
19
|
+
put(key: string, value: JSONObject | JSONValue, ttl?: number): Promise<void>;
|
|
20
|
+
delete(key: string): Promise<void>;
|
|
21
|
+
has(key: string): Promise<boolean>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { type MemoryCacheConfig, MemoryCacheProvider };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
class MemoryCacheProvider {
|
|
4
|
+
static {
|
|
5
|
+
__name(this, "MemoryCacheProvider");
|
|
6
|
+
}
|
|
7
|
+
cache = /* @__PURE__ */ new Map();
|
|
8
|
+
config = {
|
|
9
|
+
maxSize: 1e3,
|
|
10
|
+
defaultTTL: 3600,
|
|
11
|
+
cleanupInterval: 600
|
|
12
|
+
// 10 minutes
|
|
13
|
+
};
|
|
14
|
+
cleanupTimer;
|
|
15
|
+
constructor(config = {}) {
|
|
16
|
+
this.config = { ...this.config, ...config };
|
|
17
|
+
this.startCleanupTimer();
|
|
18
|
+
}
|
|
19
|
+
startCleanupTimer() {
|
|
20
|
+
if (this.config.cleanupInterval > 0) {
|
|
21
|
+
this.cleanupTimer = setInterval(() => {
|
|
22
|
+
this.cleanupExpiredEntries();
|
|
23
|
+
}, this.config.cleanupInterval * 1e3);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
stopCleanupTimer() {
|
|
27
|
+
if (this.cleanupTimer) {
|
|
28
|
+
clearInterval(this.cleanupTimer);
|
|
29
|
+
this.cleanupTimer = void 0;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
cleanupExpiredEntries() {
|
|
33
|
+
const now = Date.now();
|
|
34
|
+
for (const [key, item] of this.cache.entries()) {
|
|
35
|
+
if (item.expiresAt && item.expiresAt < now) {
|
|
36
|
+
this.cache.delete(key);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
evictLRU() {
|
|
41
|
+
if (this.cache.size <= this.config.maxSize) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
let oldestKey = null;
|
|
45
|
+
let oldestTime = Date.now();
|
|
46
|
+
for (const [key, item] of this.cache.entries()) {
|
|
47
|
+
if (item.lastAccessed < oldestTime) {
|
|
48
|
+
oldestTime = item.lastAccessed;
|
|
49
|
+
oldestKey = key;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (oldestKey) {
|
|
53
|
+
this.cache.delete(oldestKey);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async get(key) {
|
|
57
|
+
const item = this.cache.get(key);
|
|
58
|
+
if (!item) {
|
|
59
|
+
return void 0;
|
|
60
|
+
}
|
|
61
|
+
if (item.expiresAt && item.expiresAt < Date.now()) {
|
|
62
|
+
this.cache.delete(key);
|
|
63
|
+
return void 0;
|
|
64
|
+
}
|
|
65
|
+
item.lastAccessed = Date.now();
|
|
66
|
+
return item.value;
|
|
67
|
+
}
|
|
68
|
+
async put(key, value, ttl) {
|
|
69
|
+
const now = Date.now();
|
|
70
|
+
const effectiveTTL = ttl ?? this.config.defaultTTL;
|
|
71
|
+
const item = {
|
|
72
|
+
value,
|
|
73
|
+
createdAt: now,
|
|
74
|
+
lastAccessed: now,
|
|
75
|
+
expiresAt: effectiveTTL > 0 ? now + effectiveTTL * 1e3 : void 0
|
|
76
|
+
};
|
|
77
|
+
this.cache.set(key, item);
|
|
78
|
+
this.evictLRU();
|
|
79
|
+
}
|
|
80
|
+
async delete(key) {
|
|
81
|
+
this.cache.delete(key);
|
|
82
|
+
}
|
|
83
|
+
async has(key) {
|
|
84
|
+
const item = this.cache.get(key);
|
|
85
|
+
if (!item) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
if (item.expiresAt && item.expiresAt < Date.now()) {
|
|
89
|
+
this.cache.delete(key);
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export {
|
|
96
|
+
MemoryCacheProvider
|
|
97
|
+
};
|
|
98
|
+
//# sourceMappingURL=MemoryCacheProvider.mjs.map
|
|
@@ -0,0 +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":[]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { RedisClientOptions } from 'redis';
|
|
2
|
+
import { CacheProviderInterface } from '../CacheProviderInterface.mjs';
|
|
3
|
+
import { JSONValue, JSONObject } from '@devbro/neko-helper';
|
|
4
|
+
|
|
5
|
+
declare class RedisCacheProvider implements CacheProviderInterface {
|
|
6
|
+
private config;
|
|
7
|
+
private client;
|
|
8
|
+
private defaultTTL;
|
|
9
|
+
constructor(config: RedisClientOptions);
|
|
10
|
+
private createRedisClient;
|
|
11
|
+
private ensureConnection;
|
|
12
|
+
get(key: string): Promise<JSONValue | JSONObject | undefined>;
|
|
13
|
+
put(key: string, value: JSONValue | JSONObject, ttl?: number): Promise<void>;
|
|
14
|
+
delete(key: string): Promise<void>;
|
|
15
|
+
has(key: string): Promise<boolean>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { RedisCacheProvider };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
import { createClient } from "redis";
|
|
4
|
+
class RedisCacheProvider {
|
|
5
|
+
// default TTL in seconds
|
|
6
|
+
constructor(config) {
|
|
7
|
+
this.config = config;
|
|
8
|
+
this.client = this.createRedisClient();
|
|
9
|
+
this.client.connect();
|
|
10
|
+
}
|
|
11
|
+
static {
|
|
12
|
+
__name(this, "RedisCacheProvider");
|
|
13
|
+
}
|
|
14
|
+
client;
|
|
15
|
+
defaultTTL = 3600;
|
|
16
|
+
createRedisClient() {
|
|
17
|
+
let rc = createClient(this.config);
|
|
18
|
+
return rc;
|
|
19
|
+
}
|
|
20
|
+
async ensureConnection() {
|
|
21
|
+
if (!this.client.isOpen) {
|
|
22
|
+
await this.client.connect();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async get(key) {
|
|
26
|
+
await this.ensureConnection();
|
|
27
|
+
let rc = this.client.get(key);
|
|
28
|
+
return rc.then((value) => {
|
|
29
|
+
if (value === null || value === void 0) {
|
|
30
|
+
return void 0;
|
|
31
|
+
}
|
|
32
|
+
return JSON.parse(value);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
async put(key, value, ttl) {
|
|
36
|
+
await this.ensureConnection();
|
|
37
|
+
const serializedValue = JSON.stringify(value);
|
|
38
|
+
ttl = ttl ?? this.defaultTTL;
|
|
39
|
+
if (ttl && ttl > 0) {
|
|
40
|
+
await this.client.setEx(key, ttl, serializedValue);
|
|
41
|
+
} else {
|
|
42
|
+
await this.client.set(key, serializedValue);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async delete(key) {
|
|
46
|
+
await this.ensureConnection();
|
|
47
|
+
await this.client.del(key);
|
|
48
|
+
}
|
|
49
|
+
async has(key) {
|
|
50
|
+
await this.ensureConnection();
|
|
51
|
+
const result = await this.client.exists(key);
|
|
52
|
+
return result === 1;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export {
|
|
56
|
+
RedisCacheProvider
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=RedisCacheProvider.mjs.map
|
|
@@ -0,0 +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":[]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { RedisCacheProvider } from './RedisCacheProvider.mjs';
|
|
2
|
+
export { FileCacheConfig, FileCacheProvider } from './FileCacheProvider.mjs';
|
|
3
|
+
export { MemoryCacheConfig, MemoryCacheProvider } from './MemoryCacheProvider.mjs';
|
|
4
|
+
export { MemcacheCacheProvider, MemcachedConfig } from './MemcacheCacheProvider.mjs';
|
|
5
|
+
export { DisabledCacheProvider } from './DisabledCacheProvider.mjs';
|
|
6
|
+
import 'redis';
|
|
7
|
+
import '../CacheProviderInterface.mjs';
|
|
8
|
+
import '@devbro/neko-helper';
|
|
9
|
+
import 'memcached';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/providers/index.mts"],"sourcesContent":["export * from './RedisCacheProvider.mjs';\nexport * from './FileCacheProvider.mjs';\nexport * from './MemoryCacheProvider.mjs';\nexport * from './MemcacheCacheProvider.mjs';\nexport * from './DisabledCacheProvider.mjs';\n"],"mappings":"AAAA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@devbro/neko-cache",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "driver agnostic caching implementation",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.mjs",
|
|
15
|
+
"require": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup",
|
|
20
|
+
"test": "vitest run",
|
|
21
|
+
"test:watch": "vitest watch",
|
|
22
|
+
"test:coverage": "vitest run --coverage",
|
|
23
|
+
"format": "eslint . --fix --ext ts,tsx --report-unused-disable-directives --max-warnings 0 ",
|
|
24
|
+
"prepare": "husky",
|
|
25
|
+
"prettier": "prettier --write .",
|
|
26
|
+
"clean": "rm -rf dist"
|
|
27
|
+
},
|
|
28
|
+
"author": "Farzad Meow Khalafi",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/memcached": "^2.2.10",
|
|
32
|
+
"@types/node": "^22.14.1",
|
|
33
|
+
"@typescript-eslint/eslint-plugin": "^7.1.1",
|
|
34
|
+
"@typescript-eslint/parser": "^7.1.1",
|
|
35
|
+
"eslint": "8.57.0",
|
|
36
|
+
"husky": "^9.1.7",
|
|
37
|
+
"prettier": "^3.5.3",
|
|
38
|
+
"tsup": "^8.0.2",
|
|
39
|
+
"typescript": "^5.3.3",
|
|
40
|
+
"vitest": "^3.2.4"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@devbro/neko-helper": "0.1.*",
|
|
44
|
+
"memcached": "^2.2.2",
|
|
45
|
+
"redis": "^4.6.0"
|
|
46
|
+
},
|
|
47
|
+
"directories": {
|
|
48
|
+
"doc": "docs",
|
|
49
|
+
"test": "tests"
|
|
50
|
+
},
|
|
51
|
+
"repository": {
|
|
52
|
+
"type": "git",
|
|
53
|
+
"url": "git+ssh://git@github.com/devbro1/pashmak.git"
|
|
54
|
+
},
|
|
55
|
+
"keywords": [
|
|
56
|
+
"pashmak"
|
|
57
|
+
],
|
|
58
|
+
"bugs": {
|
|
59
|
+
"url": "https://github.com/devbro1/pashmak/issues"
|
|
60
|
+
},
|
|
61
|
+
"homepage": "https://devbro1.github.io/pashmak/",
|
|
62
|
+
"tags": {
|
|
63
|
+
"needsCompile": true,
|
|
64
|
+
"canPublishToNpm": true
|
|
65
|
+
}
|
|
66
|
+
}
|