1import cacache from 'cacache'; 2import { Readable } from 'stream'; 3 4function getBodyAndMetaKeys(key: string): [string, string] { 5 return [`${key}body`, `${key}meta`]; 6} 7 8export class FileSystemCache { 9 constructor(public options: { ttl?: number; cacheDirectory: string }) {} 10 11 async get(key: string) { 12 const [, metaKey] = getBodyAndMetaKeys(key); 13 14 const metaInfo = await cacache.get.info(this.options.cacheDirectory, metaKey); 15 16 if (!metaInfo) { 17 return undefined; 18 } 19 20 const metaBuffer = await cacache.get.byDigest(this.options.cacheDirectory, metaInfo.integrity); 21 const metaData = JSON.parse(metaBuffer); 22 const { bodyStreamIntegrity, empty, expiration } = metaData; 23 24 delete metaData.bodyStreamIntegrity; 25 delete metaData.empty; 26 delete metaData.expiration; 27 28 if (expiration && expiration < Date.now()) { 29 return undefined; 30 } 31 32 const bodyStream = empty 33 ? Readable.from(Buffer.alloc(0)) 34 : cacache.get.stream.byDigest(this.options.cacheDirectory, bodyStreamIntegrity); 35 36 return { 37 bodyStream, 38 metaData, 39 }; 40 } 41 42 remove(key: string) { 43 const [bodyKey, metaKey] = getBodyAndMetaKeys(key); 44 45 return Promise.all([ 46 cacache.rm.entry(this.options.cacheDirectory, bodyKey), 47 cacache.rm.entry(this.options.cacheDirectory, metaKey), 48 ]); 49 } 50 51 async set(key: string, bodyStream: NodeJS.ReadStream, metaData: any) { 52 const [bodyKey, metaKey] = getBodyAndMetaKeys(key); 53 const metaCopy = { ...metaData }; 54 55 if (typeof this.options.ttl === 'number') { 56 metaCopy.expiration = Date.now() + this.options.ttl; 57 } 58 59 try { 60 metaCopy.bodyStreamIntegrity = await new Promise((fulfill, reject) => { 61 bodyStream 62 .pipe(cacache.put.stream(this.options.cacheDirectory, bodyKey)) 63 .on('integrity', (i) => fulfill(i)) 64 .on('error', (e) => { 65 reject(e); 66 }); 67 }); 68 } catch (err: any) { 69 if (err.code !== 'ENODATA') { 70 throw err; 71 } 72 73 metaCopy.empty = true; 74 } 75 76 const metaBuffer = Buffer.from(JSON.stringify(metaCopy)); 77 await cacache.put(this.options.cacheDirectory, metaKey, metaBuffer); 78 const cachedData = await this.get(key); 79 80 return cachedData; 81 } 82} 83