@sebspark/promise-cache 2.1.2 → 3.0.1

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,31 +267,77 @@ 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, _b;
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) {
296
+ (_b = this.logger) == null ? void 0 : _b.error(`Error setting data in redis: ${error}`);
177
297
  throw new Error(`Error setting data in redis: ${error}`);
178
298
  }
179
299
  }
300
+ /**
301
+ * Get a value from the cache.
302
+ * @param key Cache key.
303
+ * @returns GetType<T> value
304
+ */
305
+ async get(key) {
306
+ var _a, _b;
307
+ if (!this.client) {
308
+ (_a = this.logger) == null ? void 0 : _a.error("Client not ready");
309
+ return null;
310
+ }
311
+ try {
312
+ const data = await this.client.get(key);
313
+ if (!data) {
314
+ return null;
315
+ }
316
+ const storedData = JSON.parse(data);
317
+ const deserialized = JSON.parse(storedData.value);
318
+ return {
319
+ ...storedData,
320
+ value: deserialize(deserialized)
321
+ };
322
+ } catch (error) {
323
+ (_b = this.logger) == null ? void 0 : _b.error(`Error getting data in redis: ${error}`);
324
+ throw new Error(`Error getting data from redis: ${error}`);
325
+ }
326
+ }
327
+ /**
328
+ * Delete a value from the cache.
329
+ * @param key Cache key
330
+ */
180
331
  async delete(key) {
332
+ var _a, _b;
181
333
  if (!this.client || !this.client.isReady) {
182
- console.error("Client not ready");
334
+ (_a = this.logger) == null ? void 0 : _a.error("Client not ready");
183
335
  return;
184
336
  }
185
337
  try {
186
338
  await this.client.del(key);
187
339
  } catch (error) {
340
+ (_b = this.logger) == null ? void 0 : _b.error(`Error deleting data from redis: ${error}`);
188
341
  throw new Error(`Error deleting data from redis: ${error}`);
189
342
  }
190
343
  }
@@ -194,6 +347,7 @@ var Persistor = class {
194
347
  var persistors = {};
195
348
  var getPersistor = ({
196
349
  redis,
350
+ logger,
197
351
  onError,
198
352
  onSuccess,
199
353
  clientId
@@ -204,17 +358,18 @@ var getPersistor = ({
204
358
  redis,
205
359
  onError: (error) => {
206
360
  onError == null ? void 0 : onError(error);
207
- console.error(
361
+ logger == null ? void 0 : logger.error(
208
362
  `\u274C REDIS | Client Error | ${connectionName} | ${redis == null ? void 0 : redis.url}: ${error}`
209
363
  );
210
364
  },
211
365
  onSuccess: () => {
212
366
  onSuccess == null ? void 0 : onSuccess();
213
- console.log(
367
+ logger == null ? void 0 : logger.info(
214
368
  `\u{1F4E6} REDIS | Connection Ready | ${connectionName} | ${redis == null ? void 0 : redis.url}`
215
369
  );
216
370
  },
217
- clientId
371
+ clientId,
372
+ logger
218
373
  });
219
374
  }
220
375
  return persistors[connectionName];
@@ -235,13 +390,15 @@ var PromiseCache = class {
235
390
  caseSensitive = false,
236
391
  redis,
237
392
  onSuccess,
238
- onError
393
+ onError,
394
+ logger
239
395
  }) {
240
396
  this.persistor = getPersistor({
241
397
  redis,
242
398
  onError,
243
399
  onSuccess,
244
- clientId: this.clientId
400
+ clientId: this.clientId,
401
+ logger
245
402
  });
246
403
  this.caseSensitive = caseSensitive;
247
404
  if (ttlInSeconds) {
@@ -256,14 +413,14 @@ var PromiseCache = class {
256
413
  return await this.persistor.size();
257
414
  }
258
415
  /**
259
- * Set a value in the cache.
416
+ * Override a value in the cache.
260
417
  * @param key Cache key.
261
418
  * @param value Cache value.
262
419
  * @param ttlInSeconds Time to live in seconds.
263
420
  */
264
421
  async override(key, value, ttlInSeconds) {
265
422
  const effectiveKey = this.caseSensitive ? key : key.toLowerCase();
266
- const effectiveTTL = ttlInSeconds !== void 0 ? ttlInSeconds * 1e3 : this.ttl;
423
+ const effectiveTTL = ttlInSeconds !== void 0 ? ttlInSeconds : this.ttl;
267
424
  await this.persistor.set(effectiveKey, {
268
425
  value,
269
426
  timestamp: Date.now(),
@@ -315,8 +472,6 @@ var PromiseCache = class {
315
472
  };
316
473
  // Annotate the CommonJS export names for ESM import in node:
317
474
  0 && (module.exports = {
318
- LocalStorage,
319
475
  Persistor,
320
- PromiseCache,
321
- createLocalMemoryClient
476
+ PromiseCache
322
477
  });
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,31 +240,77 @@ 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, _b;
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) {
269
+ (_b = this.logger) == null ? void 0 : _b.error(`Error setting data in redis: ${error}`);
148
270
  throw new Error(`Error setting data in redis: ${error}`);
149
271
  }
150
272
  }
273
+ /**
274
+ * Get a value from the cache.
275
+ * @param key Cache key.
276
+ * @returns GetType<T> value
277
+ */
278
+ async get(key) {
279
+ var _a, _b;
280
+ if (!this.client) {
281
+ (_a = this.logger) == null ? void 0 : _a.error("Client not ready");
282
+ return null;
283
+ }
284
+ try {
285
+ const data = await this.client.get(key);
286
+ if (!data) {
287
+ return null;
288
+ }
289
+ const storedData = JSON.parse(data);
290
+ const deserialized = JSON.parse(storedData.value);
291
+ return {
292
+ ...storedData,
293
+ value: deserialize(deserialized)
294
+ };
295
+ } catch (error) {
296
+ (_b = this.logger) == null ? void 0 : _b.error(`Error getting data in redis: ${error}`);
297
+ throw new Error(`Error getting data from redis: ${error}`);
298
+ }
299
+ }
300
+ /**
301
+ * Delete a value from the cache.
302
+ * @param key Cache key
303
+ */
151
304
  async delete(key) {
305
+ var _a, _b;
152
306
  if (!this.client || !this.client.isReady) {
153
- console.error("Client not ready");
307
+ (_a = this.logger) == null ? void 0 : _a.error("Client not ready");
154
308
  return;
155
309
  }
156
310
  try {
157
311
  await this.client.del(key);
158
312
  } catch (error) {
313
+ (_b = this.logger) == null ? void 0 : _b.error(`Error deleting data from redis: ${error}`);
159
314
  throw new Error(`Error deleting data from redis: ${error}`);
160
315
  }
161
316
  }
@@ -165,6 +320,7 @@ var Persistor = class {
165
320
  var persistors = {};
166
321
  var getPersistor = ({
167
322
  redis,
323
+ logger,
168
324
  onError,
169
325
  onSuccess,
170
326
  clientId
@@ -175,17 +331,18 @@ var getPersistor = ({
175
331
  redis,
176
332
  onError: (error) => {
177
333
  onError == null ? void 0 : onError(error);
178
- console.error(
334
+ logger == null ? void 0 : logger.error(
179
335
  `\u274C REDIS | Client Error | ${connectionName} | ${redis == null ? void 0 : redis.url}: ${error}`
180
336
  );
181
337
  },
182
338
  onSuccess: () => {
183
339
  onSuccess == null ? void 0 : onSuccess();
184
- console.log(
340
+ logger == null ? void 0 : logger.info(
185
341
  `\u{1F4E6} REDIS | Connection Ready | ${connectionName} | ${redis == null ? void 0 : redis.url}`
186
342
  );
187
343
  },
188
- clientId
344
+ clientId,
345
+ logger
189
346
  });
190
347
  }
191
348
  return persistors[connectionName];
@@ -206,13 +363,15 @@ var PromiseCache = class {
206
363
  caseSensitive = false,
207
364
  redis,
208
365
  onSuccess,
209
- onError
366
+ onError,
367
+ logger
210
368
  }) {
211
369
  this.persistor = getPersistor({
212
370
  redis,
213
371
  onError,
214
372
  onSuccess,
215
- clientId: this.clientId
373
+ clientId: this.clientId,
374
+ logger
216
375
  });
217
376
  this.caseSensitive = caseSensitive;
218
377
  if (ttlInSeconds) {
@@ -227,14 +386,14 @@ var PromiseCache = class {
227
386
  return await this.persistor.size();
228
387
  }
229
388
  /**
230
- * Set a value in the cache.
389
+ * Override a value in the cache.
231
390
  * @param key Cache key.
232
391
  * @param value Cache value.
233
392
  * @param ttlInSeconds Time to live in seconds.
234
393
  */
235
394
  async override(key, value, ttlInSeconds) {
236
395
  const effectiveKey = this.caseSensitive ? key : key.toLowerCase();
237
- const effectiveTTL = ttlInSeconds !== void 0 ? ttlInSeconds * 1e3 : this.ttl;
396
+ const effectiveTTL = ttlInSeconds !== void 0 ? ttlInSeconds : this.ttl;
238
397
  await this.persistor.set(effectiveKey, {
239
398
  value,
240
399
  timestamp: Date.now(),
@@ -285,8 +444,6 @@ var PromiseCache = class {
285
444
  }
286
445
  };
287
446
  export {
288
- LocalStorage,
289
447
  Persistor,
290
- PromiseCache,
291
- createLocalMemoryClient
448
+ PromiseCache
292
449
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sebspark/promise-cache",
3
- "version": "2.1.2",
3
+ "version": "3.0.1",
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
  }