@sebspark/promise-cache 4.0.1 → 4.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +112 -112
- package/dist/index.d.ts +112 -112
- package/dist/index.js +427 -432
- package/dist/index.mjs +427 -432
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -29,369 +29,20 @@ __export(index_exports, {
|
|
|
29
29
|
});
|
|
30
30
|
module.exports = __toCommonJS(index_exports);
|
|
31
31
|
|
|
32
|
-
// src/promiseCache.ts
|
|
33
|
-
var import_node_crypto = require("crypto");
|
|
34
|
-
|
|
35
|
-
// src/persistor.ts
|
|
36
|
-
var import_redis = require("redis");
|
|
37
|
-
|
|
38
|
-
// src/localMemory.ts
|
|
39
|
-
var LocalStorage = class {
|
|
40
|
-
client = /* @__PURE__ */ new Map();
|
|
41
|
-
isReady = false;
|
|
42
|
-
get(key) {
|
|
43
|
-
return this.client.get(key);
|
|
44
|
-
}
|
|
45
|
-
set(key, value, options) {
|
|
46
|
-
this.client.set(key, value);
|
|
47
|
-
if (options?.PX) {
|
|
48
|
-
setTimeout(() => {
|
|
49
|
-
this.client.delete(key);
|
|
50
|
-
}, options.PX);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
del(key) {
|
|
54
|
-
this.client.delete(key);
|
|
55
|
-
}
|
|
56
|
-
clear() {
|
|
57
|
-
this.client.clear();
|
|
58
|
-
}
|
|
59
|
-
async DBSIZE() {
|
|
60
|
-
return Promise.resolve(this.client.size);
|
|
61
|
-
}
|
|
62
|
-
// This is just for testing
|
|
63
|
-
on(event, callback) {
|
|
64
|
-
if (event === "ready" && callback) {
|
|
65
|
-
this.isReady = true;
|
|
66
|
-
callback("ready");
|
|
67
|
-
}
|
|
68
|
-
return this;
|
|
69
|
-
}
|
|
70
|
-
connect() {
|
|
71
|
-
return Promise.resolve(this);
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
var localStorage = new LocalStorage();
|
|
75
|
-
var createLocalMemoryClient = () => {
|
|
76
|
-
return localStorage;
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
// src/persistor.ts
|
|
80
|
-
var fixESM = require("fix-esm");
|
|
81
|
-
var superjson = fixESM.require("superjson");
|
|
82
|
-
var CACHE_CLIENT = import_redis.createClient;
|
|
83
|
-
var isTestRunning = process.env.NODE_ENV === "test";
|
|
84
|
-
function toMillis(seconds2) {
|
|
85
|
-
return seconds2 * 1e3;
|
|
86
|
-
}
|
|
87
|
-
var Persistor = class {
|
|
88
|
-
client = null;
|
|
89
|
-
clientId;
|
|
90
|
-
onError;
|
|
91
|
-
onSuccess;
|
|
92
|
-
logger;
|
|
93
|
-
redis;
|
|
94
|
-
constructor({
|
|
95
|
-
redis,
|
|
96
|
-
clientId,
|
|
97
|
-
onSuccess,
|
|
98
|
-
onError,
|
|
99
|
-
logger
|
|
100
|
-
}) {
|
|
101
|
-
this.onError = onError || (() => {
|
|
102
|
-
});
|
|
103
|
-
this.onSuccess = onSuccess || (() => {
|
|
104
|
-
});
|
|
105
|
-
this.clientId = clientId;
|
|
106
|
-
this.logger = logger;
|
|
107
|
-
if (redis && !isTestRunning) {
|
|
108
|
-
this.redis = redis;
|
|
109
|
-
} else {
|
|
110
|
-
CACHE_CLIENT = createLocalMemoryClient;
|
|
111
|
-
}
|
|
112
|
-
if (!this.client || !this.client.isReady) {
|
|
113
|
-
this.startConnection();
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
async startConnection() {
|
|
117
|
-
try {
|
|
118
|
-
await new Promise((resolve, reject) => {
|
|
119
|
-
this.client = CACHE_CLIENT({
|
|
120
|
-
url: this.redis?.url,
|
|
121
|
-
username: this.redis?.username,
|
|
122
|
-
password: this.redis?.password,
|
|
123
|
-
pingInterval: this.redis?.pingInterval || void 0,
|
|
124
|
-
socket: {
|
|
125
|
-
...this.redis?.socket,
|
|
126
|
-
reconnectStrategy: (retries, cause) => {
|
|
127
|
-
this.logger?.error(cause);
|
|
128
|
-
return 1e3 * 2 ** retries;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}).on("error", (err) => {
|
|
132
|
-
this.onError(err);
|
|
133
|
-
reject(err);
|
|
134
|
-
}).on("ready", () => {
|
|
135
|
-
this.onSuccess();
|
|
136
|
-
resolve(true);
|
|
137
|
-
}).on("reconnecting", () => {
|
|
138
|
-
this.logger?.info("reconnecting...", this.clientId);
|
|
139
|
-
}).on("end", () => {
|
|
140
|
-
this.logger?.info("end...", this.clientId);
|
|
141
|
-
});
|
|
142
|
-
this.client.connect();
|
|
143
|
-
});
|
|
144
|
-
} catch (ex) {
|
|
145
|
-
this.onError(`${ex}`);
|
|
146
|
-
this.logger?.error(ex);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
async size() {
|
|
150
|
-
if (!this.client) {
|
|
151
|
-
throw new Error("Client not initialized");
|
|
152
|
-
}
|
|
153
|
-
return await this.client.DBSIZE();
|
|
154
|
-
}
|
|
155
|
-
getClientId() {
|
|
156
|
-
return this.clientId;
|
|
157
|
-
}
|
|
158
|
-
getIsClientConnected() {
|
|
159
|
-
return !!this.client?.isReady;
|
|
160
|
-
}
|
|
161
|
-
createOptions(ttl) {
|
|
162
|
-
if (ttl !== null && ttl !== void 0 && ttl > 0) {
|
|
163
|
-
return { PX: Math.round(toMillis(ttl)) };
|
|
164
|
-
}
|
|
165
|
-
return {};
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* Set a value in the cache.
|
|
169
|
-
* @param key Cache key.
|
|
170
|
-
* @param object.value Value to set in the cache.
|
|
171
|
-
* @param object.ttl Time to live in seconds.
|
|
172
|
-
* @param object.timestamp Timestamp
|
|
173
|
-
*/
|
|
174
|
-
async set(key, { value, timestamp = Date.now(), ttl }) {
|
|
175
|
-
if (!this.client || !this.client.isReady) {
|
|
176
|
-
this.logger?.error("Client not ready");
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
try {
|
|
180
|
-
const serializedData = superjson.stringify({
|
|
181
|
-
value,
|
|
182
|
-
ttl,
|
|
183
|
-
timestamp
|
|
184
|
-
});
|
|
185
|
-
const options = this.createOptions(ttl);
|
|
186
|
-
await this.client.set(key, serializedData, options);
|
|
187
|
-
} catch (error) {
|
|
188
|
-
this.logger?.error(`Error setting data in redis: ${error}`);
|
|
189
|
-
throw new Error(`Error setting data in redis: ${error}`);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Get a value from the cache.
|
|
194
|
-
* @param key Cache key.
|
|
195
|
-
* @returns GetType<T> value
|
|
196
|
-
*/
|
|
197
|
-
async get(key) {
|
|
198
|
-
if (!this.client) {
|
|
199
|
-
this.logger?.error("Client not ready");
|
|
200
|
-
return null;
|
|
201
|
-
}
|
|
202
|
-
try {
|
|
203
|
-
const data = await this.client.get(key);
|
|
204
|
-
if (!data) {
|
|
205
|
-
return null;
|
|
206
|
-
}
|
|
207
|
-
return superjson.parse(data);
|
|
208
|
-
} catch (error) {
|
|
209
|
-
this.logger?.error(`Error getting data in redis: ${error}`);
|
|
210
|
-
throw new Error(`Error getting data from redis: ${error}`);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* Delete a value from the cache.
|
|
215
|
-
* @param key Cache key
|
|
216
|
-
*/
|
|
217
|
-
async delete(key) {
|
|
218
|
-
if (!this.client || !this.client.isReady) {
|
|
219
|
-
this.logger?.error("Client not ready");
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
try {
|
|
223
|
-
await this.client.del(key);
|
|
224
|
-
} catch (error) {
|
|
225
|
-
this.logger?.error(`Error deleting data from redis: ${error}`);
|
|
226
|
-
throw new Error(`Error deleting data from redis: ${error}`);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
// src/promiseCache.ts
|
|
232
|
-
var persistors = {};
|
|
233
|
-
var getPersistor = ({
|
|
234
|
-
redis,
|
|
235
|
-
logger,
|
|
236
|
-
onError,
|
|
237
|
-
onSuccess,
|
|
238
|
-
clientId
|
|
239
|
-
}) => {
|
|
240
|
-
const connectionName = redis ? redis?.name || "default" : "local";
|
|
241
|
-
if (!persistors[connectionName]) {
|
|
242
|
-
persistors[connectionName] = new Persistor({
|
|
243
|
-
redis,
|
|
244
|
-
onError: (error) => {
|
|
245
|
-
onError?.(error);
|
|
246
|
-
logger?.error(
|
|
247
|
-
`\u274C REDIS | Client Error | ${connectionName} | ${redis?.url}: ${error}`
|
|
248
|
-
);
|
|
249
|
-
},
|
|
250
|
-
onSuccess: () => {
|
|
251
|
-
onSuccess?.();
|
|
252
|
-
logger?.info(
|
|
253
|
-
`\u{1F4E6} REDIS | Connection Ready | ${connectionName} | ${redis?.url}`
|
|
254
|
-
);
|
|
255
|
-
},
|
|
256
|
-
clientId,
|
|
257
|
-
logger
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
return persistors[connectionName];
|
|
261
|
-
};
|
|
262
|
-
var PromiseCache = class {
|
|
263
|
-
persistor;
|
|
264
|
-
clientId = (0, import_node_crypto.randomUUID)();
|
|
265
|
-
caseSensitive;
|
|
266
|
-
fallbackToFunction;
|
|
267
|
-
// If true, the cache will fallback to the delegate function if there is an error retrieving the cache.
|
|
268
|
-
ttl;
|
|
269
|
-
// Time to live in milliseconds.
|
|
270
|
-
logger;
|
|
271
|
-
/**
|
|
272
|
-
* Initialize a new PromiseCache.
|
|
273
|
-
* @param ttlInSeconds Default cache TTL.
|
|
274
|
-
* @param caseSensitive Set to true if you want to differentiate between keys with different casing.
|
|
275
|
-
*/
|
|
276
|
-
constructor({
|
|
277
|
-
ttlInSeconds,
|
|
278
|
-
caseSensitive = false,
|
|
279
|
-
redis,
|
|
280
|
-
fallbackToFunction = false,
|
|
281
|
-
onSuccess,
|
|
282
|
-
onError,
|
|
283
|
-
logger
|
|
284
|
-
}) {
|
|
285
|
-
this.logger = logger;
|
|
286
|
-
this.persistor = getPersistor({
|
|
287
|
-
redis,
|
|
288
|
-
onError,
|
|
289
|
-
onSuccess,
|
|
290
|
-
clientId: this.clientId,
|
|
291
|
-
logger: this.logger
|
|
292
|
-
});
|
|
293
|
-
this.caseSensitive = caseSensitive;
|
|
294
|
-
this.fallbackToFunction = fallbackToFunction;
|
|
295
|
-
if (ttlInSeconds) {
|
|
296
|
-
this.ttl = ttlInSeconds;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
/**
|
|
300
|
-
* Cache size.
|
|
301
|
-
* @returns The number of entries in the cache.
|
|
302
|
-
*/
|
|
303
|
-
async size() {
|
|
304
|
-
return await this.persistor.size();
|
|
305
|
-
}
|
|
306
|
-
/**
|
|
307
|
-
* Override a value in the cache.
|
|
308
|
-
* @param key Cache key.
|
|
309
|
-
* @param value Cache value.
|
|
310
|
-
* @param ttlInSeconds Time to live in seconds.
|
|
311
|
-
*/
|
|
312
|
-
async override(key, value, ttlInSeconds) {
|
|
313
|
-
const effectiveKey = this.caseSensitive ? key : key.toLowerCase();
|
|
314
|
-
const effectiveTTL = ttlInSeconds !== void 0 ? ttlInSeconds : this.ttl;
|
|
315
|
-
await this.persistor.set(effectiveKey, {
|
|
316
|
-
value,
|
|
317
|
-
timestamp: Date.now(),
|
|
318
|
-
ttl: effectiveTTL
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
/**
|
|
322
|
-
* Get a value from the cache.
|
|
323
|
-
* @param key Cache key.
|
|
324
|
-
*/
|
|
325
|
-
async find(key) {
|
|
326
|
-
const result = await this.persistor.get(key);
|
|
327
|
-
return result?.value ?? null;
|
|
328
|
-
}
|
|
329
|
-
/**
|
|
330
|
-
* A simple promise cache wrapper.
|
|
331
|
-
* @param key Cache key.
|
|
332
|
-
* @param delegate The function to execute if the key is not in the cache.
|
|
333
|
-
* @param ttlInSeconds Time to live in seconds.
|
|
334
|
-
* @param ttlKeyInSeconds The key in the response object that contains the TTL.
|
|
335
|
-
* @returns The result of the delegate function.
|
|
336
|
-
*/
|
|
337
|
-
async wrap(key, delegate, ttlInSeconds, ttlKeyInSeconds) {
|
|
338
|
-
const now = Date.now();
|
|
339
|
-
const effectiveKey = this.caseSensitive ? key : key.toLowerCase();
|
|
340
|
-
let effectiveTTL = ttlInSeconds ?? this.ttl;
|
|
341
|
-
try {
|
|
342
|
-
const cached = await this.persistor.get(effectiveKey);
|
|
343
|
-
if (cached) {
|
|
344
|
-
if (!ttlKeyInSeconds && cached.ttl !== effectiveTTL) {
|
|
345
|
-
this.logger?.error(
|
|
346
|
-
"WARNING: TTL mismatch for key. It is recommended to use the same TTL for the same key."
|
|
347
|
-
);
|
|
348
|
-
}
|
|
349
|
-
return cached.value;
|
|
350
|
-
}
|
|
351
|
-
} catch (err) {
|
|
352
|
-
const error = err;
|
|
353
|
-
if (!this.fallbackToFunction) {
|
|
354
|
-
throw error;
|
|
355
|
-
}
|
|
356
|
-
this.logger?.error(
|
|
357
|
-
"redis error, falling back to function execution",
|
|
358
|
-
error instanceof Error ? error.message : String(error)
|
|
359
|
-
);
|
|
360
|
-
}
|
|
361
|
-
const response = await delegate();
|
|
362
|
-
if (ttlKeyInSeconds) {
|
|
363
|
-
const responseDict = response;
|
|
364
|
-
const responseTTL = Number(responseDict[ttlKeyInSeconds]);
|
|
365
|
-
effectiveTTL = responseTTL || effectiveTTL;
|
|
366
|
-
}
|
|
367
|
-
try {
|
|
368
|
-
await this.persistor.set(effectiveKey, {
|
|
369
|
-
value: response,
|
|
370
|
-
timestamp: now,
|
|
371
|
-
ttl: effectiveTTL
|
|
372
|
-
});
|
|
373
|
-
} catch (err) {
|
|
374
|
-
const error = err;
|
|
375
|
-
console.error("failed to cache result", error.message);
|
|
376
|
-
}
|
|
377
|
-
return response;
|
|
378
|
-
}
|
|
379
|
-
};
|
|
380
|
-
|
|
381
32
|
// src/serializer.ts
|
|
382
33
|
var serializer_exports = {};
|
|
383
34
|
__export(serializer_exports, {
|
|
384
35
|
deserialize: () => deserialize,
|
|
385
36
|
serialize: () => serialize
|
|
386
37
|
});
|
|
387
|
-
var
|
|
388
|
-
var
|
|
38
|
+
var fixESM = require("fix-esm");
|
|
39
|
+
var superjson = fixESM.require("superjson");
|
|
389
40
|
var serialize = (data) => {
|
|
390
|
-
return
|
|
41
|
+
return superjson.stringify(data);
|
|
391
42
|
};
|
|
392
43
|
var deserialize = (serialized) => {
|
|
393
44
|
if (serialized === void 0 || serialized === null) return serialized;
|
|
394
|
-
return
|
|
45
|
+
return superjson.parse(serialized);
|
|
395
46
|
};
|
|
396
47
|
|
|
397
48
|
// src/setOptions.ts
|
|
@@ -716,10 +367,7 @@ var InMemoryPersistor = class {
|
|
|
716
367
|
*/
|
|
717
368
|
async hSet(key, field, value) {
|
|
718
369
|
const existingHash = JSON.parse(this.store.get(key) ?? "{}");
|
|
719
|
-
const isNewField = !Object.
|
|
720
|
-
existingHash,
|
|
721
|
-
field
|
|
722
|
-
);
|
|
370
|
+
const isNewField = !Object.hasOwn(existingHash, field);
|
|
723
371
|
existingHash[field] = value;
|
|
724
372
|
this.store.set(key, JSON.stringify(existingHash));
|
|
725
373
|
return isNewField ? 1 : 0;
|
|
@@ -1195,103 +843,450 @@ var InMemoryMulti = class {
|
|
|
1195
843
|
* @param key - The list key.
|
|
1196
844
|
* @returns The `IPersistorMulti` instance to allow method chaining.
|
|
1197
845
|
*/
|
|
1198
|
-
rPop(key) {
|
|
1199
|
-
this.commands.add(() => this.persistor.rPop(key));
|
|
1200
|
-
return this;
|
|
846
|
+
rPop(key) {
|
|
847
|
+
this.commands.add(() => this.persistor.rPop(key));
|
|
848
|
+
return this;
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Queues an `lRange` command to retrieve a range of elements from a list.
|
|
852
|
+
* The command will be executed when `exec()` is called.
|
|
853
|
+
*
|
|
854
|
+
* @param key - The list key.
|
|
855
|
+
* @param start - The start index.
|
|
856
|
+
* @param stop - The stop index (inclusive).
|
|
857
|
+
* @returns The `IPersistorMulti` instance to allow method chaining.
|
|
858
|
+
*/
|
|
859
|
+
lRange(key, start, stop) {
|
|
860
|
+
this.commands.add(() => this.persistor.lRange(key, start, stop));
|
|
861
|
+
return this;
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* Queues an `sAdd` command to add elements to a set.
|
|
865
|
+
* The command will be executed when `exec()` is called.
|
|
866
|
+
*
|
|
867
|
+
* @param key - The set key.
|
|
868
|
+
* @param values - The values to add.
|
|
869
|
+
* @returns The `IPersistorMulti` instance to allow method chaining.
|
|
870
|
+
*/
|
|
871
|
+
sAdd(key, values) {
|
|
872
|
+
this.commands.add(() => this.persistor.sAdd(key, values));
|
|
873
|
+
return this;
|
|
874
|
+
}
|
|
875
|
+
/**
|
|
876
|
+
* Queues an `sRem` command to remove elements from a set.
|
|
877
|
+
* The command will be executed when `exec()` is called.
|
|
878
|
+
*
|
|
879
|
+
* @param key - The set key.
|
|
880
|
+
* @param values - The values to remove.
|
|
881
|
+
* @returns The `IPersistorMulti` instance to allow method chaining.
|
|
882
|
+
*/
|
|
883
|
+
sRem(key, values) {
|
|
884
|
+
this.commands.add(() => this.persistor.sRem(key, values));
|
|
885
|
+
return this;
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* Queues an `sMembers` command to retrieve all members of a set.
|
|
889
|
+
* The command will be executed when `exec()` is called.
|
|
890
|
+
*
|
|
891
|
+
* @param key - The set key.
|
|
892
|
+
* @returns The `IPersistorMulti` instance to allow method chaining.
|
|
893
|
+
*/
|
|
894
|
+
sMembers(key) {
|
|
895
|
+
this.commands.add(() => this.persistor.sMembers(key));
|
|
896
|
+
return this;
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Queues a `zAdd` command to add elements to a sorted set with scores.
|
|
900
|
+
* The command will be executed when `exec()` is called.
|
|
901
|
+
*
|
|
902
|
+
* @param key - The sorted set key.
|
|
903
|
+
* @param members - An array of objects with `score` and `value`.
|
|
904
|
+
* @returns The `IPersistorMulti` instance to allow method chaining.
|
|
905
|
+
*/
|
|
906
|
+
zAdd(key, members) {
|
|
907
|
+
this.commands.add(() => this.persistor.zAdd(key, members));
|
|
908
|
+
return this;
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* Queues a `zRange` command to retrieve a range of elements from a sorted set.
|
|
912
|
+
* The command will be executed when `exec()` is called.
|
|
913
|
+
*
|
|
914
|
+
* @param key - The sorted set key.
|
|
915
|
+
* @param start - The start index.
|
|
916
|
+
* @param stop - The stop index (inclusive).
|
|
917
|
+
* @returns The `IPersistorMulti` instance to allow method chaining.
|
|
918
|
+
*/
|
|
919
|
+
zRange(key, start, stop) {
|
|
920
|
+
this.commands.add(() => this.persistor.zRange(key, start, stop));
|
|
921
|
+
return this;
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* Queues a `zRem` command to remove elements from a sorted set.
|
|
925
|
+
* The command will be executed when `exec()` is called.
|
|
926
|
+
*
|
|
927
|
+
* @param key - The sorted set key.
|
|
928
|
+
* @param members - The members to remove.
|
|
929
|
+
* @returns The `IPersistorMulti` instance to allow method chaining.
|
|
930
|
+
*/
|
|
931
|
+
zRem(key, members) {
|
|
932
|
+
this.commands.add(() => this.persistor.zRem(key, members));
|
|
933
|
+
return this;
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* Executes multiple commands in a batch operation.
|
|
937
|
+
* Each command is executed in sequence, and results are collected in an array.
|
|
938
|
+
*
|
|
939
|
+
* @returns Resolves to an array containing the results of each queued command.
|
|
940
|
+
*/
|
|
941
|
+
async exec() {
|
|
942
|
+
return Promise.all([...this.commands].map((cmd) => cmd()));
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
|
|
946
|
+
// src/persistor.ts
|
|
947
|
+
var import_redis = require("redis");
|
|
948
|
+
|
|
949
|
+
// src/localMemory.ts
|
|
950
|
+
var LocalStorage = class {
|
|
951
|
+
client = /* @__PURE__ */ new Map();
|
|
952
|
+
isReady = false;
|
|
953
|
+
get(key) {
|
|
954
|
+
return this.client.get(key);
|
|
955
|
+
}
|
|
956
|
+
set(key, value, options) {
|
|
957
|
+
this.client.set(key, value);
|
|
958
|
+
if (options?.PX) {
|
|
959
|
+
setTimeout(() => {
|
|
960
|
+
this.client.delete(key);
|
|
961
|
+
}, options.PX);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
del(key) {
|
|
965
|
+
this.client.delete(key);
|
|
966
|
+
}
|
|
967
|
+
clear() {
|
|
968
|
+
this.client.clear();
|
|
969
|
+
}
|
|
970
|
+
async DBSIZE() {
|
|
971
|
+
return Promise.resolve(this.client.size);
|
|
972
|
+
}
|
|
973
|
+
// This is just for testing
|
|
974
|
+
on(event, callback) {
|
|
975
|
+
if (event === "ready" && callback) {
|
|
976
|
+
this.isReady = true;
|
|
977
|
+
callback("ready");
|
|
978
|
+
}
|
|
979
|
+
return this;
|
|
980
|
+
}
|
|
981
|
+
connect() {
|
|
982
|
+
return Promise.resolve(this);
|
|
983
|
+
}
|
|
984
|
+
};
|
|
985
|
+
var localStorage = new LocalStorage();
|
|
986
|
+
var createLocalMemoryClient = () => {
|
|
987
|
+
return localStorage;
|
|
988
|
+
};
|
|
989
|
+
|
|
990
|
+
// src/persistor.ts
|
|
991
|
+
var fixESM2 = require("fix-esm");
|
|
992
|
+
var superjson2 = fixESM2.require("superjson");
|
|
993
|
+
var CACHE_CLIENT = import_redis.createClient;
|
|
994
|
+
var isTestRunning = process.env.NODE_ENV === "test";
|
|
995
|
+
function toMillis(seconds2) {
|
|
996
|
+
return seconds2 * 1e3;
|
|
997
|
+
}
|
|
998
|
+
var Persistor = class {
|
|
999
|
+
client = null;
|
|
1000
|
+
clientId;
|
|
1001
|
+
onError;
|
|
1002
|
+
onSuccess;
|
|
1003
|
+
logger;
|
|
1004
|
+
redis;
|
|
1005
|
+
constructor({
|
|
1006
|
+
redis,
|
|
1007
|
+
clientId,
|
|
1008
|
+
onSuccess,
|
|
1009
|
+
onError,
|
|
1010
|
+
logger
|
|
1011
|
+
}) {
|
|
1012
|
+
this.onError = onError || (() => {
|
|
1013
|
+
});
|
|
1014
|
+
this.onSuccess = onSuccess || (() => {
|
|
1015
|
+
});
|
|
1016
|
+
this.clientId = clientId;
|
|
1017
|
+
this.logger = logger;
|
|
1018
|
+
if (redis && !isTestRunning) {
|
|
1019
|
+
this.redis = redis;
|
|
1020
|
+
} else {
|
|
1021
|
+
CACHE_CLIENT = createLocalMemoryClient;
|
|
1022
|
+
}
|
|
1023
|
+
if (!this.client || !this.client.isReady) {
|
|
1024
|
+
this.startConnection();
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
async startConnection() {
|
|
1028
|
+
try {
|
|
1029
|
+
await new Promise((resolve, reject) => {
|
|
1030
|
+
this.client = CACHE_CLIENT({
|
|
1031
|
+
url: this.redis?.url,
|
|
1032
|
+
username: this.redis?.username,
|
|
1033
|
+
password: this.redis?.password,
|
|
1034
|
+
pingInterval: this.redis?.pingInterval || void 0,
|
|
1035
|
+
socket: {
|
|
1036
|
+
...this.redis?.socket,
|
|
1037
|
+
reconnectStrategy: (retries, cause) => {
|
|
1038
|
+
this.logger?.error(cause);
|
|
1039
|
+
return 1e3 * 2 ** retries;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
}).on("error", (err) => {
|
|
1043
|
+
this.onError(err);
|
|
1044
|
+
reject(err);
|
|
1045
|
+
}).on("ready", () => {
|
|
1046
|
+
this.onSuccess();
|
|
1047
|
+
resolve(true);
|
|
1048
|
+
}).on("reconnecting", () => {
|
|
1049
|
+
this.logger?.info("reconnecting...", this.clientId);
|
|
1050
|
+
}).on("end", () => {
|
|
1051
|
+
this.logger?.info("end...", this.clientId);
|
|
1052
|
+
});
|
|
1053
|
+
this.client.connect();
|
|
1054
|
+
});
|
|
1055
|
+
} catch (ex) {
|
|
1056
|
+
this.onError(`${ex}`);
|
|
1057
|
+
this.logger?.error(ex);
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
async size() {
|
|
1061
|
+
if (!this.client) {
|
|
1062
|
+
throw new Error("Client not initialized");
|
|
1063
|
+
}
|
|
1064
|
+
return await this.client.DBSIZE();
|
|
1065
|
+
}
|
|
1066
|
+
getClientId() {
|
|
1067
|
+
return this.clientId;
|
|
1068
|
+
}
|
|
1069
|
+
getIsClientConnected() {
|
|
1070
|
+
return !!this.client?.isReady;
|
|
1071
|
+
}
|
|
1072
|
+
createOptions(ttl) {
|
|
1073
|
+
if (ttl !== null && ttl !== void 0 && ttl > 0) {
|
|
1074
|
+
return { PX: Math.round(toMillis(ttl)) };
|
|
1075
|
+
}
|
|
1076
|
+
return {};
|
|
1077
|
+
}
|
|
1078
|
+
/**
|
|
1079
|
+
* Set a value in the cache.
|
|
1080
|
+
* @param key Cache key.
|
|
1081
|
+
* @param object.value Value to set in the cache.
|
|
1082
|
+
* @param object.ttl Time to live in seconds.
|
|
1083
|
+
* @param object.timestamp Timestamp
|
|
1084
|
+
*/
|
|
1085
|
+
async set(key, { value, timestamp = Date.now(), ttl }) {
|
|
1086
|
+
if (!this.client || !this.client.isReady) {
|
|
1087
|
+
this.logger?.error("Client not ready");
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
try {
|
|
1091
|
+
const serializedData = superjson2.stringify({
|
|
1092
|
+
value,
|
|
1093
|
+
ttl,
|
|
1094
|
+
timestamp
|
|
1095
|
+
});
|
|
1096
|
+
const options = this.createOptions(ttl);
|
|
1097
|
+
await this.client.set(key, serializedData, options);
|
|
1098
|
+
} catch (error) {
|
|
1099
|
+
this.logger?.error(`Error setting data in redis: ${error}`);
|
|
1100
|
+
throw new Error(`Error setting data in redis: ${error}`);
|
|
1101
|
+
}
|
|
1201
1102
|
}
|
|
1202
1103
|
/**
|
|
1203
|
-
*
|
|
1204
|
-
*
|
|
1205
|
-
*
|
|
1206
|
-
* @param key - The list key.
|
|
1207
|
-
* @param start - The start index.
|
|
1208
|
-
* @param stop - The stop index (inclusive).
|
|
1209
|
-
* @returns The `IPersistorMulti` instance to allow method chaining.
|
|
1104
|
+
* Get a value from the cache.
|
|
1105
|
+
* @param key Cache key.
|
|
1106
|
+
* @returns GetType<T> value
|
|
1210
1107
|
*/
|
|
1211
|
-
|
|
1212
|
-
this.
|
|
1213
|
-
|
|
1108
|
+
async get(key) {
|
|
1109
|
+
if (!this.client) {
|
|
1110
|
+
this.logger?.error("Client not ready");
|
|
1111
|
+
return null;
|
|
1112
|
+
}
|
|
1113
|
+
try {
|
|
1114
|
+
const data = await this.client.get(key);
|
|
1115
|
+
if (!data) {
|
|
1116
|
+
return null;
|
|
1117
|
+
}
|
|
1118
|
+
return superjson2.parse(data);
|
|
1119
|
+
} catch (error) {
|
|
1120
|
+
this.logger?.error(`Error getting data in redis: ${error}`);
|
|
1121
|
+
throw new Error(`Error getting data from redis: ${error}`);
|
|
1122
|
+
}
|
|
1214
1123
|
}
|
|
1215
1124
|
/**
|
|
1216
|
-
*
|
|
1217
|
-
*
|
|
1218
|
-
*
|
|
1219
|
-
* @param key - The set key.
|
|
1220
|
-
* @param values - The values to add.
|
|
1221
|
-
* @returns The `IPersistorMulti` instance to allow method chaining.
|
|
1125
|
+
* Delete a value from the cache.
|
|
1126
|
+
* @param key Cache key
|
|
1222
1127
|
*/
|
|
1223
|
-
|
|
1224
|
-
this.
|
|
1225
|
-
|
|
1128
|
+
async delete(key) {
|
|
1129
|
+
if (!this.client || !this.client.isReady) {
|
|
1130
|
+
this.logger?.error("Client not ready");
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
try {
|
|
1134
|
+
await this.client.del(key);
|
|
1135
|
+
} catch (error) {
|
|
1136
|
+
this.logger?.error(`Error deleting data from redis: ${error}`);
|
|
1137
|
+
throw new Error(`Error deleting data from redis: ${error}`);
|
|
1138
|
+
}
|
|
1226
1139
|
}
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1140
|
+
};
|
|
1141
|
+
|
|
1142
|
+
// src/promiseCache.ts
|
|
1143
|
+
var import_node_crypto = require("crypto");
|
|
1144
|
+
var persistors = {};
|
|
1145
|
+
var getPersistor = ({
|
|
1146
|
+
redis,
|
|
1147
|
+
logger,
|
|
1148
|
+
onError,
|
|
1149
|
+
onSuccess,
|
|
1150
|
+
clientId
|
|
1151
|
+
}) => {
|
|
1152
|
+
const connectionName = redis ? redis?.name || "default" : "local";
|
|
1153
|
+
if (!persistors[connectionName]) {
|
|
1154
|
+
persistors[connectionName] = new Persistor({
|
|
1155
|
+
redis,
|
|
1156
|
+
onError: (error) => {
|
|
1157
|
+
onError?.(error);
|
|
1158
|
+
logger?.error(
|
|
1159
|
+
`\u274C REDIS | Client Error | ${connectionName} | ${redis?.url}: ${error}`
|
|
1160
|
+
);
|
|
1161
|
+
},
|
|
1162
|
+
onSuccess: () => {
|
|
1163
|
+
onSuccess?.();
|
|
1164
|
+
logger?.info(
|
|
1165
|
+
`\u{1F4E6} REDIS | Connection Ready | ${connectionName} | ${redis?.url}`
|
|
1166
|
+
);
|
|
1167
|
+
},
|
|
1168
|
+
clientId,
|
|
1169
|
+
logger
|
|
1170
|
+
});
|
|
1238
1171
|
}
|
|
1172
|
+
return persistors[connectionName];
|
|
1173
|
+
};
|
|
1174
|
+
var PromiseCache = class {
|
|
1175
|
+
persistor;
|
|
1176
|
+
clientId = (0, import_node_crypto.randomUUID)();
|
|
1177
|
+
caseSensitive;
|
|
1178
|
+
fallbackToFunction;
|
|
1179
|
+
// If true, the cache will fallback to the delegate function if there is an error retrieving the cache.
|
|
1180
|
+
ttl;
|
|
1181
|
+
// Time to live in milliseconds.
|
|
1182
|
+
logger;
|
|
1239
1183
|
/**
|
|
1240
|
-
*
|
|
1241
|
-
*
|
|
1242
|
-
*
|
|
1243
|
-
* @param key - The set key.
|
|
1244
|
-
* @returns The `IPersistorMulti` instance to allow method chaining.
|
|
1184
|
+
* Initialize a new PromiseCache.
|
|
1185
|
+
* @param ttlInSeconds Default cache TTL.
|
|
1186
|
+
* @param caseSensitive Set to true if you want to differentiate between keys with different casing.
|
|
1245
1187
|
*/
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1188
|
+
constructor({
|
|
1189
|
+
ttlInSeconds,
|
|
1190
|
+
caseSensitive = false,
|
|
1191
|
+
redis,
|
|
1192
|
+
fallbackToFunction = false,
|
|
1193
|
+
onSuccess,
|
|
1194
|
+
onError,
|
|
1195
|
+
logger
|
|
1196
|
+
}) {
|
|
1197
|
+
this.logger = logger;
|
|
1198
|
+
this.persistor = getPersistor({
|
|
1199
|
+
redis,
|
|
1200
|
+
onError,
|
|
1201
|
+
onSuccess,
|
|
1202
|
+
clientId: this.clientId,
|
|
1203
|
+
logger: this.logger
|
|
1204
|
+
});
|
|
1205
|
+
this.caseSensitive = caseSensitive;
|
|
1206
|
+
this.fallbackToFunction = fallbackToFunction;
|
|
1207
|
+
if (ttlInSeconds) {
|
|
1208
|
+
this.ttl = ttlInSeconds;
|
|
1209
|
+
}
|
|
1249
1210
|
}
|
|
1250
1211
|
/**
|
|
1251
|
-
*
|
|
1252
|
-
* The
|
|
1253
|
-
*
|
|
1254
|
-
* @param key - The sorted set key.
|
|
1255
|
-
* @param members - An array of objects with `score` and `value`.
|
|
1256
|
-
* @returns The `IPersistorMulti` instance to allow method chaining.
|
|
1212
|
+
* Cache size.
|
|
1213
|
+
* @returns The number of entries in the cache.
|
|
1257
1214
|
*/
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
return this;
|
|
1215
|
+
async size() {
|
|
1216
|
+
return await this.persistor.size();
|
|
1261
1217
|
}
|
|
1262
1218
|
/**
|
|
1263
|
-
*
|
|
1264
|
-
*
|
|
1265
|
-
*
|
|
1266
|
-
* @param
|
|
1267
|
-
* @param start - The start index.
|
|
1268
|
-
* @param stop - The stop index (inclusive).
|
|
1269
|
-
* @returns The `IPersistorMulti` instance to allow method chaining.
|
|
1219
|
+
* Override a value in the cache.
|
|
1220
|
+
* @param key Cache key.
|
|
1221
|
+
* @param value Cache value.
|
|
1222
|
+
* @param ttlInSeconds Time to live in seconds.
|
|
1270
1223
|
*/
|
|
1271
|
-
|
|
1272
|
-
this.
|
|
1273
|
-
|
|
1224
|
+
async override(key, value, ttlInSeconds) {
|
|
1225
|
+
const effectiveKey = this.caseSensitive ? key : key.toLowerCase();
|
|
1226
|
+
const effectiveTTL = ttlInSeconds !== void 0 ? ttlInSeconds : this.ttl;
|
|
1227
|
+
await this.persistor.set(effectiveKey, {
|
|
1228
|
+
value,
|
|
1229
|
+
timestamp: Date.now(),
|
|
1230
|
+
ttl: effectiveTTL
|
|
1231
|
+
});
|
|
1274
1232
|
}
|
|
1275
1233
|
/**
|
|
1276
|
-
*
|
|
1277
|
-
*
|
|
1278
|
-
*
|
|
1279
|
-
* @param key - The sorted set key.
|
|
1280
|
-
* @param members - The members to remove.
|
|
1281
|
-
* @returns The `IPersistorMulti` instance to allow method chaining.
|
|
1234
|
+
* Get a value from the cache.
|
|
1235
|
+
* @param key Cache key.
|
|
1282
1236
|
*/
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
return
|
|
1237
|
+
async find(key) {
|
|
1238
|
+
const result = await this.persistor.get(key);
|
|
1239
|
+
return result?.value ?? null;
|
|
1286
1240
|
}
|
|
1287
1241
|
/**
|
|
1288
|
-
*
|
|
1289
|
-
*
|
|
1290
|
-
*
|
|
1291
|
-
* @
|
|
1242
|
+
* A simple promise cache wrapper.
|
|
1243
|
+
* @param key Cache key.
|
|
1244
|
+
* @param delegate The function to execute if the key is not in the cache.
|
|
1245
|
+
* @param ttlInSeconds Time to live in seconds.
|
|
1246
|
+
* @param ttlKeyInSeconds The key in the response object that contains the TTL.
|
|
1247
|
+
* @returns The result of the delegate function.
|
|
1292
1248
|
*/
|
|
1293
|
-
async
|
|
1294
|
-
|
|
1249
|
+
async wrap(key, delegate, ttlInSeconds, ttlKeyInSeconds) {
|
|
1250
|
+
const now = Date.now();
|
|
1251
|
+
const effectiveKey = this.caseSensitive ? key : key.toLowerCase();
|
|
1252
|
+
let effectiveTTL = ttlInSeconds ?? this.ttl;
|
|
1253
|
+
try {
|
|
1254
|
+
const cached = await this.persistor.get(effectiveKey);
|
|
1255
|
+
if (cached) {
|
|
1256
|
+
if (!ttlKeyInSeconds && cached.ttl !== effectiveTTL) {
|
|
1257
|
+
this.logger?.error(
|
|
1258
|
+
"WARNING: TTL mismatch for key. It is recommended to use the same TTL for the same key."
|
|
1259
|
+
);
|
|
1260
|
+
}
|
|
1261
|
+
return cached.value;
|
|
1262
|
+
}
|
|
1263
|
+
} catch (err) {
|
|
1264
|
+
const error = err;
|
|
1265
|
+
if (!this.fallbackToFunction) {
|
|
1266
|
+
throw error;
|
|
1267
|
+
}
|
|
1268
|
+
this.logger?.error(
|
|
1269
|
+
"redis error, falling back to function execution",
|
|
1270
|
+
error instanceof Error ? error.message : String(error)
|
|
1271
|
+
);
|
|
1272
|
+
}
|
|
1273
|
+
const response = await delegate();
|
|
1274
|
+
if (ttlKeyInSeconds) {
|
|
1275
|
+
const responseDict = response;
|
|
1276
|
+
const responseTTL = Number(responseDict[ttlKeyInSeconds]);
|
|
1277
|
+
effectiveTTL = responseTTL || effectiveTTL;
|
|
1278
|
+
}
|
|
1279
|
+
try {
|
|
1280
|
+
await this.persistor.set(effectiveKey, {
|
|
1281
|
+
value: response,
|
|
1282
|
+
timestamp: now,
|
|
1283
|
+
ttl: effectiveTTL
|
|
1284
|
+
});
|
|
1285
|
+
} catch (err) {
|
|
1286
|
+
const error = err;
|
|
1287
|
+
console.error("failed to cache result", error.message);
|
|
1288
|
+
}
|
|
1289
|
+
return response;
|
|
1295
1290
|
}
|
|
1296
1291
|
};
|
|
1297
1292
|
|