Something went wrong. Try again.
A local-first event pipeline for independent agents, built on Jazz.
Something went wrong. Try again.
3.8 kB · 86 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687import fs from "node:fs/promises";import path from "node:path";import { afterEach, describe, expect, test } from "vitest";import { FilesystemConnector } from "../src/connectors/filesystem.js";import type { JazzThoughtStore } from "../src/jazz/store.js";import { temporaryProject, testStore } from "./helpers.js";
const stores: JazzThoughtStore[] = [];const roots: string[] = [];
afterEach(async () => { await Promise.all(stores.splice(0).map((store) => store.close())); await Promise.all(roots.splice(0).map((root) => fs.rm(root, { recursive: true, force: true })));});
describe("FilesystemConnector", () => { test("records add, change, rename, and delete with stable lineage inputs", async () => { const project = await temporaryProject(); roots.push(project); const vault = path.join(project, "vault"); await fs.mkdir(vault); await fs.mkdir(path.join(vault, ".obsidian")); await fs.writeFile(path.join(vault, ".obsidian", "workspace.json"), "{\"private\":true}\n"); await fs.writeFile(path.join(vault, "alpha.md"), "---\nid: alpha\n---\n# Alpha\n\n[[Beta]]\n"); await fs.writeFile(path.join(vault, "beta.md"), "# Beta\n");
const store = await testStore(project); stores.push(store); const connector = new FilesystemConnector({ id: "filesystem:fixture", root: vault });
const initial = await connector.scan(store); expect(initial.added).toBe(2); expect((await store.listCurrentDocuments("filesystem:fixture")).map((document) => document.path)) .not.toContain(".obsidian/workspace.json"); expect(initial.events.map((event) => event.type)).toEqual([ "stream.thought.source.file.added", "stream.thought.source.file.added", ]);
const unchanged = await connector.scan(store); expect(unchanged.unchanged).toBe(2); expect(unchanged.events).toHaveLength(0);
await fs.writeFile(path.join(vault, "alpha.md"), "---\nid: alpha\n---\n# Alpha changed\n\n[[Beta]]\n"); const changed = await connector.scan(store); expect(changed.changed).toBe(1); expect(changed.events[0]?.payload.diff).toContain("# Alpha changed");
const beforeRename = (await store.listCurrentDocuments("filesystem:fixture")).find((document) => document.path === "alpha.md"); await fs.rename(path.join(vault, "alpha.md"), path.join(vault, "renamed-alpha.md")); const renamed = await connector.scan(store); expect(renamed.renamed).toBe(1); expect(renamed.events[0]?.payload.previousPath).toBe("alpha.md"); expect(renamed.events[0]?.payload.documentId).toBe(beforeRename?.documentId);
await fs.rm(path.join(vault, "beta.md")); const deleted = await connector.scan(store); expect(deleted.deleted).toBe(1); expect(deleted.events[0]?.type).toBe("stream.thought.source.file.deleted");
const all = await store.listEvents({ source: "filesystem:fixture" }); expect(all.map((event) => event.type)).toEqual([ "stream.thought.source.file.added", "stream.thought.source.file.added", "stream.thought.source.file.changed", "stream.thought.source.file.renamed", "stream.thought.source.file.deleted", ]); });
test("refuses symlinks that leave the configured root", async () => { const project = await temporaryProject(); roots.push(project); const vault = path.join(project, "vault"); await fs.mkdir(vault); await fs.writeFile(path.join(project, "outside.md"), "secret"); await fs.symlink(path.join(project, "outside.md"), path.join(vault, "escape.md")); const store = await testStore(project); stores.push(store); const connector = new FilesystemConnector({ id: "filesystem:fixture", root: vault });
const result = await connector.scan(store); expect(result.scanned).toBe(0); expect(await store.listEvents()).toHaveLength(0); });});