diff --git a/packages/cache-manager/README.md b/packages/cache-manager/README.md index a3029785..39a3d463 100644 --- a/packages/cache-manager/README.md +++ b/packages/cache-manager/README.md @@ -367,7 +367,7 @@ See unit tests in [`test/del.test.ts`](./test/del.test.ts) for more information. `mdel(keys: [key]): Promise` -Delete multiple keys, an error will be throw on any failed. +Delete multiple keys, an error will be throw on any failed. Keys are deleted with a single bulk operation per store, so stores with native multi-delete support (such as `@keyv/redis`) use one command instead of one per key. ```ts await cache.mset([ diff --git a/packages/cache-manager/src/index.ts b/packages/cache-manager/src/index.ts index d6eec74d..8aa2dca2 100644 --- a/packages/cache-manager/src/index.ts +++ b/packages/cache-manager/src/index.ts @@ -289,10 +289,7 @@ export const createCache = (options?: CreateCacheOptions): Cache => { const mdel = async (keys: string[]) => { try { - const promises: Array> = []; - for (const key of keys) { - promises.push(...stores.map(async (store) => store.delete(key))); - } + const promises = stores.map(async (store) => store.deleteMany(keys)); if (nonBlocking) { Promise.all(promises); diff --git a/packages/cache-manager/test/mdel.test.ts b/packages/cache-manager/test/mdel.test.ts index 97e6962b..d2769b2b 100644 --- a/packages/cache-manager/test/mdel.test.ts +++ b/packages/cache-manager/test/mdel.test.ts @@ -33,6 +33,43 @@ describe("mdel", () => { await expect(cache.get(list[2].key)).resolves.toEqual(list[2].value); }); + it("should delete every key with a single store call", async () => { + const cache = createCache({ stores: [keyv], nonBlocking: false }); + await cache.mset(list); + const keys = list.map(({ key }) => key); + const deleteManyHandler = vi.spyOn(keyv, "deleteMany"); + + await expect(cache.mdel(keys)).resolves.toBe(true); + + expect(deleteManyHandler).toHaveBeenCalledOnce(); + expect(deleteManyHandler).toHaveBeenCalledWith(keys); + }); + + it("should call deleteMany once per store", async () => { + const secondKeyv = new Keyv(); + const cache = createCache({ + stores: [keyv, secondKeyv], + nonBlocking: false, + }); + await cache.mset(list); + const keys = list.map(({ key }) => key); + const firstHandler = vi.spyOn(keyv, "deleteMany"); + const secondHandler = vi.spyOn(secondKeyv, "deleteMany"); + + await cache.mdel(keys); + + expect(firstHandler).toHaveBeenCalledOnce(); + expect(firstHandler).toHaveBeenCalledWith(keys); + expect(secondHandler).toHaveBeenCalledOnce(); + expect(secondHandler).toHaveBeenCalledWith(keys); + }); + + it("should resolve on an empty key list", async () => { + const cache = createCache({ stores: [keyv], nonBlocking: false }); + + await expect(cache.mdel([])).resolves.toBe(true); + }); + it("should work blocking", async () => { let resolveDeleted: (value: boolean) => void = () => undefined; const deletePromise = new Promise((_resolve) => { @@ -41,7 +78,9 @@ describe("mdel", () => { const cache = createCache({ stores: [keyv], nonBlocking: false }); await cache.mset(list); - const delHandler = vi.spyOn(keyv, "delete").mockReturnValue(deletePromise); + const delHandler = vi + .spyOn(keyv, "deleteMany") + .mockReturnValue(deletePromise); const deleteResolved = vi.fn(); const deleteRejected = vi.fn(); cache @@ -49,7 +88,7 @@ describe("mdel", () => { .catch(deleteRejected) .then(deleteResolved); - expect(delHandler).toBeCalledTimes(list.length); + expect(delHandler).toHaveBeenCalledOnce(); await sleep(200); @@ -70,7 +109,9 @@ describe("mdel", () => { const cache = createCache({ stores: [keyv], nonBlocking: true }); await cache.mset(list); - const delHandler = vi.spyOn(keyv, "delete").mockReturnValue(deletePromise); + const delHandler = vi + .spyOn(keyv, "deleteMany") + .mockReturnValue(deletePromise); const deleteResolved = vi.fn(); const deleteRejected = vi.fn(); cache @@ -78,7 +119,7 @@ describe("mdel", () => { .catch(deleteRejected) .then(deleteResolved); - expect(delHandler).toBeCalledTimes(list.length); + expect(delHandler).toHaveBeenCalledOnce(); await sleep(1);