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