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

feat: add mget(), mset(), mdel() to multiCache #367

Merged
merged 1 commit into from Mar 21, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 23 additions & 0 deletions README.md
Expand Up @@ -98,6 +98,29 @@ console.log(await multiCache.get('foo2'));

// Delete from all caches
await multiCache.del('foo2');

// Sets multiple keys in all caches.
// You can pass as many key, value tuples as you want
await multiCache.mset(
[
['foo', 'bar'],
['foo2', 'bar2'],
],
ttl
);

// mget() fetches from highest priority cache.
// If the first cache does not return all the keys,
// the next cache is fetched with the keys that were not found.
// This is done recursively until either:
// - all have been found
// - all caches has been fetched
console.log(await multiCache.mget('key', 'key2');
// >> ['bar', 'bar2']

// Delete keys with mdel() passing arguments...
await multiCache.mdel('foo', 'foo2');

```

See unit tests in [`test/multi-caching.test.ts`](./test/multi-caching.test.ts) for more information.
Expand Down
25 changes: 24 additions & 1 deletion src/multi-caching.ts
@@ -1,6 +1,10 @@
import { Cache, Milliseconds } from './caching';

export type MultiCache = Omit<Cache, 'store'>;
export type MultiCache = Omit<Cache, 'store'> & {
mset(args: [string, unknown][], ttl?: Milliseconds): Promise<void>;
mget(...args: string[]): Promise<unknown[]>;
mdel(...args: string[]): Promise<void>;
};

/**
* Module that lets you specify a hierarchy of caches.
Expand Down Expand Up @@ -56,5 +60,24 @@ export function multiCaching<Caches extends Cache[]>(
reset: async () => {
await Promise.all(caches.map((x) => x.reset()));
},
mget: async (...keys: string[]) => {
const values = new Array(keys.length).fill(undefined);
for (const cache of caches) {
if (values.every((x) => x !== undefined)) break;
try {
const val = await cache.store.mget(...keys);
val.forEach((v, i) => {
if (values[i] === undefined && v !== undefined) values[i] = v;
});
} catch (e) {}
}
return values;
},
mset: async (args: [string, unknown][], ttl?: Milliseconds) => {
await Promise.all(caches.map((cache) => cache.store.mset(args, ttl)));
},
mdel: async (...keys: string[]) => {
await Promise.all(caches.map((cache) => cache.store.mdel(...keys)));
},
};
}
50 changes: 50 additions & 0 deletions test/multi-caching.test.ts
Expand Up @@ -91,6 +91,56 @@ describe('multiCaching', () => {
});
});

describe('mset()', () => {
it('lets us set multiple keys in all caches', async () => {
const keys = [faker.datatype.string(20), faker.datatype.string(20)];
const values = [faker.datatype.string(), faker.datatype.string()];
await multiCache.mset(
[
[keys[0], values[0]],
[keys[1], values[1]],
],
defaultTtl,
);
await expect(memoryCache.get(keys[0])).resolves.toEqual(values[0]);
await expect(memoryCache2.get(keys[0])).resolves.toEqual(values[0]);
await expect(memoryCache3.get(keys[0])).resolves.toEqual(values[0]);
await expect(memoryCache.get(keys[1])).resolves.toEqual(values[1]);
await expect(memoryCache2.get(keys[1])).resolves.toEqual(values[1]);
await expect(memoryCache3.get(keys[1])).resolves.toEqual(values[1]);
});
});

describe('mget()', () => {
it('lets us get multiple keys', async () => {
const keys = [faker.datatype.string(20), faker.datatype.string(20)];
const values = [faker.datatype.string(), faker.datatype.string()];
await multiCache.set(keys[0], values[0], defaultTtl);
await memoryCache3.set(keys[1], values[1], defaultTtl);
await expect(multiCache.mget(...keys)).resolves.toStrictEqual(values);
});
});

describe('mdel()', () => {
it('lets us delete multiple keys', async () => {
const keys = [faker.datatype.string(20), faker.datatype.string(20)];
const values = [faker.datatype.string(), faker.datatype.string()];
await multiCache.mset(
[
[keys[0], values[0]],
[keys[1], values[1]],
],
defaultTtl,
);
await expect(multiCache.mget(...keys)).resolves.toStrictEqual(values);
await multiCache.mdel(...keys);
await expect(multiCache.mget(...keys)).resolves.toStrictEqual([
undefined,
undefined,
]);
});
});

describe('when cache fails', () => {
// eslint-disable-next-line @typescript-eslint/no-empty-function
const empty = (async () => {}) as never;
Expand Down