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