-
Notifications
You must be signed in to change notification settings - Fork 8
/
file_system_cache.ts
97 lines (79 loc) · 2.69 KB
/
file_system_cache.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import assert from 'assert';
import { Buffer } from 'buffer';
import { Readable } from 'stream';
import cacache from 'cacache';
import type { INodeFetchCacheCache, NFCResponseMetadata } from '../../types';
type StoredMetadata = {
emptyBody?: boolean;
expiration?: number | undefined;
} & NFCResponseMetadata;
const emptyBuffer = Buffer.alloc(0);
export class FileSystemCache implements INodeFetchCacheCache {
private readonly ttl?: number | undefined;
private readonly cacheDirectory: string;
constructor(options: { ttl?: number; cacheDirectory?: string } = {}) {
this.ttl = options.ttl;
this.cacheDirectory = options.cacheDirectory ?? '.cache';
}
async get(key: string, options?: { ignoreExpiration?: boolean }) {
const cachedObjectInfo = await cacache.get.info(this.cacheDirectory, key);
if (!cachedObjectInfo) {
return undefined;
}
const storedMetadata = cachedObjectInfo.metadata as StoredMetadata;
const { emptyBody, expiration, ...nfcMetadata } = storedMetadata;
if (!options?.ignoreExpiration && expiration && expiration < Date.now()) {
return undefined;
}
if (emptyBody) {
return {
bodyStream: Readable.from(emptyBuffer),
metaData: storedMetadata,
};
}
return {
bodyStream: cacache.get.stream.byDigest(this.cacheDirectory, cachedObjectInfo.integrity),
metaData: nfcMetadata,
};
}
async remove(key: string) {
return cacache.rm.entry(this.cacheDirectory, key);
}
async set(key: string, bodyStream: NodeJS.ReadableStream, metaData: NFCResponseMetadata) {
const metaToStore = {
...metaData,
expiration: undefined as (undefined | number),
emptyBody: false,
};
if (typeof this.ttl === 'number') {
metaToStore.expiration = Date.now() + this.ttl;
}
await this.writeDataToCache(key, metaToStore, bodyStream);
const cachedData = await this.get(key, { ignoreExpiration: true });
assert(cachedData, 'Failed to cache response');
return cachedData;
}
private async writeDataToCache(
key: string,
storedMetadata: StoredMetadata,
stream: NodeJS.ReadableStream,
) {
try {
await new Promise((fulfill, reject) => {
stream.pipe(cacache.put.stream(this.cacheDirectory, key, { metadata: storedMetadata }))
.on('integrity', (i: string) => {
fulfill(i);
})
.on('error', (error: Error) => {
reject(error);
});
});
} catch (error: any) {
if (error.code !== 'ENODATA') {
throw error as Error;
}
storedMetadata.emptyBody = true;
await cacache.put(this.cacheDirectory, key, emptyBuffer, { metadata: storedMetadata });
}
}
}