@sebspark/promise-cache 2.1.1 → 3.0.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 CHANGED
@@ -1,23 +1,163 @@
1
1
  # `@sebspark/promise-cache`
2
2
 
3
- A simple caching wrapper for promises.
3
+ Simple caching wrapper
4
+
5
+ ## **Features**
6
+
7
+ - **PromiseCache**: Simple caching wraper for promises
8
+ - **Persistor**: Simple Key/Value caching wrapper, can be used with redis or with local memory.
9
+
10
+ ## **Installation**
11
+
12
+ To install promise-cache, use `yarn`:
13
+
14
+ ```bash
15
+ yarn install @sebspark/promise-cache
16
+ ```
17
+ ## **Usage**
18
+
19
+ ### **PromiseCache Class**
20
+
21
+ | Params | Type | Default |Description |
22
+ |---------------|----------|---------|---------------------------------------------|
23
+ | redis | RedisClientOptions | undefined |Redis instance url, skip if use local memory |
24
+ | ttlInSeconds | number | undefined |Persist time in Seconds |
25
+ | caseSensituve | boolean | false |Retrieving cache with case sensitive |
26
+ | onSuccess | function | undefined |Callback function if connection is success |
27
+ | onError | function | undefined |Callback function if there is an error |
28
+ | logger | winston Logger | undefined |Logger |
4
29
 
5
30
  ```typescript
31
+ import { PromiseCache } from '@sebspark/promise-cache'
32
+
33
+ // with redis
34
+ const cacheInRedis = new PromiseCache<T>({
35
+ redis: REDIS_URL,
36
+ ttlInSeconds: ttl,
37
+ caseSensitive: true
38
+ })
39
+
40
+ // without redis
41
+ const cacheLocalMemory = new PromiseCache<T>({
42
+ ttlInSeconds: ttl,
43
+ caseSensitive: true
44
+ })
45
+ ```
46
+
47
+ ## **PromiseCache Methods**
48
+
49
+ ```typescript
50
+ // Wrap
51
+ /* Simple promise cache wrapper
52
+ * @param key Cache key.
53
+ * @param delegate The function to execute if the key is not in the cache.
54
+ * @param ttlInSeconds Time to live in seconds.
55
+ * @param ttlKeyInSeconds The key in the response object that contains the TTL.
56
+ * @returns The result of the delegate function.
57
+ */
58
+ const ttl = 5 // 5 seconds
59
+ const delegate = new Promise((reject, resolve) => { resolve(123)})
60
+ const response = await cacheInRedis.wrap('Key', delegate, ttl)
61
+ expect(response).toBe(123)
62
+
63
+ // Size
6
64
  /*
7
- * Pseudocode example.
65
+ * Cache size
66
+ * @returns The number of entries in the cache
67
+ */
68
+
69
+ const entries = await cacheInRedis.size()
70
+ expect(entries).toBe(1)
71
+
72
+ // Find
73
+ /**
74
+ * Get a value from the cache.
75
+ * @param key Cache key.
76
+ */
77
+
78
+ const cachedValue = await cacheInRedis.find('Key')
79
+ expect(cachedValue).toBe(123)
80
+
81
+ // Override
82
+ /**
83
+ * Set a value in the cache.
84
+ * @param key Cache key.
85
+ * @param value Cache value.
86
+ * @param ttlInSeconds? Time to live in seconds.
8
87
  */
9
- import { PromiseCache } from '@sebspark/promise-cache'
10
88
 
11
- // Instantiate the cache with a TTL.
12
- const cache = new PromiseCache<number>(60, true, true) // 1 minute cache TTL / is case sensitive / use local storage
89
+ await cacheInRedis.override('Key', 234) // keep the same ttl
90
+ const cached = cacheInRedis.find('Key')
91
+ expect(cached).toBe(234)
92
+ ```
93
+
94
+ ### **Persistor Class**
95
+
96
+ | Params | Type | Default |Description |
97
+ |---------------|----------|---------|---------------------------------------------|
98
+ | redis | RedisClientOptions | undefined |Redis instance url, skip if use local memory |
99
+ | onSuccess | function | undefined |Callback function if connection is success |
100
+ | onError | function | undefined |Callback function if there is an error |
101
+ | logger | winston Logger | undefined |Logger |
102
+ | clientId | any | undefined |Object internal id |
103
+
104
+ ```typescript
105
+ import { Persistor } from '@sebspark/promise-cache'
13
106
 
14
- const redisCache = new PromiseCache<number>(60, false, false) // 1 minute cache TTL / is not case sensitive / use redis storage
107
+ // with redis
108
+ const store = new Persistor<T>({
109
+ redis: REDIS_URL,
110
+ })
111
+
112
+ // without redis
113
+ const store = new Persistor<T>()
114
+ ```
115
+
116
+ ## **Persistor Methods**
117
+
118
+ ```typescript
15
119
 
16
- // Use the cache wrapper for a database query to relieve the database.
17
- const query = 'SELECT username FROM users ORDER BY created DESC LIMIT 1'
18
- const newestUser = await cache.wrap('newestUser', () => database.query(query))
120
+ /**
121
+ * Size
122
+ * @returns The number of entries in the cache
123
+ */
124
+
125
+ const size = await store.size()
126
+ expect(size).toBe(0)
127
+
128
+ /**
129
+ * Set
130
+ * Set a value in the cache.
131
+ * @param key Cache key.
132
+ * @param object.value Value to set in the cache.
133
+ * @param object.ttl Time to live in seconds.
134
+ * @param object.timestamp Timestamp
135
+ */
136
+
137
+ await store.set<number>('MyKey', {
138
+ value: 123,
139
+ ttl: 10 // In seconds, default undefined
140
+ timestamp: Date.now() // default Date.now()
141
+ })
142
+
143
+ /**
144
+ * Get a value from the cache.
145
+ * @param key Cache key.
146
+ * @returns GetType<T> value
147
+ */
148
+
149
+ const cached = await store.get('MyKey')
150
+ expect(cached).toBe({
151
+ value: 43,
152
+ ttl: 10,
153
+ timestamp: // The timestamp
154
+ })
155
+
156
+ /**
157
+ * Delete a value from the cache.
158
+ * @param key Cache key
159
+ */
19
160
 
20
- // Use the cache wrapper for a database query to relieve the database.
21
- const query = 'SELECT username FROM users ORDER BY created DESC LIMIT 1'
22
- const newestUser = await redisCache.wrap('newestUserRedis', () => database.query(query))
23
- ```
161
+ await store.delete('MyKey')
162
+ expect(await store.get('MyKey').toBe(null)
163
+ ```
package/dist/index.d.mts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { RedisClientOptions, createClient } from 'redis';
2
2
  export { RedisClientOptions } from 'redis';
3
+ import { Logger } from 'winston';
3
4
  import { UUID } from 'node:crypto';
4
5
 
5
6
  type GetType<T> = {
@@ -9,29 +10,47 @@ type GetType<T> = {
9
10
  };
10
11
  type SetParams<T> = {
11
12
  value: T;
12
- timestamp: number;
13
+ timestamp?: number;
13
14
  ttl?: number;
14
15
  };
15
16
  type PersistorConstructorType = {
16
17
  redis?: RedisClientOptions;
17
18
  clientId?: UUID;
18
- onError: (error: string) => void;
19
- onSuccess: () => void;
19
+ onError?: (error: string) => void;
20
+ onSuccess?: () => void;
21
+ logger?: Logger;
20
22
  };
21
23
  declare class Persistor {
22
24
  client: ReturnType<typeof createClient> | null;
23
25
  private clientId?;
24
26
  private onError;
25
27
  private onSuccess;
28
+ private logger;
26
29
  private readonly redis?;
27
- constructor({ redis, clientId, onSuccess, onError, }: PersistorConstructorType);
30
+ constructor({ redis, clientId, onSuccess, onError, logger, }: PersistorConstructorType);
28
31
  startConnection(): Promise<void>;
29
32
  size(): Promise<number>;
30
- get<T>(key: string): Promise<GetType<T> | null>;
31
33
  getClientId(): UUID | undefined;
32
34
  getIsClientConnected(): boolean;
33
35
  private createOptions;
36
+ /**
37
+ * Set a value in the cache.
38
+ * @param key Cache key.
39
+ * @param object.value Value to set in the cache.
40
+ * @param object.ttl Time to live in seconds.
41
+ * @param object.timestamp Timestamp
42
+ */
34
43
  set<T>(key: string, { value, timestamp, ttl }: SetParams<T>): Promise<void>;
44
+ /**
45
+ * Get a value from the cache.
46
+ * @param key Cache key.
47
+ * @returns GetType<T> value
48
+ */
49
+ get<T>(key: string): Promise<GetType<T> | null>;
50
+ /**
51
+ * Delete a value from the cache.
52
+ * @param key Cache key
53
+ */
35
54
  delete(key: string): Promise<void>;
36
55
  }
37
56
 
@@ -41,6 +60,7 @@ type PromiseCacheOptions = {
41
60
  redis?: RedisClientOptions;
42
61
  onError?: (error: string) => void;
43
62
  onSuccess?: () => void;
63
+ logger?: Logger;
44
64
  };
45
65
  declare class PromiseCache<U> {
46
66
  persistor: Persistor;
@@ -52,14 +72,14 @@ declare class PromiseCache<U> {
52
72
  * @param ttlInSeconds Default cache TTL.
53
73
  * @param caseSensitive Set to true if you want to differentiate between keys with different casing.
54
74
  */
55
- constructor({ ttlInSeconds, caseSensitive, redis, onSuccess, onError, }: PromiseCacheOptions);
75
+ constructor({ ttlInSeconds, caseSensitive, redis, onSuccess, onError, logger, }: PromiseCacheOptions);
56
76
  /**
57
77
  * Cache size.
58
78
  * @returns The number of entries in the cache.
59
79
  */
60
80
  size(): Promise<number>;
61
81
  /**
62
- * Set a value in the cache.
82
+ * Override a value in the cache.
63
83
  * @param key Cache key.
64
84
  * @param value Cache value.
65
85
  * @param ttlInSeconds Time to live in seconds.
@@ -81,19 +101,4 @@ declare class PromiseCache<U> {
81
101
  wrap(key: string, delegate: () => Promise<U>, ttlInSeconds?: number, ttlKeyInSeconds?: string): Promise<U>;
82
102
  }
83
103
 
84
- declare class LocalStorage {
85
- client: Map<any, any>;
86
- isReady: boolean;
87
- get(key: string): any;
88
- set(key: string, value: string, options?: {
89
- PX: number;
90
- }): void;
91
- del(key: string): void;
92
- clear(): void;
93
- DBSIZE(): Promise<number>;
94
- on(event: string, callback: (message: string) => void): this;
95
- connect(): Promise<this>;
96
- }
97
- declare const createLocalMemoryClient: () => LocalStorage;
98
-
99
- export { LocalStorage, Persistor, type PersistorConstructorType, PromiseCache, type PromiseCacheOptions, createLocalMemoryClient };
104
+ export { Persistor, type PersistorConstructorType, PromiseCache, type PromiseCacheOptions };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { RedisClientOptions, createClient } from 'redis';
2
2
  export { RedisClientOptions } from 'redis';
3
+ import { Logger } from 'winston';
3
4
  import { UUID } from 'node:crypto';
4
5
 
5
6
  type GetType<T> = {
@@ -9,29 +10,47 @@ type GetType<T> = {
9
10
  };
10
11
  type SetParams<T> = {
11
12
  value: T;
12
- timestamp: number;
13
+ timestamp?: number;
13
14
  ttl?: number;
14
15
  };
15
16
  type PersistorConstructorType = {
16
17
  redis?: RedisClientOptions;
17
18
  clientId?: UUID;
18
- onError: (error: string) => void;
19
- onSuccess: () => void;
19
+ onError?: (error: string) => void;
20
+ onSuccess?: () => void;
21
+ logger?: Logger;
20
22
  };
21
23
  declare class Persistor {
22
24
  client: ReturnType<typeof createClient> | null;
23
25
  private clientId?;
24
26
  private onError;
25
27
  private onSuccess;
28
+ private logger;
26
29
  private readonly redis?;
27
- constructor({ redis, clientId, onSuccess, onError, }: PersistorConstructorType);
30
+ constructor({ redis, clientId, onSuccess, onError, logger, }: PersistorConstructorType);
28
31
  startConnection(): Promise<void>;
29
32
  size(): Promise<number>;
30
- get<T>(key: string): Promise<GetType<T> | null>;
31
33
  getClientId(): UUID | undefined;
32
34
  getIsClientConnected(): boolean;
33
35
  private createOptions;
36
+ /**
37
+ * Set a value in the cache.
38
+ * @param key Cache key.
39
+ * @param object.value Value to set in the cache.
40
+ * @param object.ttl Time to live in seconds.
41
+ * @param object.timestamp Timestamp
42
+ */
34
43
  set<T>(key: string, { value, timestamp, ttl }: SetParams<T>): Promise<void>;
44
+ /**
45
+ * Get a value from the cache.
46
+ * @param key Cache key.
47
+ * @returns GetType<T> value
48
+ */
49
+ get<T>(key: string): Promise<GetType<T> | null>;
50
+ /**
51
+ * Delete a value from the cache.
52
+ * @param key Cache key
53
+ */
35
54
  delete(key: string): Promise<void>;
36
55
  }
37
56
 
@@ -41,6 +60,7 @@ type PromiseCacheOptions = {
41
60
  redis?: RedisClientOptions;
42
61
  onError?: (error: string) => void;
43
62
  onSuccess?: () => void;
63
+ logger?: Logger;
44
64
  };
45
65
  declare class PromiseCache<U> {
46
66
  persistor: Persistor;
@@ -52,14 +72,14 @@ declare class PromiseCache<U> {
52
72
  * @param ttlInSeconds Default cache TTL.
53
73
  * @param caseSensitive Set to true if you want to differentiate between keys with different casing.
54
74
  */
55
- constructor({ ttlInSeconds, caseSensitive, redis, onSuccess, onError, }: PromiseCacheOptions);
75
+ constructor({ ttlInSeconds, caseSensitive, redis, onSuccess, onError, logger, }: PromiseCacheOptions);
56
76
  /**
57
77
  * Cache size.
58
78
  * @returns The number of entries in the cache.
59
79
  */
60
80
  size(): Promise<number>;
61
81
  /**
62
- * Set a value in the cache.
82
+ * Override a value in the cache.
63
83
  * @param key Cache key.
64
84
  * @param value Cache value.
65
85
  * @param ttlInSeconds Time to live in seconds.
@@ -81,19 +101,4 @@ declare class PromiseCache<U> {
81
101
  wrap(key: string, delegate: () => Promise<U>, ttlInSeconds?: number, ttlKeyInSeconds?: string): Promise<U>;
82
102
  }
83
103
 
84
- declare class LocalStorage {
85
- client: Map<any, any>;
86
- isReady: boolean;
87
- get(key: string): any;
88
- set(key: string, value: string, options?: {
89
- PX: number;
90
- }): void;
91
- del(key: string): void;
92
- clear(): void;
93
- DBSIZE(): Promise<number>;
94
- on(event: string, callback: (message: string) => void): this;
95
- connect(): Promise<this>;
96
- }
97
- declare const createLocalMemoryClient: () => LocalStorage;
98
-
99
- export { LocalStorage, Persistor, type PersistorConstructorType, PromiseCache, type PromiseCacheOptions, createLocalMemoryClient };
104
+ export { Persistor, type PersistorConstructorType, PromiseCache, type PromiseCacheOptions };
package/dist/index.js CHANGED
@@ -20,10 +20,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var src_exports = {};
22
22
  __export(src_exports, {
23
- LocalStorage: () => LocalStorage,
24
23
  Persistor: () => Persistor,
25
- PromiseCache: () => PromiseCache,
26
- createLocalMemoryClient: () => createLocalMemoryClient
24
+ PromiseCache: () => PromiseCache
27
25
  });
28
26
  module.exports = __toCommonJS(src_exports);
29
27
 
@@ -74,24 +72,144 @@ var createLocalMemoryClient = () => {
74
72
  return localStorage;
75
73
  };
76
74
 
75
+ // src/serializerUtils.ts
76
+ function serialize(value) {
77
+ const type = typeof value;
78
+ if (value === null) {
79
+ return JSON.stringify({ type: "null" });
80
+ }
81
+ if (value === void 0) {
82
+ return JSON.stringify({ type: "undefined" });
83
+ }
84
+ switch (type) {
85
+ case "string":
86
+ case "number":
87
+ case "boolean":
88
+ return JSON.stringify({ type, value });
89
+ case "bigint":
90
+ return JSON.stringify({ type: "bigint", value: value.toString() });
91
+ case "object": {
92
+ if (Array.isArray(value)) {
93
+ return JSON.stringify({ type: "array", value: value.map(serialize) });
94
+ }
95
+ if (value instanceof Map) {
96
+ const entries = Array.from(value.entries()).map(([key, val]) => [
97
+ serialize(key),
98
+ serialize(val)
99
+ ]);
100
+ return JSON.stringify({ type: "map", value: entries });
101
+ }
102
+ if (value instanceof Set) {
103
+ const entries = Array.from(value).map(serialize);
104
+ return JSON.stringify({ type: "set", value: entries });
105
+ }
106
+ if (value.constructor === Object) {
107
+ const entries = Object.entries(value).reduce(
108
+ (acc, [key, val]) => {
109
+ acc[key] = serialize(val);
110
+ return acc;
111
+ },
112
+ {}
113
+ );
114
+ return JSON.stringify({ type: "object", value: entries });
115
+ }
116
+ throw new Error("Cannot serialize non-plain objects");
117
+ }
118
+ default:
119
+ throw new Error(`Unsupported type: ${type}`);
120
+ }
121
+ }
122
+ function deserializePrimitives(serialized) {
123
+ let parsed = serialized;
124
+ if (typeof serialized === "string") {
125
+ parsed = JSON.parse(serialized);
126
+ }
127
+ switch (parsed.type) {
128
+ case "string":
129
+ return parsed.value;
130
+ case "number":
131
+ return Number(parsed.value);
132
+ case "boolean":
133
+ return Boolean(parsed.value);
134
+ case "undefined":
135
+ return void 0;
136
+ case "bigint":
137
+ return BigInt(parsed.value);
138
+ case "null":
139
+ return null;
140
+ default:
141
+ throw new Error(
142
+ `Unsupported type during deserialization: ${JSON.stringify(parsed)}`
143
+ );
144
+ }
145
+ }
146
+ function deserialize(serialized) {
147
+ let parsed = serialized;
148
+ if (typeof serialized === "string") {
149
+ parsed = JSON.parse(serialized);
150
+ }
151
+ switch (parsed.type) {
152
+ case "string":
153
+ case "number":
154
+ case "boolean":
155
+ case "undefined":
156
+ case "bigint":
157
+ case "null":
158
+ return deserializePrimitives(parsed);
159
+ case "array":
160
+ return parsed.value.map(deserialize);
161
+ case "map": {
162
+ const map = /* @__PURE__ */ new Map();
163
+ for (const [key, val] of parsed.value) {
164
+ map.set(deserialize(key), deserialize(val));
165
+ }
166
+ return map;
167
+ }
168
+ case "set": {
169
+ const set = /* @__PURE__ */ new Set();
170
+ for (const item of parsed.value) {
171
+ set.add(deserialize(item));
172
+ }
173
+ return set;
174
+ }
175
+ case "object": {
176
+ const obj = {};
177
+ for (const [key, val] of Object.entries(parsed.value)) {
178
+ obj[key] = deserialize(val);
179
+ }
180
+ return obj;
181
+ }
182
+ default:
183
+ throw new Error(`Unsupported type during deserialization: ${parsed}`);
184
+ }
185
+ }
186
+
77
187
  // src/persistor.ts
78
188
  var CACHE_CLIENT = import_redis.createClient;
79
189
  var isTestRunning = process.env.NODE_ENV === "test";
190
+ function toMillis(seconds) {
191
+ return seconds / 1e3;
192
+ }
80
193
  var Persistor = class {
81
194
  client = null;
82
195
  clientId;
83
196
  onError;
84
197
  onSuccess;
198
+ logger;
85
199
  redis;
86
200
  constructor({
87
201
  redis,
88
202
  clientId,
89
203
  onSuccess,
90
- onError
204
+ onError,
205
+ logger
91
206
  }) {
92
- this.onError = onError;
93
- this.onSuccess = onSuccess;
207
+ this.onError = onError || (() => {
208
+ });
209
+ this.onSuccess = onSuccess || (() => {
210
+ });
94
211
  this.clientId = clientId;
212
+ this.logger = logger;
95
213
  if (redis && !isTestRunning) {
96
214
  this.redis = redis;
97
215
  } else {
@@ -102,11 +220,12 @@ var Persistor = class {
102
220
  }
103
221
  }
104
222
  async startConnection() {
223
+ var _a;
105
224
  try {
106
225
  await new Promise((resolve, reject) => {
107
- var _a;
226
+ var _a2;
108
227
  this.client = CACHE_CLIENT({
109
- url: (_a = this.redis) == null ? void 0 : _a.url,
228
+ url: (_a2 = this.redis) == null ? void 0 : _a2.url,
110
229
  socket: {
111
230
  reconnectStrategy: (retries, cause) => {
112
231
  console.error(cause);
@@ -120,15 +239,17 @@ var Persistor = class {
120
239
  this.onSuccess();
121
240
  resolve(true);
122
241
  }).on("reconnecting", () => {
123
- console.log("reconnecting...", this.clientId);
242
+ var _a3;
243
+ (_a3 = this.logger) == null ? void 0 : _a3.info("reconnecting...", this.clientId);
124
244
  }).on("end", () => {
125
- console.log("end...", this.clientId);
245
+ var _a3;
246
+ (_a3 = this.logger) == null ? void 0 : _a3.info("end...", this.clientId);
126
247
  });
127
248
  this.client.connect();
128
249
  });
129
250
  } catch (ex) {
130
251
  this.onError(`${ex}`);
131
- console.error(ex);
252
+ (_a = this.logger) == null ? void 0 : _a.error(ex);
132
253
  }
133
254
  }
134
255
  async size() {
@@ -137,20 +258,6 @@ var Persistor = class {
137
258
  }
138
259
  return await this.client.DBSIZE();
139
260
  }
140
- async get(key) {
141
- if (!this.client) {
142
- throw new Error("Client not initialized");
143
- }
144
- try {
145
- const result = await this.client.get(key);
146
- if (!result) {
147
- return null;
148
- }
149
- return JSON.parse(result);
150
- } catch (error) {
151
- throw new Error(`Error getting data from redis: ${error}`);
152
- }
153
- }
154
261
  getClientId() {
155
262
  return this.clientId;
156
263
  }
@@ -160,26 +267,67 @@ var Persistor = class {
160
267
  }
161
268
  createOptions(ttl) {
162
269
  if (ttl !== null && ttl !== void 0) {
163
- return { PX: Math.round(ttl) };
270
+ return { PX: Math.round(toMillis(ttl)) };
164
271
  }
165
272
  return {};
166
273
  }
167
- async set(key, { value, timestamp, ttl }) {
274
+ /**
275
+ * Set a value in the cache.
276
+ * @param key Cache key.
277
+ * @param object.value Value to set in the cache.
278
+ * @param object.ttl Time to live in seconds.
279
+ * @param object.timestamp Timestamp
280
+ */
281
+ async set(key, { value, timestamp = Date.now(), ttl }) {
282
+ var _a;
168
283
  if (!this.client || !this.client.isReady) {
169
- console.error("Client not ready");
284
+ (_a = this.logger) == null ? void 0 : _a.error("Client not ready");
170
285
  return;
171
286
  }
172
287
  try {
173
- const serializedData = JSON.stringify({ value, ttl, timestamp });
288
+ const serializedData = JSON.stringify({
289
+ value: serialize(value),
290
+ ttl,
291
+ timestamp
292
+ });
174
293
  const options = this.createOptions(ttl);
175
294
  await this.client.set(key, serializedData, options);
176
295
  } catch (error) {
177
296
  throw new Error(`Error setting data in redis: ${error}`);
178
297
  }
179
298
  }
299
+ /**
300
+ * Get a value from the cache.
301
+ * @param key Cache key.
302
+ * @returns GetType<T> value
303
+ */
304
+ async get(key) {
305
+ if (!this.client) {
306
+ throw new Error("Client not initialized");
307
+ }
308
+ try {
309
+ const data = await this.client.get(key);
310
+ if (!data) {
311
+ return null;
312
+ }
313
+ const storedData = JSON.parse(data);
314
+ const deserialized = JSON.parse(storedData.value);
315
+ return {
316
+ ...storedData,
317
+ value: deserialize(deserialized)
318
+ };
319
+ } catch (error) {
320
+ throw new Error(`Error getting data from redis: ${error}`);
321
+ }
322
+ }
323
+ /**
324
+ * Delete a value from the cache.
325
+ * @param key Cache key
326
+ */
180
327
  async delete(key) {
328
+ var _a;
181
329
  if (!this.client || !this.client.isReady) {
182
- console.error("Client not ready");
330
+ (_a = this.logger) == null ? void 0 : _a.error("Client not ready");
183
331
  return;
184
332
  }
185
333
  try {
@@ -194,6 +342,7 @@ var Persistor = class {
194
342
  var persistors = {};
195
343
  var getPersistor = ({
196
344
  redis,
345
+ logger,
197
346
  onError,
198
347
  onSuccess,
199
348
  clientId
@@ -204,17 +353,18 @@ var getPersistor = ({
204
353
  redis,
205
354
  onError: (error) => {
206
355
  onError == null ? void 0 : onError(error);
207
- console.error(
356
+ logger == null ? void 0 : logger.error(
208
357
  `\u274C REDIS | Client Error | ${connectionName} | ${redis == null ? void 0 : redis.url}: ${error}`
209
358
  );
210
359
  },
211
360
  onSuccess: () => {
212
361
  onSuccess == null ? void 0 : onSuccess();
213
- console.log(
362
+ logger == null ? void 0 : logger.info(
214
363
  `\u{1F4E6} REDIS | Connection Ready | ${connectionName} | ${redis == null ? void 0 : redis.url}`
215
364
  );
216
365
  },
217
- clientId
366
+ clientId,
367
+ logger
218
368
  });
219
369
  }
220
370
  return persistors[connectionName];
@@ -235,13 +385,15 @@ var PromiseCache = class {
235
385
  caseSensitive = false,
236
386
  redis,
237
387
  onSuccess,
238
- onError
388
+ onError,
389
+ logger
239
390
  }) {
240
391
  this.persistor = getPersistor({
241
392
  redis,
242
393
  onError,
243
394
  onSuccess,
244
- clientId: this.clientId
395
+ clientId: this.clientId,
396
+ logger
245
397
  });
246
398
  this.caseSensitive = caseSensitive;
247
399
  if (ttlInSeconds) {
@@ -256,14 +408,14 @@ var PromiseCache = class {
256
408
  return await this.persistor.size();
257
409
  }
258
410
  /**
259
- * Set a value in the cache.
411
+ * Override a value in the cache.
260
412
  * @param key Cache key.
261
413
  * @param value Cache value.
262
414
  * @param ttlInSeconds Time to live in seconds.
263
415
  */
264
416
  async override(key, value, ttlInSeconds) {
265
417
  const effectiveKey = this.caseSensitive ? key : key.toLowerCase();
266
- const effectiveTTL = ttlInSeconds !== void 0 ? ttlInSeconds * 1e3 : this.ttl;
418
+ const effectiveTTL = ttlInSeconds !== void 0 ? ttlInSeconds : this.ttl;
267
419
  await this.persistor.set(effectiveKey, {
268
420
  value,
269
421
  timestamp: Date.now(),
@@ -294,7 +446,7 @@ var PromiseCache = class {
294
446
  if (cached) {
295
447
  if (!ttlKeyInSeconds && cached.ttl !== effectiveTTL) {
296
448
  console.error(
297
- `WARNING: TTL mismatch for key: ${effectiveKey}. It is recommended to use the same TTL for the same key.`
449
+ "WARNING: TTL mismatch for key. It is recommended to use the same TTL for the same key."
298
450
  );
299
451
  }
300
452
  return cached.value;
@@ -315,8 +467,6 @@ var PromiseCache = class {
315
467
  };
316
468
  // Annotate the CommonJS export names for ESM import in node:
317
469
  0 && (module.exports = {
318
- LocalStorage,
319
470
  Persistor,
320
- PromiseCache,
321
- createLocalMemoryClient
471
+ PromiseCache
322
472
  });
package/dist/index.mjs CHANGED
@@ -45,24 +45,144 @@ var createLocalMemoryClient = () => {
45
45
  return localStorage;
46
46
  };
47
47
 
48
+ // src/serializerUtils.ts
49
+ function serialize(value) {
50
+ const type = typeof value;
51
+ if (value === null) {
52
+ return JSON.stringify({ type: "null" });
53
+ }
54
+ if (value === void 0) {
55
+ return JSON.stringify({ type: "undefined" });
56
+ }
57
+ switch (type) {
58
+ case "string":
59
+ case "number":
60
+ case "boolean":
61
+ return JSON.stringify({ type, value });
62
+ case "bigint":
63
+ return JSON.stringify({ type: "bigint", value: value.toString() });
64
+ case "object": {
65
+ if (Array.isArray(value)) {
66
+ return JSON.stringify({ type: "array", value: value.map(serialize) });
67
+ }
68
+ if (value instanceof Map) {
69
+ const entries = Array.from(value.entries()).map(([key, val]) => [
70
+ serialize(key),
71
+ serialize(val)
72
+ ]);
73
+ return JSON.stringify({ type: "map", value: entries });
74
+ }
75
+ if (value instanceof Set) {
76
+ const entries = Array.from(value).map(serialize);
77
+ return JSON.stringify({ type: "set", value: entries });
78
+ }
79
+ if (value.constructor === Object) {
80
+ const entries = Object.entries(value).reduce(
81
+ (acc, [key, val]) => {
82
+ acc[key] = serialize(val);
83
+ return acc;
84
+ },
85
+ {}
86
+ );
87
+ return JSON.stringify({ type: "object", value: entries });
88
+ }
89
+ throw new Error("Cannot serialize non-plain objects");
90
+ }
91
+ default:
92
+ throw new Error(`Unsupported type: ${type}`);
93
+ }
94
+ }
95
+ function deserializePrimitives(serialized) {
96
+ let parsed = serialized;
97
+ if (typeof serialized === "string") {
98
+ parsed = JSON.parse(serialized);
99
+ }
100
+ switch (parsed.type) {
101
+ case "string":
102
+ return parsed.value;
103
+ case "number":
104
+ return Number(parsed.value);
105
+ case "boolean":
106
+ return Boolean(parsed.value);
107
+ case "undefined":
108
+ return void 0;
109
+ case "bigint":
110
+ return BigInt(parsed.value);
111
+ case "null":
112
+ return null;
113
+ default:
114
+ throw new Error(
115
+ `Unsupported type during deserialization: ${JSON.stringify(parsed)}`
116
+ );
117
+ }
118
+ }
119
+ function deserialize(serialized) {
120
+ let parsed = serialized;
121
+ if (typeof serialized === "string") {
122
+ parsed = JSON.parse(serialized);
123
+ }
124
+ switch (parsed.type) {
125
+ case "string":
126
+ case "number":
127
+ case "boolean":
128
+ case "undefined":
129
+ case "bigint":
130
+ case "null":
131
+ return deserializePrimitives(parsed);
132
+ case "array":
133
+ return parsed.value.map(deserialize);
134
+ case "map": {
135
+ const map = /* @__PURE__ */ new Map();
136
+ for (const [key, val] of parsed.value) {
137
+ map.set(deserialize(key), deserialize(val));
138
+ }
139
+ return map;
140
+ }
141
+ case "set": {
142
+ const set = /* @__PURE__ */ new Set();
143
+ for (const item of parsed.value) {
144
+ set.add(deserialize(item));
145
+ }
146
+ return set;
147
+ }
148
+ case "object": {
149
+ const obj = {};
150
+ for (const [key, val] of Object.entries(parsed.value)) {
151
+ obj[key] = deserialize(val);
152
+ }
153
+ return obj;
154
+ }
155
+ default:
156
+ throw new Error(`Unsupported type during deserialization: ${parsed}`);
157
+ }
158
+ }
159
+
48
160
  // src/persistor.ts
49
161
  var CACHE_CLIENT = createClient;
50
162
  var isTestRunning = process.env.NODE_ENV === "test";
163
+ function toMillis(seconds) {
164
+ return seconds / 1e3;
165
+ }
51
166
  var Persistor = class {
52
167
  client = null;
53
168
  clientId;
54
169
  onError;
55
170
  onSuccess;
171
+ logger;
56
172
  redis;
57
173
  constructor({
58
174
  redis,
59
175
  clientId,
60
176
  onSuccess,
61
- onError
177
+ onError,
178
+ logger
62
179
  }) {
63
- this.onError = onError;
64
- this.onSuccess = onSuccess;
180
+ this.onError = onError || (() => {
181
+ });
182
+ this.onSuccess = onSuccess || (() => {
183
+ });
65
184
  this.clientId = clientId;
185
+ this.logger = logger;
66
186
  if (redis && !isTestRunning) {
67
187
  this.redis = redis;
68
188
  } else {
@@ -73,11 +193,12 @@ var Persistor = class {
73
193
  }
74
194
  }
75
195
  async startConnection() {
196
+ var _a;
76
197
  try {
77
198
  await new Promise((resolve, reject) => {
78
- var _a;
199
+ var _a2;
79
200
  this.client = CACHE_CLIENT({
80
- url: (_a = this.redis) == null ? void 0 : _a.url,
201
+ url: (_a2 = this.redis) == null ? void 0 : _a2.url,
81
202
  socket: {
82
203
  reconnectStrategy: (retries, cause) => {
83
204
  console.error(cause);
@@ -91,15 +212,17 @@ var Persistor = class {
91
212
  this.onSuccess();
92
213
  resolve(true);
93
214
  }).on("reconnecting", () => {
94
- console.log("reconnecting...", this.clientId);
215
+ var _a3;
216
+ (_a3 = this.logger) == null ? void 0 : _a3.info("reconnecting...", this.clientId);
95
217
  }).on("end", () => {
96
- console.log("end...", this.clientId);
218
+ var _a3;
219
+ (_a3 = this.logger) == null ? void 0 : _a3.info("end...", this.clientId);
97
220
  });
98
221
  this.client.connect();
99
222
  });
100
223
  } catch (ex) {
101
224
  this.onError(`${ex}`);
102
- console.error(ex);
225
+ (_a = this.logger) == null ? void 0 : _a.error(ex);
103
226
  }
104
227
  }
105
228
  async size() {
@@ -108,20 +231,6 @@ var Persistor = class {
108
231
  }
109
232
  return await this.client.DBSIZE();
110
233
  }
111
- async get(key) {
112
- if (!this.client) {
113
- throw new Error("Client not initialized");
114
- }
115
- try {
116
- const result = await this.client.get(key);
117
- if (!result) {
118
- return null;
119
- }
120
- return JSON.parse(result);
121
- } catch (error) {
122
- throw new Error(`Error getting data from redis: ${error}`);
123
- }
124
- }
125
234
  getClientId() {
126
235
  return this.clientId;
127
236
  }
@@ -131,26 +240,67 @@ var Persistor = class {
131
240
  }
132
241
  createOptions(ttl) {
133
242
  if (ttl !== null && ttl !== void 0) {
134
- return { PX: Math.round(ttl) };
243
+ return { PX: Math.round(toMillis(ttl)) };
135
244
  }
136
245
  return {};
137
246
  }
138
- async set(key, { value, timestamp, ttl }) {
247
+ /**
248
+ * Set a value in the cache.
249
+ * @param key Cache key.
250
+ * @param object.value Value to set in the cache.
251
+ * @param object.ttl Time to live in seconds.
252
+ * @param object.timestamp Timestamp
253
+ */
254
+ async set(key, { value, timestamp = Date.now(), ttl }) {
255
+ var _a;
139
256
  if (!this.client || !this.client.isReady) {
140
- console.error("Client not ready");
257
+ (_a = this.logger) == null ? void 0 : _a.error("Client not ready");
141
258
  return;
142
259
  }
143
260
  try {
144
- const serializedData = JSON.stringify({ value, ttl, timestamp });
261
+ const serializedData = JSON.stringify({
262
+ value: serialize(value),
263
+ ttl,
264
+ timestamp
265
+ });
145
266
  const options = this.createOptions(ttl);
146
267
  await this.client.set(key, serializedData, options);
147
268
  } catch (error) {
148
269
  throw new Error(`Error setting data in redis: ${error}`);
149
270
  }
150
271
  }
272
+ /**
273
+ * Get a value from the cache.
274
+ * @param key Cache key.
275
+ * @returns GetType<T> value
276
+ */
277
+ async get(key) {
278
+ if (!this.client) {
279
+ throw new Error("Client not initialized");
280
+ }
281
+ try {
282
+ const data = await this.client.get(key);
283
+ if (!data) {
284
+ return null;
285
+ }
286
+ const storedData = JSON.parse(data);
287
+ const deserialized = JSON.parse(storedData.value);
288
+ return {
289
+ ...storedData,
290
+ value: deserialize(deserialized)
291
+ };
292
+ } catch (error) {
293
+ throw new Error(`Error getting data from redis: ${error}`);
294
+ }
295
+ }
296
+ /**
297
+ * Delete a value from the cache.
298
+ * @param key Cache key
299
+ */
151
300
  async delete(key) {
301
+ var _a;
152
302
  if (!this.client || !this.client.isReady) {
153
- console.error("Client not ready");
303
+ (_a = this.logger) == null ? void 0 : _a.error("Client not ready");
154
304
  return;
155
305
  }
156
306
  try {
@@ -165,6 +315,7 @@ var Persistor = class {
165
315
  var persistors = {};
166
316
  var getPersistor = ({
167
317
  redis,
318
+ logger,
168
319
  onError,
169
320
  onSuccess,
170
321
  clientId
@@ -175,17 +326,18 @@ var getPersistor = ({
175
326
  redis,
176
327
  onError: (error) => {
177
328
  onError == null ? void 0 : onError(error);
178
- console.error(
329
+ logger == null ? void 0 : logger.error(
179
330
  `\u274C REDIS | Client Error | ${connectionName} | ${redis == null ? void 0 : redis.url}: ${error}`
180
331
  );
181
332
  },
182
333
  onSuccess: () => {
183
334
  onSuccess == null ? void 0 : onSuccess();
184
- console.log(
335
+ logger == null ? void 0 : logger.info(
185
336
  `\u{1F4E6} REDIS | Connection Ready | ${connectionName} | ${redis == null ? void 0 : redis.url}`
186
337
  );
187
338
  },
188
- clientId
339
+ clientId,
340
+ logger
189
341
  });
190
342
  }
191
343
  return persistors[connectionName];
@@ -206,13 +358,15 @@ var PromiseCache = class {
206
358
  caseSensitive = false,
207
359
  redis,
208
360
  onSuccess,
209
- onError
361
+ onError,
362
+ logger
210
363
  }) {
211
364
  this.persistor = getPersistor({
212
365
  redis,
213
366
  onError,
214
367
  onSuccess,
215
- clientId: this.clientId
368
+ clientId: this.clientId,
369
+ logger
216
370
  });
217
371
  this.caseSensitive = caseSensitive;
218
372
  if (ttlInSeconds) {
@@ -227,14 +381,14 @@ var PromiseCache = class {
227
381
  return await this.persistor.size();
228
382
  }
229
383
  /**
230
- * Set a value in the cache.
384
+ * Override a value in the cache.
231
385
  * @param key Cache key.
232
386
  * @param value Cache value.
233
387
  * @param ttlInSeconds Time to live in seconds.
234
388
  */
235
389
  async override(key, value, ttlInSeconds) {
236
390
  const effectiveKey = this.caseSensitive ? key : key.toLowerCase();
237
- const effectiveTTL = ttlInSeconds !== void 0 ? ttlInSeconds * 1e3 : this.ttl;
391
+ const effectiveTTL = ttlInSeconds !== void 0 ? ttlInSeconds : this.ttl;
238
392
  await this.persistor.set(effectiveKey, {
239
393
  value,
240
394
  timestamp: Date.now(),
@@ -265,7 +419,7 @@ var PromiseCache = class {
265
419
  if (cached) {
266
420
  if (!ttlKeyInSeconds && cached.ttl !== effectiveTTL) {
267
421
  console.error(
268
- `WARNING: TTL mismatch for key: ${effectiveKey}. It is recommended to use the same TTL for the same key.`
422
+ "WARNING: TTL mismatch for key. It is recommended to use the same TTL for the same key."
269
423
  );
270
424
  }
271
425
  return cached.value;
@@ -285,8 +439,6 @@ var PromiseCache = class {
285
439
  }
286
440
  };
287
441
  export {
288
- LocalStorage,
289
442
  Persistor,
290
- PromiseCache,
291
- createLocalMemoryClient
443
+ PromiseCache
292
444
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sebspark/promise-cache",
3
- "version": "2.1.1",
3
+ "version": "3.0.0",
4
4
  "license": "Apache-2.0",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -19,7 +19,6 @@
19
19
  "tsconfig": "*"
20
20
  },
21
21
  "dependencies": {
22
- "redis": "4.7.0",
23
- "@sebspark/retry": "*"
22
+ "redis": "4.7.0"
24
23
  }
25
24
  }