Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cache-manager - adding disconnect to cache #899

Merged
merged 4 commits into from
Nov 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
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();
});
});
Loading