zz_

app/worker/src/memFs.ts

zzmq.dev@gmail.com/zz / app / worker / src / memFs.ts @ 15050aba 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 };
 }