Skip to content

Commit 51146ea

Browse files
committed
refactor(cache): extract package cache backend classes
1 parent 8cf4171 commit 51146ea

19 files changed

+1078
-618
lines changed
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import { PackageCacheFile } from './impl/file.ts';
2+
import { PackageCacheRedis } from './impl/redis.ts';
3+
import { PackageCacheSqlite } from './impl/sqlite.ts';
4+
import * as backend from './backend.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(
50+
await backend.get('_test-namespace', 'missing-key'),
51+
).toBeUndefined();
52+
});
53+
54+
it('silently ignores set when not initialized', async () => {
55+
await expect(
56+
backend.set('_test-namespace', 'key', 'value', 5),
57+
).resolves.toBeUndefined();
58+
});
59+
60+
it('silently ignores destroy when not initialized', async () => {
61+
await expect(backend.destroy()).resolves.toBeUndefined();
62+
});
63+
64+
it('initializes file backend', async () => {
65+
await backend.init({ cacheDir: 'some-dir' });
66+
expect(PackageCacheFile.create).toHaveBeenCalledWith('some-dir');
67+
expect(backend.getCacheType()).toBe('file');
68+
});
69+
70+
it('initializes redis backend', async () => {
71+
await backend.init({ redisUrl: 'some-url' });
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+
await backend.init({ cacheDir: 'some-dir' });
82+
expect(PackageCacheSqlite.create).toHaveBeenCalledWith('some-dir');
83+
expect(backend.getCacheType()).toBe('sqlite');
84+
});
85+
86+
it('delegates get and set to backend instance', async () => {
87+
await backend.init({ cacheDir: 'some-dir' });
88+
89+
await backend.get('_test-namespace', 'some-key');
90+
expect(fileBackend.get).toHaveBeenCalledWith(
91+
'_test-namespace',
92+
'some-key',
93+
);
94+
95+
await backend.set('_test-namespace', 'some-key', 'some-value', 5);
96+
expect(fileBackend.set).toHaveBeenCalledWith(
97+
'_test-namespace',
98+
'some-key',
99+
'some-value',
100+
5,
101+
);
102+
});
103+
104+
it('re-init destroys previous backend', async () => {
105+
await backend.init({ redisUrl: 'some-url' });
106+
expect(backend.getCacheType()).toBe('redis');
107+
108+
await backend.init({ cacheDir: 'some-dir' });
109+
expect(redisBackend.destroy).toHaveBeenCalled();
110+
expect(backend.getCacheType()).toBe('file');
111+
112+
await backend.get('_test-namespace', 'key');
113+
expect(redisBackend.get).not.toHaveBeenCalled();
114+
expect(fileBackend.get).toHaveBeenCalled();
115+
});
116+
117+
it('clears backend when re-init has no config', async () => {
118+
await backend.init({ cacheDir: 'some-dir' });
119+
expect(backend.getCacheType()).toBe('file');
120+
121+
await backend.init({});
122+
expect(backend.getCacheType()).toBeUndefined();
123+
expect(await backend.get('_test-namespace', 'key')).toBeUndefined();
124+
});
125+
126+
it('destroys backend and clears state', async () => {
127+
await backend.init({ redisUrl: 'some-url' });
128+
expect(backend.getCacheType()).toBe('redis');
129+
130+
await backend.destroy();
131+
expect(redisBackend.destroy).toHaveBeenCalled();
132+
expect(backend.getCacheType()).toBeUndefined();
133+
});
134+
});

lib/util/cache/package/backend.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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+
// v8 ignore else -- TODO: add test #40625
35+
if (config.cacheDir) {
36+
cacheProxy = PackageCacheFile.create(config.cacheDir);
37+
cacheType = 'file';
38+
return;
39+
}
40+
}
41+
42+
export async function get<T = unknown>(
43+
namespace: PackageCacheNamespace,
44+
key: string,
45+
): Promise<T | undefined> {
46+
return cacheProxy?.get<T>(namespace, key);
47+
}
48+
49+
export async function set(
50+
namespace: PackageCacheNamespace,
51+
key: string,
52+
value: unknown,
53+
hardTtlMinutes: number,
54+
): Promise<void> {
55+
await cacheProxy?.set(namespace, key, value, hardTtlMinutes);
56+
}
57+
58+
export async function destroy(): Promise<void> {
59+
cacheType = undefined;
60+
try {
61+
await cacheProxy?.destroy();
62+
} finally {
63+
cacheProxy = undefined;
64+
}
65+
}

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)