Skip to content

Commit

Permalink
cache-manager - adding disconnect to cache (#899)
Browse files Browse the repository at this point in the history
* cache-manager - adding disconnect()

* adding missed coverage

* adding in information on when to disconnect

---------

Co-authored-by: Jared Wray <[email protected]>
  • Loading branch information
TimothyIp and jaredwray authored Nov 17, 2024
1 parent 921ebff commit 2cdb224
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 5 deletions.
16 changes: 14 additions & 2 deletions packages/cache-manager/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[<img align="center" src="https://cacheable.org/symbol.svg" alt="Cacheable" />](https://github.com/jaredwray/cacheable)

# cache-manager
# cache-manager
[![codecov](https://codecov.io/gh/jaredwray/cacheable/graph/badge.svg?token=lWZ9OBQ7GM)](https://codecov.io/gh/jaredwray/cacheable)
[![tests](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml/badge.svg)](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml)
[![npm](https://img.shields.io/npm/dm/cache-manager)](https://npmjs.com/package/cache-manager)
Expand All @@ -18,7 +18,7 @@ A cache module for NodeJS that allows easy wrapping of functions in cache, tiere

We moved to using [Keyv](https://keyv.org/) which are more actively maintained and have a larger community.

A special thanks to [Tim Phan](https://github.com/timphandev) who tooke `cache-manager` v5 and ported it to [Keyv](https://keyv.org/) which is the foundation of v6. 🎉 Another special thanks to [Doug Ayers](https://github.com/douglascayers) who wrote `promise-coalesce` which was used in v5 and now embedded in v6.
A special thanks to [Tim Phan](https://github.com/timphandev) who took `cache-manager` v5 and ported it to [Keyv](https://keyv.org/) which is the foundation of v6. 🎉 Another special thanks to [Doug Ayers](https://github.com/douglascayers) who wrote `promise-coalesce` which was used in v5 and now embedded in v6.

If you are looking for older documentation you can find it here:
* [v5 Documentation](https://github.com/jaredwray/cacheable/blob/main/packages/cache-manager/READMEv5.md)
Expand Down Expand Up @@ -332,6 +332,18 @@ await cache.wrap('error', () => {

See unit tests in [`test/wrap.test.ts`](./test/wrap.test.ts) for more information.

## disconnect

`disconnect(key): Promise<void>`

Will disconnect from the relevant store(s). It is highly recomended to use this when using a [Keyv](https://keyv.org/) storage adapter that requires a disconnect. For example, when using `@keyv/redis` you should disconnect when you are done with the cache. For each storage adapter, the use case for when to use disconnect is different. An example is that `@keyv/redis` should be used only when you are done with the cache.

```ts
await cache.disconnect();
```

See unit tests in [`test/disconnect.test.ts`](./test/disconnect.test.ts) for more information.

# Events
## set
Fired when a key has been added or changed.
Expand Down
9 changes: 9 additions & 0 deletions packages/cache-manager/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,14 @@ export const createCache = (options?: CreateCacheOptions) => {

const off = <E extends keyof Events>(event: E, listener: Events[E]) => eventEmitter.removeListener(event, listener);

const disconnect = async () => {
try {
await Promise.all(stores.map(async store => store.disconnect()));
} catch (error) {
return Promise.reject(error);
}
};

return {
get,
mget,
Expand All @@ -241,6 +249,7 @@ export const createCache = (options?: CreateCacheOptions) => {
wrap,
on,
off,
disconnect,
};
};

Expand Down
5 changes: 5 additions & 0 deletions packages/cache-manager/src/keyv-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type CacheManagerStore = {
keys(): Promise<string[]>;
reset?(): Promise<void>;
on?(event: string, listener: (...arguments_: any[]) => void): void;
disconnect?(): Promise<void>;
};

export class KeyvAdapter implements KeyvStoreAdapter {
Expand Down Expand Up @@ -69,4 +70,8 @@ export class KeyvAdapter implements KeyvStoreAdapter {

return this;
}

async disconnect?(): Promise<void> {
await this._cache.disconnect?.();
}
}
46 changes: 46 additions & 0 deletions packages/cache-manager/test/disconnect.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {
describe, expect, test, vi,
} from 'vitest';
import {Keyv} from 'keyv';
import KeyvRedis from '@keyv/redis';
import {CacheableMemory} from 'cacheable';
import {createCache} from '../src/index.js';

describe('disconnect', () => {
test('disconnect from multiple stores', async () => {
const cacheableKeyvStore = new Keyv({
store: new CacheableMemory({ttl: 60_000, lruSize: 5000}),
});
const redisKeyvStore = new Keyv({
store: new KeyvRedis('redis://localhost:6379'),
});
// Multiple stores
const cache = createCache({
stores: [
cacheableKeyvStore,
redisKeyvStore,
],
});

const cacheableDisconnectSpy = vi.spyOn(cacheableKeyvStore, 'disconnect');
const redisDisconnectSpy = vi.spyOn(redisKeyvStore, 'disconnect');
await cache.disconnect();

expect(cacheableDisconnectSpy).toBeCalled();
expect(redisDisconnectSpy).toBeCalled();
});
test('error', async () => {
const keyv = new Keyv();
const cache = createCache({
stores: [
keyv,
],
});
const error = new Error('disconnect error');
keyv.disconnect = () => {
throw error;
};

await expect(cache.disconnect()).rejects.toThrowError(error);
});
});
39 changes: 36 additions & 3 deletions packages/cache-manager/test/keyv-adapter.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
import {Keyv} from 'keyv';
import {
describe, expect, it,
describe, expect, it, vi,
} from 'vitest';
import {faker} from '@faker-js/faker';
import {redisStore as redisYetStore} from 'cache-manager-redis-yet';
import {createCache, KeyvAdapter} from '../src/index.js';
import {createCache, KeyvAdapter, type CacheManagerStore} from '../src/index.js';

const mockCacheManagerStore: CacheManagerStore = {
name: 'MockCacheManagerStore',
isCacheable: vi.fn((value: unknown) => value !== undefined),
get: vi.fn(async (key: string) => `Value for ${key}`),
mget: vi.fn(async (...keys: string[]) => keys.map(key => `Value for ${key}`)),
set: vi.fn(async (key: string, value: any, ttl?: number) => `Set ${key} to ${value} with TTL ${ttl}`),
mset: vi.fn(async () => undefined),
del: vi.fn(async () => undefined),
mdel: vi.fn(async () => undefined),
ttl: vi.fn(async () => 0),
keys: vi.fn(async () => ['key1', 'key2', 'key3']),
reset: vi.fn(async () => undefined),
on: vi.fn((event: string) => {
console.log(`Event ${event} registered.`);
}),
disconnect: vi.fn(async () => undefined),
};

describe('keyv-adapter', async () => {
it('able to handle redis yet third party conversion', async () => {
Expand Down Expand Up @@ -109,5 +127,20 @@ describe('keyv-adapter', async () => {
const result = await keyv.get(list.map(({key}) => key));
expect(result).toEqual([undefined, undefined]);
});
});

it('should disconnect', async () => {
// Store without disconnect
const storeNoDisconnect = await redisYetStore();
const adapterNoDisconnect = new KeyvAdapter(storeNoDisconnect);
const keyvNoDisconnect = new Keyv({store: adapterNoDisconnect});

await keyvNoDisconnect.disconnect();

// Store with disconnect
const adapterWithDisconnect = new KeyvAdapter(mockCacheManagerStore);
const keyvWithDisconnect = new Keyv({store: adapterWithDisconnect});

await keyvWithDisconnect.disconnect();
expect(mockCacheManagerStore.disconnect).toBeCalled();
});
});

0 comments on commit 2cdb224

Please sign in to comment.