MCPcopy
hub / github.com/BuilderIO/agent-native / FileStore

Class FileStore

packages/pinpoint/src/storage/file-store.ts:23–148  ·  view source on GitHub ↗

Source from the content-addressed store, hash-verified

21const VALID_ID = /^[a-zA-Z0-9_-]+$/;
22
23export class FileStore implements PinStorage {
24 private dir: string;
25
26 constructor(dataDir: string = "data/pins") {
27 this.dir = resolve(dataDir);
28 }
29
30 private validateId(id: string): void {
31 if (!VALID_ID.test(id)) {
32 throw new Error(`Invalid pin ID: ${id}`);
33 }
34 }
35
36 private pinPath(id: string): string {
37 this.validateId(id);
38 const resolved = join(this.dir, `${id}.json`);
39 // Ensure resolved path is within the data directory
40 if (!resolved.startsWith(this.dir)) {
41 throw new Error("Path traversal detected");
42 }
43 return resolved;
44 }
45
46 private async ensureDir(): Promise<void> {
47 await mkdir(this.dir, { recursive: true });
48 }
49
50 private async readPin(filePath: string): Promise<Pin | null> {
51 try {
52 const content = await readFile(filePath, "utf-8");
53 const parsed = JSON.parse(content);
54 const result = PinSchema.safeParse(parsed);
55 return result.success ? (result.data as Pin) : null;
56 } catch {
57 return null;
58 }
59 }
60
61 private async atomicWrite(filePath: string, data: string): Promise<void> {
62 await this.ensureDir();
63 const tempPath = join(tmpdir(), `pinpoint-${randomUUID()}.tmp`);
64 await writeFile(tempPath, data, "utf-8");
65 await rename(tempPath, filePath);
66 }
67
68 async load(pageUrl: string): Promise<Pin[]> {
69 return this.list({ pageUrl });
70 }
71
72 async save(pin: Pin): Promise<void> {
73 const filePath = this.pinPath(pin.id);
74 await this.atomicWrite(filePath, JSON.stringify(pin, null, 2));
75 }
76
77 async update(id: string, patch: Partial<Pin>): Promise<void> {
78 const filePath = this.pinPath(id);
79 const existing = await this.readPin(filePath);
80 if (!existing) return;

Callers

nothing calls this directly

Calls

no outgoing calls

Tested by

no test coverage detected