@adviser/cement 0.4.63 → 0.4.64-dev
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/cjs/index.cjs +1 -0
- package/cjs/index.cjs.map +1 -1
- package/cjs/index.d.ts +1 -0
- package/cjs/index.d.ts.map +1 -1
- package/cjs/keyed-ng.cjs +93 -0
- package/cjs/keyed-ng.cjs.map +1 -0
- package/cjs/keyed-ng.d.ts +51 -0
- package/cjs/keyed-ng.d.ts.map +1 -0
- package/cjs/keyed-ng.test.cjs +151 -0
- package/cjs/keyed-ng.test.cjs.map +1 -0
- package/cjs/keyed-ng.test.d.ts +2 -0
- package/cjs/keyed-ng.test.d.ts.map +1 -0
- package/cjs/lru-map-set.cjs +2 -1
- package/cjs/lru-map-set.cjs.map +1 -1
- package/cjs/lru-map-set.d.ts +4 -1
- package/cjs/lru-map-set.d.ts.map +1 -1
- package/cjs/resolve-once.cjs +67 -104
- package/cjs/resolve-once.cjs.map +1 -1
- package/cjs/resolve-once.d.ts +26 -62
- package/cjs/resolve-once.d.ts.map +1 -1
- package/cjs/resolve-once.test.cjs +119 -31
- package/cjs/resolve-once.test.cjs.map +1 -1
- package/cjs/version.cjs +1 -1
- package/cjs/version.cjs.map +1 -1
- package/cjs/version.d.ts.map +1 -1
- package/deno.json +1 -1
- package/esm/index.d.ts +1 -0
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +1 -0
- package/esm/index.js.map +1 -1
- package/esm/keyed-ng.d.ts +51 -0
- package/esm/keyed-ng.d.ts.map +1 -0
- package/esm/keyed-ng.js +89 -0
- package/esm/keyed-ng.js.map +1 -0
- package/esm/keyed-ng.test.d.ts +2 -0
- package/esm/keyed-ng.test.d.ts.map +1 -0
- package/esm/keyed-ng.test.js +149 -0
- package/esm/keyed-ng.test.js.map +1 -0
- package/esm/lru-map-set.d.ts +4 -1
- package/esm/lru-map-set.d.ts.map +1 -1
- package/esm/lru-map-set.js +2 -1
- package/esm/lru-map-set.js.map +1 -1
- package/esm/resolve-once.d.ts +26 -62
- package/esm/resolve-once.d.ts.map +1 -1
- package/esm/resolve-once.js +66 -102
- package/esm/resolve-once.js.map +1 -1
- package/esm/resolve-once.test.js +119 -31
- package/esm/resolve-once.test.js.map +1 -1
- package/esm/version.d.ts.map +1 -1
- package/esm/version.js +1 -1
- package/esm/version.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +1 -0
- package/src/keyed-ng.ts +628 -0
- package/src/lru-map-set.ts +6 -2
- package/src/resolve-once.ts +281 -324
- package/ts/cjs/index.d.ts +1 -0
- package/ts/cjs/index.d.ts.map +1 -1
- package/ts/cjs/index.js +1 -0
- package/ts/cjs/index.js.map +1 -1
- package/ts/cjs/keyed-ng.d.ts +51 -0
- package/ts/cjs/keyed-ng.d.ts.map +1 -0
- package/ts/cjs/keyed-ng.js +93 -0
- package/ts/cjs/keyed-ng.js.map +1 -0
- package/ts/cjs/keyed-ng.test.d.ts +2 -0
- package/ts/cjs/keyed-ng.test.d.ts.map +1 -0
- package/ts/cjs/keyed-ng.test.js +151 -0
- package/ts/cjs/keyed-ng.test.js.map +1 -0
- package/ts/cjs/lru-map-set.d.ts +4 -1
- package/ts/cjs/lru-map-set.d.ts.map +1 -1
- package/ts/cjs/lru-map-set.js +2 -1
- package/ts/cjs/lru-map-set.js.map +1 -1
- package/ts/cjs/resolve-once.d.ts +26 -62
- package/ts/cjs/resolve-once.d.ts.map +1 -1
- package/ts/cjs/resolve-once.js +67 -104
- package/ts/cjs/resolve-once.js.map +1 -1
- package/ts/cjs/resolve-once.test.js +119 -31
- package/ts/cjs/resolve-once.test.js.map +1 -1
- package/ts/cjs/version.d.ts.map +1 -1
- package/ts/cjs/version.js +1 -1
- package/ts/cjs/version.js.map +1 -1
- package/ts/esm/index.d.ts +1 -0
- package/ts/esm/index.d.ts.map +1 -1
- package/ts/esm/index.js +1 -0
- package/ts/esm/index.js.map +1 -1
- package/ts/esm/keyed-ng.d.ts +51 -0
- package/ts/esm/keyed-ng.d.ts.map +1 -0
- package/ts/esm/keyed-ng.js +89 -0
- package/ts/esm/keyed-ng.js.map +1 -0
- package/ts/esm/keyed-ng.test.d.ts +2 -0
- package/ts/esm/keyed-ng.test.d.ts.map +1 -0
- package/ts/esm/keyed-ng.test.js +149 -0
- package/ts/esm/keyed-ng.test.js.map +1 -0
- package/ts/esm/lru-map-set.d.ts +4 -1
- package/ts/esm/lru-map-set.d.ts.map +1 -1
- package/ts/esm/lru-map-set.js +2 -1
- package/ts/esm/lru-map-set.js.map +1 -1
- package/ts/esm/resolve-once.d.ts +26 -62
- package/ts/esm/resolve-once.d.ts.map +1 -1
- package/ts/esm/resolve-once.js +66 -102
- package/ts/esm/resolve-once.js.map +1 -1
- package/ts/esm/resolve-once.test.js +119 -31
- package/ts/esm/resolve-once.test.js.map +1 -1
- package/ts/esm/version.d.ts.map +1 -1
- package/ts/esm/version.js +1 -1
- package/ts/esm/version.js.map +1 -1
package/src/keyed-ng.ts
ADDED
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic keyed factory and collection management with LRU caching support.
|
|
3
|
+
*
|
|
4
|
+
* This module provides a flexible system for managing keyed collections where values
|
|
5
|
+
* are created on-demand through factory functions. The KeyedNg class serves as a base
|
|
6
|
+
* for creating type-safe keyed collections with built-in LRU eviction support.
|
|
7
|
+
*
|
|
8
|
+
* ## Core Concepts
|
|
9
|
+
*
|
|
10
|
+
* - **KeyedNgItem**: A structured container holding the key, value, and context
|
|
11
|
+
* - **KeyedNg**: Base class for managing keyed collections with factory-based value creation
|
|
12
|
+
* - **KeyedIf**: Interface defining the contract for keyed collection implementations
|
|
13
|
+
*
|
|
14
|
+
* ## Features
|
|
15
|
+
*
|
|
16
|
+
* - Factory-based value creation on first access
|
|
17
|
+
* - Optional LRU caching with automatic eviction
|
|
18
|
+
* - Type-safe key-to-string conversion
|
|
19
|
+
* - Context passing for value creation
|
|
20
|
+
* - Event callbacks for set/delete operations
|
|
21
|
+
*
|
|
22
|
+
* @module keyed-ng
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { LRUParam, LRUMap, UnregFn } from "./lru-map-set.js";
|
|
26
|
+
import { toSortedObject } from "./utils/sorted-object.js";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Interface defining the contract for keyed collection implementations.
|
|
30
|
+
*
|
|
31
|
+
* Provides a common interface for managing keyed collections with lifecycle callbacks,
|
|
32
|
+
* LRU support, and flexible key handling. Implementations should provide type-safe
|
|
33
|
+
* access to values indexed by keys.
|
|
34
|
+
*
|
|
35
|
+
* @template ITEM - The item type stored in the collection (typically KeyedNgItem)
|
|
36
|
+
* @template V - The value type extracted from items
|
|
37
|
+
* @template K - The key type (defaults to string)
|
|
38
|
+
* @template CTX - The context type (defaults to unknown)
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* class MyKeyed implements KeyedIf<MyItem, MyValue, string, MyContext> {
|
|
43
|
+
* // Implementation...
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export interface KeyedIf<ITEM, V, K = string, CTX = unknown> {
|
|
48
|
+
/**
|
|
49
|
+
* Registers a callback that fires when a new entry is added to the map.
|
|
50
|
+
*
|
|
51
|
+
* @param fn - Callback function receiving key and value
|
|
52
|
+
* @returns Unregister function to remove the callback
|
|
53
|
+
*/
|
|
54
|
+
onSet(fn: (value: ITEM) => void): UnregFn;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Registers a callback that fires when an entry is deleted from the map.
|
|
58
|
+
*
|
|
59
|
+
* @param fn - Callback function receiving key and value
|
|
60
|
+
* @returns Unregister function to remove the callback
|
|
61
|
+
*/
|
|
62
|
+
onDelete(fn: (value: ITEM) => void): UnregFn;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Updates the LRU parameters of the underlying map.
|
|
66
|
+
*
|
|
67
|
+
* @param params - New parameters to apply
|
|
68
|
+
*/
|
|
69
|
+
setParam(params: Partial<KeyedNgOptions<K, ITEM, CTX>>): void;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Async variant of get() that accepts a function returning a promise for the key.
|
|
73
|
+
*
|
|
74
|
+
* @param key - Function that returns a promise resolving to the key
|
|
75
|
+
* @returns Promise resolving to the value
|
|
76
|
+
*/
|
|
77
|
+
asyncGet(key: () => Promise<K>): Promise<V>;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Gets or creates a value for the given key.
|
|
81
|
+
*
|
|
82
|
+
* If the key doesn't exist, creates a new instance using the factory function.
|
|
83
|
+
*
|
|
84
|
+
* @param key - The key or function returning the key
|
|
85
|
+
* @returns The value associated with the key
|
|
86
|
+
*/
|
|
87
|
+
get(key: K | (() => K)): V;
|
|
88
|
+
|
|
89
|
+
getItem(key: K, ctx?: unknown): ITEM;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Checks if a key exists in the map.
|
|
93
|
+
*
|
|
94
|
+
* @param key - The key or function returning the key
|
|
95
|
+
* @returns True if the key exists
|
|
96
|
+
*/
|
|
97
|
+
has(key: K | (() => K)): boolean;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Deletes an entry from the map.
|
|
101
|
+
*
|
|
102
|
+
* @param key - The key to delete
|
|
103
|
+
*/
|
|
104
|
+
delete(key: K): void;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Returns all values in the map.
|
|
108
|
+
*
|
|
109
|
+
* @returns Array of all values
|
|
110
|
+
*/
|
|
111
|
+
values(): ITEM[];
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Returns all keys in the map.
|
|
115
|
+
*
|
|
116
|
+
* @returns Array of all keys
|
|
117
|
+
*/
|
|
118
|
+
keys(): K[];
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Iterates over all entries in the map.
|
|
122
|
+
*
|
|
123
|
+
* @yields Key-value pairs
|
|
124
|
+
*/
|
|
125
|
+
forEach(fn: (v: ITEM, idx: number) => void): void;
|
|
126
|
+
|
|
127
|
+
entries(): Iterable<[ITEM, number]>;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Utility type that makes the 'value' property of a type writable.
|
|
132
|
+
*
|
|
133
|
+
* This is useful for internal implementations that need to construct items
|
|
134
|
+
* with initially undefined values that are later populated.
|
|
135
|
+
*
|
|
136
|
+
* @template T - The type to make writable
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```typescript
|
|
140
|
+
* type ReadOnly = { readonly value: number };
|
|
141
|
+
* type Writable = WritableValue<ReadOnly>;
|
|
142
|
+
* // Result: { value: number }
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
export type WritableValue<T> = Omit<T, "value"> & { value: T extends { value: infer V } ? V : unknown };
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Configuration options for creating a KeyedNg instance.
|
|
149
|
+
*
|
|
150
|
+
* @template K - The key type
|
|
151
|
+
* @template V - The value type
|
|
152
|
+
* @template CTX - The context type
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```typescript
|
|
156
|
+
* const options: KeyedNgOptions<string, MyValue, MyContext> = {
|
|
157
|
+
* createValue: (item) => new MyValue(item.givenKey),
|
|
158
|
+
* key2string: (key) => key.toLowerCase(),
|
|
159
|
+
* ctx: { config: 'default' },
|
|
160
|
+
* lru: { max: 100 }
|
|
161
|
+
* };
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
export interface KeyedNgOptions<K, V, CTX> {
|
|
165
|
+
/**
|
|
166
|
+
* Factory function that creates a value for a given key.
|
|
167
|
+
*
|
|
168
|
+
* Called once per unique key when the value is first accessed.
|
|
169
|
+
* The function receives a KeyedNgItem with value initially undefined.
|
|
170
|
+
*
|
|
171
|
+
* @param keyItem - The item containing key, context, and placeholder for value
|
|
172
|
+
* @returns The created value instance
|
|
173
|
+
*/
|
|
174
|
+
readonly createValue: (keyItem: KeyedNgItem<K, V, CTX>) => V;
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Optional function to convert keys to strings for internal storage.
|
|
178
|
+
*
|
|
179
|
+
* If not provided, uses default conversion:
|
|
180
|
+
* - strings: as-is
|
|
181
|
+
* - numbers: toString()
|
|
182
|
+
* - booleans: "true" or "false"
|
|
183
|
+
* - objects: JSON.stringify with sorted keys
|
|
184
|
+
*
|
|
185
|
+
* @param key - The key to convert
|
|
186
|
+
* @returns A string representation of the key
|
|
187
|
+
*/
|
|
188
|
+
readonly key2string?: (key: K) => string;
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Optional default context passed to value creation.
|
|
192
|
+
*
|
|
193
|
+
* Can be overridden on a per-get basis. Defaults to empty object.
|
|
194
|
+
*/
|
|
195
|
+
readonly ctx?: CTX;
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Optional LRU cache configuration.
|
|
199
|
+
*
|
|
200
|
+
* When provided, the collection will automatically evict least-recently-used
|
|
201
|
+
* items when size limits are exceeded.
|
|
202
|
+
*/
|
|
203
|
+
readonly lru?: Partial<LRUParam<unknown, string>>;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* A structured item containing a key, value, and context.
|
|
208
|
+
*
|
|
209
|
+
* This is the fundamental container type used throughout the keyed-ng system.
|
|
210
|
+
* Items are created by KeyedNg and passed to factory functions and callbacks.
|
|
211
|
+
*
|
|
212
|
+
* @template K - The key type
|
|
213
|
+
* @template V - The value type
|
|
214
|
+
* @template CTX - The context type
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* ```typescript
|
|
218
|
+
* const item: KeyedNgItem<string, number, MyContext> = {
|
|
219
|
+
* refKey: "user-123",
|
|
220
|
+
* givenKey: "user-123",
|
|
221
|
+
* value: 42,
|
|
222
|
+
* ctx: { config: 'production' }
|
|
223
|
+
* };
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
226
|
+
export interface KeyedNgItem<K, V, CTX> {
|
|
227
|
+
/**
|
|
228
|
+
* The normalized string key used for internal storage.
|
|
229
|
+
*
|
|
230
|
+
* This is the result of applying key2string to the givenKey.
|
|
231
|
+
*/
|
|
232
|
+
readonly refKey: string;
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* The original key as provided by the caller.
|
|
236
|
+
*
|
|
237
|
+
* This preserves the original key type and value, even if it's
|
|
238
|
+
* been converted to a string for storage.
|
|
239
|
+
*/
|
|
240
|
+
readonly givenKey: K;
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* The value associated with this key.
|
|
244
|
+
*
|
|
245
|
+
* Created by the factory function on first access.
|
|
246
|
+
*/
|
|
247
|
+
readonly value: V;
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* The context provided when this item was created.
|
|
251
|
+
*
|
|
252
|
+
* Can be used to pass configuration or state to value factories.
|
|
253
|
+
*/
|
|
254
|
+
readonly ctx: CTX;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Type helper that represents a KeyedNgItem without its value property.
|
|
259
|
+
*
|
|
260
|
+
* Useful for contexts where the value is not yet available or not needed,
|
|
261
|
+
* such as in callback signatures.
|
|
262
|
+
*
|
|
263
|
+
* @template K - The key type
|
|
264
|
+
* @template CTX - The context type
|
|
265
|
+
*
|
|
266
|
+
* @example
|
|
267
|
+
* ```typescript
|
|
268
|
+
* function process(item: KeyedNgItemWithoutValue<string, MyContext>) {
|
|
269
|
+
* console.log(item.refKey, item.givenKey, item.ctx);
|
|
270
|
+
* // item.value is not available
|
|
271
|
+
* }
|
|
272
|
+
* ```
|
|
273
|
+
*/
|
|
274
|
+
export type KeyedNgItemWithoutValue<K, CTX> = Omit<KeyedNgItem<K, never, CTX>, "value">;
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Generic keyed factory and collection with LRU caching support.
|
|
278
|
+
*
|
|
279
|
+
* KeyedNg manages a map of values indexed by keys, where values are created
|
|
280
|
+
* on-demand using a factory function. It provides type-safe key handling,
|
|
281
|
+
* optional LRU eviction, and lifecycle callbacks for monitoring changes.
|
|
282
|
+
*
|
|
283
|
+
* This class serves as the foundation for more specialized keyed collections
|
|
284
|
+
* like KeyedResolvOnce and KeyedResolvSeq.
|
|
285
|
+
*
|
|
286
|
+
* @template K - The key type (can be any type with string conversion)
|
|
287
|
+
* @template V - The value type created by the factory
|
|
288
|
+
* @template CTX - The context type passed to factory and callbacks
|
|
289
|
+
*
|
|
290
|
+
* @example
|
|
291
|
+
* ```typescript
|
|
292
|
+
* // Simple factory for objects
|
|
293
|
+
* const cache = new KeyedNg({
|
|
294
|
+
* createValue: (item) => ({
|
|
295
|
+
* id: item.refKey,
|
|
296
|
+
* created: Date.now()
|
|
297
|
+
* }),
|
|
298
|
+
* lru: { max: 100 }
|
|
299
|
+
* });
|
|
300
|
+
*
|
|
301
|
+
* const obj1 = cache.get('key1');
|
|
302
|
+
* const obj2 = cache.get('key2');
|
|
303
|
+
* ```
|
|
304
|
+
*
|
|
305
|
+
* @example
|
|
306
|
+
* ```typescript
|
|
307
|
+
* // With complex keys and context
|
|
308
|
+
* interface UserKey { org: string; userId: string; }
|
|
309
|
+
* interface UserContext { apiKey: string; }
|
|
310
|
+
*
|
|
311
|
+
* const users = new KeyedNg<UserKey, User, UserContext>({
|
|
312
|
+
* createValue: (item) => new User(item.givenKey, item.ctx),
|
|
313
|
+
* key2string: (key) => `${key.org}:${key.userId}`,
|
|
314
|
+
* ctx: { apiKey: 'default' }
|
|
315
|
+
* });
|
|
316
|
+
*
|
|
317
|
+
* const user = users.get(
|
|
318
|
+
* { org: 'acme', userId: '123' },
|
|
319
|
+
* { apiKey: 'custom' }
|
|
320
|
+
* );
|
|
321
|
+
* ```
|
|
322
|
+
*/
|
|
323
|
+
export class KeyedNg<K, V, CTX> implements KeyedIf<KeyedNgItem<K, V, CTX>, V, K, CTX> {
|
|
324
|
+
/**
|
|
325
|
+
* The resolved options with defaults applied.
|
|
326
|
+
* @internal
|
|
327
|
+
*/
|
|
328
|
+
readonly opts: Required<Omit<KeyedNgOptions<K, V, CTX>, "lru">>;
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Internal LRU map for storing items.
|
|
332
|
+
* @internal
|
|
333
|
+
*/
|
|
334
|
+
readonly #map: LRUMap<string, KeyedNgItem<K, V, CTX>>;
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Creates a new KeyedNg instance.
|
|
338
|
+
*
|
|
339
|
+
* @param opts - Configuration options
|
|
340
|
+
*/
|
|
341
|
+
constructor(opts: KeyedNgOptions<K, V, CTX>) {
|
|
342
|
+
this.opts = {
|
|
343
|
+
...opts,
|
|
344
|
+
key2string:
|
|
345
|
+
opts.key2string ??
|
|
346
|
+
((key: K): string => {
|
|
347
|
+
if (typeof key === "string") {
|
|
348
|
+
return key;
|
|
349
|
+
}
|
|
350
|
+
if (typeof key === "number") {
|
|
351
|
+
return key.toString();
|
|
352
|
+
}
|
|
353
|
+
if (typeof key === "boolean") {
|
|
354
|
+
return key ? "true" : "false";
|
|
355
|
+
}
|
|
356
|
+
return JSON.stringify(toSortedObject(key as unknown as Record<string, unknown>));
|
|
357
|
+
}),
|
|
358
|
+
ctx: opts.ctx ?? ({} as CTX),
|
|
359
|
+
};
|
|
360
|
+
this.#map = new LRUMap(opts.lru as LRUParam<KeyedNgItem<K, V, CTX>, string>);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Registers a callback that fires when a new item is added to the collection.
|
|
365
|
+
*
|
|
366
|
+
* The callback is invoked after the item is created and stored. Multiple
|
|
367
|
+
* callbacks can be registered.
|
|
368
|
+
*
|
|
369
|
+
* @param fn - Callback function receiving the new item
|
|
370
|
+
* @returns Unregister function to remove the callback
|
|
371
|
+
*
|
|
372
|
+
* @example
|
|
373
|
+
* ```typescript
|
|
374
|
+
* const unregister = keyed.onSet((item) => {
|
|
375
|
+
* console.log('Added:', item.givenKey, item.value);
|
|
376
|
+
* });
|
|
377
|
+
*
|
|
378
|
+
* // Later: remove the callback
|
|
379
|
+
* unregister();
|
|
380
|
+
* ```
|
|
381
|
+
*/
|
|
382
|
+
onSet(fn: (value: KeyedNgItem<K, V, CTX>) => void): UnregFn {
|
|
383
|
+
return this.#map.onSet((_keyStr: string, item: KeyedNgItem<K, V, CTX>) => {
|
|
384
|
+
fn(item);
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Registers a callback that fires when an item is deleted from the collection.
|
|
390
|
+
*
|
|
391
|
+
* The callback is invoked before the item is removed. This includes both
|
|
392
|
+
* explicit deletions and LRU evictions.
|
|
393
|
+
*
|
|
394
|
+
* @param fn - Callback function receiving the deleted item
|
|
395
|
+
* @returns Unregister function to remove the callback
|
|
396
|
+
*
|
|
397
|
+
* @example
|
|
398
|
+
* ```typescript
|
|
399
|
+
* const unregister = keyed.onDelete((item) => {
|
|
400
|
+
* console.log('Removed:', item.givenKey);
|
|
401
|
+
* item.value.cleanup?.(); // Perform cleanup if needed
|
|
402
|
+
* });
|
|
403
|
+
* ```
|
|
404
|
+
*/
|
|
405
|
+
onDelete(fn: (value: KeyedNgItem<K, V, CTX>) => void): UnregFn {
|
|
406
|
+
return this.#map.onDelete((_keyStr: string, item: KeyedNgItem<K, V, CTX>) => {
|
|
407
|
+
fn(item);
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Updates the LRU parameters of the underlying collection.
|
|
413
|
+
*
|
|
414
|
+
* Allows dynamic adjustment of caching behavior without recreating
|
|
415
|
+
* the entire collection.
|
|
416
|
+
*
|
|
417
|
+
* @param params - New parameters to apply (partial update)
|
|
418
|
+
*
|
|
419
|
+
* @example
|
|
420
|
+
* ```typescript
|
|
421
|
+
* keyed.setParam({ lru: { max: 200 } });
|
|
422
|
+
* ```
|
|
423
|
+
*/
|
|
424
|
+
setParam(params: Partial<KeyedNgOptions<K, KeyedNgItem<K, V, CTX>, CTX>>): void {
|
|
425
|
+
this.#map.setParam(params.lru as LRUParam<KeyedNgItem<K, V, CTX>, string>);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Asynchronously gets or creates a value for a key resolved from a promise.
|
|
430
|
+
*
|
|
431
|
+
* Useful when the key itself needs to be computed asynchronously, such as
|
|
432
|
+
* from a database lookup or API call.
|
|
433
|
+
*
|
|
434
|
+
* @param key - Function returning a promise that resolves to the key
|
|
435
|
+
* @returns Promise resolving to the value
|
|
436
|
+
*
|
|
437
|
+
* @example
|
|
438
|
+
* ```typescript
|
|
439
|
+
* const value = await keyed.asyncGet(async () => {
|
|
440
|
+
* const id = await fetchUserId();
|
|
441
|
+
* return id;
|
|
442
|
+
* });
|
|
443
|
+
* ```
|
|
444
|
+
*/
|
|
445
|
+
asyncGet(key: () => Promise<K>): Promise<V> {
|
|
446
|
+
return key().then((k) => this.get(k));
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Checks if a key exists in the collection.
|
|
451
|
+
*
|
|
452
|
+
* Does not create a new value if the key doesn't exist.
|
|
453
|
+
*
|
|
454
|
+
* @param keyOfFnKey - The key or function returning the key
|
|
455
|
+
* @returns True if the key exists in the collection
|
|
456
|
+
*
|
|
457
|
+
* @example
|
|
458
|
+
* ```typescript
|
|
459
|
+
* if (keyed.has('myKey')) {
|
|
460
|
+
* console.log('Key exists');
|
|
461
|
+
* }
|
|
462
|
+
*
|
|
463
|
+
* // With function
|
|
464
|
+
* if (keyed.has(() => computeKey())) {
|
|
465
|
+
* console.log('Computed key exists');
|
|
466
|
+
* }
|
|
467
|
+
* ```
|
|
468
|
+
*/
|
|
469
|
+
has(keyOfFnKey: K | (() => K)): boolean {
|
|
470
|
+
if (typeof keyOfFnKey === "function") {
|
|
471
|
+
keyOfFnKey = (keyOfFnKey as () => K)();
|
|
472
|
+
}
|
|
473
|
+
return this.#map.has(this.opts.key2string(keyOfFnKey));
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Deletes an item from the collection.
|
|
478
|
+
*
|
|
479
|
+
* Triggers onDelete callbacks before removal. The item will need to be
|
|
480
|
+
* recreated if accessed again.
|
|
481
|
+
*
|
|
482
|
+
* @param key - The key to delete
|
|
483
|
+
*
|
|
484
|
+
* @example
|
|
485
|
+
* ```typescript
|
|
486
|
+
* keyed.delete('myKey');
|
|
487
|
+
* ```
|
|
488
|
+
*/
|
|
489
|
+
delete(key: K): void {
|
|
490
|
+
this.#map.delete(this.opts.key2string(key));
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Returns all keys currently in the collection.
|
|
495
|
+
*
|
|
496
|
+
* Returns the original keys (givenKey), not the normalized string versions.
|
|
497
|
+
*
|
|
498
|
+
* @returns Array of all keys
|
|
499
|
+
*
|
|
500
|
+
* @example
|
|
501
|
+
* ```typescript
|
|
502
|
+
* const keys = keyed.keys();
|
|
503
|
+
* console.log(keys); // ['key1', 'key2', 'key3']
|
|
504
|
+
* ```
|
|
505
|
+
*/
|
|
506
|
+
keys(): K[] {
|
|
507
|
+
return Array.from(this.#map.entries()).map(([_, item]) => item.givenKey);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Iterates over all items in the collection.
|
|
512
|
+
*
|
|
513
|
+
* The callback receives each item and its index.
|
|
514
|
+
*
|
|
515
|
+
* @param fn - Callback function receiving item and index
|
|
516
|
+
*
|
|
517
|
+
* @example
|
|
518
|
+
* ```typescript
|
|
519
|
+
* keyed.forEach((item, idx) => {
|
|
520
|
+
* console.log(idx, item.givenKey, item.value);
|
|
521
|
+
* });
|
|
522
|
+
* ```
|
|
523
|
+
*/
|
|
524
|
+
forEach(fn: (v: KeyedNgItem<K, V, CTX>, idx: number) => void): void {
|
|
525
|
+
return this.#map.forEach((item, _, ctx) => {
|
|
526
|
+
fn(item, ctx.idx);
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Returns an iterable of all items with their indices.
|
|
532
|
+
*
|
|
533
|
+
* @returns Iterable of [item, index] pairs
|
|
534
|
+
*
|
|
535
|
+
* @example
|
|
536
|
+
* ```typescript
|
|
537
|
+
* for (const [item, idx] of keyed.entries()) {
|
|
538
|
+
* console.log(idx, item.givenKey, item.value);
|
|
539
|
+
* }
|
|
540
|
+
* ```
|
|
541
|
+
*/
|
|
542
|
+
entries(): Iterable<[KeyedNgItem<K, V, CTX>, number]> {
|
|
543
|
+
let idx = 0;
|
|
544
|
+
return Array.from(this.#map.entries()).map(([_, item]) => [item, idx++]);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Gets or creates the full KeyedNgItem for a given key.
|
|
549
|
+
*
|
|
550
|
+
* Returns the complete item structure including key, value, and context.
|
|
551
|
+
* If the key doesn't exist, creates it using the factory function.
|
|
552
|
+
*
|
|
553
|
+
* @param key - The key to get
|
|
554
|
+
* @param ctx - Optional context override for this get operation
|
|
555
|
+
* @returns The complete KeyedNgItem
|
|
556
|
+
*
|
|
557
|
+
* @example
|
|
558
|
+
* ```typescript
|
|
559
|
+
* const item = keyed.getItem('myKey', { custom: 'context' });
|
|
560
|
+
* console.log(item.refKey, item.givenKey, item.value, item.ctx);
|
|
561
|
+
* ```
|
|
562
|
+
*/
|
|
563
|
+
getItem(key: K, ctx?: CTX): KeyedNgItem<K, V, CTX> {
|
|
564
|
+
const keyStr = this.opts.key2string(key);
|
|
565
|
+
let item = this.#map.get(keyStr);
|
|
566
|
+
if (!item) {
|
|
567
|
+
item = {
|
|
568
|
+
refKey: keyStr,
|
|
569
|
+
givenKey: key,
|
|
570
|
+
ctx: ctx ?? this.opts.ctx,
|
|
571
|
+
value: undefined as unknown as V,
|
|
572
|
+
};
|
|
573
|
+
(item as { value: V }).value = this.opts.createValue(item);
|
|
574
|
+
this.#map.set(keyStr, item);
|
|
575
|
+
}
|
|
576
|
+
return item;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Gets or creates a value for the given key.
|
|
581
|
+
*
|
|
582
|
+
* This is the primary method for accessing values. If the key doesn't exist,
|
|
583
|
+
* the factory function is called to create the value. Subsequent calls with
|
|
584
|
+
* the same key return the cached value.
|
|
585
|
+
*
|
|
586
|
+
* @param key - The key or function returning the key
|
|
587
|
+
* @param ctx - Optional context override for this get operation
|
|
588
|
+
* @returns The value associated with the key
|
|
589
|
+
*
|
|
590
|
+
* @example
|
|
591
|
+
* ```typescript
|
|
592
|
+
* // Simple key
|
|
593
|
+
* const value1 = keyed.get('myKey');
|
|
594
|
+
*
|
|
595
|
+
* // With function
|
|
596
|
+
* const value2 = keyed.get(() => computeKey());
|
|
597
|
+
*
|
|
598
|
+
* // With custom context
|
|
599
|
+
* const value3 = keyed.get('myKey', { custom: 'context' });
|
|
600
|
+
* ```
|
|
601
|
+
*/
|
|
602
|
+
get(key: K | (() => K), ctx?: CTX): V {
|
|
603
|
+
if (typeof key === "function") {
|
|
604
|
+
key = (key as () => K)();
|
|
605
|
+
}
|
|
606
|
+
const item = this.getItem(key, ctx);
|
|
607
|
+
return item.value;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Returns all items currently in the collection.
|
|
612
|
+
*
|
|
613
|
+
* Returns complete KeyedNgItem objects, not just the values.
|
|
614
|
+
*
|
|
615
|
+
* @returns Array of all items
|
|
616
|
+
*
|
|
617
|
+
* @example
|
|
618
|
+
* ```typescript
|
|
619
|
+
* const items = keyed.values();
|
|
620
|
+
* items.forEach(item => {
|
|
621
|
+
* console.log(item.givenKey, item.value);
|
|
622
|
+
* });
|
|
623
|
+
* ```
|
|
624
|
+
*/
|
|
625
|
+
values(): KeyedNgItem<K, V, CTX>[] {
|
|
626
|
+
return Array.from(this.#map.entries()).map(([_, item]) => item);
|
|
627
|
+
}
|
|
628
|
+
}
|
package/src/lru-map-set.ts
CHANGED
|
@@ -70,6 +70,8 @@ export class LRUSet<T> {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
export type LRUWithIdx<T> = T & { readonly idx: number };
|
|
74
|
+
|
|
73
75
|
export interface LRUCtx<T, K> {
|
|
74
76
|
readonly update: boolean;
|
|
75
77
|
readonly ref: LRUMap<K, T>;
|
|
@@ -287,9 +289,11 @@ export class LRUMap<K, V> {
|
|
|
287
289
|
this._map.clear();
|
|
288
290
|
}
|
|
289
291
|
|
|
290
|
-
forEach(fn: (value: V, key: K, ctx: LRUCtx<V, K
|
|
292
|
+
forEach(fn: (value: V, key: K, ctx: LRUWithIdx<LRUCtx<V, K>>) => void): void {
|
|
293
|
+
let idx = 0;
|
|
291
294
|
this._map.forEach((v, k) => {
|
|
292
|
-
|
|
295
|
+
// not really efficient but ok for now
|
|
296
|
+
fn(v.value, k, { ...this.buildItemCtx(v, false), idx: idx++ });
|
|
293
297
|
});
|
|
294
298
|
}
|
|
295
299
|
|