Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/cache-manager/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ See unit tests in [`test/del.test.ts`](./test/del.test.ts) for more information.

`mdel(keys: [key]): Promise<true>`

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([
Expand Down
5 changes: 1 addition & 4 deletions packages/cache-manager/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,10 +289,7 @@ export const createCache = (options?: CreateCacheOptions): Cache => {

const mdel = async (keys: string[]) => {
try {
const promises: Array<Promise<boolean>> = [];
for (const key of keys) {
promises.push(...stores.map(async (store) => store.delete(key)));
}
const promises = stores.map(async (store) => store.deleteMany(keys));
Comment thread
jaredwray marked this conversation as resolved.

if (nonBlocking) {
Promise.all(promises);
Expand Down
49 changes: 45 additions & 4 deletions packages/cache-manager/test/mdel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<boolean>((_resolve) => {
Expand All @@ -41,15 +78,17 @@ 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
.mdel(list.map(({ key }) => key))
.catch(deleteRejected)
.then(deleteResolved);

expect(delHandler).toBeCalledTimes(list.length);
expect(delHandler).toHaveBeenCalledOnce();

await sleep(200);

Expand All @@ -70,15 +109,17 @@ 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
.mdel(list.map(({ key }) => key))
.catch(deleteRejected)
.then(deleteResolved);

expect(delHandler).toBeCalledTimes(list.length);
expect(delHandler).toHaveBeenCalledOnce();

await sleep(1);

Expand Down