@warlock.js/cache 5.19.0 → 5.20.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.
- package/CHANGELOG.md +55 -0
- package/cjs/index.cjs +1439 -331
- package/cjs/index.cjs.map +1 -1
- package/esm/cache-manager.d.mts +4 -0
- package/esm/cache-manager.d.mts.map +1 -1
- package/esm/cache-manager.mjs +23 -5
- package/esm/cache-manager.mjs.map +1 -1
- package/esm/cached/auto-key.d.mts +14 -14
- package/esm/cached/auto-key.d.mts.map +1 -1
- package/esm/cached/auto-key.mjs +50 -34
- package/esm/cached/auto-key.mjs.map +1 -1
- package/esm/cached/cached.mjs +4 -0
- package/esm/cached/cached.mjs.map +1 -1
- package/esm/drivers/base-cache-driver.d.mts +84 -8
- package/esm/drivers/base-cache-driver.d.mts.map +1 -1
- package/esm/drivers/base-cache-driver.mjs +152 -42
- package/esm/drivers/base-cache-driver.mjs.map +1 -1
- package/esm/drivers/file-cache-driver.d.mts +15 -0
- package/esm/drivers/file-cache-driver.d.mts.map +1 -1
- package/esm/drivers/file-cache-driver.mjs +68 -18
- package/esm/drivers/file-cache-driver.mjs.map +1 -1
- package/esm/drivers/in-memory-tag-index.d.mts +44 -0
- package/esm/drivers/in-memory-tag-index.d.mts.map +1 -0
- package/esm/drivers/in-memory-tag-index.mjs +73 -0
- package/esm/drivers/in-memory-tag-index.mjs.map +1 -0
- package/esm/drivers/index.d.mts +1 -1
- package/esm/drivers/index.mjs +1 -1
- package/esm/drivers/lru-memory-cache-driver.d.mts +43 -0
- package/esm/drivers/lru-memory-cache-driver.d.mts.map +1 -1
- package/esm/drivers/lru-memory-cache-driver.mjs +96 -0
- package/esm/drivers/lru-memory-cache-driver.mjs.map +1 -1
- package/esm/drivers/memory-cache-driver.d.mts +87 -30
- package/esm/drivers/memory-cache-driver.d.mts.map +1 -1
- package/esm/drivers/memory-cache-driver.mjs +214 -96
- package/esm/drivers/memory-cache-driver.mjs.map +1 -1
- package/esm/drivers/memory-extended-cache-driver.d.mts +9 -5
- package/esm/drivers/memory-extended-cache-driver.d.mts.map +1 -1
- package/esm/drivers/memory-extended-cache-driver.mjs +10 -23
- package/esm/drivers/memory-extended-cache-driver.mjs.map +1 -1
- package/esm/drivers/mock-cache-driver.d.mts +41 -0
- package/esm/drivers/mock-cache-driver.d.mts.map +1 -1
- package/esm/drivers/mock-cache-driver.mjs +105 -8
- package/esm/drivers/mock-cache-driver.mjs.map +1 -1
- package/esm/drivers/null-cache-driver.d.mts +8 -2
- package/esm/drivers/null-cache-driver.d.mts.map +1 -1
- package/esm/drivers/null-cache-driver.mjs +10 -2
- package/esm/drivers/null-cache-driver.mjs.map +1 -1
- package/esm/drivers/pg-cache-driver.d.mts +77 -3
- package/esm/drivers/pg-cache-driver.d.mts.map +1 -1
- package/esm/drivers/pg-cache-driver.mjs +229 -7
- package/esm/drivers/pg-cache-driver.mjs.map +1 -1
- package/esm/drivers/redis-cache-driver.d.mts +86 -1
- package/esm/drivers/redis-cache-driver.d.mts.map +1 -1
- package/esm/drivers/redis-cache-driver.mjs +252 -16
- package/esm/drivers/redis-cache-driver.mjs.map +1 -1
- package/esm/index.d.mts +2 -2
- package/esm/index.mjs +2 -2
- package/esm/list/memory-cache-list.d.mts +11 -8
- package/esm/list/memory-cache-list.d.mts.map +1 -1
- package/esm/list/memory-cache-list.mjs +39 -37
- package/esm/list/memory-cache-list.mjs.map +1 -1
- package/esm/scoped-cache.d.mts +9 -3
- package/esm/scoped-cache.d.mts.map +1 -1
- package/esm/scoped-cache.mjs +19 -7
- package/esm/scoped-cache.mjs.map +1 -1
- package/esm/tagged-cache.d.mts +20 -0
- package/esm/tagged-cache.d.mts.map +1 -1
- package/esm/tagged-cache.mjs +62 -24
- package/esm/tagged-cache.mjs.map +1 -1
- package/esm/tagged-scoped-cache.d.mts +7 -3
- package/esm/tagged-scoped-cache.d.mts.map +1 -1
- package/esm/tagged-scoped-cache.mjs +19 -7
- package/esm/tagged-scoped-cache.mjs.map +1 -1
- package/esm/types.d.mts +41 -3
- package/esm/types.d.mts.map +1 -1
- package/esm/types.mjs.map +1 -1
- package/esm/utils.d.mts +9 -3
- package/esm/utils.d.mts.map +1 -1
- package/esm/utils.mjs +16 -6
- package/esm/utils.mjs.map +1 -1
- package/llms-full.txt +1685 -1666
- package/llms.txt +18 -18
- package/package.json +4 -4
- package/skills/apply-cache-patterns/SKILL.md +98 -97
- package/skills/configure-pg-cache/SKILL.md +6 -5
- package/skills/configure-set-options/SKILL.md +102 -96
- package/skills/handle-cache-errors/SKILL.md +92 -92
- package/skills/index.md +20 -0
- package/skills/observe-cache/SKILL.md +103 -103
- package/skills/pick-cache-driver/SKILL.md +129 -123
- package/skills/test-cache-code/SKILL.md +219 -219
- package/skills/use-cache-atomic/SKILL.md +57 -65
- package/skills/use-cache-bulk/SKILL.md +57 -57
- package/skills/use-cache-list/SKILL.md +85 -85
- package/skills/use-cache-lock/SKILL.md +113 -104
- package/skills/use-cache-namespace/SKILL.md +88 -88
- package/skills/use-cache-similarity/SKILL.md +94 -94
- package/skills/use-cache-tags/SKILL.md +7 -4
- package/skills/use-cache-update-merge/SKILL.md +84 -84
- package/skills/use-cache-utils/SKILL.md +89 -89
- package/skills/use-cached-hof/SKILL.md +100 -99
- package/skills/use-swr/SKILL.md +104 -104
package/esm/cache-manager.d.mts
CHANGED
|
@@ -11,6 +11,10 @@ declare class CacheManager implements CacheDriver<any, any> {
|
|
|
11
11
|
* Loaded drivers
|
|
12
12
|
*/
|
|
13
13
|
loadedDrivers: Record<string, CacheDriver<any, any>>;
|
|
14
|
+
/**
|
|
15
|
+
* In-flight `load()` promises, so concurrent callers share one `connect()`.
|
|
16
|
+
*/
|
|
17
|
+
protected loadingDrivers: Map<string, Promise<CacheDriver<any, any>>>;
|
|
14
18
|
/**
|
|
15
19
|
* Configurations list
|
|
16
20
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache-manager.d.mts","names":[],"sources":["../../../../../../cache/src/cache-manager.ts"],"mappings":";;;;cAyBa,YAAA,YAAwB,WAAA;;AAArC;;EAIS,aAAA,GAAgB,WAAA;EAAA;;;EAKhB,aAAA,EAAe,MAAA,SAAe,WAAA;
|
|
1
|
+
{"version":3,"file":"cache-manager.d.mts","names":[],"sources":["../../../../../../cache/src/cache-manager.ts"],"mappings":"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"}
|
package/esm/cache-manager.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import { ScopedCache } from "./scoped-cache.mjs";
|
|
|
6
6
|
var CacheManager = class {
|
|
7
7
|
constructor() {
|
|
8
8
|
this.loadedDrivers = {};
|
|
9
|
+
this.loadingDrivers = /* @__PURE__ */ new Map();
|
|
9
10
|
this.configurations = {
|
|
10
11
|
drivers: {},
|
|
11
12
|
options: {}
|
|
@@ -237,6 +238,11 @@ var CacheManager = class {
|
|
|
237
238
|
this.assertNoConflictingReload(driver, runtimeOptions);
|
|
238
239
|
return this.loadedDrivers[driver];
|
|
239
240
|
}
|
|
241
|
+
const inFlight = this.loadingDrivers.get(driver);
|
|
242
|
+
if (inFlight) {
|
|
243
|
+
this.assertNoConflictingReload(driver, runtimeOptions);
|
|
244
|
+
return inFlight;
|
|
245
|
+
}
|
|
240
246
|
const Driver = this.configurations.drivers[driver];
|
|
241
247
|
if (!Driver) throw new CacheConfigurationError(`Cache driver ${driver} is not found, please declare it in the cache drivers in the configurations list.`);
|
|
242
248
|
const driverInstance = new Driver();
|
|
@@ -245,10 +251,18 @@ var CacheManager = class {
|
|
|
245
251
|
...configOptions,
|
|
246
252
|
...runtimeOptions ?? {}
|
|
247
253
|
});
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
254
|
+
const promise = (async () => {
|
|
255
|
+
try {
|
|
256
|
+
await driverInstance.connect();
|
|
257
|
+
this.attachGlobalListeners(driverInstance);
|
|
258
|
+
this.loadedDrivers[driver] = driverInstance;
|
|
259
|
+
return driverInstance;
|
|
260
|
+
} finally {
|
|
261
|
+
this.loadingDrivers.delete(driver);
|
|
262
|
+
}
|
|
263
|
+
})();
|
|
264
|
+
this.loadingDrivers.set(driver, promise);
|
|
265
|
+
return promise;
|
|
252
266
|
}
|
|
253
267
|
/**
|
|
254
268
|
* Guard against silently dropping runtime options on a re-load. Once a
|
|
@@ -272,7 +286,11 @@ var CacheManager = class {
|
|
|
272
286
|
* Disconnect the cache manager
|
|
273
287
|
*/
|
|
274
288
|
async disconnect() {
|
|
275
|
-
|
|
289
|
+
const drivers = new Set(Object.values(this.loadedDrivers));
|
|
290
|
+
if (this.currentDriver) drivers.add(this.currentDriver);
|
|
291
|
+
await Promise.allSettled([...drivers].map((driver) => driver.disconnect()));
|
|
292
|
+
for (const name of Object.keys(this.loadedDrivers)) delete this.loadedDrivers[name];
|
|
293
|
+
this.loadingDrivers.clear();
|
|
276
294
|
}
|
|
277
295
|
/**
|
|
278
296
|
* {@inheritdoc}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache-manager.mjs","names":[],"sources":["../../../../../../cache/src/cache-manager.ts"],"sourcesContent":["import { CacheMetricsCollector } from \"./metrics\";\r\nimport { ScopedCache } from \"./scoped-cache\";\r\nimport type {\r\n CacheConfigurations,\r\n CacheDriver,\r\n CacheEventHandler,\r\n CacheEventType,\r\n CacheKey,\r\n CacheListAccessor,\r\n CacheMetricsSnapshot,\r\n CacheNamespaceOptions,\r\n CacheSetOptions,\r\n CacheSimilarHit,\r\n CacheSimilarOptions,\r\n CacheSwrOptions,\r\n CacheTtl,\r\n DriverClass,\r\n LockOptions,\r\n LockOutcome,\r\n RememberOptions,\r\n ScopedCacheContract,\r\n TaggedCacheDriver,\r\n} from \"./types\";\r\nimport { CacheConfigurationError, CacheDriverNotInitializedError } from \"./types\";\r\n\r\nexport class CacheManager implements CacheDriver<any, any> {\r\n /**\r\n * Cache Driver\r\n */\r\n public currentDriver?: CacheDriver<any, any>;\r\n\r\n /**\r\n * Loaded drivers\r\n */\r\n public loadedDrivers: Record<string, CacheDriver<any, any>> = {};\r\n\r\n /**\r\n * Configurations list\r\n */\r\n protected configurations: CacheConfigurations = {\r\n drivers: {},\r\n options: {},\r\n };\r\n\r\n /**\r\n * Global event listeners\r\n */\r\n protected globalEventListeners: Map<CacheEventType, Set<CacheEventHandler>> = new Map();\r\n\r\n /**\r\n * Metrics collector — lazy on first {@link metrics} call so apps that\r\n * never read metrics pay zero cost. Once instantiated, it stays\r\n * subscribed to events for the manager's lifetime.\r\n */\r\n protected metricsCollector?: CacheMetricsCollector;\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public name = \"cacheManager\";\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public get client() {\r\n return this.currentDriver?.client;\r\n }\r\n\r\n /**\r\n * Set the cache configurations\r\n */\r\n public setCacheConfigurations(configurations: CacheConfigurations) {\r\n this.configurations.default = configurations.default;\r\n this.configurations.drivers = configurations.drivers;\r\n this.configurations.options = configurations.options;\r\n this.configurations.logging = configurations.logging;\r\n }\r\n\r\n /**\r\n * Set logging state\r\n */\r\n public setLoggingState(loggingState: boolean) {\r\n this.ensureDriverInitialized();\r\n\r\n this.currentDriver!.setLoggingState(loggingState);\r\n }\r\n\r\n /**\r\n * Switch the manager to a registered driver, optionally injecting runtime\r\n * options that merge over the static config.\r\n *\r\n * The string form looks the driver up in `setCacheConfigurations({ drivers })`,\r\n * loads it (or returns the cached instance), and sets it as `currentDriver`.\r\n * The instance form takes a pre-built driver and bypasses the registry; the\r\n * `runtimeOptions` argument is silently ignored in that case because the\r\n * instance was constructed externally.\r\n *\r\n * Runtime options merge over `config.options[name]` per-key — runtime wins.\r\n * Use this for constructor-only knobs that can't live in static config\r\n * (e.g. `pg`'s `client: pg.Pool`).\r\n *\r\n * @example\r\n * const pool = new Pool({ connectionString });\r\n * await cache.use(\"pg\", { client: pool });\r\n */\r\n public async use(\r\n driver: string | CacheDriver<any, any>,\r\n runtimeOptions?: Record<string, any>,\r\n ) {\r\n if (typeof driver === \"string\") {\r\n const driverInstance = await this.load(driver, runtimeOptions);\r\n\r\n if (!driverInstance) {\r\n throw new CacheConfigurationError(\r\n `Cache driver ${driver} is not found, please declare it in the cache drivers in the configurations list.`,\r\n );\r\n }\r\n\r\n driver = driverInstance;\r\n }\r\n\r\n this.attachGlobalListeners(driver);\r\n\r\n if (this.configurations.logging !== undefined) {\r\n driver.setLoggingState(this.configurations.logging);\r\n }\r\n\r\n this.currentDriver = driver;\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Ensure driver is initialized before operations\r\n */\r\n protected ensureDriverInitialized(): void {\r\n if (!this.currentDriver) {\r\n throw new CacheDriverNotInitializedError();\r\n }\r\n }\r\n\r\n /**\r\n * Return the running metrics snapshot — counters, hit-rate, latency\r\n * percentiles, per-driver breakdowns. Lazy-attaches the collector on\r\n * first call so apps that never read metrics pay zero cost.\r\n *\r\n * @example\r\n * const m = cache.metrics();\r\n * console.log(`hit rate: ${(m.hitRate * 100).toFixed(1)}%`);\r\n * console.log(`p95: ${m.latencyMs.p95.toFixed(2)}ms`);\r\n */\r\n public metrics(): CacheMetricsSnapshot {\r\n return this.ensureMetricsCollector().snapshot();\r\n }\r\n\r\n /**\r\n * Wipe every counter + latency sample and reset `startedAt` to now.\r\n * The collector itself stays subscribed to events.\r\n */\r\n public resetMetrics(): void {\r\n this.ensureMetricsCollector().reset();\r\n }\r\n\r\n /**\r\n * Lazy-construct the metrics collector and wire it to the global event\r\n * bus. Subsequent calls return the same instance — survives `cache.use()`\r\n * driver switches because handlers attach via `on()` and re-bind to every\r\n * loaded driver.\r\n */\r\n protected ensureMetricsCollector(): CacheMetricsCollector {\r\n if (this.metricsCollector) {\r\n return this.metricsCollector;\r\n }\r\n\r\n const collector = new CacheMetricsCollector();\r\n\r\n this.on(\"hit\", (data) => collector.recordEvent(\"hit\", data));\r\n this.on(\"miss\", (data) => collector.recordEvent(\"miss\", data));\r\n this.on(\"set\", (data) => collector.recordEvent(\"set\", data));\r\n this.on(\"removed\", (data) => collector.recordEvent(\"removed\", data));\r\n this.on(\"error\", (data) => collector.recordEvent(\"error\", data));\r\n\r\n this.metricsCollector = collector;\r\n\r\n return collector;\r\n }\r\n\r\n /**\r\n * Time the body, record the elapsed milliseconds against the metrics\r\n * collector for the given driver (defaults to the current driver's name).\r\n * Pass-through if the collector hasn't been instantiated yet — apps that\r\n * don't read metrics never pay for sample collection.\r\n */\r\n protected async timed<T>(\r\n body: () => Promise<T>,\r\n driverName?: string,\r\n ): Promise<T> {\r\n if (!this.metricsCollector) {\r\n return body();\r\n }\r\n\r\n const start = performance.now();\r\n\r\n try {\r\n return await body();\r\n } finally {\r\n const elapsed = performance.now() - start;\r\n const name = driverName ?? this.currentDriver?.name ?? \"unknown\";\r\n this.metricsCollector.recordLatency(name, elapsed);\r\n }\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async get<T = any>(key: CacheKey): Promise<T | null> {\r\n this.ensureDriverInitialized();\r\n return this.timed(() => this.currentDriver!.get<T>(key));\r\n }\r\n\r\n /**\r\n * Set a value in the cache.\r\n *\r\n * Accepts a positional TTL (number of seconds or duration string like `\"1h\"`)\r\n * or a rich {@link CacheSetOptions} object supporting `ttl`, `expiresAt`,\r\n * `tags`, `onConflict`, `namespace`, and per-call `driver` overrides.\r\n */\r\n public async set(key: CacheKey, value: any, ttlOrOptions?: CacheTtl | CacheSetOptions) {\r\n this.ensureDriverInitialized();\r\n\r\n const driverOverride =\r\n ttlOrOptions && typeof ttlOrOptions === \"object\" && \"driver\" in ttlOrOptions\r\n ? ttlOrOptions.driver\r\n : undefined;\r\n\r\n if (driverOverride) {\r\n const driver = await this.load(driverOverride);\r\n return this.timed(() => driver.set(key, value, ttlOrOptions), driver.name);\r\n }\r\n\r\n return this.timed(() => this.currentDriver!.set(key, value, ttlOrOptions));\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async remove(key: CacheKey) {\r\n this.ensureDriverInitialized();\r\n return this.timed(() => this.currentDriver!.remove(key));\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async removeNamespace(namespace: string) {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.removeNamespace(namespace);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async flush() {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.flush();\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async connect() {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.connect();\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public parseKey(key: CacheKey) {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.parseKey(key);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public get options() {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.options;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public setOptions(options: Record<string, any>) {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.setOptions(options || {});\r\n }\r\n\r\n /**\r\n * Return the loaded driver instance for `driverName`, loading it on first\r\n * call. Optional `runtimeOptions` follow the same merge-over-config rules\r\n * as {@link load}; passing options after the driver has already been\r\n * loaded throws to avoid silent swallowing.\r\n */\r\n public async driver(driverName: string, runtimeOptions?: Record<string, any>) {\r\n if (this.loadedDrivers[driverName]) {\r\n this.assertNoConflictingReload(driverName, runtimeOptions);\r\n\r\n return this.loadedDrivers[driverName];\r\n }\r\n\r\n return this.load(driverName, runtimeOptions);\r\n }\r\n\r\n /**\r\n * Initialize the cache manager and pick the default driver\r\n */\r\n public async init() {\r\n const defaultCacheDriverName = this.configurations.default;\r\n\r\n if (!defaultCacheDriverName) {\r\n return;\r\n }\r\n\r\n const driver = await this.driver(defaultCacheDriverName);\r\n\r\n await this.use(driver);\r\n }\r\n\r\n /**\r\n * Load and connect the registered driver named `driver`. First-call wins —\r\n * subsequent calls without `runtimeOptions` return the cached instance, and\r\n * subsequent calls *with* `runtimeOptions` throw {@link CacheConfigurationError}\r\n * to avoid silently dropping the new options.\r\n *\r\n * `runtimeOptions` merge over `config.options[driver]` per-key (runtime wins),\r\n * letting consumers split static knobs (table, ttl, globalPrefix) from\r\n * constructor-only ones (pg's `client`, custom adapters, etc.).\r\n *\r\n * @example\r\n * const pool = new Pool({ connectionString });\r\n * const pg = await cache.load(\"pg\", { client: pool });\r\n */\r\n public async load(driver: string, runtimeOptions?: Record<string, any>) {\r\n if (this.loadedDrivers[driver]) {\r\n this.assertNoConflictingReload(driver, runtimeOptions);\r\n\r\n return this.loadedDrivers[driver];\r\n }\r\n\r\n const Driver = this.configurations.drivers[\r\n driver as keyof typeof this.configurations.drivers\r\n ] as DriverClass | undefined;\r\n\r\n if (!Driver) {\r\n throw new CacheConfigurationError(\r\n `Cache driver ${driver} is not found, please declare it in the cache drivers in the configurations list.`,\r\n );\r\n }\r\n\r\n const driverInstance = new Driver();\r\n const configOptions =\r\n this.configurations.options[driver as keyof typeof this.configurations.options] || {};\r\n\r\n driverInstance.setOptions({ ...configOptions, ...(runtimeOptions ?? {}) });\r\n\r\n await driverInstance.connect();\r\n\r\n this.attachGlobalListeners(driverInstance);\r\n\r\n this.loadedDrivers[driver] = driverInstance;\r\n\r\n return driverInstance as CacheDriver<any, any>;\r\n }\r\n\r\n /**\r\n * Guard against silently dropping runtime options on a re-load. Once a\r\n * driver has been instantiated, its options are frozen — calling `load` /\r\n * `driver` / `use` again with a non-empty `runtimeOptions` would otherwise\r\n * appear to work but actually use the original options. We throw instead\r\n * so the misuse surfaces at the call site.\r\n */\r\n protected assertNoConflictingReload(\r\n driverName: string,\r\n runtimeOptions: Record<string, any> | undefined,\r\n ): void {\r\n if (runtimeOptions === undefined) {\r\n return;\r\n }\r\n\r\n if (Object.keys(runtimeOptions).length === 0) {\r\n return;\r\n }\r\n\r\n throw new CacheConfigurationError(\r\n `Cache driver '${driverName}' is already loaded; runtime options on subsequent calls are ignored — register a second driver name if you need a different configuration.`,\r\n );\r\n }\r\n\r\n /**\r\n * Register and bind a driver\r\n */\r\n public registerDriver(driverName: string, driverClass: DriverClass) {\r\n (this.configurations.drivers as Record<string, DriverClass>)[driverName] = driverClass;\r\n }\r\n\r\n /**\r\n * Disconnect the cache manager\r\n */\r\n public async disconnect() {\r\n if (this.currentDriver) {\r\n await this.currentDriver.disconnect();\r\n }\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async has(key: CacheKey): Promise<boolean> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.has(key);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async remember<T = any>(\r\n key: CacheKey,\r\n ttlOrOptions: CacheTtl | RememberOptions,\r\n callback: () => Promise<T>,\r\n ): Promise<T> {\r\n this.ensureDriverInitialized();\r\n\r\n const driverOverride =\r\n ttlOrOptions && typeof ttlOrOptions === \"object\" && \"driver\" in ttlOrOptions\r\n ? ttlOrOptions.driver\r\n : undefined;\r\n\r\n if (driverOverride) {\r\n const driver = await this.load(driverOverride);\r\n return driver.remember(key, ttlOrOptions, callback);\r\n }\r\n\r\n return this.currentDriver!.remember(key, ttlOrOptions, callback);\r\n }\r\n\r\n /**\r\n * Stale-while-revalidate. Returns cached when fresh, returns the stale\r\n * value plus a background refresh when within `freshTtl..staleTtl`,\r\n * blocks like a normal miss past `staleTtl`. Honors per-call `driver`\r\n * override the same way `remember()` does.\r\n *\r\n * @example\r\n * const product = await cache.swr(\r\n * \"product.42\",\r\n * { freshTtl: \"1m\", staleTtl: \"1h\" },\r\n * () => db.products.find(42),\r\n * );\r\n */\r\n public async swr<T = any>(\r\n key: CacheKey,\r\n options: CacheSwrOptions,\r\n callback: () => Promise<T>,\r\n ): Promise<T> {\r\n this.ensureDriverInitialized();\r\n\r\n const driverOverride = options.driver;\r\n\r\n if (driverOverride) {\r\n const driver = await this.load(driverOverride);\r\n\r\n return driver.swr<T>(key, options, callback);\r\n }\r\n\r\n return this.currentDriver!.swr<T>(key, options, callback);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async pull(key: CacheKey): Promise<any | null> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.pull(key);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async forever(key: CacheKey, value: any): Promise<any> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.forever(key, value);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async increment(key: CacheKey, value?: number): Promise<number> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.increment(key, value);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async decrement(key: CacheKey, value?: number): Promise<number> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.decrement(key, value);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async many(keys: CacheKey[]): Promise<any[]> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.many(keys);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async setMany(items: Record<string, any>, ttl?: number): Promise<void> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.setMany(items, ttl);\r\n }\r\n\r\n /**\r\n * Register a global event listener (applies to all drivers)\r\n */\r\n public on(event: CacheEventType, handler: CacheEventHandler): this {\r\n if (!this.globalEventListeners.has(event)) {\r\n this.globalEventListeners.set(event, new Set());\r\n }\r\n this.globalEventListeners.get(event)!.add(handler);\r\n\r\n // Also attach to current driver if exists\r\n if (this.currentDriver) {\r\n this.currentDriver.on(event, handler);\r\n }\r\n\r\n // Attach to all loaded drivers\r\n for (const driver of Object.values(this.loadedDrivers)) {\r\n driver.on(event, handler);\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Remove a global event listener\r\n */\r\n public off(event: CacheEventType, handler: CacheEventHandler): this {\r\n const handlers = this.globalEventListeners.get(event);\r\n if (handlers) {\r\n handlers.delete(handler);\r\n }\r\n\r\n // Also remove from current driver\r\n if (this.currentDriver) {\r\n this.currentDriver.off(event, handler);\r\n }\r\n\r\n // Remove from all loaded drivers\r\n for (const driver of Object.values(this.loadedDrivers)) {\r\n driver.off(event, handler);\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Register a one-time global event listener\r\n */\r\n public once(event: CacheEventType, handler: CacheEventHandler): this {\r\n const onceHandler: CacheEventHandler = async (data) => {\r\n await handler(data);\r\n this.off(event, onceHandler);\r\n };\r\n return this.on(event, onceHandler);\r\n }\r\n\r\n /**\r\n * Attach global listeners to a driver\r\n */\r\n protected attachGlobalListeners(driver: CacheDriver<any, any>) {\r\n for (const [event, handlers] of this.globalEventListeners) {\r\n for (const handler of handlers) {\r\n driver.on(event, handler);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Set if not exists (atomic operation)\r\n * Returns true if key was set, false if key already existed\r\n * Note: Only supported by drivers that implement setNX (e.g., Redis)\r\n */\r\n public async setNX(key: CacheKey, value: any, ttl?: number): Promise<boolean> {\r\n this.ensureDriverInitialized();\r\n\r\n if (!this.currentDriver!.setNX) {\r\n throw new Error(\r\n `setNX is not supported by the current cache driver: ${this.currentDriver!.name}`,\r\n );\r\n }\r\n\r\n return this.currentDriver!.setNX(key, value, ttl);\r\n }\r\n\r\n /**\r\n * Create a tagged cache instance for the given tags\r\n */\r\n public tags(tags: string[]): TaggedCacheDriver {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.tags(tags);\r\n }\r\n\r\n /**\r\n * Atomically read, transform, and write a cached value. Delegates to the current driver.\r\n */\r\n public async update<T = any>(\r\n key: CacheKey,\r\n fn: (current: T | null) => T | null | Promise<T | null>,\r\n options?: { ttl?: CacheTtl },\r\n ): Promise<T | null> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.update<T>(key, fn, options);\r\n }\r\n\r\n /**\r\n * Shallow-merge a partial object into a cached value.\r\n */\r\n public async merge<T extends Record<string, any> = Record<string, any>>(\r\n key: CacheKey,\r\n partial: Partial<T>,\r\n options?: { ttl?: CacheTtl },\r\n ): Promise<T> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.merge<T>(key, partial, options);\r\n }\r\n\r\n /**\r\n * Obtain a list accessor bound to the current driver.\r\n */\r\n public list<T = any>(key: CacheKey): CacheListAccessor<T> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.list<T>(key);\r\n }\r\n\r\n /**\r\n * Acquire a distributed lock, run `fn`, and auto-release. Returns a\r\n * {@link LockOutcome} discriminated union so callers can distinguish\r\n * \"ran and got this value\" from \"skipped because someone else holds it\".\r\n *\r\n * Honors the `driver` option for per-call driver override, same as `set`\r\n * and `remember`.\r\n *\r\n * @example\r\n * const outcome = await cache.lock(\"lock.import\", \"5m\", async () => {\r\n * await runImport();\r\n * return \"done\";\r\n * });\r\n * if (!outcome.acquired) {\r\n * console.log(\"another worker is already importing\");\r\n * }\r\n */\r\n public async lock<T>(\r\n key: CacheKey,\r\n ttlOrOptions: CacheTtl | LockOptions,\r\n fn: () => Promise<T>,\r\n ): Promise<LockOutcome<T>> {\r\n this.ensureDriverInitialized();\r\n\r\n const driverOverride =\r\n ttlOrOptions && typeof ttlOrOptions === \"object\" && \"driver\" in ttlOrOptions\r\n ? ttlOrOptions.driver\r\n : undefined;\r\n\r\n const driver = driverOverride\r\n ? await this.load(driverOverride)\r\n : this.currentDriver!;\r\n\r\n return driver.lock<T>(key, ttlOrOptions as CacheTtl | Omit<LockOptions, \"driver\">, fn);\r\n }\r\n\r\n /**\r\n * Similarity retrieval. Delegates to the current driver's `similar()` impl.\r\n *\r\n * Drivers that lack a similarity index throw {@link CacheUnsupportedError}.\r\n *\r\n * @example\r\n * const hits = await cache.similar(await embed(query), { topK: 5, threshold: 0.7 });\r\n */\r\n /**\r\n * Create a scoped view over the cache. Every key written through the\r\n * returned scope is automatically prefixed with `prefix`; optional defaults\r\n * (`ttl`, `tags`) flow through every write inside the scope.\r\n *\r\n * Per-call options always win over scope defaults. Scope tags merge\r\n * additively with per-call tags. Nested scopes inherit from the parent.\r\n *\r\n * @example\r\n * const chat = cache.namespace(\"chats.10\", { ttl: \"30d\" });\r\n * await chat.set(\"messages.1\", msg); // → \"chats.10.messages.1\", 30d\r\n * await chat.set(\"draft\", d, { ttl: \"1h\" }); // per-call ttl wins\r\n * await chat.namespace(\"typing\", { ttl: \"5s\" }).set(\"user.42\", true);\r\n * await chat.clear(); // wipe the whole scope\r\n */\r\n public namespace(prefix: string, options?: CacheNamespaceOptions): ScopedCacheContract {\r\n this.ensureDriverInitialized();\r\n return new ScopedCache(this, prefix, options);\r\n }\r\n\r\n public async similar<T = any>(\r\n vector: number[],\r\n options: CacheSimilarOptions,\r\n ): Promise<CacheSimilarHit<T>[]> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.similar<T>(vector, options);\r\n }\r\n}\r\n\r\nexport const cache = new CacheManager();\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cache-manager.mjs","names":[],"sources":["../../../../../../cache/src/cache-manager.ts"],"sourcesContent":["import { CacheMetricsCollector } from \"./metrics\";\nimport { ScopedCache } from \"./scoped-cache\";\nimport type {\n CacheConfigurations,\n CacheDriver,\n CacheEventHandler,\n CacheEventType,\n CacheKey,\n CacheListAccessor,\n CacheMetricsSnapshot,\n CacheNamespaceOptions,\n CacheSetOptions,\n CacheSimilarHit,\n CacheSimilarOptions,\n CacheSwrOptions,\n CacheTtl,\n DriverClass,\n LockOptions,\n LockOutcome,\n RememberOptions,\n ScopedCacheContract,\n TaggedCacheDriver,\n} from \"./types\";\nimport { CacheConfigurationError, CacheDriverNotInitializedError } from \"./types\";\n\nexport class CacheManager implements CacheDriver<any, any> {\n /**\n * Cache Driver\n */\n public currentDriver?: CacheDriver<any, any>;\n\n /**\n * Loaded drivers\n */\n public loadedDrivers: Record<string, CacheDriver<any, any>> = {};\n\n /**\n * In-flight `load()` promises, so concurrent callers share one `connect()`.\n */\n protected loadingDrivers = new Map<string, Promise<CacheDriver<any, any>>>();\n\n /**\n * Configurations list\n */\n protected configurations: CacheConfigurations = {\n drivers: {},\n options: {},\n };\n\n /**\n * Global event listeners\n */\n protected globalEventListeners: Map<CacheEventType, Set<CacheEventHandler>> = new Map();\n\n /**\n * Metrics collector — lazy on first {@link metrics} call so apps that\n * never read metrics pay zero cost. Once instantiated, it stays\n * subscribed to events for the manager's lifetime.\n */\n protected metricsCollector?: CacheMetricsCollector;\n\n /**\n * {@inheritdoc}\n */\n public name = \"cacheManager\";\n\n /**\n * {@inheritdoc}\n */\n public get client() {\n return this.currentDriver?.client;\n }\n\n /**\n * Set the cache configurations\n */\n public setCacheConfigurations(configurations: CacheConfigurations) {\n this.configurations.default = configurations.default;\n this.configurations.drivers = configurations.drivers;\n this.configurations.options = configurations.options;\n this.configurations.logging = configurations.logging;\n }\n\n /**\n * Set logging state\n */\n public setLoggingState(loggingState: boolean) {\n this.ensureDriverInitialized();\n\n this.currentDriver!.setLoggingState(loggingState);\n }\n\n /**\n * Switch the manager to a registered driver, optionally injecting runtime\n * options that merge over the static config.\n *\n * The string form looks the driver up in `setCacheConfigurations({ drivers })`,\n * loads it (or returns the cached instance), and sets it as `currentDriver`.\n * The instance form takes a pre-built driver and bypasses the registry; the\n * `runtimeOptions` argument is silently ignored in that case because the\n * instance was constructed externally.\n *\n * Runtime options merge over `config.options[name]` per-key — runtime wins.\n * Use this for constructor-only knobs that can't live in static config\n * (e.g. `pg`'s `client: pg.Pool`).\n *\n * @example\n * const pool = new Pool({ connectionString });\n * await cache.use(\"pg\", { client: pool });\n */\n public async use(\n driver: string | CacheDriver<any, any>,\n runtimeOptions?: Record<string, any>,\n ) {\n if (typeof driver === \"string\") {\n const driverInstance = await this.load(driver, runtimeOptions);\n\n if (!driverInstance) {\n throw new CacheConfigurationError(\n `Cache driver ${driver} is not found, please declare it in the cache drivers in the configurations list.`,\n );\n }\n\n driver = driverInstance;\n }\n\n this.attachGlobalListeners(driver);\n\n if (this.configurations.logging !== undefined) {\n driver.setLoggingState(this.configurations.logging);\n }\n\n this.currentDriver = driver;\n\n return this;\n }\n\n /**\n * Ensure driver is initialized before operations\n */\n protected ensureDriverInitialized(): void {\n if (!this.currentDriver) {\n throw new CacheDriverNotInitializedError();\n }\n }\n\n /**\n * Return the running metrics snapshot — counters, hit-rate, latency\n * percentiles, per-driver breakdowns. Lazy-attaches the collector on\n * first call so apps that never read metrics pay zero cost.\n *\n * @example\n * const m = cache.metrics();\n * console.log(`hit rate: ${(m.hitRate * 100).toFixed(1)}%`);\n * console.log(`p95: ${m.latencyMs.p95.toFixed(2)}ms`);\n */\n public metrics(): CacheMetricsSnapshot {\n return this.ensureMetricsCollector().snapshot();\n }\n\n /**\n * Wipe every counter + latency sample and reset `startedAt` to now.\n * The collector itself stays subscribed to events.\n */\n public resetMetrics(): void {\n this.ensureMetricsCollector().reset();\n }\n\n /**\n * Lazy-construct the metrics collector and wire it to the global event\n * bus. Subsequent calls return the same instance — survives `cache.use()`\n * driver switches because handlers attach via `on()` and re-bind to every\n * loaded driver.\n */\n protected ensureMetricsCollector(): CacheMetricsCollector {\n if (this.metricsCollector) {\n return this.metricsCollector;\n }\n\n const collector = new CacheMetricsCollector();\n\n this.on(\"hit\", (data) => collector.recordEvent(\"hit\", data));\n this.on(\"miss\", (data) => collector.recordEvent(\"miss\", data));\n this.on(\"set\", (data) => collector.recordEvent(\"set\", data));\n this.on(\"removed\", (data) => collector.recordEvent(\"removed\", data));\n this.on(\"error\", (data) => collector.recordEvent(\"error\", data));\n\n this.metricsCollector = collector;\n\n return collector;\n }\n\n /**\n * Time the body, record the elapsed milliseconds against the metrics\n * collector for the given driver (defaults to the current driver's name).\n * Pass-through if the collector hasn't been instantiated yet — apps that\n * don't read metrics never pay for sample collection.\n */\n protected async timed<T>(\n body: () => Promise<T>,\n driverName?: string,\n ): Promise<T> {\n if (!this.metricsCollector) {\n return body();\n }\n\n const start = performance.now();\n\n try {\n return await body();\n } finally {\n const elapsed = performance.now() - start;\n const name = driverName ?? this.currentDriver?.name ?? \"unknown\";\n this.metricsCollector.recordLatency(name, elapsed);\n }\n }\n\n /**\n * {@inheritdoc}\n */\n public async get<T = any>(key: CacheKey): Promise<T | null> {\n this.ensureDriverInitialized();\n return this.timed(() => this.currentDriver!.get<T>(key));\n }\n\n /**\n * Set a value in the cache.\n *\n * Accepts a positional TTL (number of seconds or duration string like `\"1h\"`)\n * or a rich {@link CacheSetOptions} object supporting `ttl`, `expiresAt`,\n * `tags`, `onConflict`, `namespace`, and per-call `driver` overrides.\n */\n public async set(key: CacheKey, value: any, ttlOrOptions?: CacheTtl | CacheSetOptions) {\n this.ensureDriverInitialized();\n\n const driverOverride =\n ttlOrOptions && typeof ttlOrOptions === \"object\" && \"driver\" in ttlOrOptions\n ? ttlOrOptions.driver\n : undefined;\n\n if (driverOverride) {\n const driver = await this.load(driverOverride);\n return this.timed(() => driver.set(key, value, ttlOrOptions), driver.name);\n }\n\n return this.timed(() => this.currentDriver!.set(key, value, ttlOrOptions));\n }\n\n /**\n * {@inheritdoc}\n */\n public async remove(key: CacheKey) {\n this.ensureDriverInitialized();\n return this.timed(() => this.currentDriver!.remove(key));\n }\n\n /**\n * {@inheritdoc}\n */\n public async removeNamespace(namespace: string) {\n this.ensureDriverInitialized();\n return this.currentDriver!.removeNamespace(namespace);\n }\n\n /**\n * {@inheritdoc}\n */\n public async flush() {\n this.ensureDriverInitialized();\n return this.currentDriver!.flush();\n }\n\n /**\n * {@inheritdoc}\n */\n public async connect() {\n this.ensureDriverInitialized();\n return this.currentDriver!.connect();\n }\n\n /**\n * {@inheritdoc}\n */\n public parseKey(key: CacheKey) {\n this.ensureDriverInitialized();\n return this.currentDriver!.parseKey(key);\n }\n\n /**\n * {@inheritdoc}\n */\n public get options() {\n this.ensureDriverInitialized();\n return this.currentDriver!.options;\n }\n\n /**\n * {@inheritdoc}\n */\n public setOptions(options: Record<string, any>) {\n this.ensureDriverInitialized();\n return this.currentDriver!.setOptions(options || {});\n }\n\n /**\n * Return the loaded driver instance for `driverName`, loading it on first\n * call. Optional `runtimeOptions` follow the same merge-over-config rules\n * as {@link load}; passing options after the driver has already been\n * loaded throws to avoid silent swallowing.\n */\n public async driver(driverName: string, runtimeOptions?: Record<string, any>) {\n if (this.loadedDrivers[driverName]) {\n this.assertNoConflictingReload(driverName, runtimeOptions);\n\n return this.loadedDrivers[driverName];\n }\n\n return this.load(driverName, runtimeOptions);\n }\n\n /**\n * Initialize the cache manager and pick the default driver\n */\n public async init() {\n const defaultCacheDriverName = this.configurations.default;\n\n if (!defaultCacheDriverName) {\n return;\n }\n\n const driver = await this.driver(defaultCacheDriverName);\n\n await this.use(driver);\n }\n\n /**\n * Load and connect the registered driver named `driver`. First-call wins —\n * subsequent calls without `runtimeOptions` return the cached instance, and\n * subsequent calls *with* `runtimeOptions` throw {@link CacheConfigurationError}\n * to avoid silently dropping the new options.\n *\n * `runtimeOptions` merge over `config.options[driver]` per-key (runtime wins),\n * letting consumers split static knobs (table, ttl, globalPrefix) from\n * constructor-only ones (pg's `client`, custom adapters, etc.).\n *\n * @example\n * const pool = new Pool({ connectionString });\n * const pg = await cache.load(\"pg\", { client: pool });\n */\n public async load(driver: string, runtimeOptions?: Record<string, any>) {\n if (this.loadedDrivers[driver]) {\n this.assertNoConflictingReload(driver, runtimeOptions);\n\n return this.loadedDrivers[driver];\n }\n\n const inFlight = this.loadingDrivers.get(driver);\n\n if (inFlight) {\n this.assertNoConflictingReload(driver, runtimeOptions);\n\n return inFlight;\n }\n\n const Driver = this.configurations.drivers[\n driver as keyof typeof this.configurations.drivers\n ] as DriverClass | undefined;\n\n if (!Driver) {\n throw new CacheConfigurationError(\n `Cache driver ${driver} is not found, please declare it in the cache drivers in the configurations list.`,\n );\n }\n\n const driverInstance = new Driver();\n const configOptions =\n this.configurations.options[driver as keyof typeof this.configurations.options] || {};\n\n driverInstance.setOptions({ ...configOptions, ...(runtimeOptions ?? {}) });\n\n const promise = (async () => {\n try {\n await driverInstance.connect();\n\n this.attachGlobalListeners(driverInstance);\n\n this.loadedDrivers[driver] = driverInstance;\n\n return driverInstance as CacheDriver<any, any>;\n } finally {\n this.loadingDrivers.delete(driver);\n }\n })();\n\n this.loadingDrivers.set(driver, promise);\n\n return promise;\n }\n\n /**\n * Guard against silently dropping runtime options on a re-load. Once a\n * driver has been instantiated, its options are frozen — calling `load` /\n * `driver` / `use` again with a non-empty `runtimeOptions` would otherwise\n * appear to work but actually use the original options. We throw instead\n * so the misuse surfaces at the call site.\n */\n protected assertNoConflictingReload(\n driverName: string,\n runtimeOptions: Record<string, any> | undefined,\n ): void {\n if (runtimeOptions === undefined) {\n return;\n }\n\n if (Object.keys(runtimeOptions).length === 0) {\n return;\n }\n\n throw new CacheConfigurationError(\n `Cache driver '${driverName}' is already loaded; runtime options on subsequent calls are ignored — register a second driver name if you need a different configuration.`,\n );\n }\n\n /**\n * Register and bind a driver\n */\n public registerDriver(driverName: string, driverClass: DriverClass) {\n (this.configurations.drivers as Record<string, DriverClass>)[driverName] = driverClass;\n }\n\n /**\n * Disconnect the cache manager\n */\n public async disconnect() {\n const drivers = new Set<any>(Object.values(this.loadedDrivers));\n\n if (this.currentDriver) {\n drivers.add(this.currentDriver);\n }\n\n await Promise.allSettled([...drivers].map(driver => driver.disconnect()));\n\n for (const name of Object.keys(this.loadedDrivers)) {\n delete this.loadedDrivers[name];\n }\n\n this.loadingDrivers.clear();\n }\n\n /**\n * {@inheritdoc}\n */\n public async has(key: CacheKey): Promise<boolean> {\n this.ensureDriverInitialized();\n return this.currentDriver!.has(key);\n }\n\n /**\n * {@inheritdoc}\n */\n public async remember<T = any>(\n key: CacheKey,\n ttlOrOptions: CacheTtl | RememberOptions,\n callback: () => Promise<T>,\n ): Promise<T> {\n this.ensureDriverInitialized();\n\n const driverOverride =\n ttlOrOptions && typeof ttlOrOptions === \"object\" && \"driver\" in ttlOrOptions\n ? ttlOrOptions.driver\n : undefined;\n\n if (driverOverride) {\n const driver = await this.load(driverOverride);\n return driver.remember(key, ttlOrOptions, callback);\n }\n\n return this.currentDriver!.remember(key, ttlOrOptions, callback);\n }\n\n /**\n * Stale-while-revalidate. Returns cached when fresh, returns the stale\n * value plus a background refresh when within `freshTtl..staleTtl`,\n * blocks like a normal miss past `staleTtl`. Honors per-call `driver`\n * override the same way `remember()` does.\n *\n * @example\n * const product = await cache.swr(\n * \"product.42\",\n * { freshTtl: \"1m\", staleTtl: \"1h\" },\n * () => db.products.find(42),\n * );\n */\n public async swr<T = any>(\n key: CacheKey,\n options: CacheSwrOptions,\n callback: () => Promise<T>,\n ): Promise<T> {\n this.ensureDriverInitialized();\n\n const driverOverride = options.driver;\n\n if (driverOverride) {\n const driver = await this.load(driverOverride);\n\n return driver.swr<T>(key, options, callback);\n }\n\n return this.currentDriver!.swr<T>(key, options, callback);\n }\n\n /**\n * {@inheritdoc}\n */\n public async pull(key: CacheKey): Promise<any | null> {\n this.ensureDriverInitialized();\n return this.currentDriver!.pull(key);\n }\n\n /**\n * {@inheritdoc}\n */\n public async forever(key: CacheKey, value: any): Promise<any> {\n this.ensureDriverInitialized();\n return this.currentDriver!.forever(key, value);\n }\n\n /**\n * {@inheritdoc}\n */\n public async increment(key: CacheKey, value?: number): Promise<number> {\n this.ensureDriverInitialized();\n return this.currentDriver!.increment(key, value);\n }\n\n /**\n * {@inheritdoc}\n */\n public async decrement(key: CacheKey, value?: number): Promise<number> {\n this.ensureDriverInitialized();\n return this.currentDriver!.decrement(key, value);\n }\n\n /**\n * {@inheritdoc}\n */\n public async many(keys: CacheKey[]): Promise<any[]> {\n this.ensureDriverInitialized();\n return this.currentDriver!.many(keys);\n }\n\n /**\n * {@inheritdoc}\n */\n public async setMany(items: Record<string, any>, ttl?: number): Promise<void> {\n this.ensureDriverInitialized();\n return this.currentDriver!.setMany(items, ttl);\n }\n\n /**\n * Register a global event listener (applies to all drivers)\n */\n public on(event: CacheEventType, handler: CacheEventHandler): this {\n if (!this.globalEventListeners.has(event)) {\n this.globalEventListeners.set(event, new Set());\n }\n this.globalEventListeners.get(event)!.add(handler);\n\n // Also attach to current driver if exists\n if (this.currentDriver) {\n this.currentDriver.on(event, handler);\n }\n\n // Attach to all loaded drivers\n for (const driver of Object.values(this.loadedDrivers)) {\n driver.on(event, handler);\n }\n\n return this;\n }\n\n /**\n * Remove a global event listener\n */\n public off(event: CacheEventType, handler: CacheEventHandler): this {\n const handlers = this.globalEventListeners.get(event);\n if (handlers) {\n handlers.delete(handler);\n }\n\n // Also remove from current driver\n if (this.currentDriver) {\n this.currentDriver.off(event, handler);\n }\n\n // Remove from all loaded drivers\n for (const driver of Object.values(this.loadedDrivers)) {\n driver.off(event, handler);\n }\n\n return this;\n }\n\n /**\n * Register a one-time global event listener\n */\n public once(event: CacheEventType, handler: CacheEventHandler): this {\n const onceHandler: CacheEventHandler = async (data) => {\n await handler(data);\n this.off(event, onceHandler);\n };\n return this.on(event, onceHandler);\n }\n\n /**\n * Attach global listeners to a driver\n */\n protected attachGlobalListeners(driver: CacheDriver<any, any>) {\n for (const [event, handlers] of this.globalEventListeners) {\n for (const handler of handlers) {\n driver.on(event, handler);\n }\n }\n }\n\n /**\n * Set if not exists (atomic operation)\n * Returns true if key was set, false if key already existed\n * Note: Only supported by drivers that implement setNX (e.g., Redis)\n */\n public async setNX(key: CacheKey, value: any, ttl?: number): Promise<boolean> {\n this.ensureDriverInitialized();\n\n if (!this.currentDriver!.setNX) {\n throw new Error(\n `setNX is not supported by the current cache driver: ${this.currentDriver!.name}`,\n );\n }\n\n return this.currentDriver!.setNX(key, value, ttl);\n }\n\n /**\n * Create a tagged cache instance for the given tags\n */\n public tags(tags: string[]): TaggedCacheDriver {\n this.ensureDriverInitialized();\n return this.currentDriver!.tags(tags);\n }\n\n /**\n * Atomically read, transform, and write a cached value. Delegates to the current driver.\n */\n public async update<T = any>(\n key: CacheKey,\n fn: (current: T | null) => T | null | Promise<T | null>,\n options?: { ttl?: CacheTtl },\n ): Promise<T | null> {\n this.ensureDriverInitialized();\n return this.currentDriver!.update<T>(key, fn, options);\n }\n\n /**\n * Shallow-merge a partial object into a cached value.\n */\n public async merge<T extends Record<string, any> = Record<string, any>>(\n key: CacheKey,\n partial: Partial<T>,\n options?: { ttl?: CacheTtl },\n ): Promise<T> {\n this.ensureDriverInitialized();\n return this.currentDriver!.merge<T>(key, partial, options);\n }\n\n /**\n * Obtain a list accessor bound to the current driver.\n */\n public list<T = any>(key: CacheKey): CacheListAccessor<T> {\n this.ensureDriverInitialized();\n return this.currentDriver!.list<T>(key);\n }\n\n /**\n * Acquire a distributed lock, run `fn`, and auto-release. Returns a\n * {@link LockOutcome} discriminated union so callers can distinguish\n * \"ran and got this value\" from \"skipped because someone else holds it\".\n *\n * Honors the `driver` option for per-call driver override, same as `set`\n * and `remember`.\n *\n * @example\n * const outcome = await cache.lock(\"lock.import\", \"5m\", async () => {\n * await runImport();\n * return \"done\";\n * });\n * if (!outcome.acquired) {\n * console.log(\"another worker is already importing\");\n * }\n */\n public async lock<T>(\n key: CacheKey,\n ttlOrOptions: CacheTtl | LockOptions,\n fn: () => Promise<T>,\n ): Promise<LockOutcome<T>> {\n this.ensureDriverInitialized();\n\n const driverOverride =\n ttlOrOptions && typeof ttlOrOptions === \"object\" && \"driver\" in ttlOrOptions\n ? ttlOrOptions.driver\n : undefined;\n\n const driver = driverOverride\n ? await this.load(driverOverride)\n : this.currentDriver!;\n\n return driver.lock<T>(key, ttlOrOptions as CacheTtl | Omit<LockOptions, \"driver\">, fn);\n }\n\n /**\n * Similarity retrieval. Delegates to the current driver's `similar()` impl.\n *\n * Drivers that lack a similarity index throw {@link CacheUnsupportedError}.\n *\n * @example\n * const hits = await cache.similar(await embed(query), { topK: 5, threshold: 0.7 });\n */\n /**\n * Create a scoped view over the cache. Every key written through the\n * returned scope is automatically prefixed with `prefix`; optional defaults\n * (`ttl`, `tags`) flow through every write inside the scope.\n *\n * Per-call options always win over scope defaults. Scope tags merge\n * additively with per-call tags. Nested scopes inherit from the parent.\n *\n * @example\n * const chat = cache.namespace(\"chats.10\", { ttl: \"30d\" });\n * await chat.set(\"messages.1\", msg); // → \"chats.10.messages.1\", 30d\n * await chat.set(\"draft\", d, { ttl: \"1h\" }); // per-call ttl wins\n * await chat.namespace(\"typing\", { ttl: \"5s\" }).set(\"user.42\", true);\n * await chat.clear(); // wipe the whole scope\n */\n public namespace(prefix: string, options?: CacheNamespaceOptions): ScopedCacheContract {\n this.ensureDriverInitialized();\n return new ScopedCache(this, prefix, options);\n }\n\n public async similar<T = any>(\n vector: number[],\n options: CacheSimilarOptions,\n ): Promise<CacheSimilarHit<T>[]> {\n this.ensureDriverInitialized();\n return this.currentDriver!.similar<T>(vector, options);\n }\n}\n\nexport const cache = new CacheManager();\n"],"mappings":"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"}
|
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Derive a cache key from a prefix and a set of function arguments.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
5
|
+
* The key is `<prefix>.<hash>` where `hash` is the first 16 hex chars of the
|
|
6
|
+
* sha1 of a stable JSON encoding of the args. Hashing keeps the mapping
|
|
7
|
+
* injective — raw punctuation-joined segments can be folded together by
|
|
8
|
+
* `parseCacheKey` (e.g. `("1.private", "")` vs `("1", "private")`).
|
|
9
|
+
*
|
|
10
|
+
* Encoding rules: object keys are sorted, `Date` → ISO string, `undefined` →
|
|
11
|
+
* `{"$u":1}`, `bigint` → decimal string with an `n` suffix.
|
|
12
|
+
*
|
|
13
|
+
* No args → the prefix alone.
|
|
14
|
+
*
|
|
15
|
+
* Map, Set, functions, symbols and class instances cannot be encoded stably
|
|
16
|
+
* and throw `CacheConfigurationError`; supply a custom `key` function instead.
|
|
14
17
|
*
|
|
15
18
|
* @example
|
|
16
|
-
* deriveAutoKey("
|
|
17
|
-
* deriveAutoKey("
|
|
18
|
-
* deriveAutoKey("featured", []); // "featured"
|
|
19
|
-
* deriveAutoKey("search", [{ q: "hello" }]); // "search.[{\"q\":\"hello\"}]"
|
|
20
|
-
* deriveAutoKey("user", [null, undefined]); // "user.null.undefined"
|
|
19
|
+
* deriveAutoKey("featured", []); // "featured"
|
|
20
|
+
* deriveAutoKey("user", [42]); // "user.<16 hex chars>"
|
|
21
21
|
*/
|
|
22
22
|
declare function deriveAutoKey(prefix: string, args: readonly unknown[]): string;
|
|
23
23
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto-key.d.mts","names":[],"sources":["../../../../../../../cache/src/cached/auto-key.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"auto-key.d.mts","names":[],"sources":["../../../../../../../cache/src/cached/auto-key.ts"],"mappings":";;AAuBA;;;;AAAsE;;;;;;;;;;;;;;;iBAAtD,aAAA,CAAc,MAAA,UAAgB,IAAwB"}
|
package/esm/cached/auto-key.mjs
CHANGED
|
@@ -1,53 +1,69 @@
|
|
|
1
1
|
import { CacheConfigurationError } from "../types.mjs";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
|
|
3
4
|
//#region ../cache/src/cached/auto-key.ts
|
|
4
5
|
/**
|
|
5
6
|
* Derive a cache key from a prefix and a set of function arguments.
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
8
|
+
* The key is `<prefix>.<hash>` where `hash` is the first 16 hex chars of the
|
|
9
|
+
* sha1 of a stable JSON encoding of the args. Hashing keeps the mapping
|
|
10
|
+
* injective — raw punctuation-joined segments can be folded together by
|
|
11
|
+
* `parseCacheKey` (e.g. `("1.private", "")` vs `("1", "private")`).
|
|
12
|
+
*
|
|
13
|
+
* Encoding rules: object keys are sorted, `Date` → ISO string, `undefined` →
|
|
14
|
+
* `{"$u":1}`, `bigint` → decimal string with an `n` suffix.
|
|
15
|
+
*
|
|
16
|
+
* No args → the prefix alone.
|
|
17
|
+
*
|
|
18
|
+
* Map, Set, functions, symbols and class instances cannot be encoded stably
|
|
19
|
+
* and throw `CacheConfigurationError`; supply a custom `key` function instead.
|
|
16
20
|
*
|
|
17
21
|
* @example
|
|
18
|
-
* deriveAutoKey("
|
|
19
|
-
* deriveAutoKey("
|
|
20
|
-
* deriveAutoKey("featured", []); // "featured"
|
|
21
|
-
* deriveAutoKey("search", [{ q: "hello" }]); // "search.[{\"q\":\"hello\"}]"
|
|
22
|
-
* deriveAutoKey("user", [null, undefined]); // "user.null.undefined"
|
|
22
|
+
* deriveAutoKey("featured", []); // "featured"
|
|
23
|
+
* deriveAutoKey("user", [42]); // "user.<16 hex chars>"
|
|
23
24
|
*/
|
|
24
25
|
function deriveAutoKey(prefix, args) {
|
|
25
26
|
if (args.length === 0) return prefix;
|
|
26
|
-
|
|
27
|
+
let encoded;
|
|
27
28
|
try {
|
|
28
|
-
|
|
29
|
+
encoded = JSON.stringify(args.map((arg) => normalize(arg, prefix, /* @__PURE__ */ new Set())));
|
|
29
30
|
} catch (error) {
|
|
30
|
-
|
|
31
|
+
if (error instanceof CacheConfigurationError) throw error;
|
|
32
|
+
throw new CacheConfigurationError(`cached(): could not derive an auto-key from args for prefix "${prefix}". The args are not serializable. Use the options form with a custom key function. Original error: ${error.message}`);
|
|
31
33
|
}
|
|
34
|
+
return `${prefix}.${createHash("sha1").update(encoded).digest("hex").slice(0, 16)}`;
|
|
32
35
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
* JSON serialization. Adding `bigint` here avoids the `JSON.stringify` throw on
|
|
36
|
-
* top-level bigint args.
|
|
37
|
-
*/
|
|
38
|
-
function isPrimitiveOrNullish(value) {
|
|
39
|
-
if (value === null || value === void 0) return true;
|
|
40
|
-
const type = typeof value;
|
|
41
|
-
return type === "string" || type === "number" || type === "boolean" || type === "bigint";
|
|
36
|
+
function unsupported(prefix, what) {
|
|
37
|
+
throw new CacheConfigurationError(`cached(): cannot derive an auto-key for prefix "${prefix}" from ${what}. Use the options form with a custom key function.`);
|
|
42
38
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
39
|
+
function normalize(value, prefix, seen) {
|
|
40
|
+
if (value === void 0) return { $u: 1 };
|
|
41
|
+
if (value === null) return null;
|
|
42
|
+
switch (typeof value) {
|
|
43
|
+
case "string":
|
|
44
|
+
case "boolean": return value;
|
|
45
|
+
case "number": return Number.isFinite(value) ? value : { $n: String(value) };
|
|
46
|
+
case "bigint": return `${value.toString()}n`;
|
|
47
|
+
case "function": return unsupported(prefix, "a function argument");
|
|
48
|
+
case "symbol": return unsupported(prefix, "a symbol argument");
|
|
49
|
+
}
|
|
50
|
+
const obj = value;
|
|
51
|
+
if (obj instanceof Date) return Number.isNaN(obj.getTime()) ? { $d: "invalid" } : obj.toISOString();
|
|
52
|
+
if (obj instanceof Map) return unsupported(prefix, "a Map argument");
|
|
53
|
+
if (obj instanceof Set) return unsupported(prefix, "a Set argument");
|
|
54
|
+
if (seen.has(obj)) throw new CacheConfigurationError(`cached(): could not derive an auto-key for prefix "${prefix}": circular reference in args. Use the options form with a custom key function.`);
|
|
55
|
+
seen.add(obj);
|
|
56
|
+
let result;
|
|
57
|
+
if (Array.isArray(obj)) result = obj.map((item) => normalize(item, prefix, seen));
|
|
58
|
+
else {
|
|
59
|
+
const proto = Object.getPrototypeOf(obj);
|
|
60
|
+
if (proto !== Object.prototype && proto !== null) unsupported(prefix, "a class instance argument");
|
|
61
|
+
const out = {};
|
|
62
|
+
for (const key of Object.keys(obj).sort()) out[key] = normalize(obj[key], prefix, seen);
|
|
63
|
+
result = out;
|
|
64
|
+
}
|
|
65
|
+
seen.delete(obj);
|
|
66
|
+
return result;
|
|
51
67
|
}
|
|
52
68
|
|
|
53
69
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto-key.mjs","names":[],"sources":["../../../../../../../cache/src/cached/auto-key.ts"],"sourcesContent":["import { CacheConfigurationError } from \"../types\";\n\n/**\n * Derive a cache key from a prefix and a set of function arguments.\n *\n *
|
|
1
|
+
{"version":3,"file":"auto-key.mjs","names":[],"sources":["../../../../../../../cache/src/cached/auto-key.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { CacheConfigurationError } from \"../types\";\n\n/**\n * Derive a cache key from a prefix and a set of function arguments.\n *\n * The key is `<prefix>.<hash>` where `hash` is the first 16 hex chars of the\n * sha1 of a stable JSON encoding of the args. Hashing keeps the mapping\n * injective — raw punctuation-joined segments can be folded together by\n * `parseCacheKey` (e.g. `(\"1.private\", \"\")` vs `(\"1\", \"private\")`).\n *\n * Encoding rules: object keys are sorted, `Date` → ISO string, `undefined` →\n * `{\"$u\":1}`, `bigint` → decimal string with an `n` suffix.\n *\n * No args → the prefix alone.\n *\n * Map, Set, functions, symbols and class instances cannot be encoded stably\n * and throw `CacheConfigurationError`; supply a custom `key` function instead.\n *\n * @example\n * deriveAutoKey(\"featured\", []); // \"featured\"\n * deriveAutoKey(\"user\", [42]); // \"user.<16 hex chars>\"\n */\nexport function deriveAutoKey(prefix: string, args: readonly unknown[]): string {\n if (args.length === 0) {\n return prefix;\n }\n\n let encoded: string;\n\n try {\n encoded = JSON.stringify(args.map((arg) => normalize(arg, prefix, new Set())));\n } catch (error) {\n if (error instanceof CacheConfigurationError) {\n throw error;\n }\n\n throw new CacheConfigurationError(\n `cached(): could not derive an auto-key from args for prefix \"${prefix}\". ` +\n `The args are not serializable. Use the options form with a custom key function. ` +\n `Original error: ${(error as Error).message}`,\n );\n }\n\n const hash = createHash(\"sha1\").update(encoded).digest(\"hex\").slice(0, 16);\n\n return `${prefix}.${hash}`;\n}\n\nfunction unsupported(prefix: string, what: string): never {\n throw new CacheConfigurationError(\n `cached(): cannot derive an auto-key for prefix \"${prefix}\" from ${what}. ` +\n `Use the options form with a custom key function.`,\n );\n}\n\nfunction normalize(value: unknown, prefix: string, seen: Set<object>): unknown {\n if (value === undefined) return { $u: 1 };\n if (value === null) return null;\n\n switch (typeof value) {\n case \"string\":\n case \"boolean\":\n return value;\n case \"number\":\n // NaN / Infinity would collapse to null in JSON\n return Number.isFinite(value) ? value : { $n: String(value) };\n case \"bigint\":\n return `${value.toString()}n`;\n case \"function\":\n return unsupported(prefix, \"a function argument\");\n case \"symbol\":\n return unsupported(prefix, \"a symbol argument\");\n }\n\n const obj = value as object;\n\n if (obj instanceof Date) {\n return Number.isNaN(obj.getTime()) ? { $d: \"invalid\" } : obj.toISOString();\n }\n\n if (obj instanceof Map) return unsupported(prefix, \"a Map argument\");\n if (obj instanceof Set) return unsupported(prefix, \"a Set argument\");\n\n if (seen.has(obj)) {\n throw new CacheConfigurationError(\n `cached(): could not derive an auto-key for prefix \"${prefix}\": circular reference in args. ` +\n `Use the options form with a custom key function.`,\n );\n }\n\n seen.add(obj);\n\n let result: unknown;\n\n if (Array.isArray(obj)) {\n result = obj.map((item) => normalize(item, prefix, seen));\n } else {\n const proto = Object.getPrototypeOf(obj);\n\n if (proto !== Object.prototype && proto !== null) {\n unsupported(prefix, \"a class instance argument\");\n }\n\n const out: Record<string, unknown> = {};\n\n for (const key of Object.keys(obj).sort()) {\n out[key] = normalize((obj as Record<string, unknown>)[key], prefix, seen);\n }\n\n result = out;\n }\n\n seen.delete(obj);\n\n return result;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,cAAc,QAAgB,MAAkC;CAC9E,IAAI,KAAK,WAAW,GAClB,OAAO;CAGT,IAAI;CAEJ,IAAI;EACF,UAAU,KAAK,UAAU,KAAK,KAAK,QAAQ,UAAU,KAAK,wBAAQ,IAAI,IAAI,CAAC,CAAC,CAAC;CAC/E,SAAS,OAAO;EACd,IAAI,iBAAiB,yBACnB,MAAM;EAGR,MAAM,IAAI,wBACR,gEAAgE,OAAO,qGAEjD,MAAgB,SACxC;CACF;CAIA,OAAO,GAAG,OAAO,GAFJ,WAAW,MAAM,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,GAAG,EAEhD;AACzB;AAEA,SAAS,YAAY,QAAgB,MAAqB;CACxD,MAAM,IAAI,wBACR,mDAAmD,OAAO,SAAS,KAAK,mDAE1E;AACF;AAEA,SAAS,UAAU,OAAgB,QAAgB,MAA4B;CAC7E,IAAI,UAAU,QAAW,OAAO,EAAE,IAAI,EAAE;CACxC,IAAI,UAAU,MAAM,OAAO;CAE3B,QAAQ,OAAO,OAAf;EACE,KAAK;EACL,KAAK,WACH,OAAO;EACT,KAAK,UAEH,OAAO,OAAO,SAAS,KAAK,IAAI,QAAQ,EAAE,IAAI,OAAO,KAAK,EAAE;EAC9D,KAAK,UACH,OAAO,GAAG,MAAM,SAAS,EAAE;EAC7B,KAAK,YACH,OAAO,YAAY,QAAQ,qBAAqB;EAClD,KAAK,UACH,OAAO,YAAY,QAAQ,mBAAmB;CAClD;CAEA,MAAM,MAAM;CAEZ,IAAI,eAAe,MACjB,OAAO,OAAO,MAAM,IAAI,QAAQ,CAAC,IAAI,EAAE,IAAI,UAAU,IAAI,IAAI,YAAY;CAG3E,IAAI,eAAe,KAAK,OAAO,YAAY,QAAQ,gBAAgB;CACnE,IAAI,eAAe,KAAK,OAAO,YAAY,QAAQ,gBAAgB;CAEnE,IAAI,KAAK,IAAI,GAAG,GACd,MAAM,IAAI,wBACR,sDAAsD,OAAO,gFAE/D;CAGF,KAAK,IAAI,GAAG;CAEZ,IAAI;CAEJ,IAAI,MAAM,QAAQ,GAAG,GACnB,SAAS,IAAI,KAAK,SAAS,UAAU,MAAM,QAAQ,IAAI,CAAC;MACnD;EACL,MAAM,QAAQ,OAAO,eAAe,GAAG;EAEvC,IAAI,UAAU,OAAO,aAAa,UAAU,MAC1C,YAAY,QAAQ,2BAA2B;EAGjD,MAAM,MAA+B,CAAC;EAEtC,KAAK,MAAM,OAAO,OAAO,KAAK,GAAG,CAAC,CAAC,KAAK,GACtC,IAAI,OAAO,UAAW,IAAgC,MAAM,QAAQ,IAAI;EAG1E,SAAS;CACX;CAEA,KAAK,OAAO,GAAG;CAEf,OAAO;AACT"}
|
package/esm/cached/cached.mjs
CHANGED
|
@@ -15,6 +15,10 @@ function cached(fn, prefixOrOptions, maybeTtl) {
|
|
|
15
15
|
});
|
|
16
16
|
wrapper.invalidate = async (...args) => {
|
|
17
17
|
const key = config.key(...args);
|
|
18
|
+
if (config.driver) {
|
|
19
|
+
await (await cache.driver(config.driver)).remove(key);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
18
22
|
await cache.remove(key);
|
|
19
23
|
};
|
|
20
24
|
return wrapper;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cached.mjs","names":[],"sources":["../../../../../../../cache/src/cached/cached.ts"],"sourcesContent":["import { cache } from \"../cache-manager\";\nimport type { CacheTtl, RememberOptions } from \"../types\";\nimport type { CachedOptions } from \"./normalize-args\";\nimport { normalizeCachedArgs } from \"./normalize-args\";\n\n/**\n * The shape returned by `cached()`. Callable like the wrapped function, plus\n * helpers for manual invalidation.\n */\nexport type CachedFn<Args extends unknown[], R> = ((...args: Args) => Promise<R>) & {\n /**\n * Drop the cache entry for a specific argument combination.\n *\n * @example\n * const getUser = cached((id: number) => db.users.find(id), \"user\", \"1h\");\n * await getUser.invalidate(42); // drops \"user.42\"\n */\n invalidate(...args: Args): Promise<void>;\n};\n\n/**\n * Wrap an async function so every invocation runs through the cache.\n *\n * Uses `cache.remember()` internally — inherits its stampede-protection\n * guarantees. Three calling shapes, `fn` always first.\n *\n * @example\n * // Shorthand — prefix auto-expands with the function's arguments.\n * // getUser(42) caches under \"user.42\".\n * const getUser = cached(\n * (id: number) => db.users.find(id),\n * \"user\",\n * \"1h\",\n * );\n *\n * @example\n * // Options form — custom key function, tags, per-call driver override.\n * const searchProducts = cached(\n * (filters: ProductFilters) => db.products.search(filters),\n * {\n * key: (filters) => `products.search.${filters.category}.${filters.sort}`,\n * ttl: \"15m\",\n * tags: [\"products\"],\n * },\n * );\n *\n * @example\n * // Manual invalidation — uses the same key scheme the wrapper uses internally.\n * await getUser.invalidate(42);\n */\nexport function cached<Args extends unknown[], R>(\n fn: (...args: Args) => Promise<R>,\n prefix: string,\n): CachedFn<Args, R>;\nexport function cached<Args extends unknown[], R>(\n fn: (...args: Args) => Promise<R>,\n prefix: string,\n ttl: CacheTtl,\n): CachedFn<Args, R>;\nexport function cached<Args extends unknown[], R>(\n fn: (...args: Args) => Promise<R>,\n options: CachedOptions<Args>,\n): CachedFn<Args, R>;\nexport function cached<Args extends unknown[], R>(\n fn: (...args: Args) => Promise<R>,\n prefixOrOptions: string | CachedOptions<Args>,\n maybeTtl?: CacheTtl,\n): CachedFn<Args, R> {\n const config = normalizeCachedArgs<Args>(prefixOrOptions, maybeTtl);\n\n // Build the RememberOptions payload once per call. `ttl` may still be undefined\n // here — that's fine, `remember` falls back to the driver's default TTL.\n const buildRememberOptions = (): RememberOptions => ({\n ttl: config.ttl,\n tags: config.tags,\n driver: config.driver,\n });\n\n const wrapper = (async (...args: Args): Promise<R> => {\n const key = config.key(...args);\n return cache.remember<R>(key, buildRememberOptions(), () => fn(...args));\n }) as CachedFn<Args, R>;\n\n wrapper.invalidate = async (...args: Args): Promise<void> => {\n const key = config.key(...args);\n await cache.remove(key);\n };\n\n return wrapper;\n}\n"],"mappings":";;;;AA+DA,SAAgB,OACd,IACA,iBACA,UACmB;CACnB,MAAM,SAAS,oBAA0B,iBAAiB,QAAQ;CAIlE,MAAM,8BAA+C;EACnD,KAAK,OAAO;EACZ,MAAM,OAAO;EACb,QAAQ,OAAO;CACjB;CAEA,MAAM,WAAW,OAAO,GAAG,SAA2B;EACpD,MAAM,MAAM,OAAO,IAAI,GAAG,IAAI;EAC9B,OAAO,MAAM,SAAY,KAAK,qBAAqB,SAAS,GAAG,GAAG,IAAI,CAAC;CACzE;CAEA,QAAQ,aAAa,OAAO,GAAG,SAA8B;EAC3D,MAAM,MAAM,OAAO,IAAI,GAAG,IAAI;EAC9B,MAAM,MAAM,OAAO,GAAG;CACxB;CAEA,OAAO;AACT"}
|
|
1
|
+
{"version":3,"file":"cached.mjs","names":[],"sources":["../../../../../../../cache/src/cached/cached.ts"],"sourcesContent":["import { cache } from \"../cache-manager\";\nimport type { CacheTtl, RememberOptions } from \"../types\";\nimport type { CachedOptions } from \"./normalize-args\";\nimport { normalizeCachedArgs } from \"./normalize-args\";\n\n/**\n * The shape returned by `cached()`. Callable like the wrapped function, plus\n * helpers for manual invalidation.\n */\nexport type CachedFn<Args extends unknown[], R> = ((...args: Args) => Promise<R>) & {\n /**\n * Drop the cache entry for a specific argument combination.\n *\n * @example\n * const getUser = cached((id: number) => db.users.find(id), \"user\", \"1h\");\n * await getUser.invalidate(42); // drops \"user.42\"\n */\n invalidate(...args: Args): Promise<void>;\n};\n\n/**\n * Wrap an async function so every invocation runs through the cache.\n *\n * Uses `cache.remember()` internally — inherits its stampede-protection\n * guarantees. Three calling shapes, `fn` always first.\n *\n * @example\n * // Shorthand — prefix auto-expands with the function's arguments.\n * // getUser(42) caches under \"user.42\".\n * const getUser = cached(\n * (id: number) => db.users.find(id),\n * \"user\",\n * \"1h\",\n * );\n *\n * @example\n * // Options form — custom key function, tags, per-call driver override.\n * const searchProducts = cached(\n * (filters: ProductFilters) => db.products.search(filters),\n * {\n * key: (filters) => `products.search.${filters.category}.${filters.sort}`,\n * ttl: \"15m\",\n * tags: [\"products\"],\n * },\n * );\n *\n * @example\n * // Manual invalidation — uses the same key scheme the wrapper uses internally.\n * await getUser.invalidate(42);\n */\nexport function cached<Args extends unknown[], R>(\n fn: (...args: Args) => Promise<R>,\n prefix: string,\n): CachedFn<Args, R>;\nexport function cached<Args extends unknown[], R>(\n fn: (...args: Args) => Promise<R>,\n prefix: string,\n ttl: CacheTtl,\n): CachedFn<Args, R>;\nexport function cached<Args extends unknown[], R>(\n fn: (...args: Args) => Promise<R>,\n options: CachedOptions<Args>,\n): CachedFn<Args, R>;\nexport function cached<Args extends unknown[], R>(\n fn: (...args: Args) => Promise<R>,\n prefixOrOptions: string | CachedOptions<Args>,\n maybeTtl?: CacheTtl,\n): CachedFn<Args, R> {\n const config = normalizeCachedArgs<Args>(prefixOrOptions, maybeTtl);\n\n // Build the RememberOptions payload once per call. `ttl` may still be undefined\n // here — that's fine, `remember` falls back to the driver's default TTL.\n const buildRememberOptions = (): RememberOptions => ({\n ttl: config.ttl,\n tags: config.tags,\n driver: config.driver,\n });\n\n const wrapper = (async (...args: Args): Promise<R> => {\n const key = config.key(...args);\n return cache.remember<R>(key, buildRememberOptions(), () => fn(...args));\n }) as CachedFn<Args, R>;\n\n wrapper.invalidate = async (...args: Args): Promise<void> => {\n const key = config.key(...args);\n if (config.driver) {\n await (await cache.driver(config.driver)).remove(key);\n return;\n }\n\n await cache.remove(key);\n };\n\n return wrapper;\n}\n"],"mappings":";;;;AA+DA,SAAgB,OACd,IACA,iBACA,UACmB;CACnB,MAAM,SAAS,oBAA0B,iBAAiB,QAAQ;CAIlE,MAAM,8BAA+C;EACnD,KAAK,OAAO;EACZ,MAAM,OAAO;EACb,QAAQ,OAAO;CACjB;CAEA,MAAM,WAAW,OAAO,GAAG,SAA2B;EACpD,MAAM,MAAM,OAAO,IAAI,GAAG,IAAI;EAC9B,OAAO,MAAM,SAAY,KAAK,qBAAqB,SAAS,GAAG,GAAG,IAAI,CAAC;CACzE;CAEA,QAAQ,aAAa,OAAO,GAAG,SAA8B;EAC3D,MAAM,MAAM,OAAO,IAAI,GAAG,IAAI;EAC9B,IAAI,OAAO,QAAQ;GACjB,OAAO,MAAM,MAAM,OAAO,OAAO,MAAM,EAAC,CAAE,OAAO,GAAG;GACpD;EACF;EAEA,MAAM,MAAM,OAAO,GAAG;CACxB;CAEA,OAAO;AACT"}
|
|
@@ -122,6 +122,35 @@ declare abstract class BaseCacheDriver<ClientType, Options extends Record<string
|
|
|
122
122
|
* callers match them against their own parsed storage keys.
|
|
123
123
|
*/
|
|
124
124
|
protected getKeysForTags(tags: string[] | undefined): Promise<Set<string> | null>;
|
|
125
|
+
/**
|
|
126
|
+
* {@inheritdoc}
|
|
127
|
+
*
|
|
128
|
+
* Default implementation: the index is an ordinary entry holding a string
|
|
129
|
+
* array, read-modified-written under {@link runSerialized} — correct within
|
|
130
|
+
* THIS process only. Drivers with a native primitive override it (redis SET,
|
|
131
|
+
* pg atomic upsert, the in-process drivers' {@link InMemoryTagIndex}).
|
|
132
|
+
*/
|
|
133
|
+
tagAdd(tagKey: CacheKey, members: string[]): Promise<void>;
|
|
134
|
+
/**
|
|
135
|
+
* {@inheritdoc}
|
|
136
|
+
*/
|
|
137
|
+
tagMembers(tagKey: CacheKey): Promise<string[]>;
|
|
138
|
+
/**
|
|
139
|
+
* {@inheritdoc}
|
|
140
|
+
*
|
|
141
|
+
* Default implementation: serialized read-filter-write (process-local); an
|
|
142
|
+
* emptied index is removed.
|
|
143
|
+
*/
|
|
144
|
+
tagRemove(tagKey: CacheKey, members: string[]): Promise<void>;
|
|
145
|
+
/**
|
|
146
|
+
* {@inheritdoc}
|
|
147
|
+
*/
|
|
148
|
+
tagDelete(tagKey: CacheKey): Promise<void>;
|
|
149
|
+
/**
|
|
150
|
+
* The index stored as a plain entry, or `[]`. Non-string members (a corrupted
|
|
151
|
+
* or foreign value) are ignored rather than trusted.
|
|
152
|
+
*/
|
|
153
|
+
protected readTagArray(tagKey: CacheKey): Promise<string[]>;
|
|
125
154
|
/**
|
|
126
155
|
* Apply tag relationships after a successful write. Called by drivers once
|
|
127
156
|
* the value is in storage.
|
|
@@ -147,9 +176,35 @@ declare abstract class BaseCacheDriver<ClientType, Options extends Record<string
|
|
|
147
176
|
*/
|
|
148
177
|
has(key: CacheKey): Promise<boolean>;
|
|
149
178
|
/**
|
|
150
|
-
*
|
|
179
|
+
* In-flight `remember()` computations, one per parsed key (stampede guard).
|
|
151
180
|
*/
|
|
152
|
-
protected
|
|
181
|
+
protected rememberInFlight: Map<string, Promise<any>>;
|
|
182
|
+
/**
|
|
183
|
+
* In-flight `swr()` cold-miss fetches, one per parsed key.
|
|
184
|
+
*/
|
|
185
|
+
protected swrFetchInFlight: Map<string, Promise<any>>;
|
|
186
|
+
/**
|
|
187
|
+
* In-flight `swr()` stale-window background refreshes, one per parsed key.
|
|
188
|
+
*/
|
|
189
|
+
protected swrRefreshes: Map<string, Promise<void>>;
|
|
190
|
+
/**
|
|
191
|
+
* Per-key serialization chains for the in-process read-modify-write
|
|
192
|
+
* operations (`update`, and the default `increment` / `pull`). Kept apart
|
|
193
|
+
* from the stampede maps so a `remember()` can never break an update chain,
|
|
194
|
+
* and never receives an unrelated promise.
|
|
195
|
+
*/
|
|
196
|
+
protected updateChains: Map<string, Promise<any>>;
|
|
197
|
+
/**
|
|
198
|
+
* Run `task` once per key at a time: concurrent callers for the same
|
|
199
|
+
* `parsedKey` share the in-flight promise registered in `inFlight`.
|
|
200
|
+
*/
|
|
201
|
+
protected singleFlight<T>(inFlight: Map<string, Promise<any>>, parsedKey: string, task: () => Promise<T>): Promise<T>;
|
|
202
|
+
/**
|
|
203
|
+
* Chain `task` after every earlier serialized task for the same key, so
|
|
204
|
+
* read-modify-write callers in THIS process run one at a time. This is
|
|
205
|
+
* process-local: it does not coordinate with other servers.
|
|
206
|
+
*/
|
|
207
|
+
protected runSerialized<T>(parsedKey: string, task: () => Promise<T>): Promise<T>;
|
|
153
208
|
/**
|
|
154
209
|
* {@inheritdoc}
|
|
155
210
|
*/
|
|
@@ -164,8 +219,9 @@ declare abstract class BaseCacheDriver<ClientType, Options extends Record<string
|
|
|
164
219
|
*
|
|
165
220
|
* Default implementation: read raw entry, branch on freshness/staleness,
|
|
166
221
|
* trigger background refresh in the stale window, fall through to
|
|
167
|
-
* `callback` on miss/expiry. Concurrent
|
|
168
|
-
*
|
|
222
|
+
* `callback` on miss/expiry. Concurrent cold-miss callers share one fetch
|
|
223
|
+
* ({@link swrFetchInFlight}); concurrent stale-window callers share one
|
|
224
|
+
* background refresh ({@link swrRefreshes}).
|
|
169
225
|
*
|
|
170
226
|
* Drivers without a real {@link getEntry} override degrade gracefully —
|
|
171
227
|
* the synthetic entry has no `staleAt`, which the freshness check treats
|
|
@@ -211,6 +267,11 @@ declare abstract class BaseCacheDriver<ClientType, Options extends Record<string
|
|
|
211
267
|
protected scheduleSwrRefresh<T>(parsedKey: string, key: CacheKey, options: CacheSwrOptions, callback: () => Promise<T>, freshSeconds: number, staleSeconds: number): void;
|
|
212
268
|
/**
|
|
213
269
|
* {@inheritdoc}
|
|
270
|
+
*
|
|
271
|
+
* Default implementation: get-then-remove, serialized per key in THIS
|
|
272
|
+
* process. Drivers with a native primitive override it to be atomic across
|
|
273
|
+
* servers (redis `GETDEL`, pg `DELETE … RETURNING`) or process-atomic
|
|
274
|
+
* (memory, lru, mock).
|
|
214
275
|
*/
|
|
215
276
|
pull(key: CacheKey): Promise<any | null>;
|
|
216
277
|
/**
|
|
@@ -219,6 +280,11 @@ declare abstract class BaseCacheDriver<ClientType, Options extends Record<string
|
|
|
219
280
|
forever(key: CacheKey, value: any): Promise<any>;
|
|
220
281
|
/**
|
|
221
282
|
* {@inheritdoc}
|
|
283
|
+
*
|
|
284
|
+
* Default implementation: read-add-write serialized per key in THIS process,
|
|
285
|
+
* keeping the entry's remaining TTL (a missing key starts from 0 with the
|
|
286
|
+
* driver default TTL). Not atomic across servers: drivers with a native
|
|
287
|
+
* primitive override it (redis `INCRBY`, pg single-statement upsert).
|
|
222
288
|
*/
|
|
223
289
|
increment(key: CacheKey, value?: number): Promise<number>;
|
|
224
290
|
/**
|
|
@@ -280,9 +346,9 @@ declare abstract class BaseCacheDriver<ClientType, Options extends Record<string
|
|
|
280
346
|
/**
|
|
281
347
|
* {@inheritdoc}
|
|
282
348
|
*
|
|
283
|
-
* Default implementation: read → transform → write
|
|
284
|
-
*
|
|
285
|
-
*
|
|
349
|
+
* Default implementation: read → transform → write, serialized per key in
|
|
350
|
+
* THIS process only. Drivers that can offer cross-server atomicity (redis,
|
|
351
|
+
* pg: compare-and-set with retries) override it.
|
|
286
352
|
*/
|
|
287
353
|
update<T = any>(key: CacheKey, fn: (current: T | null) => T | null | Promise<T | null>, options?: {
|
|
288
354
|
ttl?: CacheTtl;
|
|
@@ -308,9 +374,19 @@ declare abstract class BaseCacheDriver<ClientType, Options extends Record<string
|
|
|
308
374
|
* The lock value is the resolved `owner` (defaults to `pid.<process.pid>`).
|
|
309
375
|
*
|
|
310
376
|
* Always releases in `finally`, even if `fn` throws — the thrown error
|
|
311
|
-
* propagates to the caller unchanged
|
|
377
|
+
* propagates to the caller unchanged, and a failed release is logged rather
|
|
378
|
+
* than replacing `fn`'s result or error. The ttl must be finite and
|
|
379
|
+
* positive; there is no renewal, so it must exceed the worst-case duration.
|
|
312
380
|
*/
|
|
313
381
|
lock<T>(key: CacheKey, ttlOrOptions: CacheTtl | Omit<LockOptions, "driver">, fn: () => Promise<T>): Promise<LockOutcome<T>>;
|
|
382
|
+
/**
|
|
383
|
+
* Compare-and-delete: remove `key` only when its current value equals
|
|
384
|
+
* `expected`. Returns whether a delete happened.
|
|
385
|
+
*
|
|
386
|
+
* This default is get-then-remove and is NOT atomic across processes; drivers
|
|
387
|
+
* with a real primitive (memory, redis, pg) override it.
|
|
388
|
+
*/
|
|
389
|
+
protected deleteIfEquals(key: CacheKey, expected: unknown): Promise<boolean>;
|
|
314
390
|
/**
|
|
315
391
|
* {@inheritdoc}
|
|
316
392
|
*
|