Skip to content

Commit 0d9c269

Browse files
faizanu94jaredwray
andauthored
cache-manager - feat: add store layer identification to event payloads (#1270)
* feat(cache-manager): add store layer identification to event payloads (#1128) * adding in biome ignore on tests files --------- Co-authored-by: Jared Wray <me@jaredwray.com>
1 parent 1868881 commit 0d9c269

File tree

3 files changed

+107
-11
lines changed

3 files changed

+107
-11
lines changed

packages/cache-manager/src/index.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { isObject } from "./is-object.js";
55
import { lt } from "./lt.js";
66
import { runIfFn } from "./run-if-fn.js";
77

8+
const _storeLabel = (i: number) => (i === 0 ? "primary" : `secondary:${i - 1}`);
9+
810
export type CreateCacheOptions = {
911
stores?: Keyv[];
1012
ttl?: number;
@@ -105,16 +107,21 @@ export const createCache = (options?: CreateCacheOptions): Cache => {
105107
eventEmitter.emit("get", { key, error });
106108
}
107109
} else {
108-
for (const store of stores) {
110+
for (let i = 0; i < stores.length; i++) {
111+
const store = stores[i];
109112
try {
110113
const cacheValue = await store.get<T>(key);
111114
if (cacheValue !== undefined) {
112115
result = cacheValue;
113-
eventEmitter.emit("get", { key, value: result });
116+
eventEmitter.emit("get", {
117+
key,
118+
value: result,
119+
store: _storeLabel(i),
120+
});
114121
break;
115122
}
116123
} catch (error) {
117-
eventEmitter.emit("get", { key, error });
124+
eventEmitter.emit("get", { key, error, store: _storeLabel(i) });
118125
}
119126
}
120127
}
@@ -212,19 +219,18 @@ export const createCache = (options?: CreateCacheOptions): Cache => {
212219
ttl?: number,
213220
): Promise<T> => {
214221
try {
222+
const promises = stores.map(async (store, i) => {
223+
await store.set(key, value, ttl ?? options?.ttl);
224+
eventEmitter.emit("set", { key, value, store: _storeLabel(i) });
225+
});
226+
215227
if (nonBlocking) {
216-
Promise.all(
217-
stores.map(async (store) =>
218-
store.set(key, value, ttl ?? options?.ttl),
219-
),
220-
);
228+
Promise.all(promises);
221229
eventEmitter.emit("set", { key, value });
222230
return value;
223231
}
224232

225-
await Promise.all(
226-
stores.map(async (store) => store.set(key, value, ttl ?? options?.ttl)),
227-
);
233+
await Promise.all(promises);
228234
eventEmitter.emit("set", { key, value });
229235
return value;
230236
} catch (error) {

packages/cache-manager/test/events.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// biome-ignore-all lint/suspicious/noExplicitAny: test file
12
import { faker } from "@faker-js/faker";
23
import { Keyv } from "keyv";
34
import { beforeEach, describe, expect, it, vi } from "vitest";
@@ -132,4 +133,44 @@ describe("events", () => {
132133
error,
133134
});
134135
});
136+
137+
it("event: get includes store and value", async () => {
138+
const listener = vi.fn();
139+
140+
cache.on("get", listener);
141+
142+
await cache.set(data.key, data.value);
143+
await cache.get(data.key);
144+
145+
expect(listener).toHaveBeenCalled();
146+
147+
const payload = listener.mock.calls[0][0];
148+
149+
expect(payload).toMatchObject({
150+
key: data.key,
151+
value: data.value,
152+
store: "primary",
153+
});
154+
});
155+
156+
it("event: get error includes store", async () => {
157+
const l1 = new Keyv();
158+
const l2 = new Keyv();
159+
160+
const cache = createCache({ stores: [l1, l2] });
161+
const events: any[] = [];
162+
163+
cache.on("get", (e: any) => events.push(e));
164+
165+
l1.get = () => {
166+
throw new Error("boom");
167+
};
168+
169+
await expect(cache.get("nope")).resolves.toBeUndefined();
170+
171+
const errEvt = events.find((e) => e.error);
172+
173+
expect(errEvt).toBeTruthy();
174+
expect(errEvt.store).toBe("primary");
175+
});
135176
});

packages/cache-manager/test/multiple-stores.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// biome-ignore-all lint/suspicious/noExplicitAny: test file
12
import { faker } from "@faker-js/faker";
23
import { Keyv } from "keyv";
34
import { beforeEach, describe, expect, it, vi } from "vitest";
@@ -111,4 +112,52 @@ describe("multiple stores", () => {
111112
"new",
112113
);
113114
});
115+
116+
it("event: get emits store id for secondary hit", async () => {
117+
const events: any[] = [];
118+
119+
cache.on("get", (e: any) => events.push(e));
120+
121+
await keyv2.set(data.key, data.value);
122+
await expect(cache.get(data.key)).resolves.toEqual(data.value);
123+
124+
const rec = events.find((e) => e.key === data.key);
125+
126+
expect(rec).toBeTruthy();
127+
expect(typeof rec.store).toBe("string");
128+
expect(rec.store).not.toBe("primary");
129+
});
130+
131+
it("event: set emits store id per layer (primary + secondary)", async () => {
132+
const events: any[] = [];
133+
134+
cache.on("set", (e: any) => events.push(e));
135+
136+
await cache.set(data.key, data.value, ttl);
137+
138+
const stores = events.filter((e) => e.key === data.key).map((e) => e.store);
139+
140+
expect(stores.length).toBeGreaterThanOrEqual(2);
141+
expect(new Set(stores).size).toBeGreaterThanOrEqual(2);
142+
expect(stores).toContain("primary");
143+
expect(stores).toContain("secondary:0");
144+
});
145+
146+
it("event: get includes store for deeper layers (secondary:1)", async () => {
147+
const l1 = new Keyv();
148+
const l2 = new Keyv();
149+
const l3 = new Keyv();
150+
151+
const cache = createCache({ stores: [l1, l2, l3] });
152+
153+
const events: any[] = [];
154+
cache.on("get", (e: any) => events.push(e));
155+
156+
await l3.set(data.key, data.value);
157+
await expect(cache.get(data.key)).resolves.toEqual(data.value);
158+
159+
const rec = events.find((e) => e.key === data.key);
160+
161+
expect(rec.store).toBe("secondary:1");
162+
});
114163
});

0 commit comments

Comments
 (0)