@twin.org/core 0.9.2-next.8 → 0.9.2
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/dist/es/helpers/jsonHelper.js +1 -2
- package/dist/es/helpers/jsonHelper.js.map +1 -1
- package/dist/es/index.js +2 -0
- package/dist/es/index.js.map +1 -1
- package/dist/es/models/coerceType.js +5 -1
- package/dist/es/models/coerceType.js.map +1 -1
- package/dist/es/utils/coerce.js +30 -3
- package/dist/es/utils/coerce.js.map +1 -1
- package/dist/es/utils/is.js +24 -1
- package/dist/es/utils/is.js.map +1 -1
- package/dist/es/utils/lfuCache.js +380 -0
- package/dist/es/utils/lfuCache.js.map +1 -0
- package/dist/es/utils/lruCache.js +271 -0
- package/dist/es/utils/lruCache.js.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/models/coerceType.d.ts +4 -0
- package/dist/types/utils/coerce.d.ts +6 -0
- package/dist/types/utils/lfuCache.d.ts +100 -0
- package/dist/types/utils/lruCache.d.ts +97 -0
- package/docs/changelog.md +74 -0
- package/docs/reference/classes/Coerce.md +28 -0
- package/docs/reference/classes/LfuCache.md +265 -0
- package/docs/reference/classes/LruCache.md +262 -0
- package/docs/reference/index.md +2 -0
- package/docs/reference/variables/CoerceType.md +6 -0
- package/package.json +2 -2
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { Guards } from "./guards.js";
|
|
2
|
+
import { Is } from "./is.js";
|
|
3
|
+
import { Mutex } from "./mutex.js";
|
|
4
|
+
import { Validation } from "./validation.js";
|
|
5
|
+
import { RandomHelper } from "../helpers/randomHelper.js";
|
|
6
|
+
/**
|
|
7
|
+
* A fixed-capacity LRU cache with time-to-idle eviction.
|
|
8
|
+
*
|
|
9
|
+
* Entries are removed in two ways:
|
|
10
|
+
* - Capacity eviction: when the cache is full the least-recently-used entry is removed first.
|
|
11
|
+
* - TTI eviction: a background timer sweeps idle entries every ttiMs milliseconds.
|
|
12
|
+
* The timer only runs while there are entries; it stops automatically when the cache empties.
|
|
13
|
+
*
|
|
14
|
+
* `get` and `set` both update an entry's LRU position and reset its idle timer.
|
|
15
|
+
* `has` is a pure peek it evicts idle entries but does not refresh a live entry's TTI.
|
|
16
|
+
* Call `destroy` when the cache is no longer needed to stop the background timer.
|
|
17
|
+
*/
|
|
18
|
+
export class LruCache {
|
|
19
|
+
/**
|
|
20
|
+
* Runtime name for the class.
|
|
21
|
+
*/
|
|
22
|
+
static CLASS_NAME = "LruCache";
|
|
23
|
+
/**
|
|
24
|
+
* Default capacity.
|
|
25
|
+
*/
|
|
26
|
+
static DEFAULT_CAPACITY = 1000;
|
|
27
|
+
/**
|
|
28
|
+
* Default time-to-idle in milliseconds.
|
|
29
|
+
*/
|
|
30
|
+
static DEFAULT_TTI_MS = 10000;
|
|
31
|
+
/**
|
|
32
|
+
* The maximum number of entries the cache will hold.
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
_capacity;
|
|
36
|
+
/**
|
|
37
|
+
* The idle duration in milliseconds after which an untouched entry is evicted.
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
40
|
+
_ttiMs;
|
|
41
|
+
/**
|
|
42
|
+
* Optional timeout in milliseconds for mutex acquisition.
|
|
43
|
+
* @internal
|
|
44
|
+
*/
|
|
45
|
+
_mutexTimeoutMs;
|
|
46
|
+
/**
|
|
47
|
+
* Per-instance namespace prefix for mutex keys.
|
|
48
|
+
* @internal
|
|
49
|
+
*/
|
|
50
|
+
_mutexScope;
|
|
51
|
+
/**
|
|
52
|
+
* Underlying storage; Map iteration order tracks LRU position (first = oldest).
|
|
53
|
+
* @internal
|
|
54
|
+
*/
|
|
55
|
+
_cache;
|
|
56
|
+
/**
|
|
57
|
+
* Handle for the pending idle-sweep timeout, or undefined if no timer is scheduled.
|
|
58
|
+
* @internal
|
|
59
|
+
*/
|
|
60
|
+
_sweepTimer;
|
|
61
|
+
/**
|
|
62
|
+
* Create a new instance of LruCache.
|
|
63
|
+
* @param options The cache options.
|
|
64
|
+
* @param options.capacity Maximum number of entries. Defaults to 1000. Must be a positive integer.
|
|
65
|
+
* @param options.ttiMs Time-to-idle in milliseconds. Defaults to 10000. Must be a positive integer.
|
|
66
|
+
* @param options.mutexTimeoutMs Maximum time in milliseconds to wait for getOrSet mutex acquisition.
|
|
67
|
+
* @throws ValidationError if capacity or ttiMs is not a positive integer.
|
|
68
|
+
*/
|
|
69
|
+
constructor(options) {
|
|
70
|
+
const capacity = options?.capacity ?? LruCache.DEFAULT_CAPACITY;
|
|
71
|
+
const ttiMs = options?.ttiMs ?? LruCache.DEFAULT_TTI_MS;
|
|
72
|
+
const mutexTimeoutMs = options?.mutexTimeoutMs;
|
|
73
|
+
Guards.integer(LruCache.CLASS_NAME, "capacity", capacity);
|
|
74
|
+
Guards.integer(LruCache.CLASS_NAME, "ttiMs", ttiMs);
|
|
75
|
+
if (Is.notEmpty(mutexTimeoutMs)) {
|
|
76
|
+
Guards.integer(LruCache.CLASS_NAME, "mutexTimeoutMs", mutexTimeoutMs);
|
|
77
|
+
}
|
|
78
|
+
const failures = [];
|
|
79
|
+
Validation.integer("capacity", capacity, failures, undefined, { minValue: 1 });
|
|
80
|
+
Validation.integer("ttiMs", ttiMs, failures, undefined, { minValue: 1 });
|
|
81
|
+
if (Is.notEmpty(mutexTimeoutMs)) {
|
|
82
|
+
Validation.integer("mutexTimeoutMs", mutexTimeoutMs, failures, undefined, {
|
|
83
|
+
minValue: 0
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
Validation.asValidationError(LruCache.CLASS_NAME, "LruCache", failures);
|
|
87
|
+
this._capacity = capacity;
|
|
88
|
+
this._ttiMs = ttiMs;
|
|
89
|
+
this._mutexTimeoutMs = mutexTimeoutMs;
|
|
90
|
+
this._mutexScope = `${LruCache.CLASS_NAME}:${RandomHelper.generateUuidV7()}`;
|
|
91
|
+
this._cache = new Map();
|
|
92
|
+
this._sweepTimer = undefined;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The number of entries currently held in the cache.
|
|
96
|
+
* @returns The number of entries in the cache.
|
|
97
|
+
*/
|
|
98
|
+
count() {
|
|
99
|
+
return this._cache.size;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Get a value from the cache.
|
|
103
|
+
* Returns undefined if the key is absent or the entry has idled out.
|
|
104
|
+
* A successful hit resets the entry's idle timer and moves it to most-recently-used.
|
|
105
|
+
* @param key The key to retrieve.
|
|
106
|
+
* @returns The cached value, or undefined on a miss or idle eviction.
|
|
107
|
+
*/
|
|
108
|
+
get(key) {
|
|
109
|
+
const entry = this._cache.get(key);
|
|
110
|
+
if (entry === undefined) {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
const now = Date.now();
|
|
114
|
+
if (now - entry.lastAccessed >= this._ttiMs) {
|
|
115
|
+
this._cache.delete(key);
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
// Move to end of Map (most-recently-used) via delete + re-insert
|
|
119
|
+
this._cache.delete(key);
|
|
120
|
+
entry.lastAccessed = now;
|
|
121
|
+
this._cache.set(key, entry);
|
|
122
|
+
return entry.value;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Store a value in the cache.
|
|
126
|
+
* If the key already exists its value and idle timer are refreshed.
|
|
127
|
+
* When the cache is at capacity, idle entries are swept first; if it is still full the
|
|
128
|
+
* least-recently-used entry is evicted.
|
|
129
|
+
* @param key The key to store.
|
|
130
|
+
* @param value The value to cache.
|
|
131
|
+
*/
|
|
132
|
+
set(key, value) {
|
|
133
|
+
const now = Date.now();
|
|
134
|
+
// Remove any existing entry so the refreshed version is inserted at the end
|
|
135
|
+
this._cache.delete(key);
|
|
136
|
+
if (this._cache.size >= this._capacity) {
|
|
137
|
+
this.sweepIdle();
|
|
138
|
+
}
|
|
139
|
+
if (this._cache.size >= this._capacity) {
|
|
140
|
+
const lruKey = this._cache.keys().next().value;
|
|
141
|
+
if (lruKey !== undefined) {
|
|
142
|
+
this._cache.delete(lruKey);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
this._cache.set(key, { value, lastAccessed: now });
|
|
146
|
+
this.startTimer();
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Atomically get an existing value or create and store it once using an async factory.
|
|
150
|
+
* Concurrent calls for the same key are serialized via a mutex.
|
|
151
|
+
* @param key The key to get or create.
|
|
152
|
+
* @param valueFactory Async callback used to build a value when the key is absent.
|
|
153
|
+
* @returns The existing or newly created value.
|
|
154
|
+
*/
|
|
155
|
+
async getOrSet(key, valueFactory) {
|
|
156
|
+
Guards.stringValue(LruCache.CLASS_NAME, "key", key);
|
|
157
|
+
Guards.function(LruCache.CLASS_NAME, "valueFactory", valueFactory);
|
|
158
|
+
const mutexKey = `${this._mutexScope}:${key}`;
|
|
159
|
+
await Mutex.lock(mutexKey, {
|
|
160
|
+
timeoutMs: this._mutexTimeoutMs,
|
|
161
|
+
throwOnTimeout: true
|
|
162
|
+
});
|
|
163
|
+
try {
|
|
164
|
+
if (this.has(key)) {
|
|
165
|
+
return this.get(key);
|
|
166
|
+
}
|
|
167
|
+
const value = await valueFactory();
|
|
168
|
+
this.set(key, value);
|
|
169
|
+
return value;
|
|
170
|
+
}
|
|
171
|
+
finally {
|
|
172
|
+
Mutex.unlock(mutexKey);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Check whether a key exists in the cache and has not idled out.
|
|
177
|
+
* Idle entries are evicted on peek, but a live entry's TTI is not reset.
|
|
178
|
+
* @param key The key to test.
|
|
179
|
+
* @returns True if the key is present and not idle.
|
|
180
|
+
*/
|
|
181
|
+
has(key) {
|
|
182
|
+
const entry = this._cache.get(key);
|
|
183
|
+
if (entry === undefined) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
if (Date.now() - entry.lastAccessed >= this._ttiMs) {
|
|
187
|
+
this._cache.delete(key);
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Return all keys for entries that have not idled out.
|
|
194
|
+
* Idle entries encountered during iteration are evicted.
|
|
195
|
+
* @returns An array of live keys in least-recently-used to most-recently-used order.
|
|
196
|
+
*/
|
|
197
|
+
keys() {
|
|
198
|
+
const now = Date.now();
|
|
199
|
+
const result = [];
|
|
200
|
+
for (const [k, entry] of this._cache) {
|
|
201
|
+
if (now - entry.lastAccessed >= this._ttiMs) {
|
|
202
|
+
this._cache.delete(k);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
result.push(k);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return result;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Remove an entry from the cache.
|
|
212
|
+
* Cancels the background timer if the cache becomes empty.
|
|
213
|
+
* @param key The key to remove.
|
|
214
|
+
*/
|
|
215
|
+
delete(key) {
|
|
216
|
+
this._cache.delete(key);
|
|
217
|
+
if (this._cache.size === 0) {
|
|
218
|
+
this.cancelTimer();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Remove all entries from the cache and cancel the background timer.
|
|
223
|
+
*/
|
|
224
|
+
clear() {
|
|
225
|
+
this.cancelTimer();
|
|
226
|
+
this._cache.clear();
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Stop the background idle-sweep timer and release all entries.
|
|
230
|
+
* The cache must not be used after this call.
|
|
231
|
+
*/
|
|
232
|
+
destroy() {
|
|
233
|
+
this.cancelTimer();
|
|
234
|
+
this._cache.clear();
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Delete all entries whose idle time has been exceeded, then restart the timer
|
|
238
|
+
* if any entries remain.
|
|
239
|
+
* @internal
|
|
240
|
+
*/
|
|
241
|
+
sweepIdle() {
|
|
242
|
+
this.cancelTimer();
|
|
243
|
+
const now = Date.now();
|
|
244
|
+
for (const [k, entry] of this._cache) {
|
|
245
|
+
if (now - entry.lastAccessed >= this._ttiMs) {
|
|
246
|
+
this._cache.delete(k);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (this._cache.size > 0) {
|
|
250
|
+
this.startTimer();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Schedule the next idle sweep if no timer is already pending.
|
|
255
|
+
* @internal
|
|
256
|
+
*/
|
|
257
|
+
startTimer() {
|
|
258
|
+
this._sweepTimer ??= setTimeout(() => this.sweepIdle(), this._ttiMs);
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Cancel the pending idle-sweep timer.
|
|
262
|
+
* @internal
|
|
263
|
+
*/
|
|
264
|
+
cancelTimer() {
|
|
265
|
+
if (Is.notEmpty(this._sweepTimer)) {
|
|
266
|
+
clearTimeout(this._sweepTimer);
|
|
267
|
+
this._sweepTimer = undefined;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
//# sourceMappingURL=lruCache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lruCache.js","sourceRoot":"","sources":["../../../src/utils/lruCache.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { nameof } from \"@twin.org/nameof\";\nimport { Guards } from \"./guards.js\";\nimport { Is } from \"./is.js\";\nimport { Mutex } from \"./mutex.js\";\nimport { Validation } from \"./validation.js\";\nimport { RandomHelper } from \"../helpers/randomHelper.js\";\nimport type { IValidationFailure } from \"../models/IValidationFailure.js\";\n\n/**\n * A fixed-capacity LRU cache with time-to-idle eviction.\n *\n * Entries are removed in two ways:\n * - Capacity eviction: when the cache is full the least-recently-used entry is removed first.\n * - TTI eviction: a background timer sweeps idle entries every ttiMs milliseconds.\n * The timer only runs while there are entries; it stops automatically when the cache empties.\n *\n * `get` and `set` both update an entry's LRU position and reset its idle timer.\n * `has` is a pure peek it evicts idle entries but does not refresh a live entry's TTI.\n * Call `destroy` when the cache is no longer needed to stop the background timer.\n */\nexport class LruCache<T = unknown> {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<LruCache>();\n\n\t/**\n\t * Default capacity.\n\t */\n\tpublic static readonly DEFAULT_CAPACITY = 1000;\n\n\t/**\n\t * Default time-to-idle in milliseconds.\n\t */\n\tpublic static readonly DEFAULT_TTI_MS = 10000;\n\n\t/**\n\t * The maximum number of entries the cache will hold.\n\t * @internal\n\t */\n\tprivate readonly _capacity: number;\n\n\t/**\n\t * The idle duration in milliseconds after which an untouched entry is evicted.\n\t * @internal\n\t */\n\tprivate readonly _ttiMs: number;\n\n\t/**\n\t * Optional timeout in milliseconds for mutex acquisition.\n\t * @internal\n\t */\n\tprivate readonly _mutexTimeoutMs: number | undefined;\n\n\t/**\n\t * Per-instance namespace prefix for mutex keys.\n\t * @internal\n\t */\n\tprivate readonly _mutexScope: string;\n\n\t/**\n\t * Underlying storage; Map iteration order tracks LRU position (first = oldest).\n\t * @internal\n\t */\n\tprivate readonly _cache: Map<string, { value: T; lastAccessed: number }>;\n\n\t/**\n\t * Handle for the pending idle-sweep timeout, or undefined if no timer is scheduled.\n\t * @internal\n\t */\n\tprivate _sweepTimer: ReturnType<typeof setTimeout> | undefined;\n\n\t/**\n\t * Create a new instance of LruCache.\n\t * @param options The cache options.\n\t * @param options.capacity Maximum number of entries. Defaults to 1000. Must be a positive integer.\n\t * @param options.ttiMs Time-to-idle in milliseconds. Defaults to 10000. Must be a positive integer.\n\t * @param options.mutexTimeoutMs Maximum time in milliseconds to wait for getOrSet mutex acquisition.\n\t * @throws ValidationError if capacity or ttiMs is not a positive integer.\n\t */\n\tconstructor(options?: { capacity?: number; ttiMs?: number; mutexTimeoutMs?: number }) {\n\t\tconst capacity = options?.capacity ?? LruCache.DEFAULT_CAPACITY;\n\t\tconst ttiMs = options?.ttiMs ?? LruCache.DEFAULT_TTI_MS;\n\t\tconst mutexTimeoutMs = options?.mutexTimeoutMs;\n\n\t\tGuards.integer(LruCache.CLASS_NAME, nameof(capacity), capacity);\n\t\tGuards.integer(LruCache.CLASS_NAME, nameof(ttiMs), ttiMs);\n\t\tif (Is.notEmpty(mutexTimeoutMs)) {\n\t\t\tGuards.integer(LruCache.CLASS_NAME, nameof(mutexTimeoutMs), mutexTimeoutMs);\n\t\t}\n\n\t\tconst failures: IValidationFailure[] = [];\n\t\tValidation.integer(nameof(capacity), capacity, failures, undefined, { minValue: 1 });\n\t\tValidation.integer(nameof(ttiMs), ttiMs, failures, undefined, { minValue: 1 });\n\t\tif (Is.notEmpty(mutexTimeoutMs)) {\n\t\t\tValidation.integer(nameof(mutexTimeoutMs), mutexTimeoutMs, failures, undefined, {\n\t\t\t\tminValue: 0\n\t\t\t});\n\t\t}\n\t\tValidation.asValidationError(LruCache.CLASS_NAME, nameof<LruCache>(), failures);\n\n\t\tthis._capacity = capacity;\n\t\tthis._ttiMs = ttiMs;\n\t\tthis._mutexTimeoutMs = mutexTimeoutMs;\n\t\tthis._mutexScope = `${LruCache.CLASS_NAME}:${RandomHelper.generateUuidV7()}`;\n\t\tthis._cache = new Map();\n\t\tthis._sweepTimer = undefined;\n\t}\n\n\t/**\n\t * The number of entries currently held in the cache.\n\t * @returns The number of entries in the cache.\n\t */\n\tpublic count(): number {\n\t\treturn this._cache.size;\n\t}\n\n\t/**\n\t * Get a value from the cache.\n\t * Returns undefined if the key is absent or the entry has idled out.\n\t * A successful hit resets the entry's idle timer and moves it to most-recently-used.\n\t * @param key The key to retrieve.\n\t * @returns The cached value, or undefined on a miss or idle eviction.\n\t */\n\tpublic get(key: string): T | undefined {\n\t\tconst entry = this._cache.get(key);\n\t\tif (entry === undefined) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst now = Date.now();\n\t\tif (now - entry.lastAccessed >= this._ttiMs) {\n\t\t\tthis._cache.delete(key);\n\t\t\treturn undefined;\n\t\t}\n\t\t// Move to end of Map (most-recently-used) via delete + re-insert\n\t\tthis._cache.delete(key);\n\t\tentry.lastAccessed = now;\n\t\tthis._cache.set(key, entry);\n\t\treturn entry.value;\n\t}\n\n\t/**\n\t * Store a value in the cache.\n\t * If the key already exists its value and idle timer are refreshed.\n\t * When the cache is at capacity, idle entries are swept first; if it is still full the\n\t * least-recently-used entry is evicted.\n\t * @param key The key to store.\n\t * @param value The value to cache.\n\t */\n\tpublic set(key: string, value: T): void {\n\t\tconst now = Date.now();\n\t\t// Remove any existing entry so the refreshed version is inserted at the end\n\t\tthis._cache.delete(key);\n\t\tif (this._cache.size >= this._capacity) {\n\t\t\tthis.sweepIdle();\n\t\t}\n\t\tif (this._cache.size >= this._capacity) {\n\t\t\tconst lruKey = this._cache.keys().next().value;\n\t\t\tif (lruKey !== undefined) {\n\t\t\t\tthis._cache.delete(lruKey);\n\t\t\t}\n\t\t}\n\t\tthis._cache.set(key, { value, lastAccessed: now });\n\t\tthis.startTimer();\n\t}\n\n\t/**\n\t * Atomically get an existing value or create and store it once using an async factory.\n\t * Concurrent calls for the same key are serialized via a mutex.\n\t * @param key The key to get or create.\n\t * @param valueFactory Async callback used to build a value when the key is absent.\n\t * @returns The existing or newly created value.\n\t */\n\tpublic async getOrSet(key: string, valueFactory: () => Promise<T>): Promise<T> {\n\t\tGuards.stringValue(LruCache.CLASS_NAME, nameof(key), key);\n\t\tGuards.function(LruCache.CLASS_NAME, nameof(valueFactory), valueFactory);\n\n\t\tconst mutexKey = `${this._mutexScope}:${key}`;\n\t\tawait Mutex.lock(mutexKey, {\n\t\t\ttimeoutMs: this._mutexTimeoutMs,\n\t\t\tthrowOnTimeout: true\n\t\t});\n\n\t\ttry {\n\t\t\tif (this.has(key)) {\n\t\t\t\treturn this.get(key) as T;\n\t\t\t}\n\n\t\t\tconst value = await valueFactory();\n\t\t\tthis.set(key, value);\n\t\t\treturn value;\n\t\t} finally {\n\t\t\tMutex.unlock(mutexKey);\n\t\t}\n\t}\n\n\t/**\n\t * Check whether a key exists in the cache and has not idled out.\n\t * Idle entries are evicted on peek, but a live entry's TTI is not reset.\n\t * @param key The key to test.\n\t * @returns True if the key is present and not idle.\n\t */\n\tpublic has(key: string): boolean {\n\t\tconst entry = this._cache.get(key);\n\t\tif (entry === undefined) {\n\t\t\treturn false;\n\t\t}\n\t\tif (Date.now() - entry.lastAccessed >= this._ttiMs) {\n\t\t\tthis._cache.delete(key);\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\n\t/**\n\t * Return all keys for entries that have not idled out.\n\t * Idle entries encountered during iteration are evicted.\n\t * @returns An array of live keys in least-recently-used to most-recently-used order.\n\t */\n\tpublic keys(): string[] {\n\t\tconst now = Date.now();\n\t\tconst result: string[] = [];\n\t\tfor (const [k, entry] of this._cache) {\n\t\t\tif (now - entry.lastAccessed >= this._ttiMs) {\n\t\t\t\tthis._cache.delete(k);\n\t\t\t} else {\n\t\t\t\tresult.push(k);\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * Remove an entry from the cache.\n\t * Cancels the background timer if the cache becomes empty.\n\t * @param key The key to remove.\n\t */\n\tpublic delete(key: string): void {\n\t\tthis._cache.delete(key);\n\t\tif (this._cache.size === 0) {\n\t\t\tthis.cancelTimer();\n\t\t}\n\t}\n\n\t/**\n\t * Remove all entries from the cache and cancel the background timer.\n\t */\n\tpublic clear(): void {\n\t\tthis.cancelTimer();\n\t\tthis._cache.clear();\n\t}\n\n\t/**\n\t * Stop the background idle-sweep timer and release all entries.\n\t * The cache must not be used after this call.\n\t */\n\tpublic destroy(): void {\n\t\tthis.cancelTimer();\n\t\tthis._cache.clear();\n\t}\n\n\t/**\n\t * Delete all entries whose idle time has been exceeded, then restart the timer\n\t * if any entries remain.\n\t * @internal\n\t */\n\tprivate sweepIdle(): void {\n\t\tthis.cancelTimer();\n\t\tconst now = Date.now();\n\t\tfor (const [k, entry] of this._cache) {\n\t\t\tif (now - entry.lastAccessed >= this._ttiMs) {\n\t\t\t\tthis._cache.delete(k);\n\t\t\t}\n\t\t}\n\t\tif (this._cache.size > 0) {\n\t\t\tthis.startTimer();\n\t\t}\n\t}\n\n\t/**\n\t * Schedule the next idle sweep if no timer is already pending.\n\t * @internal\n\t */\n\tprivate startTimer(): void {\n\t\tthis._sweepTimer ??= setTimeout(() => this.sweepIdle(), this._ttiMs);\n\t}\n\n\t/**\n\t * Cancel the pending idle-sweep timer.\n\t * @internal\n\t */\n\tprivate cancelTimer(): void {\n\t\tif (Is.notEmpty(this._sweepTimer)) {\n\t\t\tclearTimeout(this._sweepTimer);\n\t\t\tthis._sweepTimer = undefined;\n\t\t}\n\t}\n}\n"]}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -54,6 +54,8 @@ export * from "./types/singleOccurrenceArrayDepthHelper.js";
|
|
|
54
54
|
export * from "./types/url.js";
|
|
55
55
|
export * from "./types/urn.js";
|
|
56
56
|
export * from "./utils/asyncCache.js";
|
|
57
|
+
export * from "./utils/lfuCache.js";
|
|
58
|
+
export * from "./utils/lruCache.js";
|
|
57
59
|
export * from "./utils/coerce.js";
|
|
58
60
|
export * from "./utils/compression.js";
|
|
59
61
|
export * from "./utils/converter.js";
|
|
@@ -60,6 +60,12 @@ export declare class Coerce {
|
|
|
60
60
|
* @returns The duration object, or undefined if the value cannot be coerced.
|
|
61
61
|
*/
|
|
62
62
|
static duration(value: unknown): IDuration | undefined;
|
|
63
|
+
/**
|
|
64
|
+
* Coerce the value to an array.
|
|
65
|
+
* @param value The value to coerce.
|
|
66
|
+
* @returns The coerced array, or undefined if the value cannot be coerced.
|
|
67
|
+
*/
|
|
68
|
+
static array<T = unknown>(value: unknown): T[] | undefined;
|
|
63
69
|
/**
|
|
64
70
|
* Coerce the value to an object.
|
|
65
71
|
* @param value The value to coerce.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A fixed-capacity LFU cache with time-to-idle eviction.
|
|
3
|
+
*
|
|
4
|
+
* Entries are removed in two ways:
|
|
5
|
+
* - Capacity eviction: when the cache is full the least-frequently-used entry is removed first.
|
|
6
|
+
* Ties in frequency are broken by recency the least-recently-used entry among those with the
|
|
7
|
+
* minimum frequency is evicted.
|
|
8
|
+
* - TTI eviction: a background timer sweeps idle entries every ttiMs milliseconds.
|
|
9
|
+
* The timer only runs while there are entries; it stops automatically when the cache empties.
|
|
10
|
+
*
|
|
11
|
+
* `get` and `set` increment an entry's access frequency and reset its idle timer.
|
|
12
|
+
* `has` and `keys` are pure peeks they evict idle entries but do not affect frequency or TTI.
|
|
13
|
+
* Call `destroy` when the cache is no longer needed to stop the background timer.
|
|
14
|
+
*/
|
|
15
|
+
export declare class LfuCache<T> {
|
|
16
|
+
/**
|
|
17
|
+
* Runtime name for the class.
|
|
18
|
+
*/
|
|
19
|
+
static readonly CLASS_NAME: string;
|
|
20
|
+
/**
|
|
21
|
+
* Default capacity.
|
|
22
|
+
*/
|
|
23
|
+
static readonly DEFAULT_CAPACITY = 1000;
|
|
24
|
+
/**
|
|
25
|
+
* Default time-to-idle in milliseconds.
|
|
26
|
+
*/
|
|
27
|
+
static readonly DEFAULT_TTI_MS = 10000;
|
|
28
|
+
/**
|
|
29
|
+
* Create a new instance of LfuCache.
|
|
30
|
+
* @param options The cache options.
|
|
31
|
+
* @param options.capacity Maximum number of entries. Defaults to 1000. Must be a positive integer.
|
|
32
|
+
* @param options.ttiMs Time-to-idle in milliseconds. Defaults to 10000. Must be a positive integer.
|
|
33
|
+
* @param options.mutexTimeoutMs Maximum time in milliseconds to wait for getOrSet mutex acquisition.
|
|
34
|
+
* @throws ValidationError if capacity or ttiMs is not a positive integer.
|
|
35
|
+
*/
|
|
36
|
+
constructor(options?: {
|
|
37
|
+
capacity?: number;
|
|
38
|
+
ttiMs?: number;
|
|
39
|
+
mutexTimeoutMs?: number;
|
|
40
|
+
});
|
|
41
|
+
/**
|
|
42
|
+
* The number of entries currently held in the cache.
|
|
43
|
+
* @returns The number of entries in the cache.
|
|
44
|
+
*/
|
|
45
|
+
count(): number;
|
|
46
|
+
/**
|
|
47
|
+
* Get a value from the cache.
|
|
48
|
+
* Returns undefined if the key is absent or the entry has idled out.
|
|
49
|
+
* A successful hit increments the entry's frequency and resets its idle timer.
|
|
50
|
+
* @param key The key to retrieve.
|
|
51
|
+
* @returns The cached value, or undefined on a miss or idle eviction.
|
|
52
|
+
*/
|
|
53
|
+
get(key: string): T | undefined;
|
|
54
|
+
/**
|
|
55
|
+
* Store a value in the cache.
|
|
56
|
+
* If the key already exists its value and frequency are updated.
|
|
57
|
+
* When the cache is at capacity, idle entries are swept first; if it is still full the
|
|
58
|
+
* least-frequently-used entry is evicted (LRU among ties).
|
|
59
|
+
* @param key The key to store.
|
|
60
|
+
* @param value The value to cache.
|
|
61
|
+
*/
|
|
62
|
+
set(key: string, value: T): void;
|
|
63
|
+
/**
|
|
64
|
+
* Atomically get an existing value or create and store it once using an async factory.
|
|
65
|
+
* Concurrent calls for the same key are serialized via a mutex.
|
|
66
|
+
* @param key The key to get or create.
|
|
67
|
+
* @param valueFactory Async callback used to build a value when the key is absent.
|
|
68
|
+
* @returns The existing or newly created value.
|
|
69
|
+
*/
|
|
70
|
+
getOrSet(key: string, valueFactory: () => Promise<T>): Promise<T>;
|
|
71
|
+
/**
|
|
72
|
+
* Check whether a key exists in the cache and has not idled out.
|
|
73
|
+
* Idle entries are evicted on peek, but a live entry's frequency and TTI are not updated.
|
|
74
|
+
* @param key The key to test.
|
|
75
|
+
* @returns True if the key is present and not idle.
|
|
76
|
+
*/
|
|
77
|
+
has(key: string): boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Return all keys for entries that have not idled out.
|
|
80
|
+
* Idle entries encountered during iteration are evicted.
|
|
81
|
+
* Keys are returned in ascending frequency order; within the same frequency, LRU first.
|
|
82
|
+
* @returns An array of live keys ordered from least-frequently-used to most-frequently-used.
|
|
83
|
+
*/
|
|
84
|
+
keys(): string[];
|
|
85
|
+
/**
|
|
86
|
+
* Remove an entry from the cache.
|
|
87
|
+
* Cancels the background timer if the cache becomes empty.
|
|
88
|
+
* @param key The key to remove.
|
|
89
|
+
*/
|
|
90
|
+
delete(key: string): void;
|
|
91
|
+
/**
|
|
92
|
+
* Remove all entries from the cache and cancel the background timer.
|
|
93
|
+
*/
|
|
94
|
+
clear(): void;
|
|
95
|
+
/**
|
|
96
|
+
* Stop the background idle-sweep timer and release all entries.
|
|
97
|
+
* The cache must not be used after this call.
|
|
98
|
+
*/
|
|
99
|
+
destroy(): void;
|
|
100
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A fixed-capacity LRU cache with time-to-idle eviction.
|
|
3
|
+
*
|
|
4
|
+
* Entries are removed in two ways:
|
|
5
|
+
* - Capacity eviction: when the cache is full the least-recently-used entry is removed first.
|
|
6
|
+
* - TTI eviction: a background timer sweeps idle entries every ttiMs milliseconds.
|
|
7
|
+
* The timer only runs while there are entries; it stops automatically when the cache empties.
|
|
8
|
+
*
|
|
9
|
+
* `get` and `set` both update an entry's LRU position and reset its idle timer.
|
|
10
|
+
* `has` is a pure peek it evicts idle entries but does not refresh a live entry's TTI.
|
|
11
|
+
* Call `destroy` when the cache is no longer needed to stop the background timer.
|
|
12
|
+
*/
|
|
13
|
+
export declare class LruCache<T = unknown> {
|
|
14
|
+
/**
|
|
15
|
+
* Runtime name for the class.
|
|
16
|
+
*/
|
|
17
|
+
static readonly CLASS_NAME: string;
|
|
18
|
+
/**
|
|
19
|
+
* Default capacity.
|
|
20
|
+
*/
|
|
21
|
+
static readonly DEFAULT_CAPACITY = 1000;
|
|
22
|
+
/**
|
|
23
|
+
* Default time-to-idle in milliseconds.
|
|
24
|
+
*/
|
|
25
|
+
static readonly DEFAULT_TTI_MS = 10000;
|
|
26
|
+
/**
|
|
27
|
+
* Create a new instance of LruCache.
|
|
28
|
+
* @param options The cache options.
|
|
29
|
+
* @param options.capacity Maximum number of entries. Defaults to 1000. Must be a positive integer.
|
|
30
|
+
* @param options.ttiMs Time-to-idle in milliseconds. Defaults to 10000. Must be a positive integer.
|
|
31
|
+
* @param options.mutexTimeoutMs Maximum time in milliseconds to wait for getOrSet mutex acquisition.
|
|
32
|
+
* @throws ValidationError if capacity or ttiMs is not a positive integer.
|
|
33
|
+
*/
|
|
34
|
+
constructor(options?: {
|
|
35
|
+
capacity?: number;
|
|
36
|
+
ttiMs?: number;
|
|
37
|
+
mutexTimeoutMs?: number;
|
|
38
|
+
});
|
|
39
|
+
/**
|
|
40
|
+
* The number of entries currently held in the cache.
|
|
41
|
+
* @returns The number of entries in the cache.
|
|
42
|
+
*/
|
|
43
|
+
count(): number;
|
|
44
|
+
/**
|
|
45
|
+
* Get a value from the cache.
|
|
46
|
+
* Returns undefined if the key is absent or the entry has idled out.
|
|
47
|
+
* A successful hit resets the entry's idle timer and moves it to most-recently-used.
|
|
48
|
+
* @param key The key to retrieve.
|
|
49
|
+
* @returns The cached value, or undefined on a miss or idle eviction.
|
|
50
|
+
*/
|
|
51
|
+
get(key: string): T | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* Store a value in the cache.
|
|
54
|
+
* If the key already exists its value and idle timer are refreshed.
|
|
55
|
+
* When the cache is at capacity, idle entries are swept first; if it is still full the
|
|
56
|
+
* least-recently-used entry is evicted.
|
|
57
|
+
* @param key The key to store.
|
|
58
|
+
* @param value The value to cache.
|
|
59
|
+
*/
|
|
60
|
+
set(key: string, value: T): void;
|
|
61
|
+
/**
|
|
62
|
+
* Atomically get an existing value or create and store it once using an async factory.
|
|
63
|
+
* Concurrent calls for the same key are serialized via a mutex.
|
|
64
|
+
* @param key The key to get or create.
|
|
65
|
+
* @param valueFactory Async callback used to build a value when the key is absent.
|
|
66
|
+
* @returns The existing or newly created value.
|
|
67
|
+
*/
|
|
68
|
+
getOrSet(key: string, valueFactory: () => Promise<T>): Promise<T>;
|
|
69
|
+
/**
|
|
70
|
+
* Check whether a key exists in the cache and has not idled out.
|
|
71
|
+
* Idle entries are evicted on peek, but a live entry's TTI is not reset.
|
|
72
|
+
* @param key The key to test.
|
|
73
|
+
* @returns True if the key is present and not idle.
|
|
74
|
+
*/
|
|
75
|
+
has(key: string): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Return all keys for entries that have not idled out.
|
|
78
|
+
* Idle entries encountered during iteration are evicted.
|
|
79
|
+
* @returns An array of live keys in least-recently-used to most-recently-used order.
|
|
80
|
+
*/
|
|
81
|
+
keys(): string[];
|
|
82
|
+
/**
|
|
83
|
+
* Remove an entry from the cache.
|
|
84
|
+
* Cancels the background timer if the cache becomes empty.
|
|
85
|
+
* @param key The key to remove.
|
|
86
|
+
*/
|
|
87
|
+
delete(key: string): void;
|
|
88
|
+
/**
|
|
89
|
+
* Remove all entries from the cache and cancel the background timer.
|
|
90
|
+
*/
|
|
91
|
+
clear(): void;
|
|
92
|
+
/**
|
|
93
|
+
* Stop the background idle-sweep timer and release all entries.
|
|
94
|
+
* The cache must not be used after this call.
|
|
95
|
+
*/
|
|
96
|
+
destroy(): void;
|
|
97
|
+
}
|
package/docs/changelog.md
CHANGED
|
@@ -1,5 +1,79 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.9.2](https://github.com/iotaledger/twin-framework/compare/core-v0.9.2...core-v0.9.2) (2026-08-24)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* release to production ([b24cba1](https://github.com/iotaledger/twin-framework/commit/b24cba1b6a969278d638e632590602ec881e49fb))
|
|
9
|
+
* release to production ([787287d](https://github.com/iotaledger/twin-framework/commit/787287d06ea8319657401589d61fff369310c422))
|
|
10
|
+
* release to production ([53f4843](https://github.com/iotaledger/twin-framework/commit/53f484326b2851d7a506d2620db24c4a65cee7b3))
|
|
11
|
+
* release to production ([56cda4d](https://github.com/iotaledger/twin-framework/commit/56cda4da93e978c5be19ec7cfd421ae2a7fe4147))
|
|
12
|
+
* release to production ([f7c6586](https://github.com/iotaledger/twin-framework/commit/f7c6586f6976b903b647b4c5ac5ad9421e0c9051))
|
|
13
|
+
* release to production ([829d53d](https://github.com/iotaledger/twin-framework/commit/829d53d3953b1e1b40b0243c04cfdfd3842aac7b))
|
|
14
|
+
* release to production ([5cf3a76](https://github.com/iotaledger/twin-framework/commit/5cf3a76a09eff2e6414d0cba846c7c37400a11d6))
|
|
15
|
+
* release to production ([#330](https://github.com/iotaledger/twin-framework/issues/330)) ([d73f565](https://github.com/iotaledger/twin-framework/commit/d73f565588d156d23ef49b2a5718973756f7a696))
|
|
16
|
+
* release to production ([#382](https://github.com/iotaledger/twin-framework/issues/382)) ([bbed01a](https://github.com/iotaledger/twin-framework/commit/bbed01a605ee9724bda77a0f7feab249118c2d90))
|
|
17
|
+
* release to production ([#417](https://github.com/iotaledger/twin-framework/issues/417)) ([59727e7](https://github.com/iotaledger/twin-framework/commit/59727e73903a137310ca48fe469189cf29879cb9))
|
|
18
|
+
* release to production ([#459](https://github.com/iotaledger/twin-framework/issues/459)) ([e26e2d9](https://github.com/iotaledger/twin-framework/commit/e26e2d9a88767364c32494c45232033447b26e22))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Miscellaneous Chores
|
|
22
|
+
|
|
23
|
+
* release to production ([63cae24](https://github.com/iotaledger/twin-framework/commit/63cae2401f6c11f93b2a01260b665064e8bd28e0))
|
|
24
|
+
|
|
25
|
+
## [0.9.2-next.11](https://github.com/iotaledger/twin-framework/compare/core-v0.9.2-next.10...core-v0.9.2-next.11) (2026-08-20)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### Features
|
|
29
|
+
|
|
30
|
+
* coerce array ([#452](https://github.com/iotaledger/twin-framework/issues/452)) ([2019567](https://github.com/iotaledger/twin-framework/commit/20195673a65fe7179272ac84f37dc927218c128a))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
### Dependencies
|
|
34
|
+
|
|
35
|
+
* The following workspace dependencies were updated
|
|
36
|
+
* dependencies
|
|
37
|
+
* @twin.org/nameof bumped from 0.9.2-next.10 to 0.9.2-next.11
|
|
38
|
+
* devDependencies
|
|
39
|
+
* @twin.org/nameof-transformer bumped from 0.9.2-next.10 to 0.9.2-next.11
|
|
40
|
+
* @twin.org/nameof-vitest-plugin bumped from 0.9.2-next.10 to 0.9.2-next.11
|
|
41
|
+
|
|
42
|
+
## [0.9.2-next.10](https://github.com/iotaledger/twin-framework/compare/core-v0.9.2-next.9...core-v0.9.2-next.10) (2026-08-11)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
### Features
|
|
46
|
+
|
|
47
|
+
* add lru and lft cache ([#449](https://github.com/iotaledger/twin-framework/issues/449)) ([67d741e](https://github.com/iotaledger/twin-framework/commit/67d741eab3b89556e3c55053b5a473a4c1592fed))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
### Dependencies
|
|
51
|
+
|
|
52
|
+
* The following workspace dependencies were updated
|
|
53
|
+
* dependencies
|
|
54
|
+
* @twin.org/nameof bumped from 0.9.2-next.9 to 0.9.2-next.10
|
|
55
|
+
* devDependencies
|
|
56
|
+
* @twin.org/nameof-transformer bumped from 0.9.2-next.9 to 0.9.2-next.10
|
|
57
|
+
* @twin.org/nameof-vitest-plugin bumped from 0.9.2-next.9 to 0.9.2-next.10
|
|
58
|
+
|
|
59
|
+
## [0.9.2-next.9](https://github.com/iotaledger/twin-framework/compare/core-v0.9.2-next.8...core-v0.9.2-next.9) (2026-08-10)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
### Bug Fixes
|
|
63
|
+
|
|
64
|
+
* number coercion wrongly accepts alpha ([c04c1af](https://github.com/iotaledger/twin-framework/commit/c04c1af24697b6c92a180a71c0722c5673313360))
|
|
65
|
+
* number coercion wrongly accepts alpha ([dd5b193](https://github.com/iotaledger/twin-framework/commit/dd5b193c2501f6d30ca94aa0fc30a6edfe405e6f))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
### Dependencies
|
|
69
|
+
|
|
70
|
+
* The following workspace dependencies were updated
|
|
71
|
+
* dependencies
|
|
72
|
+
* @twin.org/nameof bumped from 0.9.2-next.8 to 0.9.2-next.9
|
|
73
|
+
* devDependencies
|
|
74
|
+
* @twin.org/nameof-transformer bumped from 0.9.2-next.8 to 0.9.2-next.9
|
|
75
|
+
* @twin.org/nameof-vitest-plugin bumped from 0.9.2-next.8 to 0.9.2-next.9
|
|
76
|
+
|
|
3
77
|
## [0.9.2-next.8](https://github.com/iotaledger/twin-framework/compare/core-v0.9.2-next.7...core-v0.9.2-next.8) (2026-08-10)
|
|
4
78
|
|
|
5
79
|
|
|
@@ -214,6 +214,34 @@ The duration object, or undefined if the value cannot be coerced.
|
|
|
214
214
|
|
|
215
215
|
***
|
|
216
216
|
|
|
217
|
+
### array() {#array}
|
|
218
|
+
|
|
219
|
+
> `static` **array**\<`T`\>(`value`): `T`[] \| `undefined`
|
|
220
|
+
|
|
221
|
+
Coerce the value to an array.
|
|
222
|
+
|
|
223
|
+
#### Type Parameters
|
|
224
|
+
|
|
225
|
+
##### T
|
|
226
|
+
|
|
227
|
+
`T` = `unknown`
|
|
228
|
+
|
|
229
|
+
#### Parameters
|
|
230
|
+
|
|
231
|
+
##### value
|
|
232
|
+
|
|
233
|
+
`unknown`
|
|
234
|
+
|
|
235
|
+
The value to coerce.
|
|
236
|
+
|
|
237
|
+
#### Returns
|
|
238
|
+
|
|
239
|
+
`T`[] \| `undefined`
|
|
240
|
+
|
|
241
|
+
The coerced array, or undefined if the value cannot be coerced.
|
|
242
|
+
|
|
243
|
+
***
|
|
244
|
+
|
|
217
245
|
### object() {#object}
|
|
218
246
|
|
|
219
247
|
> `static` **object**\<`T`\>(`value`): `T` \| `undefined`
|