From 2cc2d9062184f69c012cab086f76a608a2eb2f1e Mon Sep 17 00:00:00 2001 From: JonasKruckenberg Date: Mon, 15 Mar 2021 15:14:44 +0100 Subject: [PATCH] feat: add caching utils --- packages/core/src/__tests__/cache.spec.ts | 114 ++++++++++++++++++++++ packages/core/src/cache.ts | 48 +++++++++ 2 files changed, 162 insertions(+) create mode 100644 packages/core/src/__tests__/cache.spec.ts create mode 100644 packages/core/src/cache.ts diff --git a/packages/core/src/__tests__/cache.spec.ts b/packages/core/src/__tests__/cache.spec.ts new file mode 100644 index 00000000..155a6425 --- /dev/null +++ b/packages/core/src/__tests__/cache.spec.ts @@ -0,0 +1,114 @@ +import { has, get, info, remove, generateKey } from '../cache' +import mock from 'mock-fs' + +describe('cache', () => { + afterAll(() => { + mock.restore() + }) + + describe('has', () => { + it('returns true if file is present', async () => { + mock({ + '/cache': { + 'test.png': 'foobar' + } + }) + + expect(await has('/cache', 'test.png')).toBeTruthy() + }) + + it('returns false if the file is missing', async () => { + mock({}) + + expect(await has('/cache', 'test.png')).toBeFalsy() + }) + }) + + describe('get', () => { + it('returns the data', async () => { + mock({ + '/cache': { + 'test.png': Buffer.from('foobar') + } + }) + + const res = await get('/cache', 'test.png') + expect(res).toHaveProperty('data', Buffer.from('foobar')) + }) + + it('returns the metadata if metadata file is present', async () => { + mock({ + '/cache': { + 'test.png': Buffer.from('foobar'), + 'test.png.json': JSON.stringify({ foo: 'bar' }) + } + }) + + const res = await get('/cache', 'test.png') + + expect(res).toHaveProperty('data', Buffer.from('foobar')) + expect(res).toHaveProperty('metadata', { foo: 'bar' }) + }) + + it('returns an empty object if metadata file is missing', async () => { + mock({ + '/cache': { + 'test.png': Buffer.from('foobar') + } + }) + + const res = await get('/cache', 'test.png') + + expect(res).toHaveProperty('data', Buffer.from('foobar')) + expect(res).toHaveProperty('metadata', {}) + }) + + it('throws if no image is found', async (done) => { + mock({}) + + try { + await get('/cache', 'test.png') + fail() + } catch { + done() + } + }) + }) + + describe('info', () => { + it('returns an object if metadata file is present', async () => { + mock({ + '/cache': { + 'test.png.json': JSON.stringify({ foo: 'bar' }) + } + }) + + const res = await info('/cache', 'test.png') + + expect(res).toHaveProperty('foo', 'bar') + }) + + it('throws if not metadata file is found', async (done) => { + mock({}) + + try { + await info('/cache', 'test.png') + fail() + } catch { + done() + } + }) + }) + + describe('remove', () => { + it('removes the image file', () => { }) + it('removes the metadata file if present', () => { }) + it('throws if no image is found', () => { }) + }) + + describe('generateKey', () => { + it('returns a string', () => { }) + test('returned string has file ending', () => { }) + test('returned file ending matches config', () => { }) + }) +}) \ No newline at end of file diff --git a/packages/core/src/cache.ts b/packages/core/src/cache.ts new file mode 100644 index 00000000..70efb928 --- /dev/null +++ b/packages/core/src/cache.ts @@ -0,0 +1,48 @@ +import { mkdir, readFile, writeFile, stat, unlink } from 'fs/promises' +import { join, extname } from 'path' + +export async function has(cachePath: string, key: string): Promise { + try { + const file = await stat(join(cachePath, key)) + return file.isFile() + } catch { + return false + } +} + +export async function get(cachePath: string, key: string): Promise<{ data: Uint8Array, metadata: Record }> { + const [data, metadata] = await Promise.all([ + readFile(join(cachePath, key)), + has(cachePath, key + '.json').then(has => has ? info(cachePath, key) : {}) + ]) + + return { data, metadata } +} + +export async function info(cachePath: string, key: string): Promise> { + const rawMeta = await readFile(join(cachePath, key + '.json'), 'utf-8') + return JSON.parse(rawMeta) +} + +export async function put(cachePath: string, key: string, data: Uint8Array, metadata?: Record): Promise { + await mkdir(cachePath, { recursive: true }) + + await Promise.all([ + writeFile(join(cachePath, key), data), + metadata ? writeFile(join(cachePath, key + '.json'), JSON.stringify(metadata)) : undefined + ]) +} + +export async function remove(cachePath: string, key: string): Promise { + await Promise.all([ + unlink(join(cachePath, key)), + has(cachePath, key + '.json').then(has => has ? unlink(join(cachePath, key + '.json')) : undefined) + ]) +} + +export function generateKey(url: URL, config: Record) { + const name = Buffer.from(JSON.stringify(config)).toString('base64') + const ext = config.format ? `.${config.format}` : extname(url.pathname) + + return name + ext +} \ No newline at end of file