@adaptic/utils 0.0.1008 → 0.0.1010

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.cjs CHANGED
@@ -63618,6 +63618,15 @@ class StampedeProtectedCache {
63618
63618
  allowStale: true,
63619
63619
  updateAgeOnGet: false,
63620
63620
  updateAgeOnHas: false,
63621
+ // LRU discard visibility for the onEvent observability hook. lru-cache
63622
+ // invokes dispose for every removal; only capacity discards ("evict")
63623
+ // are reported as evictions so deliberate delete()/invalidate() calls
63624
+ // do not inflate the eviction signal.
63625
+ dispose: (_value, key, reason) => {
63626
+ if (reason === "evict") {
63627
+ this.emitEvent("eviction", key);
63628
+ }
63629
+ },
63621
63630
  });
63622
63631
  this.options.logger.info("StampedeProtectedCache initialized", {
63623
63632
  maxSize: this.options.maxSize,
@@ -63685,6 +63694,7 @@ class StampedeProtectedCache {
63685
63694
  if (now < jitteredExpiresAt) {
63686
63695
  // Fresh hit
63687
63696
  this.stats.hits++;
63697
+ this.emitEvent("hit", key);
63688
63698
  this.options.logger.debug("Cache hit (fresh)", {
63689
63699
  key,
63690
63700
  age: now - cached.createdAt,
@@ -63696,6 +63706,7 @@ class StampedeProtectedCache {
63696
63706
  if (now < staleExpiresAt && !cached.isRefreshing) {
63697
63707
  // Serve stale and trigger background refresh
63698
63708
  this.stats.staleHits++;
63709
+ this.emitEvent("stale_hit", key);
63699
63710
  this.options.logger.debug("Cache hit (stale-while-revalidate)", {
63700
63711
  key,
63701
63712
  age: now - cached.createdAt,
@@ -63709,6 +63720,7 @@ class StampedeProtectedCache {
63709
63720
  }
63710
63721
  // Cache miss or expired - need to load
63711
63722
  this.stats.misses++;
63723
+ this.emitEvent("miss", key);
63712
63724
  this.options.logger.debug("Cache miss", { key, hadCached: !!cached });
63713
63725
  return this.loadWithCoalescing(key, loader, effectiveTtl);
63714
63726
  }
@@ -63769,6 +63781,29 @@ class StampedeProtectedCache {
63769
63781
  has(key) {
63770
63782
  return this.cache.has(key);
63771
63783
  }
63784
+ /**
63785
+ * Synchronous peek at a cached value without triggering a loader.
63786
+ *
63787
+ * @description Returns the cached value when present and FRESH (inside its
63788
+ * per-entry TTL), `undefined` otherwise — never coalesces onto or starts a
63789
+ * load, never serves stale-while-revalidate data, and counts a hit only
63790
+ * when a fresh value is returned (mirroring the read-path hit semantics so
63791
+ * hitRatio stays truthful). Added 2026-08-08 for the engine cache
63792
+ * consolidation: its fork exposed peek() and consumers rely on the
63793
+ * no-load contract on hot paths.
63794
+ *
63795
+ * @param key - Cache key to inspect.
63796
+ * @returns The fresh cached value, or `undefined` when absent or expired.
63797
+ */
63798
+ peek(key) {
63799
+ const entry = this.cache.get(key);
63800
+ if (entry && Date.now() < entry.expiresAt) {
63801
+ this.stats.hits++;
63802
+ this.emitEvent("hit", key);
63803
+ return entry.value;
63804
+ }
63805
+ return undefined;
63806
+ }
63772
63807
  /**
63773
63808
  * Delete a specific key from the cache
63774
63809
  *
@@ -63786,6 +63821,25 @@ class StampedeProtectedCache {
63786
63821
  * cache.delete(`positions:${accountId}`);
63787
63822
  * ```
63788
63823
  */
63824
+ /**
63825
+ * Fire the {@link StampedeProtectedCacheOptions.onEvent} hook, never
63826
+ * letting an observer failure propagate into the cache path.
63827
+ */
63828
+ emitEvent(event, key) {
63829
+ const hook = this.options.onEvent;
63830
+ if (!hook)
63831
+ return;
63832
+ try {
63833
+ hook(event, key);
63834
+ }
63835
+ catch (err) {
63836
+ this.options.logger.warn("cache onEvent observer threw — ignored", {
63837
+ event,
63838
+ key,
63839
+ error: err instanceof Error ? err.message : String(err),
63840
+ });
63841
+ }
63842
+ }
63789
63843
  delete(key) {
63790
63844
  const deleted = this.cache.delete(key);
63791
63845
  if (deleted) {
@@ -63918,6 +63972,7 @@ class StampedeProtectedCache {
63918
63972
  const existingPromise = this.pendingRefreshes.get(key);
63919
63973
  if (existingPromise) {
63920
63974
  this.stats.coalescedRequests++;
63975
+ this.emitEvent("coalesced", key);
63921
63976
  this.options.logger.debug("Request coalesced", { key });
63922
63977
  return existingPromise;
63923
63978
  }
@@ -63951,6 +64006,7 @@ class StampedeProtectedCache {
63951
64006
  const timeoutPromise = new Promise((_, reject) => {
63952
64007
  timeoutHandle = setTimeout(() => {
63953
64008
  this.stats.loadTimeouts++;
64009
+ this.emitEvent("load_timeout", key);
63954
64010
  // Mark the invocation abandoned BEFORE evicting the pin: a retry that
63955
64011
  // starts now must never be overwritten by this loader's late result.
63956
64012
  invocation.abandoned = true;
@@ -64007,6 +64063,7 @@ class StampedeProtectedCache {
64007
64063
  }
64008
64064
  catch (error) {
64009
64065
  this.stats.refreshErrors++;
64066
+ this.emitEvent("refresh_error", key);
64010
64067
  const loadTime = Date.now() - startTime;
64011
64068
  this.options.logger.error("Failed to load data", {
64012
64069
  key,
@@ -64039,6 +64096,7 @@ class StampedeProtectedCache {
64039
64096
  this.loadWithCoalescing(key, loader, ttl)
64040
64097
  .then(() => {
64041
64098
  this.stats.backgroundRefreshes++;
64099
+ this.emitEvent("background_refresh", key);
64042
64100
  this.options.logger.debug("Background refresh completed", { key });
64043
64101
  })
64044
64102
  .catch((error) => {