forked from konveyor/editor-extensions
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcache.ts
More file actions
201 lines (178 loc) · 5.54 KB
/
Copy pathcache.ts
File metadata and controls
201 lines (178 loc) · 5.54 KB
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
import * as pathlib from "path";
import * as fs from "fs/promises";
import * as winston from "winston";
import { createHash } from "crypto";
import { type InputOutputCache } from "@editor-extensions/shared";
export interface CacheFilePaths {
inputRecordPath: string;
outputRecordPath: string;
}
export interface FileBasedCacheOptions {
cacheSubDir?: string;
inputFileExt?: string;
outputFileExt?: string;
}
/**
* A file-based cache implementation that caches generic inputs and outputs on disk.
*
* @template K - The type of the input to cache e.g. LLM Input Prompt(s).
* @template V - The type of the output to cache e.g. LLM Response.
* @template C - The coordinates of the cache - paths to cache files.
* @template O - Additional options for the cache.
*/
export class FileBasedResponseCache<K, V> implements InputOutputCache<
K,
V,
CacheFilePaths,
FileBasedCacheOptions
> {
enabled: boolean;
constructor(
enabled: boolean,
private readonly serializeFunction: (input: K | V) => string,
private readonly deserializeFunction: (input: string) => V,
private readonly cacheDir?: string,
private readonly logger?: winston.Logger,
private readonly hashFunction?: (input: K | V) => string,
) {
this.enabled = enabled;
}
async get(input: K, opts?: FileBasedCacheOptions): Promise<V | undefined> {
if (!this.enabled) {
return undefined;
}
const cachePath = pathlib.join(
this.cacheDir ?? "",
opts?.cacheSubDir ?? "",
this.hashFunction ? this.hashFunction(input) : this.hash(input),
`output${opts?.outputFileExt ?? ".json"}`,
);
this.logger?.silly("Getting cache", { cachePath, input });
try {
const stat = await fs.stat(cachePath);
if (stat.isFile()) {
const data = await fs.readFile(cachePath, "utf-8");
return this.deserializeFunction(data);
}
} catch (err) {
this.logger?.error("Error looking up cache", err);
}
return undefined;
}
async set(input: K, value: V, opts?: FileBasedCacheOptions): Promise<CacheFilePaths | undefined> {
if (!this.enabled) {
return undefined;
}
const cacheBasePath = pathlib.join(
this.cacheDir ?? "",
opts?.cacheSubDir ?? "",
this.hashFunction ? this.hashFunction(input) : this.hash(input),
);
this.logger?.silly("Setting cache", { cacheBasePath, input });
try {
const inputRecordPath = pathlib.join(cacheBasePath, `input${opts?.inputFileExt ?? ".json"}`);
const outputRecordPath = pathlib.join(
cacheBasePath,
`output${opts?.outputFileExt ?? ".json"}`,
);
await fs.mkdir(cacheBasePath, { recursive: true });
await fs.writeFile(inputRecordPath, this.serializeFunction(input));
await fs.writeFile(outputRecordPath, this.serializeFunction(value));
return {
inputRecordPath,
outputRecordPath,
};
} catch (err) {
this.logger?.error("Error updating cache", { error: err });
}
return undefined;
}
private hash(input: K): string {
return createHash("sha256").update(this.serializeFunction(input)).digest("hex").slice(0, 16);
}
async invalidate(input: K, opts?: FileBasedCacheOptions): Promise<void> {
return await fs.rm(
pathlib.join(this.cacheDir ?? "", opts?.cacheSubDir ?? "", this.hash(input)),
{
recursive: true,
force: true,
},
);
}
async reset(): Promise<void> {
return await fs.rm(this.cacheDir ?? "", { recursive: true, force: true });
}
}
export interface InMemoryCacheWithRevisionsOptions {
maxRevisions: number;
}
export const ALL_REVISIONS = -1;
/**
* A memory-based cache implementation that caches generic inputs and outputs.
* Supports storing multiple revisions of the same input. Useful for caching fs changes.
*
* @template K - The type of the input to cache, must be hashable.
* @template V - The type of the value for the given input to cache.
* @template C - undefined (coordinates not available for in-memory cache).
* @template O - Any additional options.
*/
export class InMemoryCacheWithRevisions<K, V> implements InputOutputCache<
K,
V,
void,
InMemoryCacheWithRevisionsOptions
> {
private readonly cache: Map<K, V[]>;
enabled: boolean;
constructor(enabled: boolean) {
this.enabled = enabled;
this.cache = new Map<K, V[]>();
}
async get(input: K, _opts?: InMemoryCacheWithRevisionsOptions): Promise<V | undefined> {
if (!this.enabled) {
return undefined;
}
const stack = this.cache.get(input);
if (!stack || stack.length === 0) {
return undefined;
}
return stack[stack.length - 1];
}
async set(input: K, value: V, _opts?: InMemoryCacheWithRevisionsOptions): Promise<void> {
if (!this.enabled) {
return;
}
const existingStack = this.cache.get(input);
if (existingStack) {
existingStack.push(value);
} else {
this.cache.set(input, [value]);
}
}
async invalidate(input: K, opts?: InMemoryCacheWithRevisionsOptions): Promise<void> {
if (!this.enabled) {
return;
}
const stack = this.cache.get(input);
if (!stack || stack.length === 0) {
return;
}
const revisionsToRemove = opts?.maxRevisions ?? 1;
if (revisionsToRemove === ALL_REVISIONS) {
this.cache.delete(input);
return;
}
for (let i = 0; i < revisionsToRemove && stack.length > 0; i++) {
stack.pop();
}
if (stack.length === 0) {
this.cache.delete(input);
}
}
async reset(): Promise<void> {
if (!this.enabled) {
return;
}
this.cache.clear();
}
}