@c9up/echo 0.1.3

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 C9up
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # @c9up/echo
2
+
3
+ > Pluggable cache contract for the Ream framework, with memory + Redis drivers.
4
+
5
+ Part of **[Ream](https://github.com/C9up/ream)** — a Rust-powered, AdonisJS-compatible Node.js framework. Independent, publishable package.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pnpm add @c9up/echo
11
+ ream configure @c9up/echo
12
+ ```
13
+
14
+ ## Usage
15
+
16
+ Register the provider in your app, then configure it under `config/echo.ts`:
17
+
18
+ ```ts
19
+ // reamrc.ts
20
+ providers: [
21
+ () => import('@c9up/echo/provider'),
22
+ ]
23
+ ```
24
+
25
+ ## Entry points
26
+
27
+ - `@c9up/echo` — main API
28
+ - `@c9up/echo/provider` — Ream IoC provider
29
+ - `@c9up/echo/services/main` — container service accessor
30
+
31
+ ## License
32
+
33
+ MIT
@@ -0,0 +1,36 @@
1
+ /**
2
+ * CacheManager — unified cache API with driver abstraction.
3
+ */
4
+ export interface CacheDriver {
5
+ get<T = unknown>(key: string): Promise<T | null>;
6
+ set(key: string, value: unknown, ttlSeconds?: number): Promise<void>;
7
+ delete(key: string): Promise<boolean>;
8
+ flush(): Promise<void>;
9
+ has(key: string): Promise<boolean>;
10
+ }
11
+ export interface CacheConfig {
12
+ driver?: string;
13
+ prefix?: string;
14
+ ttl?: number;
15
+ }
16
+ export declare class CacheManager implements CacheDriver {
17
+ private driver;
18
+ private prefix;
19
+ private defaultTtl;
20
+ constructor(driver: CacheDriver, config?: CacheConfig);
21
+ private prefixKey;
22
+ get<T = unknown>(key: string): Promise<T | null>;
23
+ set(key: string, value: unknown, ttlSeconds?: number): Promise<void>;
24
+ delete(key: string): Promise<boolean>;
25
+ flush(): Promise<void>;
26
+ has(key: string): Promise<boolean>;
27
+ /** Set a value with tags for grouped invalidation. */
28
+ setWithTags(key: string, value: unknown, tags: string[], ttlSeconds?: number): Promise<void>;
29
+ /** Flush only entries with matching tags. */
30
+ flushTags(tags: string[]): Promise<void>;
31
+ /** In-flight promises for stampede prevention. Each factory is typed per-call; the map is keyed by prefixed cache key. */
32
+ private inflight;
33
+ /** Get or set — fetch from cache, or compute and store. Single-flight: concurrent misses share one factory call. */
34
+ remember<T>(key: string, ttl: number, factory: () => Promise<T>): Promise<T>;
35
+ }
36
+ //# sourceMappingURL=CacheManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CacheManager.d.ts","sourceRoot":"","sources":["../src/CacheManager.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,WAAW;IAC3B,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACjD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACnC;AAoBD,MAAM,WAAW,WAAW;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED,qBAAa,YAAa,YAAW,WAAW;IAC/C,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAAS;gBAEf,MAAM,EAAE,WAAW,EAAE,MAAM,CAAC,EAAE,WAAW;IAMrD,OAAO,CAAC,SAAS;IAIX,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAIhD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAapE,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIrC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIxC,sDAAsD;IAChD,WAAW,CAChB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,MAAM,EAAE,EACd,UAAU,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC;IAmBhB,6CAA6C;IACvC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAS9C,0HAA0H;IAC1H,OAAO,CAAC,QAAQ,CAA4C;IAE5D,oHAAoH;IAC9G,QAAQ,CAAC,CAAC,EACf,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACvB,OAAO,CAAC,CAAC,CAAC;CA0Bb"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * CacheManager — unified cache API with driver abstraction.
3
+ */
4
+ function isTaggableDriver(driver) {
5
+ const candidate = driver;
6
+ return (typeof candidate.flushTags === "function" &&
7
+ typeof candidate.setWithTags === "function");
8
+ }
9
+ export class CacheManager {
10
+ driver;
11
+ prefix;
12
+ defaultTtl;
13
+ constructor(driver, config) {
14
+ this.driver = driver;
15
+ this.prefix = config?.prefix ?? "";
16
+ this.defaultTtl = config?.ttl ?? 3600;
17
+ }
18
+ prefixKey(key) {
19
+ return this.prefix ? `${this.prefix}:${key}` : key;
20
+ }
21
+ async get(key) {
22
+ return this.driver.get(this.prefixKey(key));
23
+ }
24
+ async set(key, value, ttlSeconds) {
25
+ if (value === null || value === undefined) {
26
+ throw new TypeError("Echo: caching null/undefined values is not supported");
27
+ }
28
+ return this.driver.set(this.prefixKey(key), value, ttlSeconds ?? this.defaultTtl);
29
+ }
30
+ async delete(key) {
31
+ return this.driver.delete(this.prefixKey(key));
32
+ }
33
+ async flush() {
34
+ return this.driver.flush();
35
+ }
36
+ async has(key) {
37
+ return this.driver.has(this.prefixKey(key));
38
+ }
39
+ /** Set a value with tags for grouped invalidation. */
40
+ async setWithTags(key, value, tags, ttlSeconds) {
41
+ if (value === null || value === undefined) {
42
+ throw new TypeError("Echo: caching null/undefined values is not supported");
43
+ }
44
+ if (!isTaggableDriver(this.driver)) {
45
+ throw new Error("Echo: the configured driver does not support tag-based invalidation");
46
+ }
47
+ return this.driver.setWithTags(this.prefixKey(key), value, tags, ttlSeconds ?? this.defaultTtl);
48
+ }
49
+ /** Flush only entries with matching tags. */
50
+ async flushTags(tags) {
51
+ if (isTaggableDriver(this.driver)) {
52
+ return this.driver.flushTags(tags);
53
+ }
54
+ throw new Error("Echo: the configured driver does not support tag-based invalidation");
55
+ }
56
+ /** In-flight promises for stampede prevention. Each factory is typed per-call; the map is keyed by prefixed cache key. */
57
+ inflight = new Map();
58
+ /** Get or set — fetch from cache, or compute and store. Single-flight: concurrent misses share one factory call. */
59
+ async remember(key, ttl, factory) {
60
+ const prefixed = this.prefixKey(key);
61
+ const existing = this.inflight.get(prefixed);
62
+ if (existing)
63
+ return existing.then((v) => v);
64
+ const cached = await this.get(key);
65
+ if (cached !== null)
66
+ return cached;
67
+ const existingAfterAwait = this.inflight.get(prefixed);
68
+ if (existingAfterAwait)
69
+ return existingAfterAwait.then((v) => v);
70
+ const promise = factory()
71
+ .then(async (value) => {
72
+ await this.set(key, value, ttl);
73
+ this.inflight.delete(prefixed);
74
+ return value;
75
+ })
76
+ .catch((err) => {
77
+ this.inflight.delete(prefixed);
78
+ throw err;
79
+ });
80
+ this.inflight.set(prefixed, promise);
81
+ return promise;
82
+ }
83
+ }
84
+ //# sourceMappingURL=CacheManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CacheManager.js","sourceRoot":"","sources":["../src/CacheManager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ import { type CacheConfig } from "./CacheManager.js";
2
+ /**
3
+ * Duck-typed host context — echo stays publishable without importing
4
+ * `@c9up/ream`. Any framework that exposes a Container + a config
5
+ * store satisfies the contract.
6
+ */
7
+ interface EchoContainer {
8
+ singleton(token: unknown, factory: () => unknown): void;
9
+ resolve<T = unknown>(token: unknown): T;
10
+ }
11
+ interface EchoConfigStore {
12
+ get<T = unknown>(key: string): T | undefined;
13
+ }
14
+ export interface EchoAppContext {
15
+ container: EchoContainer;
16
+ config: EchoConfigStore;
17
+ }
18
+ export interface EchoProviderConfig extends CacheConfig {
19
+ /**
20
+ * Driver to bind by default. Only `"memory"` is created
21
+ * automatically — other drivers (Redis etc.) need custom client
22
+ * wiring, so apps build the `CacheManager` themselves and call
23
+ * `setCache(...)` from `@c9up/echo/services/main`.
24
+ *
25
+ * Default `"memory"`.
26
+ */
27
+ driver?: "memory";
28
+ }
29
+ /**
30
+ * EchoProvider — registers a default in-memory `CacheManager` so apps
31
+ * that don't need Redis can `import cache from '@c9up/echo/services/main'`
32
+ * and `await cache.get(...)` straight away.
33
+ *
34
+ * // reamrc.ts
35
+ * providers: [() => import('@c9up/echo/provider')]
36
+ *
37
+ * // config/cache.ts
38
+ * export default { driver: 'memory', prefix: 'myapp', ttl: 300 }
39
+ *
40
+ * // anywhere
41
+ * import cache from '@c9up/echo/services/main'
42
+ * await cache.set('k', v, 60)
43
+ */
44
+ export default class EchoProvider {
45
+ protected app: EchoAppContext;
46
+ constructor(app: EchoAppContext);
47
+ register(): void;
48
+ boot(): Promise<void>;
49
+ shutdown(): Promise<void>;
50
+ }
51
+ export {};
52
+ //# sourceMappingURL=EchoProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EchoProvider.d.ts","sourceRoot":"","sources":["../src/EchoProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAgB,MAAM,mBAAmB,CAAC;AAInE;;;;GAIG;AACH,UAAU,aAAa;IACtB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,GAAG,IAAI,CAAC;IACxD,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,CAAC,CAAC;CACxC;AACD,UAAU,eAAe;IACxB,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC;CAC7C;AACD,MAAM,WAAW,cAAc;IAC9B,SAAS,EAAE,aAAa,CAAC;IACzB,MAAM,EAAE,eAAe,CAAC;CACxB;AAED,MAAM,WAAW,kBAAmB,SAAQ,WAAW;IACtD;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,QAAQ,CAAC;CAClB;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,OAAO,OAAO,YAAY;IACpB,SAAS,CAAC,GAAG,EAAE,cAAc;gBAAnB,GAAG,EAAE,cAAc;IAEzC,QAAQ,IAAI,IAAI;IAiBV,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAIrB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAC/B"}
@@ -0,0 +1,41 @@
1
+ import { CacheManager } from "./CacheManager.js";
2
+ import { MemoryDriver } from "./drivers/MemoryDriver.js";
3
+ import { setCache } from "./services/main.js";
4
+ /**
5
+ * EchoProvider — registers a default in-memory `CacheManager` so apps
6
+ * that don't need Redis can `import cache from '@c9up/echo/services/main'`
7
+ * and `await cache.get(...)` straight away.
8
+ *
9
+ * // reamrc.ts
10
+ * providers: [() => import('@c9up/echo/provider')]
11
+ *
12
+ * // config/cache.ts
13
+ * export default { driver: 'memory', prefix: 'myapp', ttl: 300 }
14
+ *
15
+ * // anywhere
16
+ * import cache from '@c9up/echo/services/main'
17
+ * await cache.set('k', v, 60)
18
+ */
19
+ export default class EchoProvider {
20
+ app;
21
+ constructor(app) {
22
+ this.app = app;
23
+ }
24
+ register() {
25
+ this.app.container.singleton(CacheManager, () => {
26
+ const config = this.app.config.get("cache");
27
+ const driver = config?.driver ?? "memory";
28
+ if (driver !== "memory") {
29
+ throw new Error(`[echo] Unsupported driver '${driver}' for default provider — ` +
30
+ "wire CacheManager yourself for non-memory drivers.");
31
+ }
32
+ return new CacheManager(new MemoryDriver(), config);
33
+ });
34
+ this.app.container.singleton("cache", () => this.app.container.resolve(CacheManager));
35
+ }
36
+ async boot() {
37
+ setCache(this.app.container.resolve(CacheManager));
38
+ }
39
+ async shutdown() { }
40
+ }
41
+ //# sourceMappingURL=EchoProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EchoProvider.js","sourceRoot":"","sources":["../src/EchoProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AA+B9C;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,OAAO,OAAO,YAAY;IACV;IAAtB,YAAsB,GAAmB;QAAnB,QAAG,GAAH,GAAG,CAAgB;IAAG,CAAC;IAE7C,QAAQ;QACP,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,YAAY,EAAE,GAAG,EAAE;YAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAqB,OAAO,CAAC,CAAC;YAChE,MAAM,MAAM,GAAG,MAAM,EAAE,MAAM,IAAI,QAAQ,CAAC;YAC1C,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;gBACzB,MAAM,IAAI,KAAK,CACd,8BAA8B,MAAM,2BAA2B;oBAC9D,oDAAoD,CACrD,CAAC;YACH,CAAC;YACD,OAAO,IAAI,YAAY,CAAC,IAAI,YAAY,EAAE,EAAE,MAAM,CAAC,CAAC;QACrD,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,EAAE,CAC1C,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,CAAe,YAAY,CAAC,CACtD,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAI;QACT,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,CAAe,YAAY,CAAC,CAAC,CAAC;IAClE,CAAC;IAED,KAAK,CAAC,QAAQ,KAAmB,CAAC;CAClC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Memory cache driver — in-process Map with TTL support.
3
+ * Suitable for development and single-process deployments.
4
+ */
5
+ import type { CacheDriver } from "../CacheManager.js";
6
+ export declare class MemoryDriver implements CacheDriver {
7
+ #private;
8
+ constructor(sweepIntervalMs?: number);
9
+ destroy(): void;
10
+ get<T = unknown>(key: string): Promise<T | null>;
11
+ set(key: string, value: unknown, ttlSeconds?: number): Promise<void>;
12
+ delete(key: string): Promise<boolean>;
13
+ flush(): Promise<void>;
14
+ has(key: string): Promise<boolean>;
15
+ /** Set with tags for group invalidation. */
16
+ setWithTags(key: string, value: unknown, tags: string[], ttlSeconds?: number): Promise<void>;
17
+ /** Flush all entries tagged with any of the given tags. */
18
+ flushTags(tags: string[]): Promise<void>;
19
+ }
20
+ //# sourceMappingURL=MemoryDriver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MemoryDriver.d.ts","sourceRoot":"","sources":["../../src/drivers/MemoryDriver.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAQtD,qBAAa,YAAa,YAAW,WAAW;;gBAKnC,eAAe,SAAS;IAiBpC,OAAO,IAAI,IAAI;IAIT,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAUhD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWpE,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAUrC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKxC,4CAA4C;IACtC,WAAW,CAChB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,MAAM,EAAE,EACd,UAAU,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC;IA+BhB,2DAA2D;IACrD,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAgC9C"}
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Memory cache driver — in-process Map with TTL support.
3
+ * Suitable for development and single-process deployments.
4
+ */
5
+ export class MemoryDriver {
6
+ #store = new Map();
7
+ #tagIndex = new Map();
8
+ #sweepInterval;
9
+ constructor(sweepIntervalMs = 60_000) {
10
+ this.#sweepInterval = setInterval(() => {
11
+ const now = Date.now();
12
+ for (const [key, entry] of this.#store) {
13
+ if (entry.expiresAt > 0 && entry.expiresAt < now) {
14
+ this.#store.delete(key);
15
+ }
16
+ }
17
+ }, sweepIntervalMs);
18
+ if (typeof this.#sweepInterval === "object" &&
19
+ "unref" in this.#sweepInterval) {
20
+ this.#sweepInterval.unref();
21
+ }
22
+ }
23
+ destroy() {
24
+ clearInterval(this.#sweepInterval);
25
+ }
26
+ async get(key) {
27
+ const entry = this.#store.get(key);
28
+ if (!entry)
29
+ return null;
30
+ if (entry.expiresAt > 0 && entry.expiresAt < Date.now()) {
31
+ this.#store.delete(key);
32
+ return null;
33
+ }
34
+ return entry.value;
35
+ }
36
+ async set(key, value, ttlSeconds) {
37
+ if (value === null || value === undefined) {
38
+ throw new TypeError("Echo: caching null/undefined values is not supported");
39
+ }
40
+ const expiresAt = ttlSeconds != null && ttlSeconds > 0 ? Date.now() + ttlSeconds * 1000 : 0;
41
+ this.#store.set(key, { value, expiresAt, tags: [] });
42
+ }
43
+ async delete(key) {
44
+ const entry = this.#store.get(key);
45
+ if (entry) {
46
+ for (const tag of entry.tags) {
47
+ this.#tagIndex.get(tag)?.delete(key);
48
+ }
49
+ }
50
+ return this.#store.delete(key);
51
+ }
52
+ async flush() {
53
+ this.#store.clear();
54
+ this.#tagIndex.clear();
55
+ }
56
+ async has(key) {
57
+ const val = await this.get(key);
58
+ return val !== null;
59
+ }
60
+ /** Set with tags for group invalidation. */
61
+ async setWithTags(key, value, tags, ttlSeconds) {
62
+ // Audit 2026-05-22 F4: align with `set()` — both paths now treat any
63
+ // `ttlSeconds <= 0` (and undefined) as "no expiration". Previously
64
+ // `setWithTags` used a truthy check (`ttlSeconds ?`), which let a
65
+ // negative value through and produced `Date.now() + (-N * 1000)` —
66
+ // an already-past timestamp — so the entry was born already-expired.
67
+ // `set()` correctly returned the immortal-entry branch on the same
68
+ // input. The divergence made cache semantics depend on whether the
69
+ // caller used tags or not, which is the worst kind of "very hard to
70
+ // diagnose in prod" bug.
71
+ const expiresAt = ttlSeconds != null && ttlSeconds > 0 ? Date.now() + ttlSeconds * 1000 : 0;
72
+ // Audit 2026-05-22 F3 (overwrite leg): if `key` already exists with
73
+ // a different tag set, the old #tagIndex entries become dangling
74
+ // refs to the (now overwritten) key. Clean them up before re-tagging
75
+ // so subsequent flushTags doesn't iterate stale references.
76
+ const prev = this.#store.get(key);
77
+ if (prev !== undefined) {
78
+ for (const t of prev.tags)
79
+ this.#tagIndex.get(t)?.delete(key);
80
+ }
81
+ this.#store.set(key, { value, expiresAt, tags });
82
+ for (const tag of tags) {
83
+ let set = this.#tagIndex.get(tag);
84
+ if (!set) {
85
+ set = new Set();
86
+ this.#tagIndex.set(tag, set);
87
+ }
88
+ set.add(key);
89
+ }
90
+ }
91
+ /** Flush all entries tagged with any of the given tags. */
92
+ async flushTags(tags) {
93
+ // Audit 2026-05-22 F3: when a key is multi-tagged (e.g. `[news, fr]`)
94
+ // and we flush by `news`, the old code deleted the key from #store
95
+ // and dropped the `news` Set, but `fr`'s Set kept a dangling
96
+ // reference. A later flushTags(`fr`) would iterate over the
97
+ // stale entry, attempt `#store.delete(key)` (no-op), and the entry
98
+ // would silently linger in #tagIndex forever. Worse — if a new
99
+ // entry was later written under the same key with different tags,
100
+ // flushTags(`fr`) would WRONGLY purge it because of the residue.
101
+ // Collect keys first, then scrub each one from EVERY tag set it
102
+ // belonged to (including the tags we're not flushing this round).
103
+ const toDelete = new Set();
104
+ for (const tag of tags) {
105
+ const keys = this.#tagIndex.get(tag);
106
+ if (keys) {
107
+ for (const key of keys)
108
+ toDelete.add(key);
109
+ }
110
+ }
111
+ for (const key of toDelete) {
112
+ const entry = this.#store.get(key);
113
+ if (entry) {
114
+ for (const t of entry.tags) {
115
+ this.#tagIndex.get(t)?.delete(key);
116
+ }
117
+ }
118
+ this.#store.delete(key);
119
+ }
120
+ // Now drop the flushed tag sets themselves (any other keys still
121
+ // referenced by them have been processed in the toDelete loop and
122
+ // already removed via the inner scrub).
123
+ for (const tag of tags)
124
+ this.#tagIndex.delete(tag);
125
+ }
126
+ }
127
+ //# sourceMappingURL=MemoryDriver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MemoryDriver.js","sourceRoot":"","sources":["../../src/drivers/MemoryDriver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Redis cache driver — production-grade cache with TTL and tags.
3
+ *
4
+ * Requires a Redis client instance implementing the minimal interface below.
5
+ * Compatible with ioredis and redis (node-redis) clients.
6
+ *
7
+ * @implements MISS-10
8
+ */
9
+ import type { CacheDriver } from "../CacheManager.js";
10
+ /** Minimal Redis client interface — compatible with ioredis and node-redis. */
11
+ export interface RedisClient {
12
+ get(key: string): Promise<string | null>;
13
+ set(key: string, value: string, ...args: unknown[]): Promise<unknown>;
14
+ del(key: string | string[]): Promise<number>;
15
+ exists(key: string): Promise<number>;
16
+ keys(pattern: string): Promise<string[]>;
17
+ sadd(key: string, ...members: string[]): Promise<number>;
18
+ srem(key: string, ...members: string[]): Promise<number>;
19
+ smembers(key: string): Promise<string[]>;
20
+ expire(key: string, seconds: number): Promise<number>;
21
+ ttl(key: string): Promise<number>;
22
+ scan?(cursor: string, matchOption: "MATCH", pattern: string, countOption: "COUNT", count: number): Promise<[string, string[]]>;
23
+ }
24
+ export declare class RedisDriver implements CacheDriver {
25
+ #private;
26
+ constructor(client: RedisClient, prefix?: string);
27
+ delete(key: string): Promise<boolean>;
28
+ get<T = unknown>(key: string): Promise<T | null>;
29
+ set(key: string, value: unknown, ttlSeconds?: number): Promise<void>;
30
+ flush(): Promise<void>;
31
+ has(key: string): Promise<boolean>;
32
+ /**
33
+ * Set a value with tag memberships for group invalidation.
34
+ *
35
+ * Re-tagging an existing key (e.g. `['news']` → `['homepage']`) cleans
36
+ * the stale memberships via the per-key reverse-index — without this,
37
+ * a later `flushTags(['news'])` would silently wipe the still-current
38
+ * value because the abandoned `tag:news` set kept pointing at it.
39
+ */
40
+ setWithTags(key: string, value: unknown, tags: string[], ttlSeconds?: number): Promise<void>;
41
+ /**
42
+ * Flush all entries tagged with any of the given tags. Cleans up the
43
+ * per-key reverse-index AND cross-tag memberships so a multi-tag key
44
+ * (e.g. `['news', 'homepage']`) flushed via `news` is also removed from
45
+ * the `homepage` tag-set — otherwise the `homepage` set ends up with a
46
+ * dangling reference to a now-deleted value.
47
+ */
48
+ flushTags(tags: string[]): Promise<void>;
49
+ }
50
+ //# sourceMappingURL=RedisDriver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RedisDriver.d.ts","sourceRoot":"","sources":["../../src/drivers/RedisDriver.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD,+EAA+E;AAC/E,MAAM,WAAW,WAAW;IAC3B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACzC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7C,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACrC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACzC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACzD,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACzD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACzC,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACtD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAClC,IAAI,CAAC,CACJ,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,OAAO,EACpB,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,OAAO,EACpB,KAAK,EAAE,MAAM,GACX,OAAO,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;CAC/B;AAED,qBAAa,WAAY,YAAW,WAAW;;gBAIlC,MAAM,EAAE,WAAW,EAAE,MAAM,SAAW;IAqB5C,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAerC,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAMhD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASpE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAwBtB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKxC;;;;;;;OAOG;IACG,WAAW,CAChB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,MAAM,EAAE,EACd,UAAU,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC;IA6ChB;;;;;;OAMG;IACG,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CA6B9C"}