zz_

app/worker/src/memFs.ts

zzmq.dev@gmail.com/zz / app / worker / src / memFs.ts @ 5e5720fb Download
Ladder Summary
  • file The module implements a tiny in‑memory filesystem that mimics the subset of the Node `fs/promises` API required by **isomorphic‑git** (readFile, writeFile, unlink, readdir, mkdir, rmdir, stat, lstat, readlink, symlink, chmod). It stores files and directories in a `Map` keyed by normalized paths, handling path resolution, parent‑directory creation, and basic stat metadata, but nothing is persisted between calls. **Exported symbol:** `createMemFs` – a function that returns an object `{ promises }` exposing the async filesystem methods described above.
// A minimal in-memory filesystem implementing exactly the subset of
// node:fs's promise API isomorphic-git needs (readFile/writeFile/
// unlink/readdir/mkdir/rmdir/stat/lstat/readlink/symlink) -- Workers
// have no real filesystem at all, so this exists purely so
// isomorphic-git has *something* to write its git objects into before
// pushing them. Nothing here persists between requests; each call
// site creates a fresh one, writes only the handful of objects a
// single commit needs (the git_minimal_push approach -- see
// site/content/remote-backend/index.md), and pushes immediately.
interface Node {
  isDir: boolean;
  content?: Uint8Array;
  mtimeMs: number;
}

function normalize(path: string): string {
  const parts = path.split("/").filter((p) => p && p !== ".");
  const out: string[] = [];
  for (const part of parts) {
    if (part === "..") out.pop();
    else out.push(part);
  }
  return "/" + out.join("/");
}

function dirname(path: string): string {
  const n = normalize(path);
  const i = n.lastIndexOf("/");
  return i <= 0 ? "/" : n.slice(0, i);
}

export function createMemFs() {
  const nodes = new Map<string, Node>();
  nodes.set("/", { isDir: true, mtimeMs: Date.now() });

  function ensureParentDirs(path: string): void {
    let dir = dirname(path);
    const toCreate: string[] = [];
    while (dir !== "/" && !nodes.has(dir)) {
      toCreate.push(dir);
      dir = dirname(dir);
    }
    for (const d of toCreate.reverse()) nodes.set(d, { isDir: true, mtimeMs: Date.now() });
  }

  const promises = {
    async readFile(path: string, opts?: { encoding?: string } | string): Promise<Uint8Array | string> {
      const n = nodes.get(normalize(path));
      if (!n || n.isDir || !n.content) throw Object.assign(new Error(`ENOENT: ${path}`), { code: "ENOENT" });
      const encoding = typeof opts === "string" ? opts : opts?.encoding;
      if (encoding === "utf8" || encoding === "utf-8") return new TextDecoder().decode(n.content);
      return n.content;
    },
    async writeFile(path: string, data: Uint8Array | string): Promise<void> {
      const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
      ensureParentDirs(path);
      nodes.set(normalize(path), { isDir: false, content: bytes, mtimeMs: Date.now() });
    },
    async unlink(path: string): Promise<void> {
      nodes.delete(normalize(path));
    },
    async readdir(path: string): Promise<string[]> {
      const p = normalize(path);
      const prefix = p === "/" ? "/" : p + "/";
      const names = new Set<string>();
      for (const key of nodes.keys()) {
        if (key === p || !key.startsWith(prefix)) continue;
        const rest = key.slice(prefix.length);
        names.add(rest.split("/")[0]);
      }
      return [...names];
    },
    async mkdir(path: string): Promise<void> {
      ensureParentDirs(path);
      nodes.set(normalize(path), { isDir: true, mtimeMs: Date.now() });
    },
    async rmdir(path: string): Promise<void> {
      nodes.delete(normalize(path));
    },
    async stat(path: string) {
      const n = nodes.get(normalize(path));
      if (!n) throw Object.assign(new Error(`ENOENT: ${path}`), { code: "ENOENT" });
      return statFrom(n);
    },
    async lstat(path: string) {
      return promises.stat(path);
    },
    async readlink(): Promise<string> {
      throw new Error("symlinks not supported in memFs");
    },
    async symlink(): Promise<void> {
      throw new Error("symlinks not supported in memFs");
    },
    async chmod(): Promise<void> {
      // no-op -- isomorphic-git calls this but this fs has no real permission bits
    },
  };

  function statFrom(n: Node) {
    return {
      isFile: () => !n.isDir,
      isDirectory: () => n.isDir,
      isSymbolicLink: () => false,
      size: n.content?.byteLength ?? 0,
      mtimeMs: n.mtimeMs,
      mode: n.isDir ? 0o040000 : 0o100644,
    };
  }

  return { promises };
}