@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 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` option replaced by the
124
- * Vercube native `storage` option - `base` is derived from it so that cache keys
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 installed into the caching engine.
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
- * Installs the storage adapter into the caching engine.
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, setStorage, useStorage } from "ocache";
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 \0@oxc-project+runtime@0.140.0/helpers/esm/decorate.js
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 installed into the caching engine.
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
- return defineCachedFunction(fn, this.resolveOptions(options));
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
- * Installs the storage adapter into the caching engine.
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
- if (useStorage() instanceof CacheStorageAdapter) this.gLogger?.warn("Vercube/CacheManager::another CacheManager is already installed in this process - cached functions from every container will now route through this one");
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.2.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.2.1"
25
+ "@vercube/core": "1.3.1"
26
26
  },
27
27
  "dependencies": {
28
- "ocache": "0.2.0",
29
- "ohash": "2.0.12",
30
- "@vercube/di": "1.2.1",
31
- "@vercube/logger": "1.2.1",
32
- "@vercube/storage": "1.2.1"
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"