@nxtedition/cache 1.0.6 → 1.0.9
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 +28 -14
- package/lib/index.d.ts +9 -13
- package/lib/index.js +80 -50
- package/lib/memory.d.ts +21 -0
- package/lib/memory.js +103 -0
- package/package.json +6 -9
package/README.md
CHANGED
|
@@ -54,21 +54,35 @@ if (result.async) {
|
|
|
54
54
|
|
|
55
55
|
#### Options
|
|
56
56
|
|
|
57
|
-
| Option
|
|
58
|
-
|
|
|
59
|
-
| `ttl`
|
|
60
|
-
| `stale`
|
|
61
|
-
| `
|
|
62
|
-
| `
|
|
57
|
+
| Option | Type | Default | Description |
|
|
58
|
+
| ---------- | ---------------------------------- | ------------------------------------ | ---------------------------------------------------------------------------------------- |
|
|
59
|
+
| `ttl` | `number \| (value, key) => number` | `MAX_SAFE_INTEGER` | Time-to-live in milliseconds. After this, the entry is stale. |
|
|
60
|
+
| `stale` | `number \| (value, key) => number` | `MAX_SAFE_INTEGER` | Stale-while-revalidate window in milliseconds. After `ttl + stale`, the entry is purged. |
|
|
61
|
+
| `memory` | `MemoryOptions \| false \| null` | `{ maxSize: 16MB, maxCount: 16384 }` | In-memory cache options, or `false`/`null` to disable in-memory caching. |
|
|
62
|
+
| `database` | `DatabaseOptions \| false \| null` | `{ timeout: 20, maxSize: 256MB }` | SQLite options, or `false`/`null` to disable persistence. |
|
|
63
|
+
|
|
64
|
+
#### `MemoryOptions`
|
|
65
|
+
|
|
66
|
+
| Option | Type | Default | Description |
|
|
67
|
+
| ---------- | -------- | -------------------------- | ---------------------------------------------- |
|
|
68
|
+
| `maxSize` | `number` | `16 * 1024 * 1024` (16 MB) | Maximum total size in bytes of cached entries. |
|
|
69
|
+
| `maxCount` | `number` | `16 * 1024` (16384) | Maximum number of entries in memory. |
|
|
70
|
+
|
|
71
|
+
#### `DatabaseOptions`
|
|
72
|
+
|
|
73
|
+
| Option | Type | Default | Description |
|
|
74
|
+
| --------- | -------- | ---------------------------- | ----------------------------------------------------------------- |
|
|
75
|
+
| `timeout` | `number` | `20` | SQLite busy timeout in milliseconds. |
|
|
76
|
+
| `maxSize` | `number` | `256 * 1024 * 1024` (256 MB) | Maximum database file size. Oldest entries are evicted when full. |
|
|
63
77
|
|
|
64
78
|
### `CacheResult<V>`
|
|
65
79
|
|
|
66
80
|
Both `get()` and `peek()` return a `CacheResult<V>`, a discriminated union on the `async` property:
|
|
67
81
|
|
|
68
|
-
| `async` | `value`
|
|
69
|
-
| ------- |
|
|
70
|
-
| `false` | `V`
|
|
71
|
-
| `true` | `Promise<V
|
|
82
|
+
| `async` | `value` | Meaning |
|
|
83
|
+
| ------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
84
|
+
| `false` | `V \| undefined` | Cache hit — the value is available synchronously. Also returned for stale entries (a background refresh is triggered automatically). `undefined` when `peek()` has no cached entry. |
|
|
85
|
+
| `true` | `Promise<V>` | Cache miss — `value` is a `Promise` that resolves when the `valueSelector` completes. |
|
|
72
86
|
|
|
73
87
|
```ts
|
|
74
88
|
const result = cache.get('key')
|
|
@@ -90,19 +104,19 @@ Returns a cached value or triggers a fetch on cache miss.
|
|
|
90
104
|
|
|
91
105
|
#### `cache.peek(...args): CacheResult<V>`
|
|
92
106
|
|
|
93
|
-
Same as `get()` but does **not** trigger a refresh on cache miss. Returns `{ value: undefined, async:
|
|
107
|
+
Same as `get()` but does **not** trigger a refresh on cache miss. Returns `{ value: undefined, async: false }` for missing entries.
|
|
94
108
|
|
|
95
109
|
#### `cache.refresh(...args): Promise<V>`
|
|
96
110
|
|
|
97
111
|
Triggers a fetch via `valueSelector` regardless of cache state. If a fetch for the same key is already in-flight (from a prior `get()` or `refresh()`), the existing promise is returned instead of starting a new one.
|
|
98
112
|
|
|
99
|
-
#### `cache.delete(
|
|
113
|
+
#### `cache.delete(...args): void`
|
|
100
114
|
|
|
101
|
-
Remove
|
|
115
|
+
Remove an entry from the cache. The cache key is derived from `args` via the `keySelector`, just like `get()` and `refresh()`. Also cancels any in-flight deduplication for that key, meaning a pending fetch will not write its result to the cache.
|
|
102
116
|
|
|
103
117
|
#### `cache.purgeStale(): void`
|
|
104
118
|
|
|
105
|
-
Remove all expired entries from both the
|
|
119
|
+
Remove all expired entries from both the in-memory cache and SQLite.
|
|
106
120
|
|
|
107
121
|
#### `cache.close(): void`
|
|
108
122
|
|
package/lib/index.d.ts
CHANGED
|
@@ -1,24 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
value: V;
|
|
6
|
-
}
|
|
7
|
-
export interface AsyncCacheDbOptions {
|
|
1
|
+
import { type MemoryOptions } from './memory.ts';
|
|
2
|
+
export type { MemoryOptions, MemoryCacheEntry } from './memory.ts';
|
|
3
|
+
export { MemoryCache } from './memory.ts';
|
|
4
|
+
export interface DatabaseOptions {
|
|
8
5
|
timeout?: number;
|
|
9
6
|
maxSize?: number;
|
|
10
7
|
}
|
|
11
8
|
export interface AsyncCacheOptions<V> {
|
|
12
9
|
ttl?: number | ((value: V, key: string) => number);
|
|
13
10
|
stale?: number | ((value: V, key: string) => number);
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
memory?: MemoryOptions | false | null;
|
|
12
|
+
database?: DatabaseOptions | false | null;
|
|
16
13
|
}
|
|
17
14
|
export type CacheResult<V> = {
|
|
18
|
-
value: V;
|
|
15
|
+
value: V | undefined;
|
|
19
16
|
async: false;
|
|
20
17
|
} | {
|
|
21
|
-
value: Promise<V
|
|
18
|
+
value: Promise<V>;
|
|
22
19
|
async: true;
|
|
23
20
|
};
|
|
24
21
|
export declare class AsyncCache<V = unknown, A extends unknown[] = unknown[]> {
|
|
@@ -28,7 +25,6 @@ export declare class AsyncCache<V = unknown, A extends unknown[] = unknown[]> {
|
|
|
28
25
|
get(...args: A): CacheResult<V>;
|
|
29
26
|
peek(...args: A): CacheResult<V>;
|
|
30
27
|
refresh(...args: A): Promise<V>;
|
|
31
|
-
delete(
|
|
28
|
+
delete(...args: A): void;
|
|
32
29
|
purgeStale(): void;
|
|
33
30
|
}
|
|
34
|
-
export {};
|
package/lib/index.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { DatabaseSync, } from 'node:sqlite'
|
|
2
|
-
import {
|
|
2
|
+
import { MemoryCache, } from "./memory.js"
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export { MemoryCache } from './memory.ts'
|
|
3
6
|
|
|
4
7
|
let fastNowTime = 0
|
|
5
8
|
|
|
@@ -37,13 +40,13 @@ const dbs = new Set ()
|
|
|
37
40
|
|
|
38
41
|
|
|
39
42
|
|
|
40
|
-
|
|
43
|
+
|
|
41
44
|
|
|
42
45
|
|
|
43
46
|
|
|
44
47
|
|
|
45
48
|
|
|
46
|
-
|
|
49
|
+
|
|
47
50
|
|
|
48
51
|
|
|
49
52
|
|
|
@@ -51,27 +54,28 @@ const dbs = new Set ()
|
|
|
51
54
|
|
|
52
55
|
|
|
53
56
|
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
|
|
58
|
+
|
|
56
59
|
|
|
57
60
|
|
|
58
61
|
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
|
|
63
|
+
|
|
61
64
|
|
|
62
65
|
const VERSION = 2
|
|
63
66
|
const MAX_DURATION = 365000000e3
|
|
64
67
|
|
|
65
68
|
export class AsyncCache {
|
|
66
|
-
#
|
|
69
|
+
#memory
|
|
70
|
+
#dedupe = new Map ()
|
|
71
|
+
|
|
67
72
|
#valueSelector
|
|
68
73
|
#keySelector
|
|
69
|
-
#dedupe = new Map ()
|
|
70
74
|
|
|
71
75
|
#ttl
|
|
72
76
|
#stale
|
|
73
77
|
|
|
74
|
-
#
|
|
78
|
+
#database = null
|
|
75
79
|
#getQuery = null
|
|
76
80
|
#setQuery = null
|
|
77
81
|
#delQuery = null
|
|
@@ -116,15 +120,17 @@ export class AsyncCache {
|
|
|
116
120
|
throw new TypeError('stale must be a undefined, number or a function')
|
|
117
121
|
}
|
|
118
122
|
|
|
119
|
-
this.#
|
|
120
|
-
opts?.
|
|
123
|
+
this.#memory =
|
|
124
|
+
opts?.memory === false || opts?.memory === null ? null : new MemoryCache(opts?.memory)
|
|
121
125
|
|
|
122
|
-
for (let n = 0; opts?.
|
|
126
|
+
for (let n = 0; opts?.database !== null && opts?.database !== false; n++) {
|
|
123
127
|
try {
|
|
124
|
-
const
|
|
125
|
-
|
|
128
|
+
const maxSize = opts?.database?.maxSize ?? 256 * 1024 * 1024
|
|
129
|
+
const timeout = opts?.database?.timeout ?? 20
|
|
130
|
+
|
|
131
|
+
this.#database ??= new DatabaseSync(location, { timeout })
|
|
126
132
|
|
|
127
|
-
this.#
|
|
133
|
+
this.#database.exec(`
|
|
128
134
|
PRAGMA journal_mode = WAL;
|
|
129
135
|
PRAGMA synchronous = NORMAL;
|
|
130
136
|
PRAGMA temp_store = memory;
|
|
@@ -139,26 +145,30 @@ export class AsyncCache {
|
|
|
139
145
|
`)
|
|
140
146
|
|
|
141
147
|
{
|
|
142
|
-
const { page_size } = this.#
|
|
143
|
-
|
|
148
|
+
const { page_size } = this.#database.prepare('PRAGMA page_size').get()
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
this.#database.exec(`PRAGMA max_page_count = ${Math.ceil(maxSize / page_size)}`)
|
|
144
152
|
}
|
|
145
153
|
|
|
146
|
-
this.#getQuery = this.#
|
|
154
|
+
this.#getQuery = this.#database.prepare(
|
|
147
155
|
`SELECT val, ttl, stale FROM cache_v${VERSION} WHERE key = ? AND stale > ?`,
|
|
148
156
|
)
|
|
149
|
-
this.#setQuery = this.#
|
|
157
|
+
this.#setQuery = this.#database.prepare(
|
|
150
158
|
`INSERT OR REPLACE INTO cache_v${VERSION} (key, val, ttl, stale) VALUES (?, ?, ?, ?)`,
|
|
151
159
|
)
|
|
152
|
-
this.#delQuery = this.#
|
|
153
|
-
this.#purgeStaleQuery = this.#
|
|
154
|
-
|
|
160
|
+
this.#delQuery = this.#database.prepare(`DELETE FROM cache_v${VERSION} WHERE key = ?`)
|
|
161
|
+
this.#purgeStaleQuery = this.#database.prepare(
|
|
162
|
+
`DELETE FROM cache_v${VERSION} WHERE stale <= ?`,
|
|
163
|
+
)
|
|
164
|
+
this.#evictQuery = this.#database.prepare(
|
|
155
165
|
`DELETE FROM cache_v${VERSION} WHERE key IN (SELECT key FROM cache_v${VERSION} ORDER BY stale ASC LIMIT ?)`,
|
|
156
166
|
)
|
|
157
167
|
break
|
|
158
168
|
} catch (err) {
|
|
159
|
-
if (n >=
|
|
160
|
-
this.#
|
|
161
|
-
this.#
|
|
169
|
+
if (n >= 16) {
|
|
170
|
+
this.#database?.close()
|
|
171
|
+
this.#database = null
|
|
162
172
|
|
|
163
173
|
this.#getQuery = null
|
|
164
174
|
this.#setQuery = null
|
|
@@ -180,8 +190,8 @@ export class AsyncCache {
|
|
|
180
190
|
this.#delQuery = null
|
|
181
191
|
this.#purgeStaleQuery = null
|
|
182
192
|
this.#evictQuery = null
|
|
183
|
-
this.#
|
|
184
|
-
this.#
|
|
193
|
+
this.#database?.close()
|
|
194
|
+
this.#database = null
|
|
185
195
|
}
|
|
186
196
|
|
|
187
197
|
get(...args ) {
|
|
@@ -196,13 +206,13 @@ export class AsyncCache {
|
|
|
196
206
|
return this.#refresh(args)
|
|
197
207
|
}
|
|
198
208
|
|
|
199
|
-
delete(
|
|
200
|
-
this.#delete(
|
|
209
|
+
delete(...args ) {
|
|
210
|
+
this.#delete(this.#keySelector(...args))
|
|
201
211
|
}
|
|
202
212
|
|
|
203
213
|
purgeStale() {
|
|
204
214
|
try {
|
|
205
|
-
this.#
|
|
215
|
+
this.#memory?.purgeStale(fastNow())
|
|
206
216
|
this.#purgeStaleQuery?.run(fastNow())
|
|
207
217
|
} catch {}
|
|
208
218
|
}
|
|
@@ -216,13 +226,13 @@ export class AsyncCache {
|
|
|
216
226
|
|
|
217
227
|
const now = fastNow()
|
|
218
228
|
|
|
219
|
-
let cached = this.#
|
|
229
|
+
let cached = this.#memory?.get(key)
|
|
220
230
|
|
|
221
231
|
if (cached === undefined) {
|
|
222
232
|
try {
|
|
223
|
-
const row = this.#getQuery?.get(key, now)
|
|
233
|
+
const row = this.#getQuery?.get(key, now)
|
|
224
234
|
if (row !== undefined) {
|
|
225
|
-
|
|
235
|
+
const entry = {
|
|
226
236
|
ttl: row.ttl,
|
|
227
237
|
stale: row.stale,
|
|
228
238
|
value: ArrayBuffer.isView(row.val)
|
|
@@ -232,11 +242,15 @@ export class AsyncCache {
|
|
|
232
242
|
row.val.byteLength,
|
|
233
243
|
) )
|
|
234
244
|
: JSON.parse(row.val),
|
|
245
|
+
key,
|
|
246
|
+
size:
|
|
247
|
+
(ArrayBuffer.isView(row.val) ? row.val.byteLength : row.val.length) + key.length + 64,
|
|
248
|
+
index: -1,
|
|
249
|
+
counter: -1,
|
|
235
250
|
}
|
|
236
|
-
|
|
251
|
+
this.#memory?.set(key, entry)
|
|
237
252
|
|
|
238
|
-
|
|
239
|
-
this.#lru?.set(key, cached)
|
|
253
|
+
cached = entry
|
|
240
254
|
}
|
|
241
255
|
} catch (err) {
|
|
242
256
|
process.emitWarning(err )
|
|
@@ -250,7 +264,7 @@ export class AsyncCache {
|
|
|
250
264
|
|
|
251
265
|
if (now > cached.stale) {
|
|
252
266
|
// stale-while-revalidate has expired, purge cached value.
|
|
253
|
-
this.#
|
|
267
|
+
this.#memory?.delete(key)
|
|
254
268
|
try {
|
|
255
269
|
this.#delQuery?.run(key)
|
|
256
270
|
} catch {
|
|
@@ -263,12 +277,18 @@ export class AsyncCache {
|
|
|
263
277
|
|
|
264
278
|
const promise = refresh ? this.#refresh(args, key) : undefined
|
|
265
279
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
280
|
+
if (cached !== undefined) {
|
|
281
|
+
return { value: cached.value, async: false }
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (promise !== undefined) {
|
|
285
|
+
return { value: promise, async: true }
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return { value: undefined, async: false }
|
|
269
289
|
}
|
|
270
290
|
|
|
271
|
-
#refresh(args , key
|
|
291
|
+
#refresh(args , key = this.#keySelector(...args)) {
|
|
272
292
|
if (typeof key !== 'string' || key.length === 0) {
|
|
273
293
|
throw new TypeError('keySelector must return a non-empty string')
|
|
274
294
|
}
|
|
@@ -284,7 +304,11 @@ export class AsyncCache {
|
|
|
284
304
|
(value) => {
|
|
285
305
|
if (this.#dedupe.get(key) === promise) {
|
|
286
306
|
this.#dedupe.delete(key)
|
|
287
|
-
|
|
307
|
+
if (value === undefined) {
|
|
308
|
+
this.#delete(key)
|
|
309
|
+
} else {
|
|
310
|
+
this.#set(key, value)
|
|
311
|
+
}
|
|
288
312
|
}
|
|
289
313
|
return value
|
|
290
314
|
},
|
|
@@ -324,16 +348,22 @@ export class AsyncCache {
|
|
|
324
348
|
return
|
|
325
349
|
}
|
|
326
350
|
|
|
327
|
-
const storedValue = ArrayBuffer.isView(value)
|
|
328
|
-
? (Buffer.from(value.buffer, value.byteOffset, value.byteLength) )
|
|
329
|
-
: value
|
|
330
|
-
|
|
331
|
-
this.#lru?.set(key, { ttl, stale, value: storedValue })
|
|
332
|
-
|
|
333
351
|
const data = ArrayBuffer.isView(value)
|
|
334
352
|
? value
|
|
335
353
|
: JSON.stringify(value )
|
|
336
354
|
|
|
355
|
+
this.#memory?.set(key, {
|
|
356
|
+
ttl,
|
|
357
|
+
stale,
|
|
358
|
+
value: ArrayBuffer.isView(value)
|
|
359
|
+
? (Buffer.from(value.buffer, value.byteOffset, value.byteLength) )
|
|
360
|
+
: value,
|
|
361
|
+
key,
|
|
362
|
+
size: (ArrayBuffer.isView(data) ? data.byteLength : data.length) + key.length + 64,
|
|
363
|
+
index: -1,
|
|
364
|
+
counter: -1,
|
|
365
|
+
})
|
|
366
|
+
|
|
337
367
|
try {
|
|
338
368
|
this.#setQuery?.run(key, data , ttl, stale)
|
|
339
369
|
} catch (err) {
|
|
@@ -356,7 +386,7 @@ export class AsyncCache {
|
|
|
356
386
|
}
|
|
357
387
|
|
|
358
388
|
this.#dedupe.delete(key)
|
|
359
|
-
this.#
|
|
389
|
+
this.#memory?.delete(key)
|
|
360
390
|
try {
|
|
361
391
|
this.#delQuery?.run(key)
|
|
362
392
|
} catch (err) {
|
package/lib/memory.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface MemoryOptions {
|
|
2
|
+
maxSize?: number;
|
|
3
|
+
maxCount?: number;
|
|
4
|
+
}
|
|
5
|
+
export interface MemoryCacheEntry<V> {
|
|
6
|
+
ttl: number;
|
|
7
|
+
stale: number;
|
|
8
|
+
value: V;
|
|
9
|
+
key: string;
|
|
10
|
+
index: number;
|
|
11
|
+
size: number;
|
|
12
|
+
counter: number;
|
|
13
|
+
}
|
|
14
|
+
export declare class MemoryCache<V> {
|
|
15
|
+
#private;
|
|
16
|
+
constructor(opts?: MemoryOptions);
|
|
17
|
+
set(key: string, entry: MemoryCacheEntry<V>): void;
|
|
18
|
+
get(key: string): MemoryCacheEntry<V> | undefined;
|
|
19
|
+
delete(key: string): void;
|
|
20
|
+
purgeStale(now: number): void;
|
|
21
|
+
}
|
package/lib/memory.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
export class MemoryCache {
|
|
17
|
+
#map = new Map()
|
|
18
|
+
#arr = []
|
|
19
|
+
|
|
20
|
+
#maxSize
|
|
21
|
+
#maxCount
|
|
22
|
+
|
|
23
|
+
#size = 0
|
|
24
|
+
#count = 0
|
|
25
|
+
|
|
26
|
+
#counter = 0
|
|
27
|
+
|
|
28
|
+
constructor(opts ) {
|
|
29
|
+
if (opts?.maxSize != null && (!Number.isInteger(opts.maxSize) || opts.maxSize < 1)) {
|
|
30
|
+
throw new Error('maxSize must be a positive integer')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (opts?.maxCount != null && (!Number.isInteger(opts.maxCount) || opts.maxCount < 1)) {
|
|
34
|
+
throw new Error('maxCount must be a positive integer')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
this.#maxSize = opts?.maxSize ?? 16 * 1024 * 1024
|
|
38
|
+
this.#maxCount = opts?.maxCount ?? 16 * 1024
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
set(key , entry ) {
|
|
42
|
+
const existing = this.#map.get(key)
|
|
43
|
+
if (existing != null) {
|
|
44
|
+
this.#delete(existing)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
this.#map.set(key, entry)
|
|
48
|
+
entry.key = key
|
|
49
|
+
entry.index = this.#arr.push(entry) - 1
|
|
50
|
+
entry.counter = this.#counter++
|
|
51
|
+
|
|
52
|
+
this.#size += entry.size ?? 0
|
|
53
|
+
this.#count += 1
|
|
54
|
+
|
|
55
|
+
this.#prune()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
get(key ) {
|
|
59
|
+
const entry = this.#map.get(key)
|
|
60
|
+
if (entry != null) {
|
|
61
|
+
entry.counter = this.#counter++
|
|
62
|
+
}
|
|
63
|
+
return entry
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
delete(key ) {
|
|
67
|
+
const entry = this.#map.get(key)
|
|
68
|
+
if (entry != null) {
|
|
69
|
+
this.#delete(entry)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
#delete(entry ) {
|
|
74
|
+
this.#map.delete(entry.key)
|
|
75
|
+
|
|
76
|
+
this.#size -= entry.size
|
|
77
|
+
this.#count -= 1
|
|
78
|
+
|
|
79
|
+
const tmp = this.#arr.pop()
|
|
80
|
+
if (tmp !== entry) {
|
|
81
|
+
this.#arr[entry.index] = tmp
|
|
82
|
+
this.#arr[entry.index].index = entry.index
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
purgeStale(now ) {
|
|
87
|
+
for (let i = this.#arr.length - 1; i >= 0; i--) {
|
|
88
|
+
const entry = this.#arr[i]
|
|
89
|
+
if (now > entry.stale) {
|
|
90
|
+
this.#delete(entry)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
#prune() {
|
|
96
|
+
while (this.#size > this.#maxSize || this.#count > this.#maxCount) {
|
|
97
|
+
const e1 = this.#arr[(Math.random() * this.#arr.length) | 0]
|
|
98
|
+
const e2 = this.#arr[(Math.random() * this.#arr.length) | 0]
|
|
99
|
+
const e = e2.counter > e1.counter ? e1 : e2
|
|
100
|
+
this.#delete(e)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxtedition/cache",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -14,12 +14,12 @@
|
|
|
14
14
|
"access": "public"
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
|
-
"build": "rimraf lib && tsc && amaroc ./src/index.ts && mv src/index.js lib/",
|
|
17
|
+
"build": "rimraf lib && tsc && amaroc ./src/index.ts && mv src/index.js lib/ && amaroc ./src/memory.ts && mv src/memory.js lib/",
|
|
18
18
|
"prepublishOnly": "yarn build",
|
|
19
19
|
"typecheck": "tsc --noEmit",
|
|
20
|
-
"test": "node --test",
|
|
21
|
-
"test:ci": "node --test",
|
|
22
|
-
"test:coverage": "node --test --experimental-test-coverage --test-coverage-include=src/index.ts --test-coverage-lines=90 --test-coverage-branches=90 --test-coverage-functions=100"
|
|
20
|
+
"test": "node --test && yarn build",
|
|
21
|
+
"test:ci": "node --test && yarn build",
|
|
22
|
+
"test:coverage": "node --test --experimental-test-coverage --test-coverage-include=src/index.ts --test-coverage-include=src/memory.ts --test-coverage-lines=90 --test-coverage-branches=90 --test-coverage-functions=100"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/node": "^25.2.3",
|
|
@@ -28,8 +28,5 @@
|
|
|
28
28
|
"rimraf": "^6.1.2",
|
|
29
29
|
"typescript": "^5.9.3"
|
|
30
30
|
},
|
|
31
|
-
"
|
|
32
|
-
"lru-cache": "^11.2.6"
|
|
33
|
-
},
|
|
34
|
-
"gitHead": "5c8b45723e68c527888e75a1536569479da7e4c5"
|
|
31
|
+
"gitHead": "0dd08f59b4d41fc8b3038386cfbd5adf970863c7"
|
|
35
32
|
}
|