-
Notifications
You must be signed in to change notification settings - Fork 2.2k
/
file_system.ts
153 lines (141 loc) Β· 4.77 KB
/
file_system.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
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
import * as fs from "node:fs/promises";
import * as path from "node:path";
import { BaseStore } from "../schema/storage.js";
/**
* File system implementation of the BaseStore using a dictionary. Used for
* storing key-value pairs in the file system.
*/
export class LocalFileStore extends BaseStore<string, Uint8Array> {
lc_namespace = ["langchain", "storage"];
rootPath: string;
constructor(fields: { rootPath: string }) {
super(fields);
this.rootPath = fields.rootPath;
}
/**
* Read and parse the file at the given path.
* @param key The key to read the file for.
* @returns Promise that resolves to the parsed file content.
*/
private async getParsedFile(key: string): Promise<Uint8Array | undefined> {
try {
const fileContent = await fs.readFile(this.getFullPath(key));
if (!fileContent) {
return undefined;
}
return fileContent;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (e: any) {
// File does not exist yet.
// eslint-disable-next-line no-instanceof/no-instanceof
if ("code" in e && e.code === "ENOENT") {
return undefined;
}
throw new Error(
`Error reading and parsing file at path: ${
this.rootPath
}.\nError: ${JSON.stringify(e)}`
);
}
}
/**
* Writes the given key-value pairs to the file at the given path.
* @param fileContent An object with the key-value pairs to be written to the file.
*/
private async setFileContent(content: Uint8Array, key: string) {
try {
await fs.writeFile(this.getFullPath(key), content);
} catch (error) {
throw new Error(
`Error writing file at path: ${this.getFullPath(
key
)}.\nError: ${JSON.stringify(error)}`
);
}
}
/**
* Returns the full path of the file where the value of the given key is stored.
* @param key the key to get the full path for
*/
private getFullPath(key: string): string {
try {
const keyAsTxtFile = `${key}.txt`;
const fullPath = path.join(this.rootPath, keyAsTxtFile);
return fullPath;
} catch (e) {
throw new Error(
`Error getting full path for key: ${key}.\nError: ${JSON.stringify(e)}`
);
}
}
/**
* Retrieves the values associated with the given keys from the store.
* @param keys Keys to retrieve values for.
* @returns Array of values associated with the given keys.
*/
async mget(keys: string[]) {
const values: (Uint8Array | undefined)[] = [];
for (const key of keys) {
const fileContent = await this.getParsedFile(key);
values.push(fileContent);
}
return values;
}
/**
* Sets the values for the given keys in the store.
* @param keyValuePairs Array of key-value pairs to set in the store.
* @returns Promise that resolves when all key-value pairs have been set.
*/
async mset(keyValuePairs: [string, Uint8Array][]): Promise<void> {
await Promise.all(
keyValuePairs.map(([key, value]) => this.setFileContent(value, key))
);
}
/**
* Deletes the given keys and their associated values from the store.
* @param keys Keys to delete from the store.
* @returns Promise that resolves when all keys have been deleted.
*/
async mdelete(keys: string[]): Promise<void> {
await Promise.all(keys.map((key) => fs.unlink(this.getFullPath(key))));
}
/**
* Asynchronous generator that yields keys from the store. If a prefix is
* provided, it only yields keys that start with the prefix.
* @param prefix Optional prefix to filter keys.
* @returns AsyncGenerator that yields keys from the store.
*/
async *yieldKeys(prefix?: string): AsyncGenerator<string> {
const allFiles = await fs.readdir(this.rootPath);
const allKeys = allFiles.map((file) => file.replace(".txt", ""));
for (const key of allKeys) {
if (prefix === undefined || key.startsWith(prefix)) {
yield key;
}
}
}
/**
* Static method for initializing the class.
* Preforms a check to see if the directory exists, and if not, creates it.
* @param path Path to the directory.
* @returns Promise that resolves to an instance of the class.
*/
static async fromPath(rootPath: string): Promise<LocalFileStore> {
try {
// Verifies the directory exists at the provided path, and that it is readable and writable.
await fs.access(rootPath, fs.constants.R_OK | fs.constants.W_OK);
} catch (_) {
try {
// Directory does not exist, create it.
await fs.mkdir(rootPath, { recursive: true });
} catch (error) {
throw new Error(
`An error occurred creating directory at: ${rootPath}.\nError: ${JSON.stringify(
error
)}`
);
}
}
return new this({ rootPath });
}
}