@warlock.js/cache 5.31.0 → 5.32.0

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.
Files changed (56) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/cjs/index.cjs +4 -4
  3. package/cjs/index.cjs.map +1 -1
  4. package/esm/cache-manager.d.mts +2 -4
  5. package/esm/cache-manager.d.mts.map +1 -1
  6. package/esm/cached/auto-key.d.mts +1 -2
  7. package/esm/cached/auto-key.d.mts.map +1 -1
  8. package/esm/cached/cached.d.mts +4 -6
  9. package/esm/cached/cached.d.mts.map +1 -1
  10. package/esm/cached/index.d.mts +2 -1
  11. package/esm/cached/index.mjs +1 -1
  12. package/esm/cached/normalize-args.d.mts +3 -5
  13. package/esm/cached/normalize-args.d.mts.map +1 -1
  14. package/esm/drivers/base-cache-driver.d.mts +2 -4
  15. package/esm/drivers/base-cache-driver.d.mts.map +1 -1
  16. package/esm/drivers/file-cache-driver.d.mts +1 -3
  17. package/esm/drivers/file-cache-driver.d.mts.map +1 -1
  18. package/esm/drivers/in-memory-tag-index.d.mts +1 -2
  19. package/esm/drivers/in-memory-tag-index.d.mts.map +1 -1
  20. package/esm/drivers/index.d.mts +2 -1
  21. package/esm/drivers/index.mjs +1 -1
  22. package/esm/drivers/lru-memory-cache-driver.d.mts +2 -3
  23. package/esm/drivers/lru-memory-cache-driver.d.mts.map +1 -1
  24. package/esm/drivers/memory-cache-driver.d.mts +1 -3
  25. package/esm/drivers/memory-cache-driver.d.mts.map +1 -1
  26. package/esm/drivers/memory-extended-cache-driver.d.mts +1 -3
  27. package/esm/drivers/memory-extended-cache-driver.d.mts.map +1 -1
  28. package/esm/drivers/mock-cache-driver.d.mts +1 -3
  29. package/esm/drivers/mock-cache-driver.d.mts.map +1 -1
  30. package/esm/drivers/null-cache-driver.d.mts +1 -3
  31. package/esm/drivers/null-cache-driver.d.mts.map +1 -1
  32. package/esm/drivers/pg-cache-driver.d.mts +1 -3
  33. package/esm/drivers/pg-cache-driver.d.mts.map +1 -1
  34. package/esm/drivers/redis-cache-driver.d.mts +5 -7
  35. package/esm/drivers/redis-cache-driver.d.mts.map +1 -1
  36. package/esm/index.d.mts +3 -0
  37. package/esm/list/index.d.mts +2 -1
  38. package/esm/list/memory-cache-list.d.mts +1 -3
  39. package/esm/list/memory-cache-list.d.mts.map +1 -1
  40. package/esm/metrics.d.mts +4 -4
  41. package/esm/metrics.d.mts.map +1 -1
  42. package/esm/metrics.mjs +2 -2
  43. package/esm/metrics.mjs.map +1 -1
  44. package/esm/scoped-cache.d.mts +1 -3
  45. package/esm/scoped-cache.d.mts.map +1 -1
  46. package/esm/tagged-cache.d.mts +1 -3
  47. package/esm/tagged-cache.d.mts.map +1 -1
  48. package/esm/tagged-cache.mjs +1 -1
  49. package/esm/tagged-cache.mjs.map +1 -1
  50. package/esm/tagged-scoped-cache.d.mts +1 -3
  51. package/esm/tagged-scoped-cache.d.mts.map +1 -1
  52. package/esm/types.d.mts +70 -57
  53. package/esm/types.d.mts.map +1 -1
  54. package/esm/utils.d.mts +12 -14
  55. package/esm/utils.d.mts.map +1 -1
  56. package/package.json +3 -3
package/esm/metrics.d.mts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { CacheEventData, CacheMetricsSnapshot } from "./types.mjs";
2
-
3
2
  //#region ../../packages/cache/src/metrics.d.ts
4
3
  /**
5
4
  * Per-driver counter row tracked inside {@link CacheMetricsCollector}.
@@ -14,7 +13,8 @@ type DriverCounters = {
14
13
  sets: number;
15
14
  removed: number;
16
15
  errors: number;
17
- latencySamples: number[]; /** Pointer into `latencySamples` for the next write — circular buffer. */
16
+ latencySamples: number[];
17
+ /** Pointer into `latencySamples` for the next write — circular buffer. */
18
18
  latencyCursor: number;
19
19
  };
20
20
  /**
@@ -45,7 +45,7 @@ type DriverCounters = {
45
45
  * // startedAt: 1714185600000,
46
46
  * // }
47
47
  */
48
- declare class CacheMetricsCollector {
48
+ export declare class CacheMetricsCollector {
49
49
  /**
50
50
  * Maximum number of latency samples retained per driver. Older samples
51
51
  * are overwritten in arrival order — quantile calc operates on whatever
@@ -114,5 +114,5 @@ declare class CacheMetricsCollector {
114
114
  protected resetCounters(bucket: DriverCounters): void;
115
115
  }
116
116
  //#endregion
117
- export { CacheMetricsCollector };
117
+ export {};
118
118
  //# sourceMappingURL=metrics.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.d.mts","names":[],"sources":["../../../../../../../packages/cache/src/metrics.ts"],"mappings":";;;;;AAAoE;;;;;KAgB/D,cAAA;EACH,IAAA;EACA,MAAA;EACA,IAAA;EACA,OAAA;EACA,MAAA;EACA,cAAA,YAEa;EAAb,aAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA+BW,qBAAA;EAoEJ;;;;;EAAA,mBA9DY,UAAA;EAoGT;;;;EAAA,mBA9FS,SAAA,EAAW,cAAA;EA6Gd;;;;EAAA,mBAvGG,QAAA,EAAU,GAAA,SAAY,cAAA;EA6HI;EAAA,UA1HnC,SAAA;cAES,UAAA;EAqJK;;;;EA5IjB,WAAA,CACL,KAAA,gDACA,IAAA,EAAM,cAAA;EAmKwB;;;AAAc;;EAjIvC,aAAA,CAAc,MAAA,UAAgB,UAAA;;;;;;EAU9B,QAAA,IAAY,oBAAA;;;;;EAkBZ,KAAA;;;;;YAUG,SAAA,CAAU,UAAA,WAAqB,cAAA;;;;;YAe/B,KAAA,CAAM,MAAA,EAAQ,cAAA,GAAiB,IAAA,CAAK,oBAAA;;;;;;YAsBpC,cAAA,CAAe,OAAA,aAAoB,oBAAA;;;;;YA6BnC,aAAA,CAAc,MAAA,EAAQ,cAAA,EAAgB,UAAA;;YAYtC,cAAA,IAAkB,cAAA;;YAalB,aAAA,CAAc,MAAA,EAAQ,cAAA;AAAA"}
1
+ {"version":3,"file":"metrics.d.mts","names":[],"sources":["../../../../../../../packages/cache/src/metrics.ts"],"mappings":";;;;;;;;;KAgBK;EACH;EACA;EACA;EACA;EACA;EACA;;EAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBA+BW;;;;;;qBAMQ;;;;;qBAMA,WAAW;;;;;qBAMX,UAAU,YAAY;;YAG/B;EAEV,YAAmB;;;;;EASZ,YACL,qDACA,MAAM;;;;;;EAkCD,cAAc,gBAAgB;;;;;;EAU9B,YAAY;;;;;EAkBZ;;;;;YAUG,UAAU,qBAAqB;;;;;YAe/B,MAAM,QAAQ,iBAAiB,KAAK;;;;;;YAsBpC,eAAe,oBAAoB;;;;;YA6BnC,cAAc,QAAQ,gBAAgB;;YAYtC,kBAAkB;;YAalB,cAAc,QAAQ"}
package/esm/metrics.mjs CHANGED
@@ -67,7 +67,6 @@ var CacheMetricsCollector = class {
67
67
  case "error":
68
68
  aggregate.errors += 1;
69
69
  driverBucket.errors += 1;
70
- break;
71
70
  }
72
71
  }
73
72
  /**
@@ -147,7 +146,8 @@ var CacheMetricsCollector = class {
147
146
  };
148
147
  const sorted = [...samples].sort((a, b) => a - b);
149
148
  const pick = (quantile) => {
150
- return sorted[Math.min(sorted.length - 1, Math.floor(quantile * sorted.length))] ?? 0;
149
+ const index = Math.min(sorted.length - 1, Math.floor(quantile * sorted.length));
150
+ return sorted[index] ?? 0;
151
151
  };
152
152
  return {
153
153
  p50: pick(.5),
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.mjs","names":[],"sources":["../../../../../../../packages/cache/src/metrics.ts"],"sourcesContent":["import type { CacheEventData, CacheMetricsSnapshot } from \"./types\";\n\n/**\n * Default size of the circular latency-sample buffer. 1000 samples covers\n * \"tell me the current p95\" for every realistic workload while keeping the\n * memory cost negligible (8KB at 8 bytes per number).\n */\nconst DEFAULT_LATENCY_BUFFER_SIZE = 1000;\n\n/**\n * Per-driver counter row tracked inside {@link CacheMetricsCollector}.\n *\n * Same shape as the public {@link CacheMetricsSnapshot} except for the lack\n * of `byDriver` (which is the breakdown itself) and the `latencySamples`\n * array — the buffer that p50/p95/p99 are computed from at snapshot time.\n */\ntype DriverCounters = {\n hits: number;\n misses: number;\n sets: number;\n removed: number;\n errors: number;\n latencySamples: number[];\n /** Pointer into `latencySamples` for the next write — circular buffer. */\n latencyCursor: number;\n};\n\n/**\n * Listens to `CacheManager` events and accumulates running counters + a\n * circular latency buffer per driver. Returned to consumers via\n * `cache.metrics()` as a {@link CacheMetricsSnapshot}.\n *\n * **Role.** Single-instance observability layer attached to the manager.\n * Subscribes once at construction; survives `cache.use()` driver switches\n * because the global event registry on the manager re-attaches handlers to\n * every loaded driver.\n *\n * **Responsibility.**\n * - Owns: per-driver and aggregate counters (`hits`, `misses`, `sets`,\n * `removed`, `errors`), the latency circular buffer, and snapshot\n * computation including hit-rate + percentile calculation.\n * - Does NOT own: event emission (driven by drivers via `BaseCacheDriver.emit`),\n * timing instrumentation (done at the manager level via `recordLatency`),\n * or persistence — every metric resets on `resetMetrics()` and on process\n * restart.\n *\n * @example\n * cache.metrics();\n * // {\n * // hits: 9821, misses: 173, hitRate: 0.983,\n * // latencyMs: { p50: 0.4, p95: 2.1, p99: 8.2, samples: 1000 },\n * // byDriver: { memory: { ... }, redis: { ... } },\n * // startedAt: 1714185600000,\n * // }\n */\nexport class CacheMetricsCollector {\n /**\n * Maximum number of latency samples retained per driver. Older samples\n * are overwritten in arrival order — quantile calc operates on whatever\n * window is currently in the buffer.\n */\n protected readonly bufferSize: number;\n\n /**\n * Aggregate counters across every driver. Mirrors what one driver bucket\n * holds — the snapshot reports both totals and per-driver breakdowns.\n */\n protected readonly aggregate: DriverCounters;\n\n /**\n * Per-driver buckets keyed by driver name (`\"memory\"`, `\"redis\"`, …).\n * Lazy-allocated on first event.\n */\n protected readonly byDriver: Map<string, DriverCounters> = new Map();\n\n /** Millisecond timestamp the collector last reset. */\n protected startedAt: number = Date.now();\n\n public constructor(bufferSize: number = DEFAULT_LATENCY_BUFFER_SIZE) {\n this.bufferSize = bufferSize;\n this.aggregate = this.createCounters();\n }\n\n /**\n * Increment the appropriate counters for a cache event. Called from the\n * manager's global listeners (one per event type).\n */\n public recordEvent(\n event: \"hit\" | \"miss\" | \"set\" | \"removed\" | \"error\",\n data: CacheEventData,\n ): void {\n const driverBucket = this.bucketFor(data.driver);\n const aggregate = this.aggregate;\n\n switch (event) {\n case \"hit\":\n aggregate.hits += 1;\n driverBucket.hits += 1;\n break;\n case \"miss\":\n aggregate.misses += 1;\n driverBucket.misses += 1;\n break;\n case \"set\":\n aggregate.sets += 1;\n driverBucket.sets += 1;\n break;\n case \"removed\":\n aggregate.removed += 1;\n driverBucket.removed += 1;\n break;\n case \"error\":\n aggregate.errors += 1;\n driverBucket.errors += 1;\n break;\n }\n }\n\n /**\n * Append a latency sample for `driver`. Called by the manager from its\n * timed wrappers around `get` / `set` / `remove`. Uses circular-buffer\n * semantics: oldest samples are overwritten once the buffer is full.\n */\n public recordLatency(driver: string, durationMs: number): void {\n this.appendLatency(this.aggregate, durationMs);\n this.appendLatency(this.bucketFor(driver), durationMs);\n }\n\n /**\n * Compute and return the current snapshot. Latency percentiles are\n * derived from a sorted copy of the buffer at call time — O(N log N)\n * on N=1000 is cheap enough that we don't bother caching.\n */\n public snapshot(): CacheMetricsSnapshot {\n const byDriver: Record<string, Omit<CacheMetricsSnapshot, \"byDriver\">> = {};\n\n for (const [driverName, bucket] of this.byDriver) {\n byDriver[driverName] = this.toRow(bucket);\n }\n\n return {\n ...this.toRow(this.aggregate),\n byDriver,\n startedAt: this.startedAt,\n };\n }\n\n /**\n * Wipe every counter, drop every latency sample, and reset `startedAt`.\n * The collector itself stays subscribed to events.\n */\n public reset(): void {\n this.resetCounters(this.aggregate);\n this.byDriver.clear();\n this.startedAt = Date.now();\n }\n\n /**\n * Locate the per-driver bucket, creating it on first reference. Driver\n * names are taken verbatim from `CacheEventData.driver`.\n */\n protected bucketFor(driverName: string): DriverCounters {\n let bucket = this.byDriver.get(driverName);\n\n if (!bucket) {\n bucket = this.createCounters();\n this.byDriver.set(driverName, bucket);\n }\n\n return bucket;\n }\n\n /**\n * Convert raw counters into the public snapshot row shape. Computes\n * `hitRate` and the latency percentiles on the fly.\n */\n protected toRow(bucket: DriverCounters): Omit<CacheMetricsSnapshot, \"byDriver\"> {\n const totalReads = bucket.hits + bucket.misses;\n const hitRate = totalReads === 0 ? 0 : bucket.hits / totalReads;\n const latency = this.computeLatency(bucket.latencySamples);\n\n return {\n hits: bucket.hits,\n misses: bucket.misses,\n sets: bucket.sets,\n removed: bucket.removed,\n errors: bucket.errors,\n hitRate,\n latencyMs: latency,\n startedAt: this.startedAt,\n };\n }\n\n /**\n * Sort a copy of the buffer and pick the percentile entries directly.\n * Empty buffers return zeroed percentiles so consumers can render\n * dashboards without null-checking.\n */\n protected computeLatency(samples: number[]): CacheMetricsSnapshot[\"latencyMs\"] {\n if (samples.length === 0) {\n return { p50: 0, p95: 0, p99: 0, samples: 0 };\n }\n\n const sorted = [...samples].sort((a, b) => a - b);\n const pick = (quantile: number): number => {\n const index = Math.min(sorted.length - 1, Math.floor(quantile * sorted.length));\n\n // `sorted` is non-empty (the `samples.length === 0` early return above)\n // and `index` is clamped to its last element, so this always hits — but\n // the clamp is arithmetic the compiler does not evaluate. `?? 0` states\n // the floor without asserting: a percentile of an empty set is 0, which\n // is exactly what the early return above already answers.\n return sorted[index] ?? 0;\n };\n\n return {\n p50: pick(0.5),\n p95: pick(0.95),\n p99: pick(0.99),\n samples: sorted.length,\n };\n }\n\n /**\n * Append a latency sample using circular-buffer semantics — overwrite the\n * oldest entry once the buffer is full instead of growing unbounded.\n */\n protected appendLatency(bucket: DriverCounters, durationMs: number): void {\n if (bucket.latencySamples.length < this.bufferSize) {\n bucket.latencySamples.push(durationMs);\n\n return;\n }\n\n bucket.latencySamples[bucket.latencyCursor] = durationMs;\n bucket.latencyCursor = (bucket.latencyCursor + 1) % this.bufferSize;\n }\n\n /** Build a fresh counter row with zeroed totals and an empty buffer. */\n protected createCounters(): DriverCounters {\n return {\n hits: 0,\n misses: 0,\n sets: 0,\n removed: 0,\n errors: 0,\n latencySamples: [],\n latencyCursor: 0,\n };\n }\n\n /** Reset an existing counter row in place. Used by `reset()` for the aggregate. */\n protected resetCounters(bucket: DriverCounters): void {\n bucket.hits = 0;\n bucket.misses = 0;\n bucket.sets = 0;\n bucket.removed = 0;\n bucket.errors = 0;\n bucket.latencySamples.length = 0;\n bucket.latencyCursor = 0;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"metrics.mjs","names":[],"sources":["../../../../../../../packages/cache/src/metrics.ts"],"sourcesContent":["import type { CacheEventData, CacheMetricsSnapshot } from \"./types\";\n\n/**\n * Default size of the circular latency-sample buffer. 1000 samples covers\n * \"tell me the current p95\" for every realistic workload while keeping the\n * memory cost negligible (8KB at 8 bytes per number).\n */\nconst DEFAULT_LATENCY_BUFFER_SIZE = 1000;\n\n/**\n * Per-driver counter row tracked inside {@link CacheMetricsCollector}.\n *\n * Same shape as the public {@link CacheMetricsSnapshot} except for the lack\n * of `byDriver` (which is the breakdown itself) and the `latencySamples`\n * array — the buffer that p50/p95/p99 are computed from at snapshot time.\n */\ntype DriverCounters = {\n hits: number;\n misses: number;\n sets: number;\n removed: number;\n errors: number;\n latencySamples: number[];\n /** Pointer into `latencySamples` for the next write — circular buffer. */\n latencyCursor: number;\n};\n\n/**\n * Listens to `CacheManager` events and accumulates running counters + a\n * circular latency buffer per driver. Returned to consumers via\n * `cache.metrics()` as a {@link CacheMetricsSnapshot}.\n *\n * **Role.** Single-instance observability layer attached to the manager.\n * Subscribes once at construction; survives `cache.use()` driver switches\n * because the global event registry on the manager re-attaches handlers to\n * every loaded driver.\n *\n * **Responsibility.**\n * - Owns: per-driver and aggregate counters (`hits`, `misses`, `sets`,\n * `removed`, `errors`), the latency circular buffer, and snapshot\n * computation including hit-rate + percentile calculation.\n * - Does NOT own: event emission (driven by drivers via `BaseCacheDriver.emit`),\n * timing instrumentation (done at the manager level via `recordLatency`),\n * or persistence — every metric resets on `resetMetrics()` and on process\n * restart.\n *\n * @example\n * cache.metrics();\n * // {\n * // hits: 9821, misses: 173, hitRate: 0.983,\n * // latencyMs: { p50: 0.4, p95: 2.1, p99: 8.2, samples: 1000 },\n * // byDriver: { memory: { ... }, redis: { ... } },\n * // startedAt: 1714185600000,\n * // }\n */\nexport class CacheMetricsCollector {\n /**\n * Maximum number of latency samples retained per driver. Older samples\n * are overwritten in arrival order — quantile calc operates on whatever\n * window is currently in the buffer.\n */\n protected readonly bufferSize: number;\n\n /**\n * Aggregate counters across every driver. Mirrors what one driver bucket\n * holds — the snapshot reports both totals and per-driver breakdowns.\n */\n protected readonly aggregate: DriverCounters;\n\n /**\n * Per-driver buckets keyed by driver name (`\"memory\"`, `\"redis\"`, …).\n * Lazy-allocated on first event.\n */\n protected readonly byDriver: Map<string, DriverCounters> = new Map();\n\n /** Millisecond timestamp the collector last reset. */\n protected startedAt: number = Date.now();\n\n public constructor(bufferSize: number = DEFAULT_LATENCY_BUFFER_SIZE) {\n this.bufferSize = bufferSize;\n this.aggregate = this.createCounters();\n }\n\n /**\n * Increment the appropriate counters for a cache event. Called from the\n * manager's global listeners (one per event type).\n */\n public recordEvent(\n event: \"hit\" | \"miss\" | \"set\" | \"removed\" | \"error\",\n data: CacheEventData,\n ): void {\n const driverBucket = this.bucketFor(data.driver);\n const aggregate = this.aggregate;\n\n switch (event) {\n case \"hit\":\n aggregate.hits += 1;\n driverBucket.hits += 1;\n break;\n case \"miss\":\n aggregate.misses += 1;\n driverBucket.misses += 1;\n break;\n case \"set\":\n aggregate.sets += 1;\n driverBucket.sets += 1;\n break;\n case \"removed\":\n aggregate.removed += 1;\n driverBucket.removed += 1;\n break;\n case \"error\":\n aggregate.errors += 1;\n driverBucket.errors += 1;\n break;\n }\n }\n\n /**\n * Append a latency sample for `driver`. Called by the manager from its\n * timed wrappers around `get` / `set` / `remove`. Uses circular-buffer\n * semantics: oldest samples are overwritten once the buffer is full.\n */\n public recordLatency(driver: string, durationMs: number): void {\n this.appendLatency(this.aggregate, durationMs);\n this.appendLatency(this.bucketFor(driver), durationMs);\n }\n\n /**\n * Compute and return the current snapshot. Latency percentiles are\n * derived from a sorted copy of the buffer at call time — O(N log N)\n * on N=1000 is cheap enough that we don't bother caching.\n */\n public snapshot(): CacheMetricsSnapshot {\n const byDriver: Record<string, Omit<CacheMetricsSnapshot, \"byDriver\">> = {};\n\n for (const [driverName, bucket] of this.byDriver) {\n byDriver[driverName] = this.toRow(bucket);\n }\n\n return {\n ...this.toRow(this.aggregate),\n byDriver,\n startedAt: this.startedAt,\n };\n }\n\n /**\n * Wipe every counter, drop every latency sample, and reset `startedAt`.\n * The collector itself stays subscribed to events.\n */\n public reset(): void {\n this.resetCounters(this.aggregate);\n this.byDriver.clear();\n this.startedAt = Date.now();\n }\n\n /**\n * Locate the per-driver bucket, creating it on first reference. Driver\n * names are taken verbatim from `CacheEventData.driver`.\n */\n protected bucketFor(driverName: string): DriverCounters {\n let bucket = this.byDriver.get(driverName);\n\n if (!bucket) {\n bucket = this.createCounters();\n this.byDriver.set(driverName, bucket);\n }\n\n return bucket;\n }\n\n /**\n * Convert raw counters into the public snapshot row shape. Computes\n * `hitRate` and the latency percentiles on the fly.\n */\n protected toRow(bucket: DriverCounters): Omit<CacheMetricsSnapshot, \"byDriver\"> {\n const totalReads = bucket.hits + bucket.misses;\n const hitRate = totalReads === 0 ? 0 : bucket.hits / totalReads;\n const latency = this.computeLatency(bucket.latencySamples);\n\n return {\n hits: bucket.hits,\n misses: bucket.misses,\n sets: bucket.sets,\n removed: bucket.removed,\n errors: bucket.errors,\n hitRate,\n latencyMs: latency,\n startedAt: this.startedAt,\n };\n }\n\n /**\n * Sort a copy of the buffer and pick the percentile entries directly.\n * Empty buffers return zeroed percentiles so consumers can render\n * dashboards without null-checking.\n */\n protected computeLatency(samples: number[]): CacheMetricsSnapshot[\"latencyMs\"] {\n if (samples.length === 0) {\n return { p50: 0, p95: 0, p99: 0, samples: 0 };\n }\n\n const sorted = [...samples].sort((a, b) => a - b);\n const pick = (quantile: number): number => {\n const index = Math.min(sorted.length - 1, Math.floor(quantile * sorted.length));\n\n // `sorted` is non-empty (the `samples.length === 0` early return above)\n // and `index` is clamped to its last element, so this always hits — but\n // the clamp is arithmetic the compiler does not evaluate. `?? 0` states\n // the floor without asserting: a percentile of an empty set is 0, which\n // is exactly what the early return above already answers.\n return sorted[index] ?? 0;\n };\n\n return {\n p50: pick(0.5),\n p95: pick(0.95),\n p99: pick(0.99),\n samples: sorted.length,\n };\n }\n\n /**\n * Append a latency sample using circular-buffer semantics — overwrite the\n * oldest entry once the buffer is full instead of growing unbounded.\n */\n protected appendLatency(bucket: DriverCounters, durationMs: number): void {\n if (bucket.latencySamples.length < this.bufferSize) {\n bucket.latencySamples.push(durationMs);\n\n return;\n }\n\n bucket.latencySamples[bucket.latencyCursor] = durationMs;\n bucket.latencyCursor = (bucket.latencyCursor + 1) % this.bufferSize;\n }\n\n /** Build a fresh counter row with zeroed totals and an empty buffer. */\n protected createCounters(): DriverCounters {\n return {\n hits: 0,\n misses: 0,\n sets: 0,\n removed: 0,\n errors: 0,\n latencySamples: [],\n latencyCursor: 0,\n };\n }\n\n /** Reset an existing counter row in place. Used by `reset()` for the aggregate. */\n protected resetCounters(bucket: DriverCounters): void {\n bucket.hits = 0;\n bucket.misses = 0;\n bucket.sets = 0;\n bucket.removed = 0;\n bucket.errors = 0;\n bucket.latencySamples.length = 0;\n bucket.latencyCursor = 0;\n }\n}\n"],"mappings":"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"}
@@ -1,5 +1,4 @@
1
1
  import { CacheDriver, CacheKey, CacheListAccessor, CacheNamespaceOptions, CacheSetOptions, CacheSimilarHit, CacheSimilarOptions, CacheSwrOptions, CacheTtl, LockOptions, LockOutcome, RememberOptions, ScopedCacheContract, TaggedScopedCacheContract } from "./types.mjs";
2
-
3
2
  //#region ../../packages/cache/src/scoped-cache.d.ts
4
3
  /**
5
4
  * Scoped view over a cache source. Returned by `cache.namespace(prefix, options?)`.
@@ -30,7 +29,7 @@ import { CacheDriver, CacheKey, CacheListAccessor, CacheNamespaceOptions, CacheS
30
29
  * await chat.namespace("typing", { ttl: "5s" }).set("user.42", true);
31
30
  * await chat.clear();
32
31
  */
33
- declare class ScopedCache implements ScopedCacheContract {
32
+ export declare class ScopedCache implements ScopedCacheContract {
34
33
  /**
35
34
  * Fully-qualified prefix prepended to every key handled by this scope.
36
35
  * Normalized through {@link parseCacheKey} on construction so colon-form
@@ -209,5 +208,4 @@ declare class ScopedCache implements ScopedCacheContract {
209
208
  protected scopeTtlSeconds(): number | undefined;
210
209
  }
211
210
  //#endregion
212
- export { ScopedCache };
213
211
  //# sourceMappingURL=scoped-cache.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scoped-cache.d.mts","names":[],"sources":["../../../../../../../packages/cache/src/scoped-cache.ts"],"mappings":"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"}
1
+ {"version":3,"file":"scoped-cache.d.mts","names":[],"sources":["../../../../../../../packages/cache/src/scoped-cache.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAsDa,uBAAuB;;;;;;WAMlB;;;;;;;;WASA,QAAQ;;;;;;;WAQR,UAAU;;;;;EAM1B,YACE,QAAQ,uBACR,gBACA,WAAU;;;;;;;;;;EAmBL,UAAU,gBAAgB,UAAS,wBAA6B;;;;;;EAchE,KAAK,iBAAiB;;;;;;EAStB,SAAS;;;;EAOT,IAAI,SAAS,KAAK,WAAW,QAAQ;;;;EAOrC,IAAI,KAAK,WAAW;;;;EAOpB,KAAK,MAAM,aAAa;;;;EAOxB,KAAK,SAAS,KAAK,WAAW,QAAQ;;;;;;EAStC,IAAI,KAAK,UAAU,YAAY,eAAe,WAAW,kBAAkB;;;;;;EAS3E,QAAQ,OAAO,qBAAqB,eAAe;;;;;EAcnD,MAAM,KAAK,UAAU,YAAY,eAAe;;;;;EAYhD,QAAQ,SAAS,KAAK,UAAU,OAAO,IAAI,QAAQ;;;;EAOnD,OAAO,KAAK,WAAW;;;;;EAQvB,SAAS,SACd,KAAK,UACL,cAAc,WAAW,iBACzB,gBAAgB,QAAQ,KACvB,QAAQ;;;;;;;EAcJ,IAAI,SACT,KAAK,UACL,SAAS,iBACT,gBAAgB,QAAQ,KACvB,QAAQ;;;;;EAaJ,UAAU,KAAK,UAAU,gBAAgB;IAAY,MAAM;MAAa;;;;;EAUxE,UAAU,KAAK,UAAU,iBAAiB;;;;;;;EAUpC,OAAO,SAClB,KAAK,UACL,KAAK,SAAS,aAAa,WAAW,QAAQ,WAC9C;IAAY,MAAM;MACjB,QAAQ;;;;;EAUE,MAAM,UAAU,sBAAsB,qBACjD,KAAK,UACL,SAAS,QAAQ,IACjB;IAAY,MAAM;MACjB,QAAQ;;;;;UAUG;;;;;EAmBP,KAAK,SAAS,KAAK,WAAW,kBAAkB;;;;;EAQhD,KAAK,GACV,KAAK,UACL,cAAc,WAAW,KAAK,wBAC9B,UAAU,QAAQ,KACjB,QAAQ,YAAY;;;;;;;EAsBV,QAAQ,SACnB,kBACA,SAAS,sBACR,QAAQ,gBAAgB;;;;;;YAYjB,UAAU,KAAK;;;;;;;YAgBf,gBAAgB,QAAQ,WAAW,kBAAkB;;;;;YAsBrD,qBAAqB,OAAO,WAAW,kBAAkB,WAAW;;;;;;YAuBpE"}
@@ -1,11 +1,10 @@
1
1
  import { CacheDriver, CacheKey, CacheSetOptions, CacheTtl, TaggedCacheDriver } from "./types.mjs";
2
-
3
2
  //#region ../../packages/cache/src/tagged-cache.d.ts
4
3
  /**
5
4
  * Tagged Cache Wrapper
6
5
  * Wraps a cache driver to automatically manage tag relationships
7
6
  */
8
- declare class TaggedCache implements TaggedCacheDriver {
7
+ export declare class TaggedCache implements TaggedCacheDriver {
9
8
  /**
10
9
  * The tags associated with this tagged cache instance
11
10
  */
@@ -120,5 +119,4 @@ declare class TaggedCache implements TaggedCacheDriver {
120
119
  decrement(key: CacheKey, value?: number): Promise<number>;
121
120
  }
122
121
  //#endregion
123
- export { TaggedCache };
124
122
  //# sourceMappingURL=tagged-cache.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tagged-cache.d.mts","names":[],"sources":["../../../../../../../packages/cache/src/tagged-cache.ts"],"mappings":";;;;;AAYA;;cAAa,WAAA,YAAuB,iBAAA;EAShB;;;EAAA,UALR,SAAA;EAsC6B;;;EAAA,UAjC7B,MAAA,EAAQ,WAAA;EAqGyB;;;cAhGxB,IAAA,YAAgB,MAAA,EAAQ,WAAA;EAqJF;;;EAAA,UA7I/B,MAAA,CAAO,GAAA;EA+JW;;;EAAA,UAxJZ,cAAA,CAAe,GAAA,EAAK,QAAA,GAAW,OAAA;EA2KtB;;;;;;;;;EA9JZ,oBAAA,CAAqB,GAAA,EAAK,QAAA,GAAW,OAAA;EAqP3B;;;;EAAA,0BAtOG,sBAAA;EA+PN;;;;;;EAAA,UAvPJ,QAAA,CAAS,MAAA,UAAgB,WAAA,WAAsB,OAAA;EAAA,UAgC/C,QAAA,CAAS,MAAA,UAAgB,OAAA,aAAoB,OAAA;EAAA,UAa7C,SAAA,CAAU,MAAA,WAAiB,OAAA;EAAA,UAQ3B,aAAA,CAAc,MAAA,UAAgB,OAAA,aAAoB,OAAA;EAAA,UAoBlD,aAAA,CAAc,MAAA,WAAiB,OAAA;;;;;;;;;;;YAkBrC,UAAA,CAAW,GAAA,EAAK,QAAA;EAlHa;;;EAAA,UAyHvB,aAAA,IAAiB,OAAA,CAAQ,GAAA;EAlGzB;;;EAiHH,GAAA,CACX,GAAA,EAAK,QAAA,EACL,KAAA,OACA,YAAA,GAAe,QAAA,GAAW,eAAA,GACzB,OAAA;EArFa;;;EAgGH,GAAA,CAAI,GAAA,EAAK,QAAA,GAAW,OAAA;EAnFjB;;;EA0FH,MAAA,CAAO,GAAA,EAAK,QAAA,GAAW,OAAA;EAlFN;;;EA+FjB,UAAA,IAAc,OAAA;EA3EG;;;EAAA,UA6Gd,cAAA,CAAe,UAAA,WAAqB,OAAA;EA3F/B;;;;EAqGR,KAAA,IAAS,OAAA;EA9Ef;;;EAqFM,GAAA,CAAI,GAAA,EAAK,QAAA,GAAW,OAAA;EAnFL;;;EA0Ff,QAAA,CAAS,GAAA,EAAK,QAAA,EAAU,GAAA,UAAa,QAAA,QAAgB,OAAA,QAAe,OAAA;EA9E3D;;;EA8FT,IAAA,CAAK,GAAA,EAAK,QAAA,GAAW,OAAA;EAvFT;;;EAsGZ,OAAA,CAAQ,GAAA,EAAK,QAAA,EAAU,KAAA,QAAa,OAAA;EAzFtB;;;EAgGd,SAAA,CACX,GAAA,EAAK,QAAA,EACL,KAAA,WACA,OAAA;IAAY,GAAA,GAAM,QAAA;EAAA,IACjB,OAAA;EAjDU;;;EA8DA,SAAA,CAAU,GAAA,EAAK,QAAA,EAAU,KAAA,YAAoB,OAAA;AAAA"}
1
+ {"version":3,"file":"tagged-cache.d.mts","names":[],"sources":["../../../../../../../packages/cache/src/tagged-cache.ts"],"mappings":";;;;;;qBAYa,uBAAuB;;;;YAIxB;;;;YAKA,QAAQ;;;;EAKlB,YAAmB,gBAAgB,QAAQ;;;;YAQjC,OAAO;;;;YAOD,eAAe,KAAK,WAAW;;;;;;;;;;EAalC,qBAAqB,KAAK,WAAW;;;;;4BAexB;;;;;;;YAQV,SAAS,gBAAgB,sBAAsB;YAgC/C,SAAS,gBAAgB,oBAAoB;YAa7C,UAAU,iBAAiB;YAQ3B,cAAc,gBAAgB,oBAAoB;YAoBlD,cAAc,iBAAiB;;;;;;;;;;;YAkBrC,WAAW,KAAK;;;;YAOV,iBAAiB,QAAQ;;;;EAe5B,IACX,KAAK,UACL,YACA,eAAe,WAAW,kBACzB;;;;EAWU,IAAI,KAAK,WAAW;;;;EAOpB,OAAO,KAAK,WAAW;;;;EAavB,cAAc;;;;YAkCX,eAAe,qBAAqB;;;;;EAUvC,SAAS;;;;EAOT,IAAI,KAAK,WAAW;;;;EAOpB,SAAS,KAAK,UAAU,aAAa,gBAAgB,eAAe;;;;EAgBpE,KAAK,KAAK,WAAW;;;;EAerB,QAAQ,KAAK,UAAU,aAAa;;;;EAOpC,UACX,KAAK,UACL,gBACA;IAAY,MAAM;MACjB;;;;EAaU,UAAU,KAAK,UAAU,iBAAoB"}
@@ -64,7 +64,7 @@ var TaggedCache = class TaggedCache {
64
64
  async addToTag(tagKey, members) {
65
65
  if (typeof this.driver.tagAdd === "function") return this.driver.tagAdd(tagKey, members);
66
66
  const current = await this.legacyMembers(tagKey);
67
- const merged = [...new Set([...current, ...members])];
67
+ const merged = [.../* @__PURE__ */ new Set([...current, ...members])];
68
68
  if (merged.length !== current.length) await this.driver.set(tagKey, merged, Infinity);
69
69
  }
70
70
  async membersOf(tagKey) {
@@ -1 +1 @@
1
- {"version":3,"file":"tagged-cache.mjs","names":[],"sources":["../../../../../../../packages/cache/src/tagged-cache.ts"],"sourcesContent":["import type { CacheDriver, CacheKey, CacheSetOptions, CacheTtl, TaggedCacheDriver } from \"./types\";\nimport { parseCacheKey } from \"./utils\";\n\n/** Implemented by `BaseCacheDriver`; a custom driver may not have it. */\ntype TagsInvalidatedEmitter = {\n emitTagsInvalidated(data: { tags: string[]; keys: string[]; durationMs: number }): Promise<void>;\n};\n\n/**\n * Tagged Cache Wrapper\n * Wraps a cache driver to automatically manage tag relationships\n */\nexport class TaggedCache implements TaggedCacheDriver {\n /**\n * The tags associated with this tagged cache instance\n */\n protected cacheTags: string[];\n\n /**\n * The underlying cache driver\n */\n protected driver: CacheDriver<any, any>;\n\n /**\n * Constructor\n */\n public constructor(tags: string[], driver: CacheDriver<any, any>) {\n this.cacheTags = tags;\n this.driver = driver;\n }\n\n /**\n * Get the tag key prefix for storing tag-key relationships\n */\n protected tagKey(tag: string): string {\n return `cache:tags:${tag}`;\n }\n\n /**\n * Store tag-key relationship\n */\n protected async storeTaggedKey(key: CacheKey): Promise<void> {\n await this.storeTagRelationship(key);\n }\n\n /**\n * Public alias of the tag-index writer. Called by `BaseCacheDriver.applyTags`\n * when tags are passed inline through `CacheSetOptions.tags`.\n *\n * Takes the caller's key, never the driver-parsed one — see {@link indexedKey}.\n *\n * @internal — public for cross-class use within this package; not part of the\n * stable consumer API.\n */\n public async storeTagRelationship(key: CacheKey): Promise<void> {\n const indexedKey = this.indexedKey(key);\n\n for (const tag of this.cacheTags) {\n const tagKey = this.tagKey(tag);\n\n await this.addToTag(tagKey, [indexedKey]);\n await this.pruneTag(tagKey, indexedKey);\n }\n }\n\n /**\n * Upper bound on member-existence checks per tagged write, so pruning stays\n * O(1) per write no matter how large an index grows.\n */\n protected static readonly PRUNE_CHECKS_PER_WRITE = 20;\n\n /**\n * Drop index members whose entry no longer exists (expired, evicted, or\n * removed outside a tagged handle). Checks a random window of at most\n * {@link PRUNE_CHECKS_PER_WRITE} members, so a long-lived index converges\n * to its live members without any write paying for a full scan.\n */\n protected async pruneTag(tagKey: string, justWritten: string): Promise<void> {\n const members = (await this.membersOf(tagKey)).filter((member) => member !== justWritten);\n\n if (members.length === 0) {\n return;\n }\n\n const limit = TaggedCache.PRUNE_CHECKS_PER_WRITE;\n const start = members.length > limit ? Math.floor(Math.random() * members.length) : 0;\n const window = Array.from(\n { length: Math.min(limit, members.length) },\n (_, index) => members[(start + index) % members.length]!,\n );\n\n const missing: string[] = [];\n\n for (const member of window) {\n if (!(await this.driver.has(member))) {\n missing.push(member);\n }\n }\n\n if (missing.length > 0) {\n await this.removeFromTag(tagKey, missing);\n }\n }\n\n // ------------------------------------------------------------\n // Tag-index access: the driver's primitives when it has them, else\n // the legacy get/set array (third-party drivers without primitives).\n // ------------------------------------------------------------\n\n protected async addToTag(tagKey: string, members: string[]): Promise<void> {\n if (typeof this.driver.tagAdd === \"function\") {\n return this.driver.tagAdd(tagKey, members);\n }\n\n const current = await this.legacyMembers(tagKey);\n const merged = [...new Set([...current, ...members])];\n\n if (merged.length !== current.length) {\n await this.driver.set(tagKey, merged, Infinity);\n }\n }\n\n protected async membersOf(tagKey: string): Promise<string[]> {\n if (typeof this.driver.tagMembers === \"function\") {\n return this.driver.tagMembers(tagKey);\n }\n\n return this.legacyMembers(tagKey);\n }\n\n protected async removeFromTag(tagKey: string, members: string[]): Promise<void> {\n if (typeof this.driver.tagRemove === \"function\") {\n return this.driver.tagRemove(tagKey, members);\n }\n\n const dropped = new Set(members);\n const current = await this.legacyMembers(tagKey);\n const kept = current.filter((member) => !dropped.has(member));\n\n if (kept.length === current.length) {\n return;\n }\n\n if (kept.length === 0) {\n await this.driver.remove(tagKey);\n } else {\n await this.driver.set(tagKey, kept, Infinity);\n }\n }\n\n protected async legacyMembers(tagKey: string): Promise<string[]> {\n const value = await this.driver.get(tagKey);\n\n return Array.isArray(value)\n ? value.filter((member): member is string => typeof member === \"string\")\n : [];\n }\n\n /**\n * The form a key takes inside a tag index: normalized but un-prefixed.\n *\n * The index is read back and deleted through `driver.remove(key)`, which\n * applies `globalPrefix` itself — so the index must hold the key *before*\n * the prefix, or invalidation would prefix it twice and delete nothing.\n * With a function prefix, the current prefix is re-applied at invalidation\n * time; the tag index key is resolved under that same prefix, so the two\n * agree as long as the prefix is stable for a given request/tenant.\n */\n protected indexedKey(key: CacheKey): string {\n return parseCacheKey(key);\n }\n\n /**\n * Get all keys associated with tags\n */\n protected async getTaggedKeys(): Promise<Set<string>> {\n const allKeys = new Set<string>();\n\n for (const tag of this.cacheTags) {\n for (const key of await this.membersOf(this.tagKey(tag))) {\n allKeys.add(key);\n }\n }\n\n return allKeys;\n }\n\n /**\n * {@inheritdoc}\n */\n public async set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n await this.driver.set(key, value, ttlOrOptions);\n\n await this.storeTaggedKey(key);\n\n return value;\n }\n\n /**\n * {@inheritdoc}\n */\n public async get(key: CacheKey): Promise<any | null> {\n return this.driver.get(key);\n }\n\n /**\n * {@inheritdoc}\n */\n public async remove(key: CacheKey): Promise<void> {\n const indexedKey = this.indexedKey(key);\n\n // Remove the value\n await this.driver.remove(key);\n\n // Remove from all tag relationships\n await this.detachFromTags(indexedKey);\n }\n\n /**\n * Invalidate (clear) all keys associated with the current tags\n */\n public async invalidate(): Promise<void> {\n const startedAt = performance.now();\n const removed = new Set<string>();\n\n for (const tag of this.cacheTags) {\n const tagKey = this.tagKey(tag);\n const members = await this.membersOf(tagKey);\n\n // Remove the tagged entries — indexed un-prefixed, so `remove()` prefixes once\n for (const member of members) {\n await this.driver.remove(member);\n removed.add(member);\n }\n\n // Remove exactly the members read above rather than dropping the whole\n // index: a key tagged concurrently (after the read) keeps its entry in\n // the index, so a later invalidate still reaches it.\n await this.removeFromTag(tagKey, members);\n }\n\n // Drivers built on `BaseCacheDriver` emit `invalidated`; a custom driver\n // without the method simply emits nothing.\n const driver = this.driver as Partial<TagsInvalidatedEmitter>;\n\n await driver.emitTagsInvalidated?.({\n tags: [...this.cacheTags],\n keys: [...removed],\n durationMs: performance.now() - startedAt,\n });\n }\n\n /**\n * Remove `indexedKey` from every tag of this handle.\n */\n protected async detachFromTags(indexedKey: string): Promise<void> {\n for (const tag of this.cacheTags) {\n await this.removeFromTag(this.tagKey(tag), [indexedKey]);\n }\n }\n\n /**\n * Flush all keys associated with the current tags\n * @deprecated Use invalidate() instead for better semantics\n */\n public async flush(): Promise<void> {\n return this.invalidate();\n }\n\n /**\n * {@inheritdoc}\n */\n public async has(key: CacheKey): Promise<boolean> {\n return this.driver.has(key);\n }\n\n /**\n * {@inheritdoc}\n */\n public async remember(key: CacheKey, ttl: number, callback: () => Promise<any>): Promise<any> {\n const value = await this.get(key);\n\n if (value !== null) {\n return value;\n }\n\n const result = await callback();\n await this.set(key, result, ttl);\n\n return result;\n }\n\n /**\n * {@inheritdoc}\n */\n public async pull(key: CacheKey): Promise<any | null> {\n // The driver's own `pull` (atomic on redis / pg / in-process drivers)\n // hands the value out once; then the key leaves this handle's tags.\n const value = await this.driver.pull(key);\n\n if (value !== null) {\n await this.detachFromTags(this.indexedKey(key));\n }\n\n return value;\n }\n\n /**\n * {@inheritdoc}\n */\n public async forever(key: CacheKey, value: any): Promise<any> {\n return this.set(key, value, Infinity);\n }\n\n /**\n * {@inheritdoc}\n */\n public async increment(\n key: CacheKey,\n value: number = 1,\n options?: { ttl?: CacheTtl },\n ): Promise<number> {\n // The driver's atomic increment (INCRBY / pg upsert / synchronous\n // in-process), then record the tag relationship.\n const newValue = await this.driver.increment(key, value, options);\n\n await this.storeTaggedKey(key);\n\n return newValue;\n }\n\n /**\n * {@inheritdoc}\n */\n public async decrement(key: CacheKey, value: number = 1): Promise<number> {\n return this.increment(key, -value);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"tagged-cache.mjs","names":[],"sources":["../../../../../../../packages/cache/src/tagged-cache.ts"],"sourcesContent":["import type { CacheDriver, CacheKey, CacheSetOptions, CacheTtl, TaggedCacheDriver } from \"./types\";\nimport { parseCacheKey } from \"./utils\";\n\n/** Implemented by `BaseCacheDriver`; a custom driver may not have it. */\ntype TagsInvalidatedEmitter = {\n emitTagsInvalidated(data: { tags: string[]; keys: string[]; durationMs: number }): Promise<void>;\n};\n\n/**\n * Tagged Cache Wrapper\n * Wraps a cache driver to automatically manage tag relationships\n */\nexport class TaggedCache implements TaggedCacheDriver {\n /**\n * The tags associated with this tagged cache instance\n */\n protected cacheTags: string[];\n\n /**\n * The underlying cache driver\n */\n protected driver: CacheDriver<any, any>;\n\n /**\n * Constructor\n */\n public constructor(tags: string[], driver: CacheDriver<any, any>) {\n this.cacheTags = tags;\n this.driver = driver;\n }\n\n /**\n * Get the tag key prefix for storing tag-key relationships\n */\n protected tagKey(tag: string): string {\n return `cache:tags:${tag}`;\n }\n\n /**\n * Store tag-key relationship\n */\n protected async storeTaggedKey(key: CacheKey): Promise<void> {\n await this.storeTagRelationship(key);\n }\n\n /**\n * Public alias of the tag-index writer. Called by `BaseCacheDriver.applyTags`\n * when tags are passed inline through `CacheSetOptions.tags`.\n *\n * Takes the caller's key, never the driver-parsed one — see {@link indexedKey}.\n *\n * @internal — public for cross-class use within this package; not part of the\n * stable consumer API.\n */\n public async storeTagRelationship(key: CacheKey): Promise<void> {\n const indexedKey = this.indexedKey(key);\n\n for (const tag of this.cacheTags) {\n const tagKey = this.tagKey(tag);\n\n await this.addToTag(tagKey, [indexedKey]);\n await this.pruneTag(tagKey, indexedKey);\n }\n }\n\n /**\n * Upper bound on member-existence checks per tagged write, so pruning stays\n * O(1) per write no matter how large an index grows.\n */\n protected static readonly PRUNE_CHECKS_PER_WRITE = 20;\n\n /**\n * Drop index members whose entry no longer exists (expired, evicted, or\n * removed outside a tagged handle). Checks a random window of at most\n * {@link PRUNE_CHECKS_PER_WRITE} members, so a long-lived index converges\n * to its live members without any write paying for a full scan.\n */\n protected async pruneTag(tagKey: string, justWritten: string): Promise<void> {\n const members = (await this.membersOf(tagKey)).filter((member) => member !== justWritten);\n\n if (members.length === 0) {\n return;\n }\n\n const limit = TaggedCache.PRUNE_CHECKS_PER_WRITE;\n const start = members.length > limit ? Math.floor(Math.random() * members.length) : 0;\n const window = Array.from(\n { length: Math.min(limit, members.length) },\n (_, index) => members[(start + index) % members.length]!,\n );\n\n const missing: string[] = [];\n\n for (const member of window) {\n if (!(await this.driver.has(member))) {\n missing.push(member);\n }\n }\n\n if (missing.length > 0) {\n await this.removeFromTag(tagKey, missing);\n }\n }\n\n // ------------------------------------------------------------\n // Tag-index access: the driver's primitives when it has them, else\n // the legacy get/set array (third-party drivers without primitives).\n // ------------------------------------------------------------\n\n protected async addToTag(tagKey: string, members: string[]): Promise<void> {\n if (typeof this.driver.tagAdd === \"function\") {\n return this.driver.tagAdd(tagKey, members);\n }\n\n const current = await this.legacyMembers(tagKey);\n const merged = [...new Set([...current, ...members])];\n\n if (merged.length !== current.length) {\n await this.driver.set(tagKey, merged, Infinity);\n }\n }\n\n protected async membersOf(tagKey: string): Promise<string[]> {\n if (typeof this.driver.tagMembers === \"function\") {\n return this.driver.tagMembers(tagKey);\n }\n\n return this.legacyMembers(tagKey);\n }\n\n protected async removeFromTag(tagKey: string, members: string[]): Promise<void> {\n if (typeof this.driver.tagRemove === \"function\") {\n return this.driver.tagRemove(tagKey, members);\n }\n\n const dropped = new Set(members);\n const current = await this.legacyMembers(tagKey);\n const kept = current.filter((member) => !dropped.has(member));\n\n if (kept.length === current.length) {\n return;\n }\n\n if (kept.length === 0) {\n await this.driver.remove(tagKey);\n } else {\n await this.driver.set(tagKey, kept, Infinity);\n }\n }\n\n protected async legacyMembers(tagKey: string): Promise<string[]> {\n const value = await this.driver.get(tagKey);\n\n return Array.isArray(value)\n ? value.filter((member): member is string => typeof member === \"string\")\n : [];\n }\n\n /**\n * The form a key takes inside a tag index: normalized but un-prefixed.\n *\n * The index is read back and deleted through `driver.remove(key)`, which\n * applies `globalPrefix` itself — so the index must hold the key *before*\n * the prefix, or invalidation would prefix it twice and delete nothing.\n * With a function prefix, the current prefix is re-applied at invalidation\n * time; the tag index key is resolved under that same prefix, so the two\n * agree as long as the prefix is stable for a given request/tenant.\n */\n protected indexedKey(key: CacheKey): string {\n return parseCacheKey(key);\n }\n\n /**\n * Get all keys associated with tags\n */\n protected async getTaggedKeys(): Promise<Set<string>> {\n const allKeys = new Set<string>();\n\n for (const tag of this.cacheTags) {\n for (const key of await this.membersOf(this.tagKey(tag))) {\n allKeys.add(key);\n }\n }\n\n return allKeys;\n }\n\n /**\n * {@inheritdoc}\n */\n public async set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n await this.driver.set(key, value, ttlOrOptions);\n\n await this.storeTaggedKey(key);\n\n return value;\n }\n\n /**\n * {@inheritdoc}\n */\n public async get(key: CacheKey): Promise<any | null> {\n return this.driver.get(key);\n }\n\n /**\n * {@inheritdoc}\n */\n public async remove(key: CacheKey): Promise<void> {\n const indexedKey = this.indexedKey(key);\n\n // Remove the value\n await this.driver.remove(key);\n\n // Remove from all tag relationships\n await this.detachFromTags(indexedKey);\n }\n\n /**\n * Invalidate (clear) all keys associated with the current tags\n */\n public async invalidate(): Promise<void> {\n const startedAt = performance.now();\n const removed = new Set<string>();\n\n for (const tag of this.cacheTags) {\n const tagKey = this.tagKey(tag);\n const members = await this.membersOf(tagKey);\n\n // Remove the tagged entries — indexed un-prefixed, so `remove()` prefixes once\n for (const member of members) {\n await this.driver.remove(member);\n removed.add(member);\n }\n\n // Remove exactly the members read above rather than dropping the whole\n // index: a key tagged concurrently (after the read) keeps its entry in\n // the index, so a later invalidate still reaches it.\n await this.removeFromTag(tagKey, members);\n }\n\n // Drivers built on `BaseCacheDriver` emit `invalidated`; a custom driver\n // without the method simply emits nothing.\n const driver = this.driver as Partial<TagsInvalidatedEmitter>;\n\n await driver.emitTagsInvalidated?.({\n tags: [...this.cacheTags],\n keys: [...removed],\n durationMs: performance.now() - startedAt,\n });\n }\n\n /**\n * Remove `indexedKey` from every tag of this handle.\n */\n protected async detachFromTags(indexedKey: string): Promise<void> {\n for (const tag of this.cacheTags) {\n await this.removeFromTag(this.tagKey(tag), [indexedKey]);\n }\n }\n\n /**\n * Flush all keys associated with the current tags\n * @deprecated Use invalidate() instead for better semantics\n */\n public async flush(): Promise<void> {\n return this.invalidate();\n }\n\n /**\n * {@inheritdoc}\n */\n public async has(key: CacheKey): Promise<boolean> {\n return this.driver.has(key);\n }\n\n /**\n * {@inheritdoc}\n */\n public async remember(key: CacheKey, ttl: number, callback: () => Promise<any>): Promise<any> {\n const value = await this.get(key);\n\n if (value !== null) {\n return value;\n }\n\n const result = await callback();\n await this.set(key, result, ttl);\n\n return result;\n }\n\n /**\n * {@inheritdoc}\n */\n public async pull(key: CacheKey): Promise<any | null> {\n // The driver's own `pull` (atomic on redis / pg / in-process drivers)\n // hands the value out once; then the key leaves this handle's tags.\n const value = await this.driver.pull(key);\n\n if (value !== null) {\n await this.detachFromTags(this.indexedKey(key));\n }\n\n return value;\n }\n\n /**\n * {@inheritdoc}\n */\n public async forever(key: CacheKey, value: any): Promise<any> {\n return this.set(key, value, Infinity);\n }\n\n /**\n * {@inheritdoc}\n */\n public async increment(\n key: CacheKey,\n value: number = 1,\n options?: { ttl?: CacheTtl },\n ): Promise<number> {\n // The driver's atomic increment (INCRBY / pg upsert / synchronous\n // in-process), then record the tag relationship.\n const newValue = await this.driver.increment(key, value, options);\n\n await this.storeTaggedKey(key);\n\n return newValue;\n }\n\n /**\n * {@inheritdoc}\n */\n public async decrement(key: CacheKey, value: number = 1): Promise<number> {\n return this.increment(key, -value);\n }\n}\n"],"mappings":"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"}
@@ -1,6 +1,5 @@
1
1
  import { CacheKey, CacheSetOptions, CacheTtl, RememberOptions, TaggedScopedCacheContract } from "./types.mjs";
2
2
  import { ScopedCache } from "./scoped-cache.mjs";
3
-
4
3
  //#region ../../packages/cache/src/tagged-scoped-cache.d.ts
5
4
  /**
6
5
  * One-shot tagged write handle on top of a {@link ScopedCache}.
@@ -30,7 +29,7 @@ import { ScopedCache } from "./scoped-cache.mjs";
30
29
  * await feed.tags(["unread"]).invalidate();
31
30
  * // → wipes everything tagged with user.<id> OR unread.
32
31
  */
33
- declare class TaggedScopedCache implements TaggedScopedCacheContract {
32
+ export declare class TaggedScopedCache implements TaggedScopedCacheContract {
34
33
  /**
35
34
  * The {@link ScopedCache} this handle delegates to. Held by reference so
36
35
  * scope-default changes (none today, but the option is preserved for the
@@ -113,5 +112,4 @@ declare class TaggedScopedCache implements TaggedScopedCacheContract {
113
112
  protected buildScopedKey(key: CacheKey): string;
114
113
  }
115
114
  //#endregion
116
- export { TaggedScopedCache };
117
115
  //# sourceMappingURL=tagged-scoped-cache.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tagged-scoped-cache.d.mts","names":[],"sources":["../../../../../../../packages/cache/src/tagged-scoped-cache.ts"],"mappings":";;;;;;AA4CA;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa,iBAAA,YAA6B,yBAAA;EAoIjC;;;;;EAAA,mBA9HY,KAAA,EAAO,WAAA;EAiIvB;;;;EAAA,mBA3HgB,UAAA;EA4I8B;;;;cAtI9B,KAAA,EAAO,WAAA,EAAa,UAAA;EAlB0B;;;;EA2B1D,GAAA,CAAI,GAAA,EAAK,QAAA,EAAU,KAAA,OAAY,YAAA,GAAe,QAAA,GAAW,eAAA,GAAkB,OAAA;;;;EAS3E,GAAA,UAAa,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,CAAA;EATrC;;;EAgBA,GAAA,CAAI,GAAA,EAAK,QAAA,GAAW,OAAA;EAhB0B;;;;EAwBxC,MAAA,CAAO,GAAA,EAAK,QAAA,GAAW,OAAA;EAfzB;;;EAuBE,IAAA,UAAc,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,CAAA;EAvBP;;;EAAA,UAoC5B,cAAA,CAAe,GAAA,EAAK,QAAA,GAAW,OAAA;EA7BpB;;;;EAgDpB,OAAA,UAAiB,GAAA,EAAK,QAAA,EAAU,KAAA,EAAO,CAAA,GAAI,OAAA,CAAQ,CAAA;EAhC7C;;;;;EA4CA,KAAA,CAAM,GAAA,EAAK,QAAA,EAAU,KAAA,OAAY,GAAA,YAAe,OAAA;EA/B7C;;;EA2DT,QAAA,UACL,GAAA,EAAK,QAAA,EACL,YAAA,EAAc,QAAA,GAAW,eAAA,EACzB,QAAA,QAAgB,OAAA,CAAQ,CAAA,IACvB,OAAA,CAAQ,CAAA;EA5CJ;;;;EAsDA,SAAA,CAAU,GAAA,EAAK,QAAA,EAAU,KAAA,WAAgB,OAAA;IAAY,GAAA,GAAM,QAAA;EAAA,IAAa,OAAA;EAtDrB;;;EA6DnD,SAAA,CAAU,GAAA,EAAK,QAAA,EAAU,KAAA,YAAiB,OAAA;EAjDf;;;;;EA0DrB,UAAA,IAAc,OAAA;EA7BzB;;;;;EAAA,UA4CQ,cAAA,CAAe,GAAA,EAAK,QAAA;AAAA"}
1
+ {"version":3,"file":"tagged-scoped-cache.d.mts","names":[],"sources":["../../../../../../../packages/cache/src/tagged-scoped-cache.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBA4Ca,6BAA6B;;;;;;qBAMrB,OAAO;;;;;qBAMP;;;;;EAMnB,YAAmB,OAAO,aAAa;;;;;EAShC,IAAI,KAAK,UAAU,YAAY,eAAe,WAAW,kBAAkB;;;;EAS3E,IAAI,SAAS,KAAK,WAAW,QAAQ;;;;EAOrC,IAAI,KAAK,WAAW;;;;;EAQd,OAAO,KAAK,WAAW;;;;EAQvB,KAAK,SAAS,KAAK,WAAW,QAAQ;;;;YAanC,eAAe,KAAK,WAAW;;;;;EAmBxC,QAAQ,SAAS,KAAK,UAAU,OAAO,IAAI,QAAQ;;;;;;EAY7C,MAAM,KAAK,UAAU,YAAY,eAAe;;;;EA4BtD,SAAS,SACd,KAAK,UACL,cAAc,WAAW,iBACzB,gBAAgB,QAAQ,KACvB,QAAQ;;;;;EAUJ,UAAU,KAAK,UAAU,gBAAgB;IAAY,MAAM;MAAa;;;;EAOxE,UAAU,KAAK,UAAU,iBAAiB;;;;;;EASpC,cAAc;;;;;;YAejB,eAAe,KAAK"}