@rapidrest/core 3.1.0 → 4.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.
@@ -12,11 +12,14 @@ export class CacheUtils {
12
12
  * whatever entry was `set()` least recently among those still present.
13
13
  *
14
14
  * @param map The map to evict the oldest entry from.
15
+ * @param num The total number of items to evict. Default is `1`.
15
16
  */
16
- static evictOldest(map) {
17
- const oldestKey = map.keys().next().value;
18
- if (oldestKey !== undefined) {
19
- map.delete(oldestKey);
17
+ static evictOldest(map, num = 1) {
18
+ for (let i = 0; i < num; i++) {
19
+ const oldestKey = map.keys().next().value;
20
+ if (oldestKey !== undefined) {
21
+ map.delete(oldestKey);
22
+ }
20
23
  }
21
24
  }
22
25
  }
@@ -1 +1 @@
1
- {"version":3,"file":"CacheUtils.js","sourceRoot":"","sources":["../../src/CacheUtils.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,+EAA+E;AAE/E;;;;GAIG;AACH,MAAM,OAAO,UAAU;IACnB;;;;;OAKG;IACI,MAAM,CAAC,WAAW,CAAO,GAAc;QAC1C,MAAM,SAAS,GAAkB,GAAG,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;QACzD,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC1B,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC1B,CAAC;IACL,CAAC;CACJ"}
1
+ {"version":3,"file":"CacheUtils.js","sourceRoot":"","sources":["../../src/CacheUtils.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,+EAA+E;AAE/E;;;;GAIG;AACH,MAAM,OAAO,UAAU;IACnB;;;;;;OAMG;IACI,MAAM,CAAC,WAAW,CAAO,GAAc,EAAE,MAAc,CAAC;QAC3D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3B,MAAM,SAAS,GAAkB,GAAG,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;YACzD,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;gBAC1B,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YAC1B,CAAC;QACL,CAAC;IACL,CAAC;CACJ"}
@@ -26,9 +26,28 @@ export class MemoryStore {
26
26
  this.entries = new Map();
27
27
  /** The maximum number of records to store. */
28
28
  this.maxSize = 10000;
29
+ this.sets = new Map();
29
30
  this.sweepTimer = setInterval(() => this.sweep(), SWEEP_INTERVAL_MS);
30
31
  this.sweepTimer.unref?.();
31
32
  }
33
+ clear() {
34
+ this.entries.clear();
35
+ this.sets.clear();
36
+ }
37
+ delete(id) {
38
+ this.entries.delete(id);
39
+ }
40
+ deleteMany(ids) {
41
+ for (const id of ids) {
42
+ this.entries.delete(id);
43
+ }
44
+ }
45
+ deleteSet(id) {
46
+ // We purposefully don't delete the individually stored records
47
+ // as they may be relevant to other valid queries. Let them expire
48
+ // naturally.
49
+ this.sets.delete(id);
50
+ }
32
51
  /** Stops the background sweep timer. Call when the owning `SessionManager` is destroyed. */
33
52
  destroy() {
34
53
  clearInterval(this.sweepTimer);
@@ -45,6 +64,20 @@ export class MemoryStore {
45
64
  }
46
65
  return entry.data;
47
66
  }
67
+ loadMany(ids) {
68
+ const results = [];
69
+ for (const id of ids) {
70
+ results.push(this.load(id));
71
+ }
72
+ return results;
73
+ }
74
+ loadSet(id) {
75
+ const ids = this.sets.get(id);
76
+ if (!ids) {
77
+ return undefined;
78
+ }
79
+ return this.loadMany(ids);
80
+ }
48
81
  save(id, data, ttl = this.defaultTTL) {
49
82
  if (!this.entries.has(id) && this.entries.size >= this.maxSize) {
50
83
  // Reclaim space by sweeping expired entries first, then evicting the oldest surviving entries
@@ -60,8 +93,34 @@ export class MemoryStore {
60
93
  this.entries.delete(id);
61
94
  this.entries.set(id, { data, expiresAt: Date.now() + ttl * 1000 });
62
95
  }
63
- delete(id) {
64
- this.entries.delete(id);
96
+ saveMany(ids, data, ttl = this.defaultTTL) {
97
+ if (ids.length !== data.length) {
98
+ throw new Error("The ids and data arrays have different lengths.");
99
+ }
100
+ for (let i = 0; i < ids.length; i++) {
101
+ this.save(ids[i], data[i], ttl);
102
+ }
103
+ }
104
+ saveSet(id, data, idProp = "uid", ttl = this.defaultTTL) {
105
+ // Extract the list of ids from the data set, skipping any record that has no idProp value — otherwise
106
+ // every such record would collide under the same String(undefined) === "undefined" key and silently
107
+ // overwrite one another. We convert this to string so that it's a usable reference in our primary
108
+ // storage map.
109
+ const ids = [];
110
+ const records = [];
111
+ for (const record of data) {
112
+ const recordId = record[idProp];
113
+ if (recordId !== undefined && recordId !== null) {
114
+ ids.push(String(recordId));
115
+ records.push(record);
116
+ }
117
+ }
118
+ // Now store each record
119
+ for (let i = 0; i < ids.length; i++) {
120
+ this.save(ids[i], records[i], ttl);
121
+ }
122
+ // Now store the id set
123
+ this.sets.set(id, ids);
65
124
  }
66
125
  sweep() {
67
126
  const now = Date.now();
@@ -1 +1 @@
1
- {"version":3,"file":"MemoryStore.js","sourceRoot":"","sources":["../../src/MemoryStore.ts"],"names":[],"mappings":";;;;;;;;;AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,+EAA+E;AAC/E,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAC;AAG3D,8EAA8E;AAC9E,MAAM,iBAAiB,GAAG,KAAM,CAAC;AAEjC;;;;GAIG;AACH,MAAM,OAAO,WAAW;IAWpB;QAVA,2CAA2C;QACpC,eAAU,GAAW,EAAE,CAAC;QAErB,YAAO,GAAkC,IAAI,GAAG,EAAE,CAAC;QAE7D,8CAA8C;QACvC,YAAO,GAAW,KAAK,CAAC;QAK3B,IAAI,CAAC,UAAU,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,iBAAiB,CAAC,CAAC;QACrE,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,CAAC;IAC9B,CAAC;IAED,4FAA4F;IAErF,OAAO;QACV,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACnC,CAAC;IAEM,IAAI,CAAC,EAAU;QAClB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACnC,IAAI,CAAC,KAAK,EAAE,CAAC;YACT,OAAO,SAAS,CAAC;QACrB,CAAC;QAED,2CAA2C;QAC3C,IAAI,KAAK,CAAC,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;YAChC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACxB,OAAO,SAAS,CAAC;QACrB,CAAC;QAED,OAAO,KAAK,CAAC,IAAI,CAAC;IACtB,CAAC;IAEM,IAAI,CAAC,EAAU,EAAE,IAAyB,EAAE,MAAc,IAAI,CAAC,UAAU;QAC5E,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAC7D,8FAA8F;YAC9F,IAAI,CAAC,KAAK,EAAE,CAAC;YACb,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;gBAChE,UAAU,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACzC,CAAC;QACL,CAAC;QACD,uGAAuG;QACvG,0GAA0G;QAC1G,uGAAuG;QACvG,kBAAkB;QAClB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACxB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI,EAAE,CAAC,CAAC;IACvE,CAAC;IAEM,MAAM,CAAC,EAAU;QACpB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC5B,CAAC;IAEO,KAAK;QACT,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;YACtD,IAAI,KAAK,CAAC,SAAS,IAAI,GAAG;gBAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC/D,CAAC;IACL,CAAC;CACJ;AA7CU;IADN,OAAO;;;;0CAGP"}
1
+ {"version":3,"file":"MemoryStore.js","sourceRoot":"","sources":["../../src/MemoryStore.ts"],"names":[],"mappings":"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"}
@@ -22,22 +22,69 @@ const SWEEP_INTERVAL_MS = 60000;
22
22
  */
23
23
  export class RedisStore {
24
24
  /**
25
+ * @param baseKey The base key that is pre-pended to all IDs.
25
26
  * @param client The Redis client to back this store with. When omitted, the store operates purely in-memory.
26
27
  */
27
- constructor(client) {
28
+ constructor(baseKey = "store", client) {
28
29
  /** The default record TTL (in seconds). */
29
30
  this.defaultTTL = 60;
30
31
  this.entries = new Map();
31
32
  /** The maximum number of records to store. */
32
33
  this.maxSize = 10000;
34
+ this.sets = new Map();
35
+ this.baseKey = baseKey;
33
36
  this.client = client;
34
37
  this.sweepTimer = setInterval(() => this.sweep(), SWEEP_INTERVAL_MS);
35
38
  this.sweepTimer.unref?.();
36
39
  }
40
+ async clear() {
41
+ this.entries.clear();
42
+ this.sets.clear();
43
+ // Delete all entries in redis
44
+ if (this.client) {
45
+ let keys = [];
46
+ for await (const results of this.client.scanIterator({ MATCH: this.baseKey + "*" })) {
47
+ keys.push(...results);
48
+ }
49
+ if (keys.length > 0) {
50
+ await this.client.unlink(keys);
51
+ }
52
+ }
53
+ }
54
+ async delete(id) {
55
+ // Prepend the base key to the id
56
+ id = this.baseKey + id;
57
+ this.entries.delete(id);
58
+ await this.client?.del(id);
59
+ }
60
+ async deleteMany(ids) {
61
+ if (ids.length === 0) {
62
+ return;
63
+ }
64
+ // Pre-pend the baseKey to all ids
65
+ ids = ids.map((id) => this.baseKey + id);
66
+ // Delete all local entries
67
+ for (const id of ids) {
68
+ this.entries.delete(id);
69
+ }
70
+ // Now delete all entries in redis
71
+ await this.client?.del(ids);
72
+ }
73
+ async deleteSet(id) {
74
+ // Prepend the base key to the id
75
+ id = this.baseKey + id;
76
+ // We purposefully don't delete the individually stored records
77
+ // as they may be relevant to other valid queries. Let them expire
78
+ // naturally.
79
+ this.sets.delete(id);
80
+ await this.client?.del(id);
81
+ }
37
82
  destroy() {
38
83
  clearInterval(this.sweepTimer);
39
84
  }
40
- async load(id) {
85
+ async load(id, skipRedis = false) {
86
+ // Prepend the base key to the id
87
+ id = this.baseKey + id;
41
88
  const entry = this.entries.get(id);
42
89
  if (entry) {
43
90
  // If the local copy hasn't expired, it's safe to serve directly.
@@ -50,22 +97,12 @@ export class RedisStore {
50
97
  this.entries.delete(id);
51
98
  }
52
99
  // Without a Redis client, this is a pure in-memory store: a local miss/expiry is a real miss.
53
- if (!this.client) {
100
+ if (!this.client || skipRedis) {
54
101
  return undefined;
55
102
  }
56
103
  // The entry wasn't found locally. Try retrieving from redis.
57
104
  // We'll also grab the ttl so we can add it to our local map.
58
- const result = await this.client
59
- .multi([
60
- ["get", id],
61
- ["ttl", id],
62
- ])
63
- .exec();
64
- if (!result) {
65
- return undefined;
66
- }
67
- const data = result[0][1] ? result[0][1] : undefined;
68
- const ttl = result[1][1] ?? undefined;
105
+ const [data, ttl] = await this.client.multi().get(id).ttl(id).execAsPipelineTyped();
69
106
  if (!data) {
70
107
  return undefined;
71
108
  }
@@ -85,12 +122,98 @@ export class RedisStore {
85
122
  this.entries.set(id, newEntry);
86
123
  return newEntry.data;
87
124
  }
88
- async save(id, data, ttl = this.defaultTTL) {
125
+ async loadMany(ids) {
126
+ const results = new Array(ids.length);
127
+ // Load as many from the local store as possible
128
+ let missing = [];
129
+ for (let idx = 0; idx < ids.length; idx++) {
130
+ const id = ids[idx];
131
+ const result = await this.load(id, true);
132
+ if (result) {
133
+ results[idx] = result;
134
+ }
135
+ else {
136
+ missing.push({ idx, id });
137
+ }
138
+ }
139
+ if (!this.client) {
140
+ return results;
141
+ }
142
+ // Now load all remaining items from redis. Note that we don't call load() here, instead we use the multi
143
+ // command to reduce the number of times we hit redis (1 call vs n*2 calls).
144
+ if (missing.length > 0) {
145
+ let pipe = this.client.multi();
146
+ for (const pair of missing) {
147
+ const id = this.baseKey + pair.id;
148
+ pipe.get(id).ttl(id);
149
+ }
150
+ const result = await pipe.execAsPipeline();
151
+ let toSave = [];
152
+ for (let i = 0; i < missing.length; i++) {
153
+ const pair = missing[i];
154
+ const data = result[i * 2];
155
+ const ttl = result[i * 2 + 1];
156
+ if (data) {
157
+ const record = JSON.parse(data);
158
+ // Store the result in the same position in our final array
159
+ results[pair.idx] = record;
160
+ // Now add the list of items to store locally
161
+ toSave.push({ id: pair.id, data: record, ttl });
162
+ }
163
+ }
164
+ // Finally go through all items to save.
165
+ if (toSave.length > 0) {
166
+ // Let's do a sweep on the local map and free up space while we're at it. Every id in `toSave` is
167
+ // guaranteed to be a genuinely new key (load() already deleted any expired local copy before
168
+ // reporting a miss), so `toSave.length` is exactly how much the map is about to grow by.
169
+ if (this.entries.size + toSave.length > this.maxSize) {
170
+ // Reclaim space by sweeping expired entries first, then evicting exactly enough of the oldest
171
+ // surviving entries for the whole batch to fit — evicting a fixed `toSave.length` regardless
172
+ // of how much the sweep already freed would evict more live entries than necessary.
173
+ this.sweep();
174
+ const overflow = this.entries.size + toSave.length - this.maxSize;
175
+ if (overflow > 0) {
176
+ CacheUtils.evictOldest(this.entries, Math.min(overflow, this.entries.size));
177
+ }
178
+ }
179
+ for (const entry of toSave) {
180
+ this.entries.set(this.baseKey + entry.id, {
181
+ data: entry.data,
182
+ expiresAt: Date.now() + entry.ttl * 1000,
183
+ });
184
+ }
185
+ }
186
+ }
187
+ return results;
188
+ }
189
+ async loadSet(id) {
190
+ id = this.baseKey + id;
191
+ // First look up the list of ids from local storage
192
+ const ids = this.sets.get(id);
193
+ if (ids) {
194
+ return this.loadMany(ids);
195
+ }
196
+ if (!this.client) {
197
+ return undefined;
198
+ }
199
+ // The set ids aren't stored locally, so let's try redis.
200
+ const json = await this.client.get(id);
201
+ if (json) {
202
+ const ids = JSON.parse(json);
203
+ // Store the list of ids locally for faster retrieval next time
204
+ this.sets.set(id, ids);
205
+ return this.loadMany(ids);
206
+ }
207
+ return undefined;
208
+ }
209
+ async save(id, data, ttl = this.defaultTTL, skipRedis = false) {
210
+ // Prepend the base key to the id
211
+ id = this.baseKey + id;
89
212
  // Write to Redis first (when configured): if it fails (bad connection, etc.), the local cache is left
90
213
  // untouched instead of reporting a "saved" value locally that never made it to the shared store other
91
214
  // instances read from.
92
- if (this.client) {
93
- await this.client.setex(id, ttl, JSON.stringify(data));
215
+ if (this.client && !skipRedis) {
216
+ await this.client.setEx(id, ttl, JSON.stringify(data));
94
217
  }
95
218
  if (!this.entries.has(id) && this.entries.size >= this.maxSize) {
96
219
  // Reclaim space by sweeping expired entries first, then evicting the oldest surviving entries
@@ -106,11 +229,63 @@ export class RedisStore {
106
229
  this.entries.delete(id);
107
230
  this.entries.set(id, { data, expiresAt: Date.now() + ttl * 1000 });
108
231
  }
109
- async delete(id) {
110
- this.entries.delete(id);
232
+ async saveMany(ids, data, ttl = this.defaultTTL) {
233
+ if (ids.length !== data.length) {
234
+ throw new Error("The ids and data arguments must have the same length.");
235
+ }
236
+ // Prepend the base key to each id
237
+ ids = ids.map((id) => this.baseKey + id);
238
+ // Write to Redis first (when configured): if it fails (bad connection, etc.), the local cache is left
239
+ // untouched instead of reporting a "saved" value locally that never made it to the shared store other
240
+ // instances read from.
111
241
  if (this.client) {
112
- await this.client.del(id);
242
+ // Insert into redis using a pipeline to redis network traffic
243
+ const pipe = this.client.multi();
244
+ for (let i = 0; i < ids.length; i++) {
245
+ const id = ids[i];
246
+ const record = data[i];
247
+ pipe.setEx(id, ttl, JSON.stringify(record));
248
+ }
249
+ await pipe.execAsPipeline();
250
+ }
251
+ // Evict per-id, exactly like save() does, rather than pre-computing a single batch eviction count: a
252
+ // batched "evict N oldest, then insert the batch" approach can evict an entry that is itself one of the
253
+ // ids being (re-)saved in this same batch — that id then gets reinserted right after anyway, so the
254
+ // eviction bought no real space and the map ends up larger than maxSize.
255
+ for (let i = 0; i < ids.length; i++) {
256
+ const id = ids[i];
257
+ const record = data[i];
258
+ if (!this.entries.has(id) && this.entries.size >= this.maxSize) {
259
+ // Reclaim space by sweeping expired entries first, then evicting the oldest surviving entries
260
+ this.sweep();
261
+ while (this.entries.size >= this.maxSize && this.entries.size > 0) {
262
+ CacheUtils.evictOldest(this.entries);
263
+ }
264
+ }
265
+ // Delete before re-inserting so a renewed entry moves to the end of the Map's iteration order. A
266
+ // `Map` does not reorder on `set()` of an already-present key, so without this, an actively-renewed
267
+ // entry stays at its original (oldest) position and `evictOldest()` would evict it ahead of a
268
+ // genuinely idle entry inserted later.
269
+ this.entries.delete(id);
270
+ this.entries.set(id, { data: record, expiresAt: Date.now() + ttl * 1000 });
271
+ }
272
+ }
273
+ async saveSet(id, data, idProp = "uid", ttl = this.defaultTTL) {
274
+ const ids = [];
275
+ const records = [];
276
+ // Create a list of ids that we'll store as a set
277
+ for (const record of data) {
278
+ const recordId = record[idProp];
279
+ if (recordId !== undefined && recordId !== null) {
280
+ ids.push(recordId);
281
+ records.push(record);
282
+ }
113
283
  }
284
+ // Now store all the records in the cache
285
+ await this.saveMany(ids, records, ttl);
286
+ // Finally, store our set of ids
287
+ await this.client?.setEx(this.baseKey + id, ttl, JSON.stringify(ids));
288
+ this.sets.set(this.baseKey + id, ids);
114
289
  }
115
290
  sweep() {
116
291
  const now = Date.now();
@@ -1 +1 @@
1
- {"version":3,"file":"RedisStore.js","sourceRoot":"","sources":["../../src/RedisStore.ts"],"names":[],"mappings":";;;;;;;;;AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,+EAA+E;AAC/E,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAC;AAI3D,8EAA8E;AAC9E,MAAM,iBAAiB,GAAG,KAAM,CAAC;AAEjC;;;;;GAKG;AACH,MAAM,OAAO,UAAU;IAanB;;OAEG;IACH,YAAY,MAAsB;QAblC,2CAA2C;QACpC,eAAU,GAAW,EAAE,CAAC;QAErB,YAAO,GAAkC,IAAI,GAAG,EAAE,CAAC;QAE7D,8CAA8C;QACvC,YAAO,GAAW,KAAK,CAAC;QAQ3B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,UAAU,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,iBAAiB,CAAC,CAAC;QACrE,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,CAAC;IAC9B,CAAC;IAGM,OAAO;QACV,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACnC,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,EAAU;QACxB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACnC,IAAI,KAAK,EAAE,CAAC;YACR,iEAAiE;YACjE,IAAI,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;gBAC/B,OAAO,KAAK,CAAC,IAAI,CAAC;YACtB,CAAC;YAED,iGAAiG;YACjG,gGAAgG;YAChG,2EAA2E;YAC3E,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC5B,CAAC;QAED,8FAA8F;QAC9F,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACf,OAAO,SAAS,CAAC;QACrB,CAAC;QAED,6DAA6D;QAC7D,6DAA6D;QAC7D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM;aAC3B,KAAK,CAAC;YACH,CAAC,KAAK,EAAE,EAAE,CAAC;YACX,CAAC,KAAK,EAAE,EAAE,CAAC;SACd,CAAC;aACD,IAAI,EAAE,CAAC;QACZ,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,OAAO,SAAS,CAAC;QACrB,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACrD,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC;QACtC,IAAI,CAAC,IAAI,EAAE,CAAC;YACR,OAAO,SAAS,CAAC;QACrB,CAAC;QAED,wGAAwG;QACxG,qGAAqG;QACrG,4EAA4E;QAC5E,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAC7D,IAAI,CAAC,KAAK,EAAE,CAAC;YACb,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;gBAChE,UAAU,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACzC,CAAC;QACL,CAAC;QAED,MAAM,QAAQ,GAAqB;YAC/B,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,IAAc,CAAC;YAChC,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAI,GAAc,GAAG,IAAI;SACjD,CAAC;QACF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;QAE/B,OAAO,QAAQ,CAAC,IAAI,CAAC;IACzB,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,EAAU,EAAE,IAAyB,EAAE,MAAc,IAAI,CAAC,UAAU;QAClF,sGAAsG;QACtG,sGAAsG;QACtG,uBAAuB;QACvB,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;QAC3D,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAC7D,8FAA8F;YAC9F,IAAI,CAAC,KAAK,EAAE,CAAC;YACb,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;gBAChE,UAAU,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACzC,CAAC;QACL,CAAC;QAED,uGAAuG;QACvG,0GAA0G;QAC1G,uGAAuG;QACvG,kBAAkB;QAClB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACxB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI,EAAE,CAAC,CAAC;IACvE,CAAC;IAEM,KAAK,CAAC,MAAM,CAAC,EAAU;QAC1B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACxB,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC9B,CAAC;IACL,CAAC;IAEO,KAAK;QACT,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;YACtD,IAAI,KAAK,CAAC,SAAS,IAAI,GAAG;gBAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC/D,CAAC;IACL,CAAC;CACJ;AAjGU;IADN,OAAO;;;;yCAGP"}
1
+ {"version":3,"file":"RedisStore.js","sourceRoot":"","sources":["../../src/RedisStore.ts"],"names":[],"mappings":"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"}
@@ -9,6 +9,7 @@ export declare class CacheUtils {
9
9
  * whatever entry was `set()` least recently among those still present.
10
10
  *
11
11
  * @param map The map to evict the oldest entry from.
12
+ * @param num The total number of items to evict. Default is `1`.
12
13
  */
13
- static evictOldest<K, V>(map: Map<K, V>): void;
14
+ static evictOldest<K, V>(map: Map<K, V>, num?: number): void;
14
15
  }
@@ -10,12 +10,20 @@ export declare class MemoryStore implements SimpleStore {
10
10
  protected entries: Map<string, MemoryStoreEntry>;
11
11
  /** The maximum number of records to store. */
12
12
  maxSize: number;
13
+ protected sets: Map<string, any[]>;
13
14
  private sweepTimer;
14
15
  constructor();
16
+ clear(): void;
17
+ delete(id: string): void;
18
+ deleteMany(ids: string[]): void;
19
+ deleteSet(id: string): void;
15
20
  /** Stops the background sweep timer. Call when the owning `SessionManager` is destroyed. */
16
21
  destroy(): void;
17
22
  load(id: string): Record<string, any> | undefined;
23
+ loadMany(ids: string[]): (Record<string, any> | undefined)[];
24
+ loadSet(id: string): (Record<string, any> | undefined)[] | undefined;
18
25
  save(id: string, data: Record<string, any>, ttl?: number): void;
19
- delete(id: string): void;
26
+ saveMany(ids: string[], data: Record<string, any>[], ttl?: number): void;
27
+ saveSet(id: string, data: Record<string, any>[], idProp?: string, ttl?: number): void;
20
28
  private sweep;
21
29
  }
@@ -1,5 +1,5 @@
1
1
  import { MemoryStoreEntry, SimpleStore } from "./SimpleStore.js";
2
- import type * as ioredis from "ioredis";
2
+ import { RedisClientType } from "redis";
3
3
  /**
4
4
  * Implements a SimpleStore storage system that combines a local in-memory store optionally backed by Redis. When
5
5
  * no Redis client is given, the store behaves as a plain in-memory store (equivalent to `MemoryStore`).
@@ -7,20 +7,33 @@ import type * as ioredis from "ioredis";
7
7
  * @author Jean-Philippe Steinmetz
8
8
  */
9
9
  export declare class RedisStore implements SimpleStore {
10
- client?: ioredis.Redis;
10
+ /**
11
+ * The base key that is pre-pended to all IDs.
12
+ */
13
+ readonly baseKey: string;
14
+ client?: RedisClientType;
11
15
  /** The default record TTL (in seconds). */
12
16
  defaultTTL: number;
13
17
  protected entries: Map<string, MemoryStoreEntry>;
14
18
  /** The maximum number of records to store. */
15
19
  maxSize: number;
20
+ protected sets: Map<string, any[]>;
16
21
  private sweepTimer;
17
22
  /**
23
+ * @param baseKey The base key that is pre-pended to all IDs.
18
24
  * @param client The Redis client to back this store with. When omitted, the store operates purely in-memory.
19
25
  */
20
- constructor(client?: ioredis.Redis);
21
- destroy(): void;
22
- load(id: string): Promise<Record<string, any> | undefined>;
23
- save(id: string, data: Record<string, any>, ttl?: number): Promise<void>;
26
+ constructor(baseKey?: string, client?: RedisClientType);
27
+ clear(): Promise<void>;
24
28
  delete(id: string): Promise<void>;
29
+ deleteMany(ids: string[]): Promise<void>;
30
+ deleteSet(id: string): Promise<void>;
31
+ destroy(): void;
32
+ load(id: string, skipRedis?: boolean): Promise<Record<string, any> | undefined>;
33
+ loadMany(ids: string[]): Promise<(Record<string, any> | undefined)[]>;
34
+ loadSet(id: string): Promise<(Record<string, any> | undefined)[] | undefined>;
35
+ save(id: string, data: Record<string, any>, ttl?: number, skipRedis?: boolean): Promise<void>;
36
+ saveMany(ids: string[], data: Record<string, any>[], ttl?: number): Promise<void>;
37
+ saveSet(id: string, data: Record<string, any>[], idProp?: string, ttl?: number): Promise<void>;
25
38
  private sweep;
26
39
  }
@@ -4,7 +4,7 @@ export interface MemoryStoreEntry {
4
4
  }
5
5
  /**
6
6
  * Defines an interface for a simple key-value storage system that stores temporary records with a specified
7
- * lifetime (TTL) and size.
7
+ * lifetime (TTL) and size. Supports the storage and retrieval of individual records as well as sets of records.
8
8
  *
9
9
  * @author Jean-Philippe Steinmetz
10
10
  */
@@ -14,20 +14,59 @@ export interface SimpleStore {
14
14
  /** The maximum number of records to store. */
15
15
  maxSize: number;
16
16
  /**
17
- * Deletes the record with the given id.
17
+ * Deletes all entries in the storage system.
18
+ */
19
+ clear(): Promise<void> | void;
20
+ /**
21
+ * Deletes a single record with the given id.
18
22
  * @param id The id of the record to delete.
19
23
  */
20
24
  delete(id: string): Promise<void> | void;
21
25
  /**
22
- * Retrieves the record with the given id.
26
+ * Deletes the records associated with the list of given ids.
27
+ * @param ids The list of ids to delete.
28
+ */
29
+ deleteMany(ids: string[]): Promise<void> | void;
30
+ /**
31
+ * Deletes the set of records associated with the given id.
32
+ * @param ids The id of the set to delete.
33
+ */
34
+ deleteSet(id: string): Promise<void> | void;
35
+ /**
36
+ * Retrieves a single record with the given id.
23
37
  * @param id The id of the record to retrieve.
24
38
  */
25
39
  load(id: string): Promise<Record<string, any> | undefined> | Record<string, any> | undefined;
26
40
  /**
27
- * Stores the record with the given id for the specified TTL.
41
+ * Retrieves the list of records for the given list of ids.
42
+ * @param ids The list of ids of records to retrieve.
43
+ */
44
+ loadMany(ids: string[]): Promise<(Record<string, any> | undefined)[]> | (Record<string, any> | undefined)[];
45
+ /**
46
+ * Retrieves the set of records with the given id.
47
+ * @param ids The id associated with the set of records to retrieve.
48
+ */
49
+ loadSet(id: string): Promise<(Record<string, any> | undefined)[] | undefined> | (Record<string, any> | undefined)[] | undefined;
50
+ /**
51
+ * Stores a single record with the given id for the specified TTL.
28
52
  * @param id The id of the record to retrieve.
29
53
  * @param data The record to store.
30
54
  * @param ttl The number of seconds that the record will be stored.
31
55
  */
32
- save(id: string, data: Record<string, any>, ttl: number): Promise<void> | void;
56
+ save(id: string, data: Record<string, any>, ttl?: number): Promise<void> | void;
57
+ /**
58
+ * Stores a list of records with the given list of ids for the specified TTL.
59
+ * @param id The list of ids to store records for.
60
+ * @param data The list of records to store.
61
+ * @param ttl The number of seconds that each record will be stored.
62
+ */
63
+ saveMany(ids: string[], data: Record<string, any>[], ttl?: number): Promise<void> | void;
64
+ /**
65
+ * Stores a set of records with the given id for the specified TTL.
66
+ * @param id The id of the record set to store.
67
+ * @param data The record to store.
68
+ * @param idProp The name of the property in data to use as an id for storage.
69
+ * @param ttl The number of seconds that the set will be stored.
70
+ */
71
+ saveSet(id: string, data: Record<string, any>[], idProp: string, ttl?: number): Promise<void> | void;
33
72
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rapidrest/core",
3
- "version": "3.1.0",
3
+ "version": "4.0.0",
4
4
  "description": "A collection of common utilities and core functionality for rapidly building RESTful applications.",
5
5
  "repository": "https://github.com/rapidrest/core.git",
6
6
  "author": "RapidREST <rapidrests@gmail.com>",
@@ -34,7 +34,7 @@
34
34
  },
35
35
  "peerDependencies": {
36
36
  "axios": "1.x",
37
- "ioredis": "6.x",
37
+ "redis": "6.x",
38
38
  "reflect-metadata": "0.2.x",
39
39
  "winston": "3.x",
40
40
  "winston-transport": "4.x"
@@ -59,12 +59,12 @@
59
59
  "eslint-config-prettier": "^10.1.8",
60
60
  "eslint-plugin-import": "^2.32.0",
61
61
  "eslint-plugin-jsdoc": "^63.0.2",
62
- "ioredis": "^6.0.0",
63
62
  "mkdirp": "^3.0.1",
64
63
  "nconf": "^0.13.0",
65
64
  "nock": "^14.0.15",
66
65
  "nodemailer": "^8.0.10",
67
66
  "prettier": "^3.8.4",
67
+ "redis": "^6.2.1",
68
68
  "reflect-metadata": "^0.2.2",
69
69
  "rimraf": "^6.1.3",
70
70
  "ts-node": "^10.9.2",