@vercube/cache 1.2.1 → 1.3.1
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/index.d.mts +25 -17
- package/dist/index.mjs +95 -9
- package/package.json +8 -7
package/dist/index.d.mts
CHANGED
|
@@ -10,21 +10,21 @@ import { CacheEntry, CacheOptions, CacheStatus, StorageInterface } from "ocache"
|
|
|
10
10
|
* ocache builds keys as `<base>:<group>:<name>:<key>.json`, so the base is always
|
|
11
11
|
* the first colon separated segment and can be parsed back out on read/write.
|
|
12
12
|
*/
|
|
13
|
-
declare const CACHE_BASE_PREFIX = "/cache";
|
|
13
|
+
export declare const CACHE_BASE_PREFIX = "/cache";
|
|
14
14
|
/**
|
|
15
15
|
* Builds the cache key base for a storage mounted in {@link StorageManager}.
|
|
16
16
|
*
|
|
17
17
|
* @param {string} [storage] - Name of the mounted storage, `default` when omitted
|
|
18
18
|
* @returns {string} The base prefix cache keys for that storage start with
|
|
19
19
|
*/
|
|
20
|
-
declare function cacheBaseForStorage(storage?: string): string;
|
|
20
|
+
export declare function cacheBaseForStorage(storage?: string): string;
|
|
21
21
|
/**
|
|
22
22
|
* Extracts the mounted storage name back out of a full cache key.
|
|
23
23
|
*
|
|
24
24
|
* @param {string} key - A cache key produced by the caching engine
|
|
25
25
|
* @returns {string} Name of the storage the key belongs to, `default` when the key carries no storage segment
|
|
26
26
|
*/
|
|
27
|
-
declare function storageNameFromCacheKey(key: string): string;
|
|
27
|
+
export declare function storageNameFromCacheKey(key: string): string;
|
|
28
28
|
/**
|
|
29
29
|
* Bridges the caching engine onto the Vercube {@link StorageManager}.
|
|
30
30
|
*
|
|
@@ -42,7 +42,7 @@ declare function storageNameFromCacheKey(key: string): string;
|
|
|
42
42
|
* caching works with zero configuration. Mounting the name yourself - before or
|
|
43
43
|
* after - is what swaps the backend for a real one.
|
|
44
44
|
*/
|
|
45
|
-
declare class CacheStorageAdapter implements StorageInterface {
|
|
45
|
+
export declare class CacheStorageAdapter implements StorageInterface {
|
|
46
46
|
/** Storage manager holding every mounted storage */
|
|
47
47
|
protected gStorageManager: StorageManager | null;
|
|
48
48
|
/** Logger instance */
|
|
@@ -94,7 +94,7 @@ declare class CacheStorageAdapter implements StorageInterface {
|
|
|
94
94
|
}
|
|
95
95
|
//#endregion
|
|
96
96
|
//#region src/Types/CacheTypes.d.ts
|
|
97
|
-
declare namespace CacheTypes {
|
|
97
|
+
export declare namespace CacheTypes {
|
|
98
98
|
/**
|
|
99
99
|
* Name (or ordered list of names) of storages mounted in the {@link StorageManager}
|
|
100
100
|
* that should back a cached function.
|
|
@@ -120,11 +120,12 @@ declare namespace CacheTypes {
|
|
|
120
120
|
/**
|
|
121
121
|
* Options accepted by {@link CacheManager.cached} and the `@Cache()` decorator.
|
|
122
122
|
*
|
|
123
|
-
* This is the ocache option set with the low level `base`
|
|
124
|
-
* Vercube native `storage` option - `base` is derived from it so
|
|
125
|
-
* can be routed to the right mounted storage
|
|
123
|
+
* This is the ocache option set with the low level `base` and `storage` options
|
|
124
|
+
* replaced by the Vercube native `storage` option - `base` is derived from it so
|
|
125
|
+
* that cache keys can be routed to the right mounted storage, and the backend is
|
|
126
|
+
* always the {@link CacheStorageAdapter} the CacheManager owns.
|
|
126
127
|
*/
|
|
127
|
-
type Options<T = any, ArgsT extends unknown[] = any[]> = Omit<CacheOptions<T, ArgsT>, 'base'> & {
|
|
128
|
+
type Options<T = any, ArgsT extends unknown[] = any[]> = Omit<CacheOptions<T, ArgsT>, 'base' | 'storage'> & {
|
|
128
129
|
/** Storage (or storages, for multi-tier caching) mounted in StorageManager to keep entries in. */
|
|
129
130
|
storage?: StorageRef;
|
|
130
131
|
};
|
|
@@ -200,7 +201,7 @@ declare namespace CacheTypes {
|
|
|
200
201
|
* await getUser.invalidate('123'); // drops the entry
|
|
201
202
|
* ```
|
|
202
203
|
*/
|
|
203
|
-
declare class CacheManager {
|
|
204
|
+
export declare class CacheManager {
|
|
204
205
|
/** Container instance */
|
|
205
206
|
protected gContainer: Container;
|
|
206
207
|
/** Logger instance */
|
|
@@ -210,7 +211,7 @@ declare class CacheManager {
|
|
|
210
211
|
/** Storage adapter bridging the caching engine onto the mounted storages */
|
|
211
212
|
protected fAdapter: CacheStorageAdapter | null;
|
|
212
213
|
/**
|
|
213
|
-
* Returns the storage adapter this manager
|
|
214
|
+
* Returns the storage adapter this manager binds every cached function to.
|
|
214
215
|
*
|
|
215
216
|
* @returns {CacheStorageAdapter | null} The adapter, or null before initialization
|
|
216
217
|
*/
|
|
@@ -289,6 +290,14 @@ declare class CacheManager {
|
|
|
289
290
|
* @protected
|
|
290
291
|
*/
|
|
291
292
|
protected resolveOptions<T, ArgsT extends unknown[]>(options: CacheTypes.Options<T, ArgsT>): CacheOptions<T, ArgsT>;
|
|
293
|
+
/**
|
|
294
|
+
* Returns the storage adapter every cached function of this manager is bound to,
|
|
295
|
+
* resolving it on first use so that options can be built before `@Init()` ran.
|
|
296
|
+
*
|
|
297
|
+
* @returns {CacheStorageAdapter} The adapter backing this manager
|
|
298
|
+
* @protected
|
|
299
|
+
*/
|
|
300
|
+
protected resolveAdapter(): CacheStorageAdapter;
|
|
292
301
|
/**
|
|
293
302
|
* Drops keys whose value is `undefined`, so that an explicitly passed
|
|
294
303
|
* `undefined` never shadows a configured default with a missing value.
|
|
@@ -300,7 +309,7 @@ declare class CacheManager {
|
|
|
300
309
|
*/
|
|
301
310
|
protected stripUndefined<T extends object>(options: T): Partial<T>;
|
|
302
311
|
/**
|
|
303
|
-
*
|
|
312
|
+
* Creates the storage adapter every cached function of this manager routes through.
|
|
304
313
|
* Called automatically with the `@Init()` decorator.
|
|
305
314
|
*
|
|
306
315
|
* @returns {void}
|
|
@@ -318,7 +327,7 @@ declare class CacheManager {
|
|
|
318
327
|
* the container has been flushed - and defaults configured during bootstrap -
|
|
319
328
|
* are still picked up.
|
|
320
329
|
*/
|
|
321
|
-
declare class CacheDecorator extends BaseDecorator<CacheTypes.DecoratorOptions> {
|
|
330
|
+
export declare class CacheDecorator extends BaseDecorator<CacheTypes.DecoratorOptions> {
|
|
322
331
|
/** Cache manager owning the caching engine wiring */
|
|
323
332
|
protected gCacheManager: CacheManager;
|
|
324
333
|
/** The untouched method, kept so it can be restored on destroy */
|
|
@@ -381,14 +390,14 @@ declare class CacheDecorator extends BaseDecorator<CacheTypes.DecoratorOptions>
|
|
|
381
390
|
* }
|
|
382
391
|
* ```
|
|
383
392
|
*/
|
|
384
|
-
declare function Cache(options?: CacheTypes.DecoratorOptions): Function;
|
|
393
|
+
export declare function Cache(options?: CacheTypes.DecoratorOptions): Function;
|
|
385
394
|
//#endregion
|
|
386
395
|
//#region src/Errors/CacheError.d.ts
|
|
387
396
|
/**
|
|
388
397
|
* Custom error class for cache-related errors.
|
|
389
398
|
* Wraps underlying cache/storage errors with standardized error messages.
|
|
390
399
|
*/
|
|
391
|
-
declare class CacheError extends Error {
|
|
400
|
+
export declare class CacheError extends Error {
|
|
392
401
|
/**
|
|
393
402
|
* The original error that caused this cache error
|
|
394
403
|
*/
|
|
@@ -403,5 +412,4 @@ declare class CacheError extends Error {
|
|
|
403
412
|
readonly metadata?: Record<string, unknown>;
|
|
404
413
|
constructor(message: string, operation: string, cause?: Error, metadata?: Record<string, unknown>);
|
|
405
414
|
}
|
|
406
|
-
//#endregion
|
|
407
|
-
export { CACHE_BASE_PREFIX, Cache, CacheDecorator, CacheError, CacheManager, CacheStorageAdapter, CacheTypes, cacheBaseForStorage, storageNameFromCacheKey };
|
|
415
|
+
//#endregion
|
package/dist/index.mjs
CHANGED
|
@@ -2,7 +2,8 @@ import { BaseDecorator, Container, Init, Inject, InjectOptional, createDecorator
|
|
|
2
2
|
import { hash } from "ohash";
|
|
3
3
|
import { Logger } from "@vercube/logger";
|
|
4
4
|
import { StorageManager } from "@vercube/storage";
|
|
5
|
-
import { defineCachedFunction, expireCache, invalidateCache, resolveCacheKeys
|
|
5
|
+
import { defineCachedFunction, expireCache, invalidateCache, resolveCacheKeys } from "ocache";
|
|
6
|
+
import { SpanKind, ValueType, createInstrument } from "@vercube/telemetry/instrument";
|
|
6
7
|
import { MemoryStorage } from "@vercube/storage/drivers/MemoryStorage";
|
|
7
8
|
//#region src/Errors/CacheError.ts
|
|
8
9
|
/**
|
|
@@ -32,7 +33,71 @@ var CacheError = class CacheError extends Error {
|
|
|
32
33
|
}
|
|
33
34
|
};
|
|
34
35
|
//#endregion
|
|
35
|
-
//#region
|
|
36
|
+
//#region src/Common/Instrument.ts
|
|
37
|
+
/**
|
|
38
|
+
* Traces and counts cache activity.
|
|
39
|
+
*
|
|
40
|
+
* The toolkit comes from `@vercube/telemetry/instrument`, which is the only
|
|
41
|
+
* place in the framework that speaks to OpenTelemetry directly, and it creates
|
|
42
|
+
* no instrument until one is actually used.
|
|
43
|
+
*/
|
|
44
|
+
const instrument = createInstrument("@vercube/cache");
|
|
45
|
+
/** Attribute marking whether a lookup was served from the cache. */
|
|
46
|
+
const CACHE_HIT = "vercube.cache.hit";
|
|
47
|
+
/** Attribute carrying the cached function's name. */
|
|
48
|
+
const CACHE_NAME = "vercube.cache.name";
|
|
49
|
+
/**
|
|
50
|
+
* Counts one cache lookup.
|
|
51
|
+
*
|
|
52
|
+
* Hits are not counted directly: a hit is the absence of a resolve, and
|
|
53
|
+
* deriving it from `lookups - misses` avoids having to decide, at the moment a
|
|
54
|
+
* value comes back, whether the engine actually consulted the origin. Two
|
|
55
|
+
* monotonic counters also survive being scraped at any interval, which a
|
|
56
|
+
* hit/miss ratio computed in-process does not.
|
|
57
|
+
*
|
|
58
|
+
* @param name - The cached function's name
|
|
59
|
+
*/
|
|
60
|
+
function countLookup(name) {
|
|
61
|
+
instrument.counter("vercube.cache.lookups", {
|
|
62
|
+
description: "Calls to a cached function.",
|
|
63
|
+
unit: "{lookup}",
|
|
64
|
+
valueType: ValueType.INT
|
|
65
|
+
}).add(1, { [CACHE_NAME]: name });
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Counts one cache miss and marks the active span as a miss.
|
|
69
|
+
*
|
|
70
|
+
* Called from inside the cached function itself, so it runs in the span opened
|
|
71
|
+
* for that lookup and needs no per-call bookkeeping of its own.
|
|
72
|
+
*
|
|
73
|
+
* @param name - The cached function's name
|
|
74
|
+
*/
|
|
75
|
+
function countMiss(name) {
|
|
76
|
+
instrument.counter("vercube.cache.misses", {
|
|
77
|
+
description: "Cached function calls that had to resolve the value.",
|
|
78
|
+
unit: "{miss}",
|
|
79
|
+
valueType: ValueType.INT
|
|
80
|
+
}).add(1, { [CACHE_NAME]: name });
|
|
81
|
+
instrument.activeSpan()?.setAttribute(CACHE_HIT, false);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Traces one cache lookup.
|
|
85
|
+
*
|
|
86
|
+
* @param name - The cached function's name
|
|
87
|
+
* @param fn - The lookup
|
|
88
|
+
* @returns Whatever the lookup returned
|
|
89
|
+
*/
|
|
90
|
+
function traceLookup(name, fn) {
|
|
91
|
+
return instrument.span(`cache.${name}`, {
|
|
92
|
+
kind: SpanKind.CLIENT,
|
|
93
|
+
attributes: {
|
|
94
|
+
[CACHE_NAME]: name,
|
|
95
|
+
[CACHE_HIT]: true
|
|
96
|
+
}
|
|
97
|
+
}, fn);
|
|
98
|
+
}
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region \0@oxc-project+runtime@0.150.0/helpers/esm/decorate.js
|
|
36
101
|
function __decorate(decorators, target, key, desc) {
|
|
37
102
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
38
103
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -204,7 +269,7 @@ var CacheManager = class {
|
|
|
204
269
|
/** Storage adapter bridging the caching engine onto the mounted storages */
|
|
205
270
|
fAdapter = null;
|
|
206
271
|
/**
|
|
207
|
-
* Returns the storage adapter this manager
|
|
272
|
+
* Returns the storage adapter this manager binds every cached function to.
|
|
208
273
|
*
|
|
209
274
|
* @returns {CacheStorageAdapter | null} The adapter, or null before initialization
|
|
210
275
|
*/
|
|
@@ -247,7 +312,18 @@ var CacheManager = class {
|
|
|
247
312
|
*/
|
|
248
313
|
cached(fn, options = {}) {
|
|
249
314
|
if (typeof fn !== "function") throw new CacheError("Cached target must be a function", "cached", void 0, { received: typeof fn });
|
|
250
|
-
|
|
315
|
+
const resolved = this.resolveOptions(options);
|
|
316
|
+
const name = resolved.name ?? fn.name ?? "anonymous";
|
|
317
|
+
const instrumented = (...args) => {
|
|
318
|
+
countMiss(name);
|
|
319
|
+
return fn(...args);
|
|
320
|
+
};
|
|
321
|
+
const cached = defineCachedFunction(instrumented, resolved);
|
|
322
|
+
const wrapper = ((...args) => {
|
|
323
|
+
countLookup(name);
|
|
324
|
+
return traceLookup(name, () => Promise.resolve(cached(...args)));
|
|
325
|
+
});
|
|
326
|
+
return Object.assign(wrapper, cached);
|
|
251
327
|
}
|
|
252
328
|
/**
|
|
253
329
|
* Removes cached entries for the given arguments from every storage tier.
|
|
@@ -319,10 +395,22 @@ var CacheManager = class {
|
|
|
319
395
|
const base = storages.map((name) => cacheBaseForStorage(name));
|
|
320
396
|
return {
|
|
321
397
|
...rest,
|
|
322
|
-
base: base.length === 1 ? base[0] : base
|
|
398
|
+
base: base.length === 1 ? base[0] : base,
|
|
399
|
+
storage: this.resolveAdapter()
|
|
323
400
|
};
|
|
324
401
|
}
|
|
325
402
|
/**
|
|
403
|
+
* Returns the storage adapter every cached function of this manager is bound to,
|
|
404
|
+
* resolving it on first use so that options can be built before `@Init()` ran.
|
|
405
|
+
*
|
|
406
|
+
* @returns {CacheStorageAdapter} The adapter backing this manager
|
|
407
|
+
* @protected
|
|
408
|
+
*/
|
|
409
|
+
resolveAdapter() {
|
|
410
|
+
this.fAdapter ??= this.gContainer.resolve(CacheStorageAdapter);
|
|
411
|
+
return this.fAdapter;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
326
414
|
* Drops keys whose value is `undefined`, so that an explicitly passed
|
|
327
415
|
* `undefined` never shadows a configured default with a missing value.
|
|
328
416
|
*
|
|
@@ -335,7 +423,7 @@ var CacheManager = class {
|
|
|
335
423
|
return Object.fromEntries(Object.entries(options).filter(([, value]) => value !== void 0));
|
|
336
424
|
}
|
|
337
425
|
/**
|
|
338
|
-
*
|
|
426
|
+
* Creates the storage adapter every cached function of this manager routes through.
|
|
339
427
|
* Called automatically with the `@Init()` decorator.
|
|
340
428
|
*
|
|
341
429
|
* @returns {void}
|
|
@@ -343,9 +431,7 @@ var CacheManager = class {
|
|
|
343
431
|
*/
|
|
344
432
|
init() {
|
|
345
433
|
if (!this.gContainer.getOptional(StorageManager)) this.gLogger?.warn("Vercube/CacheManager::StorageManager is not registered - bind it so cached entries have a storage to live in");
|
|
346
|
-
|
|
347
|
-
this.fAdapter = this.gContainer.resolve(CacheStorageAdapter);
|
|
348
|
-
setStorage(this.fAdapter);
|
|
434
|
+
this.resolveAdapter();
|
|
349
435
|
}
|
|
350
436
|
};
|
|
351
437
|
__decorate([Inject(Container)], CacheManager.prototype, "gContainer", void 0);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vercube/cache",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "Cache module for Vercube framework",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -22,14 +22,15 @@
|
|
|
22
22
|
"README.md"
|
|
23
23
|
],
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@vercube/core": "1.
|
|
25
|
+
"@vercube/core": "1.3.1"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"@vercube/
|
|
31
|
-
"@vercube/
|
|
32
|
-
"
|
|
28
|
+
"@vercube/di": "1.3.1",
|
|
29
|
+
"@vercube/logger": "1.3.1",
|
|
30
|
+
"@vercube/storage": "1.3.1",
|
|
31
|
+
"@vercube/telemetry": "1.3.1",
|
|
32
|
+
"ocache": "0.3.0",
|
|
33
|
+
"ohash": "2.0.12"
|
|
33
34
|
},
|
|
34
35
|
"publishConfig": {
|
|
35
36
|
"access": "public"
|