Skip to content

Commit f5d1cea

Browse files
committed
feat: cache service
1 parent f524e69 commit f5d1cea

9 files changed

+506
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import type { CacheService } from '../cache.types';
2+
import { getCacheValue } from '../cache.utils';
3+
import { MemoryCacheServiceImpl } from '../memory-cache.service';
4+
5+
describe('CacheUtils tests', () => {
6+
let cacheService: CacheService;
7+
8+
beforeEach(() => {
9+
cacheService = new MemoryCacheServiceImpl();
10+
});
11+
12+
describe('#getCacheValue', () => {
13+
test('when cache miss and no callback then return undefined', async () => {
14+
expect(await cacheService.get('mykey')).toBeUndefined();
15+
16+
const value = await getCacheValue({
17+
cacheService,
18+
key: 'mykey',
19+
});
20+
21+
expect(value).toBeUndefined();
22+
});
23+
24+
test('when cache miss and yes callback then returns callback value', async () => {
25+
expect(await cacheService.get('mykey')).toBeUndefined();
26+
27+
const value = await getCacheValue({
28+
cacheService,
29+
key: 'mykey',
30+
onCacheMiss: async () => 'cacheMiss',
31+
});
32+
33+
expect(value).toBe('cacheMiss');
34+
35+
expect(await cacheService.get('mykey')).toBe('cacheMiss');
36+
});
37+
38+
test('when cache hit then return it regardless of callback', async () => {
39+
await cacheService.set('mykey', 'cacheHit');
40+
41+
expect(await cacheService.get('mykey')).toBe('cacheHit');
42+
43+
const value = await getCacheValue({
44+
cacheService,
45+
key: 'mykey',
46+
onCacheMiss: async () => 'cacheMiss',
47+
});
48+
49+
expect(value).toBe('cacheHit');
50+
51+
expect(await cacheService.get('mykey')).toBe('cacheHit');
52+
});
53+
});
54+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import * as fs from 'fs-extra';
2+
import { DiskCacheServiceImpl } from '../disk-cache.service';
3+
4+
describe('DiskCacheService tests', () => {
5+
const filepath = '/tmp/dsa2d';
6+
7+
beforeEach(() => {
8+
fs.writeJsonSync(filepath, {});
9+
});
10+
11+
afterEach(() => {
12+
fs.removeSync(filepath);
13+
});
14+
15+
test('set - primitive', async () => {
16+
const cacheBefore = await fs.readJson(filepath);
17+
18+
const cacheService = new DiskCacheServiceImpl({
19+
filepath,
20+
});
21+
22+
await cacheService.set('key', 42);
23+
24+
const cacheAfter = await fs.readJson(filepath);
25+
26+
expect(cacheBefore.key).toEqual(undefined);
27+
expect(cacheAfter.key).toEqual(42);
28+
});
29+
30+
test('set - object', async () => {
31+
const cacheBefore = await fs.readJson(filepath);
32+
33+
const cacheService = new DiskCacheServiceImpl({
34+
filepath,
35+
});
36+
37+
await cacheService.set('key', { value: 42 });
38+
39+
const cacheAfter = await fs.readJson(filepath);
40+
41+
expect(cacheBefore.key).toEqual(undefined);
42+
expect(cacheAfter.key).toEqual({ value: 42 });
43+
});
44+
45+
test('get - primitive', async () => {
46+
await fs.writeJson(filepath, { key: 42 });
47+
48+
const cacheService = new DiskCacheServiceImpl({
49+
filepath,
50+
});
51+
52+
const value = await cacheService.get('key');
53+
54+
expect(value).toEqual(42);
55+
});
56+
57+
test('get - object', async () => {
58+
await fs.writeJson(filepath, { key: { value: 42 } });
59+
60+
const cacheService = new DiskCacheServiceImpl({
61+
filepath,
62+
});
63+
64+
const value = await cacheService.get('key');
65+
66+
expect(value).toEqual({ value: 42 });
67+
});
68+
69+
test('get - key not exists', async () => {
70+
const cacheService = new DiskCacheServiceImpl({
71+
filepath,
72+
});
73+
74+
const value = await cacheService.get('key');
75+
76+
expect(value).toEqual(undefined);
77+
});
78+
79+
test('remove - primitive', async () => {
80+
await fs.writeJson(filepath, { key: 42 });
81+
82+
const cacheBefore = await fs.readJson(filepath);
83+
84+
const cacheService = new DiskCacheServiceImpl({
85+
filepath,
86+
});
87+
88+
await cacheService.remove('key');
89+
90+
const cacheAfter = await fs.readJson(filepath);
91+
92+
expect(cacheBefore.key).toEqual(42);
93+
expect(cacheAfter.key).toEqual(undefined);
94+
});
95+
96+
test('remove - object', async () => {
97+
await fs.writeJson(filepath, { key: { value: 42 } });
98+
99+
const cacheBefore = await fs.readJson(filepath);
100+
101+
const cacheService = new DiskCacheServiceImpl({
102+
filepath,
103+
});
104+
105+
await cacheService.remove('key');
106+
107+
const cacheAfter = await fs.readJson(filepath);
108+
109+
expect(cacheBefore.key).toEqual({ value: 42 });
110+
expect(cacheAfter.key).toEqual(undefined);
111+
});
112+
113+
test('remove - key not exists', async () => {
114+
const cacheBefore = await fs.readJson(filepath);
115+
116+
const cacheService = new DiskCacheServiceImpl({
117+
filepath,
118+
});
119+
120+
await cacheService.remove('non-existant-key');
121+
122+
const cacheAfter = await fs.readJson(filepath);
123+
124+
expect(cacheBefore.key).toEqual(undefined);
125+
expect(cacheAfter.key).toEqual(undefined);
126+
});
127+
128+
test('clear', async () => {
129+
await fs.writeJson(filepath, { key: { value: 42 } });
130+
131+
const cacheBefore = await fs.readJson(filepath);
132+
133+
const cacheService = new DiskCacheServiceImpl({
134+
filepath,
135+
});
136+
137+
await cacheService.clear();
138+
139+
const cacheAfter = await fs.readJson(filepath);
140+
141+
expect(cacheBefore).toEqual({ key: { value: 42 } });
142+
expect(cacheAfter).toEqual({});
143+
});
144+
145+
test('read cache', async () => {
146+
const cacheService = new DiskCacheServiceImpl({
147+
filepath,
148+
});
149+
150+
expect(await cacheService.readCache()).toEqual({});
151+
152+
await cacheService.set('key', 42);
153+
expect(await cacheService.readCache()).toEqual({ key: 42 });
154+
155+
await cacheService.set('foo', 'bar');
156+
expect(await cacheService.readCache()).toEqual({ key: 42, foo: 'bar' });
157+
158+
await cacheService.clear();
159+
expect(await cacheService.readCache()).toEqual({});
160+
});
161+
162+
test('write cache', async () => {
163+
const cacheService = new DiskCacheServiceImpl({
164+
filepath,
165+
});
166+
167+
expect(await cacheService.readCache()).toEqual({});
168+
169+
await cacheService.writeCache({ key: 42, foo: 'bar' });
170+
171+
expect(await cacheService.readCache()).toEqual({ key: 42, foo: 'bar' });
172+
});
173+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { MemoryCacheServiceImpl } from '../memory-cache.service';
2+
3+
describe('MemoryCacheService', () => {
4+
let cacheService: MemoryCacheServiceImpl;
5+
6+
beforeEach(() => {
7+
cacheService = new MemoryCacheServiceImpl();
8+
});
9+
10+
test('set/get/remove - primitive', async () => {
11+
await cacheService.set('key', 42);
12+
expect(await cacheService.get('key')).toEqual(42);
13+
14+
await cacheService.remove('key');
15+
expect(await cacheService.get('key')).toEqual(undefined);
16+
});
17+
18+
test('set/get/remove - object', async () => {
19+
await cacheService.set('key', { value: 42 });
20+
expect(await cacheService.get('key')).toEqual({ value: 42 });
21+
22+
await cacheService.remove('key');
23+
expect(await cacheService.get('key')).toEqual(undefined);
24+
});
25+
26+
test('get/remove - key not exists', async () => {
27+
// Nothing here...
28+
expect(await cacheService.get('non-existant-key')).toEqual(undefined);
29+
30+
// Still nothing here...
31+
await cacheService.remove('non-existant-key');
32+
expect(await cacheService.get('non-existant-key')).toEqual(undefined);
33+
});
34+
35+
test('read/clear cache', async () => {
36+
expect(await cacheService.readCache()).toEqual({});
37+
38+
await cacheService.set('key', 42);
39+
expect(await cacheService.readCache()).toEqual({ key: 42 });
40+
41+
await cacheService.set('foo', 'bar');
42+
expect(await cacheService.readCache()).toEqual({ key: 42, foo: 'bar' });
43+
44+
await cacheService.clear();
45+
expect(await cacheService.readCache()).toEqual({});
46+
});
47+
48+
test('write cache', async () => {
49+
expect(await cacheService.readCache()).toEqual({});
50+
51+
await cacheService.writeCache({ key: 42, foo: 'bar' });
52+
53+
expect(await cacheService.readCache()).toEqual({ key: 42, foo: 'bar' });
54+
});
55+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type { Cache, CacheService } from './cache.types';
2+
3+
abstract class AbstractCacheService implements CacheService {
4+
public abstract set<T>(key: string | number, data: T): Promise<void>;
5+
6+
public abstract get<T>(key: string | number): Promise<T | undefined>;
7+
8+
public abstract remove(key: string | number): Promise<void>;
9+
10+
public abstract clear(): Promise<void>;
11+
12+
public abstract readCache(): Promise<Cache>;
13+
14+
public async writeCache(newCache: Cache): Promise<void> {
15+
await this.clear();
16+
17+
for (const [key, value] of Object.entries(newCache)) {
18+
await this.set(key, value);
19+
}
20+
}
21+
}
22+
23+
export { AbstractCacheService };

electron/main/cache/cache.types.ts

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
export type Cache = Record<string, any>;
2+
3+
export interface CacheService {
4+
/**
5+
* Sets a value for the key in the cache.
6+
*/
7+
set<T>(key: string | number, value: T): Promise<void>;
8+
/**
9+
* Gets a value for the key from the cache.
10+
*/
11+
get<T>(key: string | number): Promise<T | undefined>;
12+
/**
13+
* Removes a value for the key from the cache.
14+
*/
15+
remove(key: string | number): Promise<void>;
16+
/**
17+
* Removes all entries from the cache.
18+
*/
19+
clear(): Promise<void>;
20+
/**
21+
* Gets all entries from the cache.
22+
*/
23+
readCache(): Promise<Cache>;
24+
/**
25+
* Replaces the current cache with another one.
26+
*/
27+
writeCache(newCache: Cache): Promise<void>;
28+
}
29+
30+
export interface DiskCacheOptions {
31+
/**
32+
* Path to the file where to store the cache.
33+
*/
34+
filepath: string;
35+
}

electron/main/cache/cache.utils.ts

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import type { CacheService } from './cache.types';
2+
3+
/**
4+
* Gets a value from the cache service with option
5+
* to provide your own function to generate a value
6+
* on cache miss. The generated value will be cached.
7+
*
8+
* A cache miss occurs when the value in the cache
9+
* for the given key comes back as `undefined`.
10+
*/
11+
export async function getCacheValue<T>(options: {
12+
cacheService: CacheService;
13+
key: string;
14+
onCacheMiss: () => Promise<T>;
15+
}): Promise<T>; // callback returns a value so this method returns a value
16+
17+
export async function getCacheValue<T>(options: {
18+
cacheService: CacheService;
19+
key: string;
20+
onCacheMiss: () => Promise<T | undefined>;
21+
}): Promise<T | undefined>; // callback may or may not return a value so unsure
22+
23+
export async function getCacheValue<T>(options: {
24+
cacheService: CacheService;
25+
key: string;
26+
}): Promise<T | undefined>; // no callback, so cache may or may not have value
27+
28+
export async function getCacheValue<T>(options: {
29+
cacheService: CacheService;
30+
key: string;
31+
onCacheMiss?: () => Promise<T | undefined>;
32+
}): Promise<T | undefined> {
33+
const { cacheService, key, onCacheMiss } = options;
34+
35+
let value = await cacheService.get<T>(key);
36+
37+
if (value !== undefined) {
38+
return value;
39+
}
40+
41+
if (onCacheMiss) {
42+
value = await onCacheMiss();
43+
if (value !== undefined) {
44+
await cacheService.set<T>(key, value);
45+
}
46+
}
47+
48+
return value;
49+
}

0 commit comments

Comments
 (0)