@ix-xs/node-comfort 1.2.0 → 2.1.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/CHANGELOG.md +340 -0
- package/README.md +137 -548
- package/index.js +80 -35
- package/package.json +161 -33
- package/src/Arr.js +1050 -418
- package/src/Async.js +817 -0
- package/src/Cache.js +433 -0
- package/src/Checker.js +1202 -539
- package/src/Cli.js +1293 -0
- package/src/Color.js +556 -0
- package/src/Crypto.js +1082 -0
- package/src/Emitter.js +356 -0
- package/src/Env.js +689 -0
- package/src/FS.js +1626 -894
- package/src/Func.js +823 -356
- package/src/Http.js +700 -0
- package/src/Id.js +568 -200
- package/src/Logger.js +963 -378
- package/src/Num.js +965 -360
- package/src/Obj.js +1077 -379
- package/src/SQLite.js +1629 -1005
- package/src/Schema.js +2101 -0
- package/src/Str.js +1209 -432
- package/src/Sys.js +654 -0
- package/src/Time.js +1533 -440
- package/src/Utils.js +342 -325
- package/src/errors.js +335 -0
- package/src/internal/shared.js +415 -0
- package/types/index.d.ts +735 -526
- package/types/src/Arr.d.ts +533 -145
- package/types/src/Arr.d.ts.map +1 -1
- package/types/src/Async.d.ts +372 -0
- package/types/src/Async.d.ts.map +1 -0
- package/types/src/Cache.d.ts +221 -0
- package/types/src/Cache.d.ts.map +1 -0
- package/types/src/Checker.d.ts +651 -232
- package/types/src/Checker.d.ts.map +1 -1
- package/types/src/Cli.d.ts +664 -0
- package/types/src/Cli.d.ts.map +1 -0
- package/types/src/Color.d.ts +300 -0
- package/types/src/Color.d.ts.map +1 -0
- package/types/src/Crypto.d.ts +620 -0
- package/types/src/Crypto.d.ts.map +1 -0
- package/types/src/Emitter.d.ts +189 -0
- package/types/src/Emitter.d.ts.map +1 -0
- package/types/src/Env.d.ts +412 -0
- package/types/src/Env.d.ts.map +1 -0
- package/types/src/FS.d.ts +717 -238
- package/types/src/FS.d.ts.map +1 -1
- package/types/src/Func.d.ts +508 -135
- package/types/src/Func.d.ts.map +1 -1
- package/types/src/Http.d.ts +444 -0
- package/types/src/Http.d.ts.map +1 -0
- package/types/src/Id.d.ts +199 -66
- package/types/src/Id.d.ts.map +1 -1
- package/types/src/Logger.d.ts +414 -46
- package/types/src/Logger.d.ts.map +1 -1
- package/types/src/Num.d.ts +514 -137
- package/types/src/Num.d.ts.map +1 -1
- package/types/src/Obj.d.ts +453 -104
- package/types/src/Obj.d.ts.map +1 -1
- package/types/src/SQLite.d.ts +754 -308
- package/types/src/SQLite.d.ts.map +1 -1
- package/types/src/Schema.d.ts +1189 -0
- package/types/src/Schema.d.ts.map +1 -0
- package/types/src/Str.d.ts +617 -166
- package/types/src/Str.d.ts.map +1 -1
- package/types/src/Sys.d.ts +314 -0
- package/types/src/Sys.d.ts.map +1 -0
- package/types/src/Time.d.ts +759 -204
- package/types/src/Time.d.ts.map +1 -1
- package/types/src/Utils.d.ts +158 -102
- package/types/src/Utils.d.ts.map +1 -1
- package/types/src/errors.d.ts +316 -0
- package/types/src/errors.d.ts.map +1 -0
- package/types/src/internal/shared.d.ts +123 -0
- package/types/src/internal/shared.d.ts.map +1 -0
- package/types/index.d.ts.map +0 -1
package/src/Cache.js
ADDED
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* An in-memory cache with a size limit (the least recently used entries go
|
|
5
|
+
* first), expiry per entry, and stats. `getOrSet()` loads a missing value
|
|
6
|
+
* only once, even when many callers ask for it at the same moment.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* const users = new nc.Cache({ max: 1000, ttl: "5m" });
|
|
10
|
+
* const user = await users.getOrSet(id, () => db.users.find(id));
|
|
11
|
+
* users.stats; // { hits: 12, misses: 3, hitRate: 0.8, ... }
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const { toMs, argsKey } = require("./internal/shared.js");
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Why an entry left the cache.
|
|
18
|
+
* @typedef {"evict" | "expire" | "delete" | "set" | "clear"} CacheRemovalReason
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Options for `new Cache()`.
|
|
23
|
+
* @template K
|
|
24
|
+
* @template V
|
|
25
|
+
* @typedef {object} CacheOptions
|
|
26
|
+
* @property {number} [max] Maximum number of entries. When full, the least recently used goes. No limit by default.
|
|
27
|
+
* @property {number | string} [ttl] How long entries live, in ms or like `"10m"`. `0` means forever, the default.
|
|
28
|
+
* @property {number | string} [staleWhileRevalidate] After an entry expires, `getOrSet()` may still return it for this long while it loads a new value in the background.
|
|
29
|
+
* @property {boolean} [updateAgeOnGet] Reading an entry restarts its time to live.
|
|
30
|
+
* @property {(key: K, value: V, reason: CacheRemovalReason) => void} [onRemove] Called whenever an entry leaves the cache.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Options for `set()` and `getOrSet()`.
|
|
35
|
+
* @typedef {object} CacheSetOptions
|
|
36
|
+
* @property {number | string} [ttl] Time to live of this entry. `0` means forever.
|
|
37
|
+
* @property {number | string} [staleWhileRevalidate] How long this entry can be served by `getOrSet()` after it expires, while a new value loads.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Cache stats.
|
|
42
|
+
* @typedef {object} CacheStats
|
|
43
|
+
* @property {number} hits Reads that found a usable entry.
|
|
44
|
+
* @property {number} misses Reads that found nothing or an expired entry.
|
|
45
|
+
* @property {number} sets
|
|
46
|
+
* @property {number} evictions Entries dropped because the cache was full.
|
|
47
|
+
* @property {number} expirations Entries dropped because they expired.
|
|
48
|
+
* @property {number} hitRate From 0 to 1.
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @template V
|
|
53
|
+
* @typedef {{ value: V, expires: number, ttl: number, stale: number }} CacheEntry
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {number | string | undefined} value
|
|
58
|
+
* @param {string} what
|
|
59
|
+
* @returns {number}
|
|
60
|
+
*/
|
|
61
|
+
const _duration = (value, what) => (value === undefined ? 0 : Math.max(0, toMs(value, what)));
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* An in-memory cache with a size limit and expiry.
|
|
65
|
+
*
|
|
66
|
+
* @template K
|
|
67
|
+
* @template V
|
|
68
|
+
*/
|
|
69
|
+
class Cache {
|
|
70
|
+
/** @type {Map<K, CacheEntry<V>>} */
|
|
71
|
+
#map = new Map();
|
|
72
|
+
/** @type {Map<K, Promise<V>>} */
|
|
73
|
+
#loading = new Map();
|
|
74
|
+
#max;
|
|
75
|
+
#ttl;
|
|
76
|
+
#stale;
|
|
77
|
+
#updateAgeOnGet;
|
|
78
|
+
/** @type {((key: K, value: V, reason: CacheRemovalReason) => void) | undefined} */
|
|
79
|
+
#onRemove;
|
|
80
|
+
#stats = { hits: 0, misses: 0, sets: 0, evictions: 0, expirations: 0 };
|
|
81
|
+
/**
|
|
82
|
+
* The most recently used key, so reading it again doesn't reorder anything.
|
|
83
|
+
* @type {K | undefined}
|
|
84
|
+
*/
|
|
85
|
+
#newest = undefined;
|
|
86
|
+
#hasNewest = false;
|
|
87
|
+
#sweepAt = 1024;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @example
|
|
91
|
+
* const cache = new nc.Cache({ max: 500, ttl: "1h", updateAgeOnGet: true });
|
|
92
|
+
*
|
|
93
|
+
* @param {CacheOptions<K, V>} [options]
|
|
94
|
+
*/
|
|
95
|
+
constructor(options = {}) {
|
|
96
|
+
this.#max = Math.max(1, options.max ?? Infinity);
|
|
97
|
+
this.#ttl = _duration(options.ttl, "ttl");
|
|
98
|
+
this.#stale = _duration(options.staleWhileRevalidate, "staleWhileRevalidate");
|
|
99
|
+
this.#updateAgeOnGet = options.updateAgeOnGet ?? false;
|
|
100
|
+
this.#onRemove = options.onRemove;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @param {K} key
|
|
105
|
+
* @param {{ value: V }} entry
|
|
106
|
+
* @param {CacheRemovalReason} reason
|
|
107
|
+
*/
|
|
108
|
+
#remove(key, entry, reason) {
|
|
109
|
+
this.#map.delete(key);
|
|
110
|
+
if (this.#hasNewest && this.#newest === key) this.#hasNewest = false;
|
|
111
|
+
if (reason === "expire") this.#stats.expirations++;
|
|
112
|
+
if (reason === "evict") this.#stats.evictions++;
|
|
113
|
+
this.#onRemove?.(key, entry.value, reason);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The entry if it exists and is fresh. Expired entries are removed, unless
|
|
118
|
+
* `getOrSet()` may still serve them while it reloads.
|
|
119
|
+
* @param {K} key
|
|
120
|
+
*/
|
|
121
|
+
#fresh(key) {
|
|
122
|
+
const entry = this.#map.get(key);
|
|
123
|
+
if (!entry) return undefined;
|
|
124
|
+
if (entry.expires) {
|
|
125
|
+
const now = Date.now();
|
|
126
|
+
if (entry.expires <= now) {
|
|
127
|
+
if (entry.stale <= now) this.#remove(key, entry, "expire");
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return entry;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Moves an entry to the "most recently used" end.
|
|
136
|
+
* @param {K} key
|
|
137
|
+
* @param {CacheEntry<V>} entry
|
|
138
|
+
*/
|
|
139
|
+
#touch(key, entry) {
|
|
140
|
+
if (this.#hasNewest && this.#newest === key) return;
|
|
141
|
+
this.#map.delete(key);
|
|
142
|
+
this.#map.set(key, entry);
|
|
143
|
+
this.#newest = key;
|
|
144
|
+
this.#hasNewest = true;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Reads a value and marks it as recently used.
|
|
149
|
+
*
|
|
150
|
+
* @param {K} key
|
|
151
|
+
* @returns {V | undefined} `undefined` if missing or expired.
|
|
152
|
+
*/
|
|
153
|
+
get(key) {
|
|
154
|
+
const entry = this.#fresh(key);
|
|
155
|
+
if (!entry) {
|
|
156
|
+
this.#stats.misses++;
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
this.#stats.hits++;
|
|
160
|
+
if (this.#updateAgeOnGet && entry.ttl) {
|
|
161
|
+
const now = Date.now();
|
|
162
|
+
entry.stale += now + entry.ttl - entry.expires;
|
|
163
|
+
entry.expires = now + entry.ttl;
|
|
164
|
+
}
|
|
165
|
+
this.#touch(key, entry);
|
|
166
|
+
return entry.value;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Reads a value without counting it as a use or touching the stats.
|
|
171
|
+
*
|
|
172
|
+
* @param {K} key
|
|
173
|
+
* @returns {V | undefined}
|
|
174
|
+
*/
|
|
175
|
+
peek(key) {
|
|
176
|
+
return this.#fresh(key)?.value;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Stores a value. When the cache is full, the least recently used entry
|
|
181
|
+
* goes. A value being loaded for this key by `getOrSet()` is not stored
|
|
182
|
+
* when it arrives: the one you set stays.
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* cache.set("token", token, { ttl: "15m" });
|
|
186
|
+
*
|
|
187
|
+
* @param {K} key
|
|
188
|
+
* @param {V} value
|
|
189
|
+
* @param {CacheSetOptions} [options]
|
|
190
|
+
* @returns {this}
|
|
191
|
+
*/
|
|
192
|
+
set(key, value, options = {}) {
|
|
193
|
+
this.#loading.delete(key);
|
|
194
|
+
const ttl = options.ttl === undefined ? this.#ttl : _duration(options.ttl, "ttl");
|
|
195
|
+
const grace = options.staleWhileRevalidate === undefined ? this.#stale : _duration(options.staleWhileRevalidate, "staleWhileRevalidate");
|
|
196
|
+
const previous = this.#map.get(key);
|
|
197
|
+
if (previous) this.#remove(key, previous, "set");
|
|
198
|
+
const expires = ttl ? Date.now() + ttl : 0;
|
|
199
|
+
this.#map.set(key, { value, ttl, expires, stale: expires ? expires + grace : 0 });
|
|
200
|
+
this.#newest = key;
|
|
201
|
+
this.#hasNewest = true;
|
|
202
|
+
this.#stats.sets++;
|
|
203
|
+
if (this.#map.size > this.#max) {
|
|
204
|
+
while (this.#map.size > this.#max) {
|
|
205
|
+
const [oldestKey, oldest] = /** @type {[K, { value: V }]} */ (this.#map.entries().next().value);
|
|
206
|
+
this.#remove(oldestKey, oldest, "evict");
|
|
207
|
+
}
|
|
208
|
+
} else if (expires && this.#max === Infinity && this.#map.size >= this.#sweepAt) {
|
|
209
|
+
// Without a size limit, entries nobody reads again would stay forever.
|
|
210
|
+
this.prune();
|
|
211
|
+
this.#sweepAt = Math.max(1024, this.#map.size * 2);
|
|
212
|
+
}
|
|
213
|
+
return this;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Returns the cached value, or calls `loader`, stores the result and
|
|
218
|
+
* returns it. Simultaneous calls for the same key share one `loader` call,
|
|
219
|
+
* and a failed load caches nothing.
|
|
220
|
+
*
|
|
221
|
+
* With `staleWhileRevalidate`, an entry that just expired is returned at
|
|
222
|
+
* once while a new value loads in the background, so callers never wait
|
|
223
|
+
* on a refresh.
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* const profile = await cache.getOrSet(`profile:${id}`, () => api.fetchProfile(id), { ttl: "10m" });
|
|
227
|
+
* const rates = await cache.getOrSet("rates", fetchRates, { ttl: "1m", staleWhileRevalidate: "10m" });
|
|
228
|
+
*
|
|
229
|
+
* @param {K} key
|
|
230
|
+
* @param {() => V | PromiseLike<V>} loader
|
|
231
|
+
* @param {CacheSetOptions} [options]
|
|
232
|
+
* @returns {Promise<V>}
|
|
233
|
+
*/
|
|
234
|
+
getOrSet(key, loader, options = {}) {
|
|
235
|
+
const entry = this.#map.get(key);
|
|
236
|
+
if (entry) {
|
|
237
|
+
const now = entry.expires ? Date.now() : 0;
|
|
238
|
+
if (!entry.expires || entry.expires > now) return Promise.resolve(/** @type {V} */ (this.get(key)));
|
|
239
|
+
if (entry.stale > now) {
|
|
240
|
+
this.#stats.hits++;
|
|
241
|
+
// The stale value is good enough if the refresh fails; the next call tries again.
|
|
242
|
+
if (!this.#loading.has(key)) this.#load(key, loader, options).catch(() => {});
|
|
243
|
+
return Promise.resolve(entry.value);
|
|
244
|
+
}
|
|
245
|
+
this.#remove(key, entry, "expire");
|
|
246
|
+
}
|
|
247
|
+
const inflight = this.#loading.get(key);
|
|
248
|
+
if (inflight) return inflight;
|
|
249
|
+
this.#stats.misses++;
|
|
250
|
+
return this.#load(key, loader, options);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* @param {K} key
|
|
255
|
+
* @param {() => V | PromiseLike<V>} loader
|
|
256
|
+
* @param {CacheSetOptions} options
|
|
257
|
+
* @returns {Promise<V>}
|
|
258
|
+
*/
|
|
259
|
+
#load(key, loader, options) {
|
|
260
|
+
/** @type {Promise<V>} */
|
|
261
|
+
const promise = Promise.resolve()
|
|
262
|
+
.then(loader)
|
|
263
|
+
.then(
|
|
264
|
+
(value) => {
|
|
265
|
+
// A delete() or clear() during the load means the result is no longer wanted.
|
|
266
|
+
if (this.#loading.get(key) === promise) {
|
|
267
|
+
this.#loading.delete(key);
|
|
268
|
+
this.set(key, value, options);
|
|
269
|
+
}
|
|
270
|
+
return value;
|
|
271
|
+
},
|
|
272
|
+
(error) => {
|
|
273
|
+
if (this.#loading.get(key) === promise) this.#loading.delete(key);
|
|
274
|
+
throw error;
|
|
275
|
+
},
|
|
276
|
+
);
|
|
277
|
+
this.#loading.set(key, promise);
|
|
278
|
+
return promise;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Is there a fresh entry for this key? Doesn't count as a use.
|
|
283
|
+
*
|
|
284
|
+
* @param {K} key
|
|
285
|
+
* @returns {boolean}
|
|
286
|
+
*/
|
|
287
|
+
has(key) {
|
|
288
|
+
return this.#fresh(key) !== undefined;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Removes an entry. A value being loaded for this key by `getOrSet()` is
|
|
293
|
+
* not stored when it arrives.
|
|
294
|
+
*
|
|
295
|
+
* @param {K} key
|
|
296
|
+
* @returns {boolean} `true` if there was one.
|
|
297
|
+
*/
|
|
298
|
+
delete(key) {
|
|
299
|
+
this.#loading.delete(key);
|
|
300
|
+
const entry = this.#map.get(key);
|
|
301
|
+
if (!entry) return false;
|
|
302
|
+
this.#remove(key, entry, "delete");
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Removes every entry. Stats are kept.
|
|
308
|
+
*
|
|
309
|
+
* @returns {void}
|
|
310
|
+
*/
|
|
311
|
+
clear() {
|
|
312
|
+
this.#loading.clear();
|
|
313
|
+
for (const [key, entry] of [...this.#map]) this.#remove(key, entry, "clear");
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Removes expired entries now instead of waiting for them to be read.
|
|
318
|
+
*
|
|
319
|
+
* @returns {number} How many were removed.
|
|
320
|
+
*/
|
|
321
|
+
prune() {
|
|
322
|
+
let removed = 0;
|
|
323
|
+
const now = Date.now();
|
|
324
|
+
for (const [key, entry] of this.#map) {
|
|
325
|
+
if (entry.expires && entry.stale <= now) {
|
|
326
|
+
this.#remove(key, entry, "expire");
|
|
327
|
+
removed++;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return removed;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Time left before an entry expires.
|
|
335
|
+
*
|
|
336
|
+
* @param {K} key
|
|
337
|
+
* @returns {number | undefined} Milliseconds, `Infinity` if it never expires, `undefined` if missing.
|
|
338
|
+
*/
|
|
339
|
+
ttl(key) {
|
|
340
|
+
const entry = this.#fresh(key);
|
|
341
|
+
if (!entry) return undefined;
|
|
342
|
+
return entry.expires ? entry.expires - Date.now() : Infinity;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Wraps a function so its results are cached here.
|
|
347
|
+
*
|
|
348
|
+
* @example
|
|
349
|
+
* const getWeather = cache.wrap((city) => api.weather(city), { key: (city) => city.toLowerCase(), ttl: "30m" });
|
|
350
|
+
* await getWeather("Paris");
|
|
351
|
+
*
|
|
352
|
+
* @template {any[]} A
|
|
353
|
+
* @param {(...args: A) => V | PromiseLike<V>} fn
|
|
354
|
+
* @param {{ key?: (...args: A) => K } & CacheSetOptions} [options] `key` builds the cache key from the arguments. By default, primitives are compared by value and plain objects by content.
|
|
355
|
+
* @returns {(...args: A) => Promise<V>}
|
|
356
|
+
*/
|
|
357
|
+
wrap(fn, options = {}) {
|
|
358
|
+
const settings = { ttl: options.ttl, staleWhileRevalidate: options.staleWhileRevalidate };
|
|
359
|
+
return (...args) => {
|
|
360
|
+
const key = options.key ? options.key(...args) : /** @type {K} */ (argsKey(args));
|
|
361
|
+
return this.getOrSet(key, () => fn(...args), settings);
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** Number of entries, including expired ones not yet removed. */
|
|
366
|
+
get size() {
|
|
367
|
+
return this.#map.size;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** Hit and miss counts. */
|
|
371
|
+
get stats() {
|
|
372
|
+
const total = this.#stats.hits + this.#stats.misses;
|
|
373
|
+
return /** @type {CacheStats} */ ({ ...this.#stats, hitRate: total ? this.#stats.hits / total : 0 });
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Puts every counter of `stats` back to zero.
|
|
378
|
+
*
|
|
379
|
+
* @returns {this}
|
|
380
|
+
*/
|
|
381
|
+
resetStats() {
|
|
382
|
+
this.#stats = { hits: 0, misses: 0, sets: 0, evictions: 0, expirations: 0 };
|
|
383
|
+
return this;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* @template R
|
|
388
|
+
* @param {(key: K, entry: CacheEntry<V>) => R} pick
|
|
389
|
+
* @returns {R[]}
|
|
390
|
+
*/
|
|
391
|
+
#list(pick) {
|
|
392
|
+
this.prune();
|
|
393
|
+
const now = Date.now();
|
|
394
|
+
/** @type {R[]} */
|
|
395
|
+
const out = [];
|
|
396
|
+
for (const [key, entry] of this.#map) if (!entry.expires || entry.expires > now) out.push(pick(key, entry));
|
|
397
|
+
return out;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Fresh keys, least recently used first.
|
|
402
|
+
* @returns {K[]}
|
|
403
|
+
*/
|
|
404
|
+
keys() {
|
|
405
|
+
return this.#list((key) => key);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Fresh values, least recently used first.
|
|
410
|
+
* @returns {V[]}
|
|
411
|
+
*/
|
|
412
|
+
values() {
|
|
413
|
+
return this.#list((_, entry) => entry.value);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Fresh `[key, value]` pairs, least recently used first.
|
|
418
|
+
* @returns {Array<[K, V]>}
|
|
419
|
+
*/
|
|
420
|
+
entries() {
|
|
421
|
+
return this.#list((key, entry) => /** @type {[K, V]} */ ([key, entry.value]));
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Lets you write `for (const [key, value] of cache)`.
|
|
426
|
+
* @returns {IterableIterator<[K, V]>}
|
|
427
|
+
*/
|
|
428
|
+
[Symbol.iterator]() {
|
|
429
|
+
return this.entries()[Symbol.iterator]();
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
module.exports = Cache;
|