From b16630f26efcbfd12b4dbe42764089878ef6cc0b Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 21 Mar 2026 23:57:57 +0000 Subject: [PATCH 1/2] fix(node-cache): resolve generic type shadowing on mget, take, and get (#1601) Method-level generics on mget, take (NodeCache) and get, mget, take (NodeCacheStore) shadowed the class-level , causing TypeScript to infer unknown instead of the user-specified type. Rename to so the class type is used by default while still allowing per-call overrides. https://claude.ai/code/session_01AfCP11dTBM4KuAcBdJRc3j --- packages/node-cache/src/index.ts | 14 +++++++----- packages/node-cache/src/store.ts | 16 ++++++------- packages/node-cache/test/index.test.ts | 26 +++++++++++++++++++++ packages/node-cache/test/store.test.ts | 31 ++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 14 deletions(-) diff --git a/packages/node-cache/src/index.ts b/packages/node-cache/src/index.ts index 6516e239..770016c4 100644 --- a/packages/node-cache/src/index.ts +++ b/packages/node-cache/src/index.ts @@ -264,14 +264,16 @@ export class NodeCache extends Hookified { * @param {Array} an object with the key as a property and the value as the value */ - public mget(keys: Array): Record { - const result: Record = {}; + public mget( + keys: Array, + ): Record { + const result: Record = {}; for (const key of keys) { const value = this.get(key); /* v8 ignore next -- @preserve */ if (value) { - result[this.formatKey(key)] = value as T; + result[this.formatKey(key)] = value as V; } } @@ -284,16 +286,16 @@ export class NodeCache extends Hookified { * @param {string | number} key * @returns {T | undefined} the value or undefined */ - public take(key: string | number): T | undefined { + public take(key: string | number): V | undefined { const result = this.get(key); if (result) { this.del(key); if (this.options.useClones) { - return this.clone(result) as T; + return this.clone(result) as V; } - return result as T; + return result as V; } return undefined; diff --git a/packages/node-cache/src/store.ts b/packages/node-cache/src/store.ts index e3cc8ca3..05aa1754 100644 --- a/packages/node-cache/src/store.ts +++ b/packages/node-cache/src/store.ts @@ -136,8 +136,8 @@ export class NodeCacheStore extends Hookified { * @param {string | number} key * @returns {any | undefined} */ - public async get(key: string | number): Promise { - const result = await this._keyv.get(key.toString()); + public async get(key: string | number): Promise { + const result = await this._keyv.get(key.toString()); if (result !== undefined) { this._stats.incrementHits(); } else { @@ -152,12 +152,12 @@ export class NodeCacheStore extends Hookified { * @param {Array} keys * @returns {Record} */ - public async mget( + public async mget( keys: Array, - ): Promise> { - const result: Record = {}; + ): Promise> { + const result: Record = {}; for (const key of keys) { - const value = await this._keyv.get(key.toString()); + const value = await this._keyv.get(key.toString()); if (value !== undefined) { this._stats.incrementHits(); } else { @@ -233,8 +233,8 @@ export class NodeCacheStore extends Hookified { * @param {string | number} key * @returns {T | undefined} */ - public async take(key: string | number): Promise { - const result = await this._keyv.get(key.toString()); + public async take(key: string | number): Promise { + const result = await this._keyv.get(key.toString()); if (result !== undefined) { this._stats.incrementHits(); await this._keyv.delete(key.toString()); diff --git a/packages/node-cache/test/index.test.ts b/packages/node-cache/test/index.test.ts index 8cfd976f..6e385be7 100644 --- a/packages/node-cache/test/index.test.ts +++ b/packages/node-cache/test/index.test.ts @@ -392,4 +392,30 @@ describe("NodeCache", () => { cache.set("nullKey", null); expect(cache.get("nullKey")).toBe(null); }); + + test("should propagate class-level generic type through mget and take", () => { + type MyType = { name: string; age: number }; + const cache = new NodeCache({ checkperiod: 0 }); + cache.set("user1", { name: "Alice", age: 30 }); + cache.set("user2", { name: "Bob", age: 25 }); + + const mgetResult = cache.mget(["user1", "user2"]); + // Verify the type is correctly inferred as Record + const user1 = mgetResult.user1; + expect(user1).toEqual({ name: "Alice", age: 30 }); + if (user1) { + expect(user1.name).toBe("Alice"); + expect(user1.age).toBe(30); + } + + const taken = cache.take("user2"); + // Verify the type is correctly inferred as MyType | undefined + expect(taken).toEqual({ name: "Bob", age: 25 }); + if (taken) { + expect(taken.name).toBe("Bob"); + } + + // Verify take removed the key + expect(cache.get("user2")).toBeUndefined(); + }); }); diff --git a/packages/node-cache/test/store.test.ts b/packages/node-cache/test/store.test.ts index dfa9aa0a..25aafe45 100644 --- a/packages/node-cache/test/store.test.ts +++ b/packages/node-cache/test/store.test.ts @@ -163,4 +163,35 @@ describe("NodeCacheStore", () => { const result = await store.get("test"); expect(result).toBe("value"); }); + + test("should propagate class-level generic type through get, mget, and take", async () => { + type MyType = { name: string; age: number }; + const store = new NodeCacheStore(); + await store.set("user1", { name: "Alice", age: 30 }); + await store.set("user2", { name: "Bob", age: 25 }); + + const getResult = await store.get("user1"); + expect(getResult).toEqual({ name: "Alice", age: 30 }); + if (getResult) { + expect(getResult.name).toBe("Alice"); + } + + const mgetResult = await store.mget(["user1", "user2"]); + const user1 = mgetResult.user1; + expect(user1).toEqual({ name: "Alice", age: 30 }); + if (user1) { + expect(user1.name).toBe("Alice"); + expect(user1.age).toBe(30); + } + + const taken = await store.take("user2"); + expect(taken).toEqual({ name: "Bob", age: 25 }); + if (taken) { + expect(taken.name).toBe("Bob"); + } + + // Verify take removed the key + const afterTake = await store.get("user2"); + expect(afterTake).toBeUndefined(); + }); }); From 63f8668ba98032c26087cfc905478431aaf5f4bf Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 22 Mar 2026 00:11:07 +0000 Subject: [PATCH 2/2] fix(node-cache): improve test assertions per review feedback Replace redundant toEqual + incomplete property checks with explicit toBeDefined + individual property assertions for all fields. https://claude.ai/code/session_01AfCP11dTBM4KuAcBdJRc3j --- packages/node-cache/test/index.test.ts | 15 ++++++--------- packages/node-cache/test/store.test.ts | 22 +++++++++------------- 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/packages/node-cache/test/index.test.ts b/packages/node-cache/test/index.test.ts index 6e385be7..f86d89c4 100644 --- a/packages/node-cache/test/index.test.ts +++ b/packages/node-cache/test/index.test.ts @@ -402,18 +402,15 @@ describe("NodeCache", () => { const mgetResult = cache.mget(["user1", "user2"]); // Verify the type is correctly inferred as Record const user1 = mgetResult.user1; - expect(user1).toEqual({ name: "Alice", age: 30 }); - if (user1) { - expect(user1.name).toBe("Alice"); - expect(user1.age).toBe(30); - } + expect(user1).toBeDefined(); + expect(user1!.name).toBe("Alice"); + expect(user1!.age).toBe(30); const taken = cache.take("user2"); // Verify the type is correctly inferred as MyType | undefined - expect(taken).toEqual({ name: "Bob", age: 25 }); - if (taken) { - expect(taken.name).toBe("Bob"); - } + expect(taken).toBeDefined(); + expect(taken!.name).toBe("Bob"); + expect(taken!.age).toBe(25); // Verify take removed the key expect(cache.get("user2")).toBeUndefined(); diff --git a/packages/node-cache/test/store.test.ts b/packages/node-cache/test/store.test.ts index 25aafe45..41fa1a8e 100644 --- a/packages/node-cache/test/store.test.ts +++ b/packages/node-cache/test/store.test.ts @@ -171,24 +171,20 @@ describe("NodeCacheStore", () => { await store.set("user2", { name: "Bob", age: 25 }); const getResult = await store.get("user1"); - expect(getResult).toEqual({ name: "Alice", age: 30 }); - if (getResult) { - expect(getResult.name).toBe("Alice"); - } + expect(getResult).toBeDefined(); + expect(getResult?.name).toBe("Alice"); + expect(getResult?.age).toBe(30); const mgetResult = await store.mget(["user1", "user2"]); const user1 = mgetResult.user1; - expect(user1).toEqual({ name: "Alice", age: 30 }); - if (user1) { - expect(user1.name).toBe("Alice"); - expect(user1.age).toBe(30); - } + expect(user1).toBeDefined(); + expect(user1?.name).toBe("Alice"); + expect(user1?.age).toBe(30); const taken = await store.take("user2"); - expect(taken).toEqual({ name: "Bob", age: 25 }); - if (taken) { - expect(taken.name).toBe("Bob"); - } + expect(taken).toBeDefined(); + expect(taken?.name).toBe("Bob"); + expect(taken?.age).toBe(25); // Verify take removed the key const afterTake = await store.get("user2");