Skip to content

Commit 6afc6e9

Browse files
authored
refactor(cache): Convert package cache backends to classes (#41434)
refactor(cache): extract package cache backend classes
1 parent bdf7bc6 commit 6afc6e9

20 files changed

+1212
-625
lines changed
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import * as backend from './backend.ts';
2+
import { PackageCacheFile } from './impl/file.ts';
3+
import { PackageCacheRedis } from './impl/redis.ts';
4+
import { PackageCacheSqlite } from './impl/sqlite.ts';
5+
6+
vi.mock('./impl/file.ts');
7+
vi.mock('./impl/redis.ts');
8+
vi.mock('./impl/sqlite.ts');
9+
10+
function mockBackend(): {
11+
get: ReturnType<typeof vi.fn>;
12+
set: ReturnType<typeof vi.fn>;
13+
destroy: ReturnType<typeof vi.fn>;
14+
} {
15+
return {
16+
get: vi.fn().mockResolvedValue(undefined),
17+
set: vi.fn().mockResolvedValue(undefined),
18+
destroy: vi.fn().mockResolvedValue(undefined),
19+
};
20+
}
21+
22+
describe('util/cache/package/backend', () => {
23+
let fileBackend: ReturnType<typeof mockBackend>;
24+
let redisBackend: ReturnType<typeof mockBackend>;
25+
let sqliteBackend: ReturnType<typeof mockBackend>;
26+
27+
beforeEach(() => {
28+
delete process.env.RENOVATE_X_SQLITE_PACKAGE_CACHE;
29+
fileBackend = mockBackend();
30+
redisBackend = mockBackend();
31+
sqliteBackend = mockBackend();
32+
vi.mocked(PackageCacheFile.create).mockReturnValue(
33+
fileBackend as unknown as PackageCacheFile,
34+
);
35+
vi.mocked(PackageCacheRedis.create).mockResolvedValue(
36+
redisBackend as unknown as PackageCacheRedis,
37+
);
38+
vi.mocked(PackageCacheSqlite.create).mockResolvedValue(
39+
sqliteBackend as unknown as PackageCacheSqlite,
40+
);
41+
});
42+
43+
afterEach(async () => {
44+
await backend.destroy();
45+
});
46+
47+
it('returns undefined when not initialized', async () => {
48+
expect(backend.getCacheType()).toBeUndefined();
49+
expect(await backend.get('_test-namespace', 'missing-key')).toBeUndefined();
50+
});
51+
52+
it('silently ignores set when not initialized', async () => {
53+
await expect(
54+
backend.set('_test-namespace', 'key', 'value', 5),
55+
).resolves.toBeUndefined();
56+
});
57+
58+
it('silently ignores destroy when not initialized', async () => {
59+
await expect(backend.destroy()).resolves.toBeUndefined();
60+
});
61+
62+
it('initializes file backend', async () => {
63+
await backend.init({ cacheDir: 'some-dir' });
64+
65+
expect(PackageCacheFile.create).toHaveBeenCalledWith('some-dir');
66+
expect(backend.getCacheType()).toBe('file');
67+
});
68+
69+
it('initializes redis backend', async () => {
70+
await backend.init({ redisUrl: 'some-url' });
71+
72+
expect(PackageCacheRedis.create).toHaveBeenCalledWith(
73+
'some-url',
74+
undefined,
75+
);
76+
expect(backend.getCacheType()).toBe('redis');
77+
});
78+
79+
it('initializes sqlite backend', async () => {
80+
process.env.RENOVATE_X_SQLITE_PACKAGE_CACHE = 'true';
81+
82+
await backend.init({ cacheDir: 'some-dir' });
83+
84+
expect(PackageCacheSqlite.create).toHaveBeenCalledWith('some-dir');
85+
expect(backend.getCacheType()).toBe('sqlite');
86+
});
87+
88+
it('delegates get and set to backend instance', async () => {
89+
await backend.init({ cacheDir: 'some-dir' });
90+
91+
await backend.get('_test-namespace', 'some-key');
92+
93+
expect(fileBackend.get).toHaveBeenCalledWith('_test-namespace', 'some-key');
94+
95+
await backend.set('_test-namespace', 'some-key', 'some-value', 5);
96+
97+
expect(fileBackend.set).toHaveBeenCalledWith(
98+
'_test-namespace',
99+
'some-key',
100+
'some-value',
101+
5,
102+
);
103+
});
104+
105+
it('re-init destroys previous backend', async () => {
106+
await backend.init({ redisUrl: 'some-url' });
107+
108+
expect(backend.getCacheType()).toBe('redis');
109+
110+
await backend.init({ cacheDir: 'some-dir' });
111+
112+
expect(redisBackend.destroy).toHaveBeenCalled();
113+
expect(backend.getCacheType()).toBe('file');
114+
115+
await backend.get('_test-namespace', 'key');
116+
117+
expect(redisBackend.get).not.toHaveBeenCalled();
118+
expect(fileBackend.get).toHaveBeenCalled();
119+
});
120+
121+
it('clears backend when re-init has no config', async () => {
122+
await backend.init({ cacheDir: 'some-dir' });
123+
124+
expect(backend.getCacheType()).toBe('file');
125+
126+
await backend.init({});
127+
128+
expect(backend.getCacheType()).toBeUndefined();
129+
expect(await backend.get('_test-namespace', 'key')).toBeUndefined();
130+
});
131+
132+
it('destroys backend and clears state', async () => {
133+
await backend.init({ redisUrl: 'some-url' });
134+
135+
expect(backend.getCacheType()).toBe('redis');
136+
137+
await backend.destroy();
138+
139+
expect(redisBackend.destroy).toHaveBeenCalled();
140+
expect(backend.getCacheType()).toBeUndefined();
141+
});
142+
});

lib/util/cache/package/backend.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import type { AllConfig } from '../../../config/types.ts';
2+
import { getEnv } from '../../env.ts';
3+
import type { PackageCacheBase } from './impl/base.ts';
4+
import { PackageCacheFile } from './impl/file.ts';
5+
import { PackageCacheRedis } from './impl/redis.ts';
6+
import { PackageCacheSqlite } from './impl/sqlite.ts';
7+
import type { PackageCacheNamespace } from './types.ts';
8+
9+
let cacheProxy: PackageCacheBase | undefined;
10+
let cacheType: 'redis' | 'sqlite' | 'file' | undefined;
11+
12+
export function getCacheType(): typeof cacheType {
13+
return cacheType;
14+
}
15+
16+
export async function init(config: AllConfig): Promise<void> {
17+
await destroy();
18+
19+
if (config.redisUrl) {
20+
cacheProxy = await PackageCacheRedis.create(
21+
config.redisUrl,
22+
config.redisPrefix,
23+
);
24+
cacheType = 'redis';
25+
return;
26+
}
27+
28+
if (getEnv().RENOVATE_X_SQLITE_PACKAGE_CACHE && config.cacheDir) {
29+
cacheProxy = await PackageCacheSqlite.create(config.cacheDir);
30+
cacheType = 'sqlite';
31+
return;
32+
}
33+
34+
if (config.cacheDir) {
35+
cacheProxy = PackageCacheFile.create(config.cacheDir);
36+
cacheType = 'file';
37+
return;
38+
}
39+
}
40+
41+
export async function get<T = unknown>(
42+
namespace: PackageCacheNamespace,
43+
key: string,
44+
): Promise<T | undefined> {
45+
return await cacheProxy?.get<T>(namespace, key);
46+
}
47+
48+
export async function set(
49+
namespace: PackageCacheNamespace,
50+
key: string,
51+
value: unknown,
52+
hardTtlMinutes: number,
53+
): Promise<void> {
54+
await cacheProxy?.set(namespace, key, value, hardTtlMinutes);
55+
}
56+
57+
export async function destroy(): Promise<void> {
58+
cacheType = undefined;
59+
try {
60+
await cacheProxy?.destroy();
61+
} finally {
62+
cacheProxy = undefined;
63+
}
64+
}

lib/util/cache/package/file.spec.ts

Lines changed: 0 additions & 43 deletions
This file was deleted.

lib/util/cache/package/file.ts

Lines changed: 0 additions & 117 deletions
This file was deleted.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type { PackageCacheNamespace } from '../types.ts';
2+
3+
export abstract class PackageCacheBase {
4+
abstract get<T = unknown>(
5+
namespace: PackageCacheNamespace,
6+
key: string,
7+
): Promise<T | undefined>;
8+
9+
abstract set(
10+
namespace: PackageCacheNamespace,
11+
key: string,
12+
value: unknown,
13+
hardTtlMinutes: number,
14+
): Promise<void>;
15+
16+
abstract destroy(): Promise<void>;
17+
}

0 commit comments

Comments
 (0)