@cloudflare/workspace 0.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["sha256","stat","statImpl","#nextFd","#fds","readdirImpl","#fdOrThrow","readlinkImpl","#txDepth","hex","#options","#handle","#armUpgrade","#waitForPort","#postConnect","#waitForUpgrade","#resolveUpgrade","#pendingUpgrade","#rejectUpgrade","sleep","#clearUpgrade","RpcTarget","#container","#ctx","#url","#shell","#sync","disposeOnDone","maybeDispose","#ws","#pending","#fs","#shell","#db","#fs","#mounts","#done","#inFlight","#db","#fs","#backends","#reconnect","#now","#mounts","#mountIndex","#provider","#shell","#readyPromise","#connect","#serialize","#handle","#mutationTail","#connectOnce"],"sources":["../../dofs/src/errors.ts","../../dofs/src/path.ts","../../dofs/src/schema/core.ts","../../dofs/src/schema/migrations.ts","../../dofs/src/schema/sync.ts","../../dofs/src/schema/index.ts","../../dofs/src/fs/resolve.ts","../../dofs/src/fs/find.ts","../../dofs/src/fs/readFile.ts","../../dofs/src/fs/grep.ts","../../dofs/src/fs/ls.ts","../../dofs/src/rev.ts","../../dofs/src/fs/mount-guard.ts","../../dofs/src/fs/mkdir.ts","../../dofs/src/fs/readdir.ts","../../dofs/src/sync/changes.ts","../../dofs/src/fs/rm.ts","../../dofs/src/fs/stat.ts","../../dofs/src/sync/blobs.ts","../../dofs/src/sync/manifests.ts","../../dofs/src/fs/writeFile.ts","../../dofs/src/fs/filesystem.ts","../../dofs/src/fs/readlink.ts","../../dofs/src/fs/symlink.ts","../../dofs/src/sync/ignore.ts","../../dofs/src/sync/coalesce.ts","../../dofs/src/sync/watermarks.ts","../../dofs/src/fs/watch.ts","../../dofs/src/provider.ts","../../dofs/src/storage.ts","../../dofs/src/sync/apply.ts","../../dofs/src/sync/invariant.ts","../src/heartbeat.ts","../src/backends/cloudflare-container.ts","../src/backends/container-host.ts","../../rpc/dist/client.js","../src/backends/test.ts","../src/mounts/providers/r2.ts","../src/proxy.ts","../src/shell.ts","../../rpc/dist/debug.js","../src/stub.ts","../../rpc/src/sync-driver.ts","../src/mounts/index.ts","../src/mounts/registry.ts","../src/workspace.ts"],"sourcesContent":["export type WorkspaceErrorCode =\n | \"ENOENT\"\n | \"ENOTEMPTY\"\n | \"ENOTDIR\"\n | \"EISDIR\"\n | \"EEXIST\"\n | \"EINVAL\"\n | \"EACCES\"\n | \"EPERM\"\n | \"EROFS\"\n | \"ENOSYS\"\n | \"EBADF\"\n | \"ELOOP\"\n | \"EUNKNOWN_HASH\"\n | \"EIO\";\n\nexport interface WorkspaceFsError extends Error {\n code: WorkspaceErrorCode;\n path?: string;\n}\n\nexport function createWorkspaceError(\n code: WorkspaceErrorCode,\n message: string,\n path?: string,\n): WorkspaceFsError {\n const error = new Error(path === undefined ? message : `${message}: ${path}`) as WorkspaceFsError;\n error.name = \"WorkspaceFsError\";\n error.code = code;\n error.path = path;\n return error;\n}\n\nexport function invalidPath(path: string, reason: string): WorkspaceFsError {\n return createWorkspaceError(\"EINVAL\", `Invalid path (${reason})`, path);\n}\n","import { invalidPath } from \"./errors.js\";\n\nexport interface CanonicalPath {\n path: string;\n parts: string[];\n name: string;\n parentPath: string | undefined;\n}\n\nexport function canonicalizePath(path: string): CanonicalPath {\n if (path.length === 0) {\n throw invalidPath(path, \"empty\");\n }\n\n if (!path.startsWith(\"/\")) {\n throw invalidPath(path, \"must be absolute\");\n }\n\n if (path.includes(\"\\0\")) {\n throw invalidPath(path, \"contains NUL byte\");\n }\n\n const parts: string[] = [];\n for (const part of path.split(\"/\")) {\n if (part === \"\" || part === \".\") {\n continue;\n }\n\n if (part === \"..\") {\n if (parts.length === 0) {\n throw invalidPath(path, \"escapes root\");\n }\n parts.pop();\n continue;\n }\n\n parts.push(part);\n }\n\n const canonical = parts.length === 0 ? \"/\" : `/${parts.join(\"/\")}`;\n const name = parts.length === 0 ? \"\" : parts[parts.length - 1];\n const parentParts = parts.slice(0, -1);\n const parentPath =\n parts.length === 0 ? undefined : parentParts.length === 0 ? \"/\" : `/${parentParts.join(\"/\")}`;\n\n return {\n path: canonical,\n parts,\n name,\n parentPath,\n };\n}\n","// Filesystem-side tables. These hold the inode graph and the\n// content-addressed blob store. See docs/03_filesystem_schema.md.\n\n// Bumped to 2 when `_vfs_mounts.mode` landed (read-only mount\n// enforcement at the data layer). See `schema/migrations.ts` for the\n// migration list; `sync.ts` carries the fresh-install DDL.\nexport const SCHEMA_VERSION = 2;\nexport const ROOT_INODE = 1;\n\nexport const CORE_STATEMENTS = [\n `CREATE TABLE IF NOT EXISTS vfs_meta (\n k TEXT PRIMARY KEY,\n v INTEGER NOT NULL\n )`,\n `CREATE TABLE IF NOT EXISTS vfs_nodes (\n inode INTEGER PRIMARY KEY AUTOINCREMENT,\n type TEXT NOT NULL CHECK(type IN ('file','dir','symlink')),\n mode INTEGER NOT NULL DEFAULT 493,\n mtime INTEGER NOT NULL,\n rev INTEGER NOT NULL DEFAULT 0,\n mount_root TEXT,\n stub_size INTEGER,\n manifest_hash BLOB,\n link_target TEXT\n )`,\n `CREATE TABLE IF NOT EXISTS vfs_dirents (\n parent_inode INTEGER NOT NULL,\n name TEXT NOT NULL,\n child_inode INTEGER NOT NULL,\n PRIMARY KEY (parent_inode, name)\n )`,\n `CREATE INDEX IF NOT EXISTS vfs_dirents_by_child ON vfs_dirents(child_inode)`,\n `CREATE INDEX IF NOT EXISTS vfs_nodes_by_rev ON vfs_nodes(rev)`,\n // gc/manifests checks every manifest row against vfs_nodes via a\n // correlated NOT EXISTS (manifest_hash = ?). Without this index\n // gc full-scans vfs_nodes per candidate manifest — O(N×M).\n // Partial because the column is null on every dir and symlink\n // node, and on files until they get their first content write.\n `CREATE INDEX IF NOT EXISTS vfs_nodes_by_manifest_hash\n ON vfs_nodes(manifest_hash) WHERE manifest_hash IS NOT NULL`,\n `CREATE TABLE IF NOT EXISTS vfs_blobs (\n hash BLOB PRIMARY KEY,\n size INTEGER NOT NULL,\n last_seen INTEGER NOT NULL\n )`,\n `CREATE TABLE IF NOT EXISTS vfs_blob_bytes (\n hash BLOB PRIMARY KEY REFERENCES vfs_blobs(hash) ON DELETE CASCADE,\n bytes BLOB NOT NULL\n )`,\n `CREATE TABLE IF NOT EXISTS vfs_chunks (\n inode INTEGER NOT NULL,\n idx INTEGER NOT NULL,\n hash BLOB NOT NULL,\n size INTEGER NOT NULL,\n PRIMARY KEY (inode, idx)\n )`,\n `CREATE INDEX IF NOT EXISTS vfs_chunks_by_hash ON vfs_chunks(hash)`,\n] as const;\n","// Schema migration runner.\n//\n// The schema's \"CREATE TABLE IF NOT EXISTS\" baseline handles fresh\n// databases. When a schema column changes shape — added, dropped,\n// renamed, retyped — IF NOT EXISTS does nothing and the older\n// rows stay incompatible. Migrations close that gap.\n//\n// Shape: an ordered list of `(from, to, migrator)` tuples. The\n// runner reads `vfs_meta.schema_version` (defaulting to 0 when the\n// row is absent), picks every migration whose `from === current`,\n// runs it, advances `current`, and repeats until `current >=\n// SCHEMA_VERSION`. The whole pass runs inside the caller's\n// transactionSync so a partial migration rolls back.\n//\n// Each migrator is a `(db: Database) => void` and may assume the\n// previous version's schema is in place. Migrators land schema\n// changes only; they don't touch user data unless the column shape\n// requires it.\n\nimport type { Database } from \"../storage.js\";\n\nexport interface Migration {\n readonly from: number;\n readonly to: number;\n readonly migrator: (db: Database) => void;\n}\n\n// v1 → v2 — add `_vfs_mounts.mode` so dofs can enforce read-only\n// mounts at the data layer. Existing rows default to 'read-only';\n// the workspace re-stamps them with the registered mount's mode on\n// the next index pass.\n//\n// The CHECK constraint is duplicated in `sync.ts`'s fresh-install\n// DDL; both paths must keep the same allowed set.\nfunction v1_to_v2_add_mounts_mode(db: Database): void {\n db.run(\n `ALTER TABLE _vfs_mounts\n ADD COLUMN mode TEXT NOT NULL DEFAULT 'read-only'\n CHECK(mode IN ('read-only', 'read-write'))`,\n );\n}\n\nexport const MIGRATIONS: readonly Migration[] = [\n { from: 1, to: 2, migrator: v1_to_v2_add_mounts_mode },\n] as const;\n\n// Apply every migration whose `from` matches the current version,\n// in order, until we reach the target. The caller has already\n// wrapped this in a transactionSync; failures here roll the whole\n// initializeSchema call back.\nexport function runMigrations(db: Database, current: number, target: number): number {\n let version = current;\n while (version < target) {\n const next = MIGRATIONS.find((m) => m.from === version);\n if (next === undefined) {\n // No migration registered for this jump. This is a bug — the\n // version was bumped without a matching migration.\n throw new Error(`dofs schema: no migration registered for v${version} -> v${target}`);\n }\n next.migrator(db);\n version = next.to;\n }\n return version;\n}\n","// Sync-protocol tables. Populated by the sync module; the FS module\n// only writes to vfs_changes (via sync/changes.ts) on rm. The rest\n// of these tables stay empty until the sync task is implemented.\n\nexport const SYNC_STATEMENTS = [\n `CREATE TABLE IF NOT EXISTS vfs_manifests (\n hash BLOB PRIMARY KEY,\n size INTEGER NOT NULL,\n encoded BLOB NOT NULL,\n last_seen INTEGER NOT NULL DEFAULT 0\n )`,\n `CREATE TABLE IF NOT EXISTS vfs_changes (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n rev INTEGER NOT NULL,\n path TEXT NOT NULL,\n op TEXT NOT NULL CHECK(op IN ('delete'))\n )`,\n `CREATE INDEX IF NOT EXISTS vfs_changes_by_rev ON vfs_changes(rev)`,\n // changes.ts looks up the latest op for a path via\n // `WHERE path = ? ORDER BY id DESC LIMIT 1`. Without the index\n // SQLite falls back to a full scan; with (path, id DESC) the\n // lookup is O(log n) and the ORDER BY drains straight from the\n // index. Used on every recordDelete and on every push-tick that\n // processes tombstones.\n `CREATE INDEX IF NOT EXISTS vfs_changes_by_path ON vfs_changes(path, id DESC)`,\n `CREATE TABLE IF NOT EXISTS _vfs_watermark (\n k TEXT PRIMARY KEY,\n v INTEGER NOT NULL\n )`,\n // The `mode` column was added at schema v2; `schema/migrations.ts`\n // owns the ALTER for existing databases. Keep the CHECK\n // constraint here aligned with the migration's CHECK so fresh\n // installs and upgrades enforce the same allowed set.\n `CREATE TABLE IF NOT EXISTS _vfs_mounts (\n root TEXT PRIMARY KEY,\n kind TEXT NOT NULL,\n indexed INTEGER NOT NULL DEFAULT 0,\n mode TEXT NOT NULL DEFAULT 'read-only'\n CHECK(mode IN ('read-only', 'read-write'))\n )`,\n] as const;\n","import { createWorkspaceError } from \"../errors.js\";\nimport type { Database } from \"../storage.js\";\nimport { CORE_STATEMENTS, ROOT_INODE, SCHEMA_VERSION } from \"./core.js\";\nimport { runMigrations } from \"./migrations.js\";\nimport { SYNC_STATEMENTS } from \"./sync.js\";\n\nexport { ROOT_INODE, SCHEMA_VERSION } from \"./core.js\";\n\ninterface MetaRow {\n v: number;\n}\n\nexport function initializeSchema(db: Database, now: () => number): void {\n db.transactionSync(() => {\n // 1. Baseline DDL. Every statement is \"CREATE TABLE IF NOT\n // EXISTS\" / \"CREATE INDEX IF NOT EXISTS\" so this is a no-op\n // on already-initialized databases. Fresh databases come out\n // of this step at the latest column shape (SCHEMA_VERSION).\n for (const statement of CORE_STATEMENTS) {\n db.run(statement);\n }\n for (const statement of SYNC_STATEMENTS) {\n db.run(statement);\n }\n\n // 2. Read the on-disk schema version. Absent → 0 (very first\n // boot of this database). The baseline above just created\n // every table at the latest shape, so a 0 → SCHEMA_VERSION\n // jump has nothing to migrate.\n const storedVersion = db.one<MetaRow>(\n \"SELECT v FROM vfs_meta WHERE k = ?\",\n \"schema_version\",\n )?.v;\n const onDiskVersion = storedVersion ?? 0;\n\n if (onDiskVersion > SCHEMA_VERSION) {\n throw createWorkspaceError(\n \"EIO\",\n `Unsupported workspace filesystem schema version ${onDiskVersion}`,\n );\n }\n\n // 3. Migrate. Skip when the database is fresh (0) — the\n // baseline DDL already shipped the latest shape. Otherwise\n // dispatch each registered migrator until we hit the\n // target.\n if (onDiskVersion > 0 && onDiskVersion < SCHEMA_VERSION) {\n runMigrations(db, onDiskVersion, SCHEMA_VERSION);\n }\n\n // 4. Stamp the version and seed the boot rows. Both shapes\n // (insert-if-missing, then update) keep this idempotent so\n // repeat calls do nothing.\n db.run(\"INSERT OR IGNORE INTO vfs_meta (k, v) VALUES (?, ?)\", \"schema_version\", SCHEMA_VERSION);\n db.run(\"UPDATE vfs_meta SET v = ? WHERE k = ?\", SCHEMA_VERSION, \"schema_version\");\n db.run(\"INSERT OR IGNORE INTO vfs_meta (k, v) VALUES (?, ?)\", \"rev\", 1);\n db.run(\"INSERT OR IGNORE INTO _vfs_watermark (k, v) VALUES (?, ?)\", \"pushRev\", 0);\n db.run(\"INSERT OR IGNORE INTO _vfs_watermark (k, v) VALUES (?, ?)\", \"fetchRev\", 0);\n\n db.run(\n `INSERT OR IGNORE INTO vfs_nodes\n (inode, type, mode, mtime, rev)\n VALUES (?, 'dir', ?, ?, 0)`,\n ROOT_INODE,\n 0o755,\n now(),\n );\n });\n}\n","import { createWorkspaceError } from \"../errors.js\";\nimport { canonicalizePath } from \"../path.js\";\nimport { ROOT_INODE } from \"../schema/index.js\";\nimport type { Database } from \"../storage.js\";\n\nexport interface ResolvedInode {\n inode: number;\n type: \"file\" | \"dir\" | \"symlink\";\n mode: number;\n mtime: number;\n // Populated only when type === \"symlink\". Higher layers (readlink,\n // lstat) consume this; resolveInode follows it transparently unless\n // the caller asks otherwise.\n linkTarget?: string;\n}\n\nexport interface ResolveOptions {\n // Default true. Pass false to land on a symlink itself — the\n // lstat / readlink code paths rely on this. Loops are still\n // detected when following.\n followSymlinks?: boolean;\n}\n\ninterface NodeRow {\n inode: number;\n type: \"file\" | \"dir\" | \"symlink\";\n mode: number;\n mtime: number;\n link_target: string | null;\n}\n\ninterface ChildRow {\n child_inode: number;\n}\n\n// Cap the total number of symlinks resolved across a single\n// resolveInode() call. Matches Linux's default SYMLOOP_MAX of 40.\nconst MAX_SYMLINK_FOLLOWS = 40;\n\n// Walk vfs_dirents from ROOT_INODE down to `path`. Returns null when\n// any segment is missing, when an intermediate segment is a file\n// (which a real filesystem would surface as ENOTDIR — callers map\n// the `null` to the appropriate POSIX code), or when a final-segment\n// symlink dangles. Throws ELOOP when a cycle is detected.\n//\n// `path` is canonicalized internally so callers can pass user input\n// directly. Pre-canonicalized paths are also accepted and incur the\n// same trivial re-canonicalization cost.\nexport function resolveInode(\n db: Database,\n path: string,\n options: ResolveOptions = {},\n): ResolvedInode | null {\n const followFinal = options.followSymlinks !== false;\n return resolveParts(db, canonicalizePath(path).parts, followFinal, 0);\n}\n\nfunction resolveParts(\n db: Database,\n parts: string[],\n followFinal: boolean,\n follows: number,\n): ResolvedInode | null {\n const root = readNode(db, ROOT_INODE);\n if (root === null) {\n return null;\n }\n\n let current: NodeRow = root;\n for (let i = 0; i < parts.length; i++) {\n const isFinal = i === parts.length - 1;\n if (current.type !== \"dir\") {\n return null;\n }\n const child = db.one<ChildRow>(\n \"SELECT child_inode FROM vfs_dirents WHERE parent_inode = ? AND name = ?\",\n current.inode,\n parts[i],\n );\n if (child === undefined) {\n return null;\n }\n const next = readNode(db, child.child_inode);\n if (next === null) {\n return null;\n }\n // Intermediate symlinks always get followed; final-segment symlinks\n // are only followed when the caller wants. A dangling intermediate\n // is the same as a missing intermediate (return null).\n if (next.type === \"symlink\" && (!isFinal || followFinal)) {\n follows += 1;\n if (follows > MAX_SYMLINK_FOLLOWS) {\n throw createWorkspaceError(\"ELOOP\", \"too many symlinks resolving path\");\n }\n const target = next.link_target ?? \"\";\n const resolved = resolveParts(db, canonicalizePath(target).parts, true, follows);\n if (resolved === null) {\n return null;\n }\n // Replace the current dirent-resolved node with the followed\n // result, then keep walking remaining segments (if any).\n current = {\n inode: resolved.inode,\n type: resolved.type,\n mode: resolved.mode,\n mtime: resolved.mtime,\n link_target: resolved.linkTarget ?? null,\n };\n continue;\n }\n current = next;\n }\n\n return {\n inode: current.inode,\n type: current.type,\n mode: current.mode,\n mtime: current.mtime,\n linkTarget: current.link_target ?? undefined,\n };\n}\n\nfunction readNode(db: Database, inode: number): NodeRow | null {\n const row = db.one<NodeRow>(\n \"SELECT inode, type, mode, mtime, link_target FROM vfs_nodes WHERE inode = ?\",\n inode,\n );\n return row ?? null;\n}\n","import { createWorkspaceError } from \"../errors.js\";\nimport { canonicalizePath } from \"../path.js\";\nimport type { Database } from \"../storage.js\";\nimport { resolveInode } from \"./resolve.js\";\n\nexport interface WorkspaceFoundEntry {\n path: string;\n type: \"file\" | \"dir\";\n}\n\ninterface ChildRow {\n name: string;\n child_inode: number;\n type: \"file\" | \"dir\";\n}\n\nexport function find(db: Database, directory: string, pattern?: string): WorkspaceFoundEntry[] {\n const { path: canonical } = canonicalizePath(directory);\n const node = resolveInode(db, canonical);\n if (node === null) {\n throw createWorkspaceError(\"ENOENT\", `no such path: ${canonical}`, canonical);\n }\n if (node.type !== \"dir\") {\n throw createWorkspaceError(\"ENOTDIR\", `not a directory: ${canonical}`, canonical);\n }\n\n const out: WorkspaceFoundEntry[] = [];\n const regex = pattern !== undefined ? compileGlob(pattern) : undefined;\n\n walk(db, node.inode, canonical, out);\n\n if (regex === undefined) {\n return out;\n }\n // Glob matches against the path relative to the start directory.\n const prefix = canonical === \"/\" ? \"/\" : `${canonical}/`;\n return out.filter((entry) => {\n if (!entry.path.startsWith(prefix)) return false;\n const rel = entry.path.slice(prefix.length);\n return regex.test(rel);\n });\n}\n\nfunction walk(db: Database, parentInode: number, parentPath: string, out: WorkspaceFoundEntry[]) {\n const children = db.all<ChildRow>(\n `SELECT d.name AS name, d.child_inode AS child_inode, n.type AS type\n FROM vfs_dirents d\n JOIN vfs_nodes n ON n.inode = d.child_inode\n WHERE d.parent_inode = ?\n ORDER BY d.name`,\n parentInode,\n );\n for (const child of children) {\n const childPath = parentPath === \"/\" ? `/${child.name}` : `${parentPath}/${child.name}`;\n out.push({ path: childPath, type: child.type });\n if (child.type === \"dir\") {\n walk(db, child.child_inode, childPath, out);\n }\n }\n}\n\n// Compile a simple glob into a regex. Supported:\n// * matches any run of characters except '/'\n// ** matches any run of characters including '/'\n// Anything else is a literal. Regex metacharacters in literals are\n// escaped so '.' in '*.ts' doesn't match an arbitrary character.\nfunction compileGlob(pattern: string): RegExp {\n let re = \"\";\n let i = 0;\n while (i < pattern.length) {\n const ch = pattern[i];\n if (ch === \"*\") {\n if (pattern[i + 1] === \"*\") {\n // '**/' matches zero or more path segments. Without the slash, '**'\n // matches any run including slashes.\n if (pattern[i + 2] === \"/\") {\n re += \"(?:.*/)?\";\n i += 3;\n } else {\n re += \".*\";\n i += 2;\n }\n } else {\n re += \"[^/]*\";\n i += 1;\n }\n continue;\n }\n if (REGEX_METACHARS.has(ch)) {\n re += `\\\\${ch}`;\n } else {\n re += ch;\n }\n i += 1;\n }\n return new RegExp(`^${re}$`);\n}\n\nconst REGEX_METACHARS = new Set([\".\", \"+\", \"?\", \"^\", \"$\", \"(\", \")\", \"[\", \"]\", \"{\", \"}\", \"|\", \"\\\\\"]);\n","import { createWorkspaceError } from \"../errors.js\";\nimport type { Database } from \"../storage.js\";\nimport { resolveInode } from \"./resolve.js\";\n\nexport interface ReadFileOptions {\n encoding?: \"utf8\";\n}\n\ninterface ChunkRow {\n hash: Uint8Array;\n size: number;\n}\n\n// Overloads match docs/04_filesystem_interface.md exactly.\nexport function readFile(db: Database, path: string): Promise<ReadableStream<Uint8Array>>;\nexport function readFile(\n db: Database,\n path: string,\n encoding: \"utf8\",\n now?: () => number,\n): Promise<string>;\nexport function readFile(\n db: Database,\n path: string,\n options: ReadFileOptions,\n now?: () => number,\n): Promise<string | ReadableStream<Uint8Array>>;\nexport async function readFile(\n db: Database,\n path: string,\n optionsOrEncoding?: \"utf8\" | ReadFileOptions,\n now: () => number = Date.now,\n): Promise<string | ReadableStream<Uint8Array>> {\n const wantString =\n optionsOrEncoding === \"utf8\" ||\n (typeof optionsOrEncoding === \"object\" && optionsOrEncoding?.encoding === \"utf8\");\n\n // Resolve up front so we surface ENOENT/EISDIR before doing any\n // streaming work.\n const node = resolveInode(db, path);\n if (node === null) {\n throw createWorkspaceError(\"ENOENT\", `no such file: ${path}`, path);\n }\n if (node.type !== \"file\") {\n throw createWorkspaceError(\"EISDIR\", `path is a directory: ${path}`, path);\n }\n\n const chunks = db.all<ChunkRow>(\n \"SELECT hash, size FROM vfs_chunks WHERE inode = ? ORDER BY idx\",\n node.inode,\n );\n\n if (wantString) {\n // Fast path — concatenate everything and decode once. Matches the\n // node:fs/promises.readFile semantics for an encoding argument:\n // memory cost = whole file.\n const totalSize = chunks.reduce((acc, c) => acc + c.size, 0);\n const out = new Uint8Array(totalSize);\n let offset = 0;\n const touched = now();\n for (const chunk of chunks) {\n const row = db.one<{ bytes: Uint8Array }>(\n \"SELECT bytes FROM vfs_blob_bytes WHERE hash = ?\",\n chunk.hash,\n );\n if (row === undefined) {\n throw createWorkspaceError(\"EIO\", `missing blob bytes for ${path}`, path);\n }\n out.set(row.bytes, offset);\n offset += row.bytes.byteLength;\n }\n if (chunks.length > 0) {\n touchBlobs(db, chunks, touched);\n }\n return new TextDecoder().decode(out);\n }\n\n // Stream form. We enqueue one Uint8Array per chunk, lazily pulled.\n // last_seen is touched per chunk on read; that's the GC clock signal\n // documented in 03_filesystem_schema.md.\n let i = 0;\n return new ReadableStream<Uint8Array>({\n pull(controller) {\n if (i >= chunks.length) {\n controller.close();\n return;\n }\n const chunk = chunks[i++];\n const row = db.one<{ bytes: Uint8Array }>(\n \"SELECT bytes FROM vfs_blob_bytes WHERE hash = ?\",\n chunk.hash,\n );\n if (row === undefined) {\n controller.error(createWorkspaceError(\"EIO\", `missing blob bytes for ${path}`, path));\n return;\n }\n db.run(\"UPDATE vfs_blobs SET last_seen = ? WHERE hash = ?\", now(), chunk.hash);\n controller.enqueue(row.bytes);\n },\n });\n}\n\nfunction touchBlobs(db: Database, chunks: ChunkRow[], at: number): void {\n // Dedupe in case the same chunk hash appears multiple times in a\n // single file — keeps the UPDATE count low without changing semantics.\n const seen = new Set<string>();\n for (const chunk of chunks) {\n const key = bufferKey(chunk.hash);\n if (seen.has(key)) continue;\n seen.add(key);\n db.run(\"UPDATE vfs_blobs SET last_seen = ? WHERE hash = ?\", at, chunk.hash);\n }\n}\n\nfunction bufferKey(bytes: Uint8Array): string {\n // crypto digests are 32 bytes; this is fine.\n let key = \"\";\n for (const byte of bytes) {\n key += byte.toString(16).padStart(2, \"0\");\n }\n return key;\n}\n","import { createWorkspaceError } from \"../errors.js\";\nimport { canonicalizePath } from \"../path.js\";\nimport type { Database } from \"../storage.js\";\nimport { find } from \"./find.js\";\nimport { readFile } from \"./readFile.js\";\nimport { resolveInode } from \"./resolve.js\";\n\nexport interface WorkspaceGrepMatch {\n path: string;\n line: number;\n text: string;\n}\n\nexport interface GrepOptions {\n ignoreCase?: boolean;\n}\n\nexport async function grep(\n db: Database,\n pattern: string,\n path: string,\n options: GrepOptions = {},\n): Promise<WorkspaceGrepMatch[]> {\n const { path: canonical } = canonicalizePath(path);\n const node = resolveInode(db, canonical);\n if (node === null) {\n throw createWorkspaceError(\"ENOENT\", `no such path: ${canonical}`, canonical);\n }\n\n const filePaths =\n node.type === \"file\"\n ? [canonical]\n : find(db, canonical)\n .filter((entry) => entry.type === \"file\")\n .map((entry) => entry.path);\n\n const matches: WorkspaceGrepMatch[] = [];\n for (const filePath of filePaths) {\n await scanFile(db, filePath, pattern, options, matches);\n }\n return matches;\n}\n\n// Stream the file in chunks so very large files don't load fully into\n// memory. Carry a partial-line tail between chunks (everything after\n// the last '\\n') so a line that straddles a chunk boundary still\n// matches as one line. Line numbers are 1-indexed.\nasync function scanFile(\n db: Database,\n path: string,\n pattern: string,\n options: GrepOptions,\n out: WorkspaceGrepMatch[],\n): Promise<void> {\n const stream = await readFile(db, path);\n const reader = stream.getReader();\n const decoder = new TextDecoder(\"utf-8\", { fatal: false });\n const needle = options.ignoreCase ? pattern.toUpperCase() : pattern;\n\n let tail = \"\";\n let lineNo = 1;\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n if (value === undefined) continue;\n const text = tail + decoder.decode(value, { stream: true });\n const newlineIdx = text.lastIndexOf(\"\\n\");\n const ready = newlineIdx === -1 ? \"\" : text.slice(0, newlineIdx);\n tail = newlineIdx === -1 ? text : text.slice(newlineIdx + 1);\n if (ready.length > 0) {\n lineNo = scanLines(ready, lineNo, needle, options.ignoreCase === true, path, out);\n }\n }\n // Drain the decoder and scan whatever's left (final line without a\n // trailing newline).\n tail += decoder.decode();\n if (tail.length > 0) {\n scanLines(tail, lineNo, needle, options.ignoreCase === true, path, out);\n }\n}\n\n// Walk `block` line-by-line, push matches into `out`, return the next\n// 1-indexed line number to use for the following block.\nfunction scanLines(\n block: string,\n startLine: number,\n needle: string,\n ignoreCase: boolean,\n path: string,\n out: WorkspaceGrepMatch[],\n): number {\n let line = startLine;\n let cursor = 0;\n while (cursor <= block.length) {\n const next = block.indexOf(\"\\n\", cursor);\n const end = next === -1 ? block.length : next;\n const text = block.slice(cursor, end);\n const haystack = ignoreCase ? text.toUpperCase() : text;\n if (haystack.includes(needle)) {\n out.push({ path, line, text });\n }\n line += 1;\n if (next === -1) break;\n cursor = next + 1;\n }\n return line;\n}\n","import { canonicalizePath } from \"../path.js\";\nimport { ROOT_INODE } from \"../schema/index.js\";\nimport type { Database } from \"../storage.js\";\n\ninterface PathRow {\n path: string;\n}\n\n// Recursive CTE that materializes every file path in the tree, then\n// filters by the requested prefix. Files only (no directory entries)\n// because that's the documented \"flat list of file paths\" semantics.\n//\n// The CTE walks from ROOT_INODE: each row is (inode, path, type). The\n// path is built by concatenating dirent names with '/' separators;\n// root contributes the empty string so its children start with '/'.\n//\n// Prefix matching is exact: '/wsp' must not match '/workspace/x'. We\n// require either path == prefix (file at the exact prefix) or\n// path starts with prefix + '/' (descendants of a directory prefix).\nconst LS_QUERY = `\n WITH RECURSIVE walk(inode, path, type) AS (\n SELECT inode, '', type FROM vfs_nodes WHERE inode = ?\n UNION ALL\n SELECT n.inode, w.path || '/' || d.name, n.type\n FROM walk w\n JOIN vfs_dirents d ON d.parent_inode = w.inode\n JOIN vfs_nodes n ON n.inode = d.child_inode\n )\n SELECT path FROM walk\n WHERE type = 'file'\n AND (? = '/' OR path = ? OR path LIKE ? || '/%')\n ORDER BY path\n`;\n\nexport function ls(db: Database, prefix: string): string[] {\n const { path: canonical } = canonicalizePath(prefix);\n return db\n .all<PathRow>(LS_QUERY, ROOT_INODE, canonical, canonical, canonical)\n .map((row) => row.path);\n}\n","import type { Database } from \"./storage.js\";\n\n// Atomic monotonic rev counter. Every FS mutation (mkdir, writeFile,\n// rm, ...) calls incrementRev once per transaction and stamps the returned\n// value into vfs_nodes.rev. The sync layer reads vfs_meta.rev as\n// currentRev and consumes vfs_changes.rev for tombstones.\n//\n// Must be called inside a transactionSync — the UPDATE and SELECT\n// otherwise race with concurrent mutations. The DO single-writer model\n// makes that unlikely in practice, but the contract is \"wrap me\".\nexport function incrementRev(db: Database): number {\n db.run(\"UPDATE vfs_meta SET v = v + 1 WHERE k = 'rev'\");\n const next = db.scalar<number>(\"SELECT v FROM vfs_meta WHERE k = ?\", \"rev\");\n if (next === undefined) {\n throw new Error(\"vfs_meta.rev row missing; was initializeSchema run?\");\n }\n return next;\n}\n","// Read-only mount guard.\n//\n// Every dofs mutating entry point (writeFile, mkdir, rm, and the\n// apply path in sync/apply.ts) consults this module to reject\n// writes that fall under a registered read-only mount root. The\n// guard lives at the data layer so container-side writes that\n// arrive via pullOnce -> applyChanges are caught too — the\n// workspace-side surface wrapper alone cannot see them.\n//\n// The set of read-only roots is small (one row per registered\n// mount per workspace, typically <10) and changes only at indexer\n// write time. Cache it per Database in a WeakMap so repeat lookups\n// don't hit SQLite. The mount indexer in @cloudflare/workspace\n// invalidates the cache via `invalidateReadOnlyMountCache(db)` after\n// it writes _vfs_mounts.\n\nimport { createWorkspaceError } from \"../errors.js\";\nimport type { Database } from \"../storage.js\";\n\n// undefined sentinel = \"not loaded yet\"; an empty array means\n// \"loaded, no read-only mounts registered\". The two are not the\n// same: the empty case must skip the SQL lookup on every check.\nconst cache = new WeakMap<Database, readonly string[]>();\n\n// Public so the workspace-side indexer can drop the cache after it\n// writes a new _vfs_mounts row. Tests also call it when they stage\n// a mount fixture by direct SQL.\nexport function invalidateReadOnlyMountCache(db: Database): void {\n cache.delete(db);\n}\n\nfunction loadReadOnlyRoots(db: Database): readonly string[] {\n const rows = db.all<{ root: string }>(\"SELECT root FROM _vfs_mounts WHERE mode = 'read-only'\");\n const roots = rows.map((r) => r.root);\n cache.set(db, roots);\n return roots;\n}\n\nexport function getReadOnlyMountRoots(db: Database): readonly string[] {\n const cached = cache.get(db);\n if (cached !== undefined) return cached;\n return loadReadOnlyRoots(db);\n}\n\n// Symmetric overlap check between a candidate write path and a\n// mount root. Either:\n// - `path` is at or below `root` (a direct write or rm under the\n// mount root), OR\n// - `root` is below `path` (an ancestor rm that would recurse\n// through the mount).\n// Both shapes must be blocked so a read-only mount survives both\n// vectors. Mirrors the predicate that lived in\n// GuardedWorkspaceFilesystem before the data-layer move.\nfunction overlapsRoot(path: string, root: string): boolean {\n return path === root || path.startsWith(`${root}/`) || root.startsWith(`${path}/`);\n}\n\n// Throws EROFS when the path overlaps any read-only mount root.\n// Callers should invoke this before any DB mutation. The error\n// shape matches the existing createWorkspaceError contract so\n// surface callers see a normal WorkspaceFsError.\nexport function assertNotReadOnly(db: Database, path: string): void {\n const roots = getReadOnlyMountRoots(db);\n if (roots.length === 0) return;\n for (const root of roots) {\n if (overlapsRoot(path, root)) {\n throw createWorkspaceError(\"EROFS\", `read-only mount at ${root}: cannot modify`, path);\n }\n }\n}\n\n// Variant for callers that already know the path is canonicalised\n// and want to reject a single descendant during a recursive walk\n// (rm's walkPostOrder). Returns the matching root or undefined; the\n// caller decides whether to throw, log, or skip.\nexport function readOnlyRootFor(db: Database, path: string): string | undefined {\n const roots = getReadOnlyMountRoots(db);\n for (const root of roots) {\n if (overlapsRoot(path, root)) return root;\n }\n return undefined;\n}\n","import { createWorkspaceError } from \"../errors.js\";\nimport { canonicalizePath } from \"../path.js\";\nimport { incrementRev } from \"../rev.js\";\nimport { ROOT_INODE } from \"../schema/index.js\";\nimport type { Database } from \"../storage.js\";\nimport { assertNotReadOnly } from \"./mount-guard.js\";\n\nexport interface MkdirOptions {\n recursive?: boolean;\n mode?: number;\n}\n\ninterface ResolvedSegment {\n inode: number;\n type: \"file\" | \"dir\";\n}\n\n// Look up a child by name under a parent directory. Returns undefined\n// when there's no dirent. The caller decides whether that's an error.\nfunction lookupChild(db: Database, parentInode: number, name: string): ResolvedSegment | undefined {\n const row = db.one<{ child_inode: number }>(\n \"SELECT child_inode FROM vfs_dirents WHERE parent_inode = ? AND name = ?\",\n parentInode,\n name,\n );\n if (row === undefined) {\n return undefined;\n }\n const node = db.one<{ inode: number; type: \"file\" | \"dir\" }>(\n \"SELECT inode, type FROM vfs_nodes WHERE inode = ?\",\n row.child_inode,\n );\n if (node === undefined) {\n return undefined;\n }\n return node;\n}\n\n// Create one directory entry under `parentInode`, returning the new\n// inode. The caller has already verified the name is not taken.\nfunction createDir(\n db: Database,\n parentInode: number,\n name: string,\n mode: number,\n mtime: number,\n rev: number,\n): number {\n db.run(\n \"INSERT INTO vfs_nodes (type, mode, mtime, rev) VALUES ('dir', ?, ?, ?)\",\n mode,\n mtime,\n rev,\n );\n const inode = db.scalar<number>(\"SELECT last_insert_rowid()\");\n if (inode === undefined) {\n throw createWorkspaceError(\"EIO\", \"failed to allocate inode\");\n }\n db.run(\n \"INSERT INTO vfs_dirents (parent_inode, name, child_inode) VALUES (?, ?, ?)\",\n parentInode,\n name,\n inode,\n );\n return inode;\n}\n\nexport function mkdir(db: Database, path: string, options: MkdirOptions, now: () => number): void {\n const { parts, path: canonical } = canonicalizePath(path);\n const recursive = options.recursive === true;\n const mode = (options.mode ?? 0o755) & 0o7777;\n\n if (parts.length === 0) {\n // Root always exists post-initializeSchema; mkdir(\"/\") is EEXIST\n // even with recursive (matches Node fs.mkdir's \"EEXIST on root\"\n // behaviour for non-recursive; for recursive Node returns\n // undefined, but our docs treat mkdir(\"/\") as nonsensical).\n throw createWorkspaceError(\"EEXIST\", `path exists: ${canonical}`, canonical);\n }\n assertNotReadOnly(db, canonical);\n\n db.transactionSync(() => {\n const rev = incrementRev(db);\n const mtime = now();\n\n let parentInode = ROOT_INODE;\n // Walk all but the final segment. Each must already exist as a\n // directory; if `recursive`, we create missing ones.\n for (let i = 0; i < parts.length - 1; i++) {\n const name = parts[i];\n const existing = lookupChild(db, parentInode, name);\n if (existing === undefined) {\n if (!recursive) {\n throw createWorkspaceError(\"ENOENT\", `parent directory missing: ${canonical}`, canonical);\n }\n parentInode = createDir(db, parentInode, name, 0o755, mtime, rev);\n continue;\n }\n if (existing.type !== \"dir\") {\n throw createWorkspaceError(\n \"ENOTDIR\",\n `parent path segment is not a directory: ${canonical}`,\n canonical,\n );\n }\n parentInode = existing.inode;\n }\n\n // Final segment.\n const leafName = parts[parts.length - 1];\n const existing = lookupChild(db, parentInode, leafName);\n if (existing !== undefined) {\n // EEXIST is correct for both \"already a directory\" and\n // \"already a file\" per docs/04. Recursive only swallows the\n // already-a-directory case.\n if (recursive && existing.type === \"dir\") {\n return;\n }\n throw createWorkspaceError(\"EEXIST\", `path exists: ${canonical}`, canonical);\n }\n\n createDir(db, parentInode, leafName, mode, mtime, rev);\n });\n}\n","import { createWorkspaceError } from \"../errors.js\";\nimport { canonicalizePath } from \"../path.js\";\nimport type { Database } from \"../storage.js\";\nimport { resolveInode } from \"./resolve.js\";\n\nexport interface WorkspaceDirentResult {\n name: string;\n parentPath: string;\n isFile: boolean;\n isDirectory: boolean;\n}\n\ninterface DirentRow {\n name: string;\n type: \"file\" | \"dir\";\n}\n\nexport function readdir(db: Database, path: string): WorkspaceDirentResult[] {\n const { path: canonical } = canonicalizePath(path);\n const node = resolveInode(db, canonical);\n if (node === null) {\n throw createWorkspaceError(\"ENOENT\", `no such path: ${canonical}`, canonical);\n }\n if (node.type !== \"dir\") {\n throw createWorkspaceError(\"ENOTDIR\", `not a directory: ${canonical}`, canonical);\n }\n\n const rows = db.all<DirentRow>(\n `SELECT d.name AS name, n.type AS type\n FROM vfs_dirents d\n JOIN vfs_nodes n ON n.inode = d.child_inode\n WHERE d.parent_inode = ?\n ORDER BY d.name`,\n node.inode,\n );\n\n return rows.map((row) => ({\n name: row.name,\n parentPath: canonical,\n isFile: row.type === \"file\",\n isDirectory: row.type === \"dir\",\n }));\n}\n","import { resolveInode } from \"../fs/resolve.js\";\nimport { canonicalizePath } from \"../path.js\";\nimport type { Database } from \"../storage.js\";\n\n// Record a tombstone for a deleted path so the next push to the\n// container learns the path is gone. Called by fs/rm inside the same\n// transaction that bumped rev and removed the inode rows; the caller\n// passes the post-bump rev value.\nexport function recordDelete(db: Database, rev: number, path: string): void {\n db.run(\"INSERT INTO vfs_changes (rev, path, op) VALUES (?, ?, 'delete')\", rev, path);\n}\n\n// One row of the sync wire. The DO pushes these to the container\n// and the container fetches them back. Bytes are never inline:\n// file entries carry chunk hashes and the receiver does its own\n// hasObjects probe + fetchObjects pull for the bytes it lacks.\n//\n// `rev` is the sender's currentRev at the moment this entry was\n// stamped — vfs_nodes.rev for live mutations, vfs_changes.rev for\n// tombstones. The puller uses it as a per-entry cursor so it can\n// advance fetchRev per committed batch instead of waiting for the\n// whole stream to drain.\nexport type ChangeEntry =\n | {\n kind: \"file\";\n rev: number;\n path: string;\n mode: number;\n mtime: number;\n size: number;\n chunks: { hash: Uint8Array; size: number }[];\n }\n | { kind: \"dir\"; rev: number; path: string; mode: number; mtime: number }\n | {\n kind: \"symlink\";\n rev: number;\n path: string;\n target: string;\n mode: number;\n mtime: number;\n }\n | { kind: \"delete\"; rev: number; path: string };\n\n// Read the current state of `path` and turn it into a wire entry.\n// Returns null when the path was never touched (no live inode and no\n// tombstone in vfs_changes). Live inodes win over tombstones, which\n// handles the delete-then-recreate case correctly.\n//\n// Symlinks are returned as symlink entries; we never follow them on\n// the sync wire. Callers that want \"the file the link points at\"\n// resolve it themselves after applying the symlink entry.\nexport function materialiseChange(db: Database, path: string): ChangeEntry | null {\n const canonical = canonicalizePath(path).path;\n const live = resolveInode(db, canonical, { followSymlinks: false });\n if (live !== null) {\n // Read the rev stamped on this inode. Used as the per-entry\n // cursor on the sync wire; coalesceChanges yields entries in\n // ascending rev order so the puller can checkpoint per batch.\n const revRow = db.one<{ rev: number }>(\"SELECT rev FROM vfs_nodes WHERE inode = ?\", live.inode);\n const rev = revRow?.rev ?? 0;\n if (live.type === \"dir\") {\n return { kind: \"dir\", rev, path: canonical, mode: live.mode, mtime: live.mtime };\n }\n if (live.type === \"symlink\") {\n return {\n kind: \"symlink\",\n rev,\n path: canonical,\n target: live.linkTarget ?? \"\",\n mode: live.mode,\n mtime: live.mtime,\n };\n }\n // file: collect chunk rows in index order. Each row carries hash\n // and size so the receiver can probe hasObjects without a\n // separate manifest lookup. Total size is the sum of the chunks;\n // an empty file produces zero rows and size 0.\n const chunks = db.all<{ hash: Uint8Array; size: number }>(\n \"SELECT hash, size FROM vfs_chunks WHERE inode = ? ORDER BY idx\",\n live.inode,\n );\n let size = 0;\n for (const c of chunks) size += c.size;\n return {\n kind: \"file\",\n rev,\n path: canonical,\n mode: live.mode,\n mtime: live.mtime,\n size,\n chunks,\n };\n }\n // No live inode — check for a tombstone. The last row wins if the\n // path was deleted and never recreated; an indexed scan by path is\n // cheap because vfs_changes is bounded by the watermark window.\n const tomb = db.one<{ rev: number; op: string }>(\n \"SELECT rev, op FROM vfs_changes WHERE path = ? ORDER BY id DESC LIMIT 1\",\n canonical,\n );\n if (tomb?.op === \"delete\") {\n return { kind: \"delete\", rev: tomb.rev, path: canonical };\n }\n return null;\n}\n","import { createWorkspaceError } from \"../errors.js\";\nimport { canonicalizePath } from \"../path.js\";\nimport { incrementRev } from \"../rev.js\";\nimport type { Database } from \"../storage.js\";\nimport { recordDelete } from \"../sync/changes.js\";\nimport { assertNotReadOnly } from \"./mount-guard.js\";\nimport { resolveInode } from \"./resolve.js\";\n\nexport interface RmOptions {\n recursive?: boolean;\n force?: boolean;\n}\n\ninterface DirChild {\n name: string;\n child_inode: number;\n type: \"file\" | \"dir\" | \"symlink\";\n}\n\n// Walk a directory subtree post-order so we delete leaves before\n// parents. Yields { path, inode, type } for each node to remove. The\n// caller appends one tombstone per yielded path and clears\n// vfs_chunks for file inodes.\nfunction* walkPostOrder(\n db: Database,\n rootInode: number,\n rootPath: string,\n): Generator<{ path: string; inode: number; type: \"file\" | \"dir\" | \"symlink\" }> {\n // Stack-based DFS to avoid recursion limits on deep trees.\n type Frame = { inode: number; path: string; type: \"file\" | \"dir\" | \"symlink\"; expanded: boolean };\n const stack: Frame[] = [{ inode: rootInode, path: rootPath, type: \"dir\", expanded: false }];\n\n while (stack.length > 0) {\n const top = stack[stack.length - 1];\n if (top.type === \"file\" || top.expanded) {\n stack.pop();\n yield { path: top.path, inode: top.inode, type: top.type };\n continue;\n }\n top.expanded = true;\n const children = db.all<DirChild>(\n `SELECT d.name AS name, d.child_inode AS child_inode, n.type AS type\n FROM vfs_dirents d\n JOIN vfs_nodes n ON n.inode = d.child_inode\n WHERE d.parent_inode = ?\n ORDER BY d.name`,\n top.inode,\n );\n for (const child of children) {\n const childPath = top.path === \"/\" ? `/${child.name}` : `${top.path}/${child.name}`;\n stack.push({\n inode: child.child_inode,\n path: childPath,\n type: child.type,\n expanded: false,\n });\n }\n }\n}\n\nexport function rm(db: Database, path: string, options: RmOptions): void {\n const { parts, path: canonical } = canonicalizePath(path);\n\n if (parts.length === 0) {\n // The workspace root is structural; refuse to delete it even with\n // recursive+force. Matches the doc's example.\n throw createWorkspaceError(\"EPERM\", `cannot remove the root directory`, canonical);\n }\n\n // assertNotReadOnly uses the symmetric overlap predicate, so a\n // recursive rm of an ancestor whose subtree contains a read-only\n // mount root is caught here without walking the tree.\n assertNotReadOnly(db, canonical);\n\n const force = options.force === true;\n const recursive = options.recursive === true;\n\n db.transactionSync(() => {\n const node = resolveInode(db, canonical);\n if (node === null) {\n if (force) return;\n throw createWorkspaceError(\"ENOENT\", `no such path: ${canonical}`, canonical);\n }\n\n if (node.type === \"dir\" && !recursive) {\n const childCount = db.scalar<number>(\n \"SELECT COUNT(*) FROM vfs_dirents WHERE parent_inode = ?\",\n node.inode,\n );\n if ((childCount ?? 0) > 0) {\n throw createWorkspaceError(\"ENOTEMPTY\", `directory not empty: ${canonical}`, canonical);\n }\n }\n\n const rev = incrementRev(db);\n\n if (node.type === \"file\" || !recursive) {\n // Single inode removal — file, or empty directory.\n removeInode(db, node.inode, node.type);\n recordDelete(db, rev, canonical);\n return;\n }\n\n // Recursive directory removal. Walk leaves first so each delete\n // sees an empty parent by the time we get to it.\n for (const entry of walkPostOrder(db, node.inode, canonical)) {\n removeInode(db, entry.inode, entry.type);\n recordDelete(db, rev, entry.path);\n }\n });\n}\n\nfunction removeInode(db: Database, inode: number, type: \"file\" | \"dir\" | \"symlink\"): void {\n // Drop the dirent referencing this inode. There should be exactly one\n // (no hardlinks yet); if zero, we're deleting the root which we've\n // already refused.\n db.run(\"DELETE FROM vfs_dirents WHERE child_inode = ?\", inode);\n if (type === \"file\") {\n db.run(\"DELETE FROM vfs_chunks WHERE inode = ?\", inode);\n }\n db.run(\"DELETE FROM vfs_nodes WHERE inode = ?\", inode);\n}\n","import { createWorkspaceError } from \"../errors.js\";\nimport { canonicalizePath } from \"../path.js\";\nimport type { Database } from \"../storage.js\";\nimport { resolveInode } from \"./resolve.js\";\n\nexport interface WorkspaceStatResult {\n name: string;\n mode: number;\n mtime: number;\n size: number;\n isFile: boolean;\n isDirectory: boolean;\n}\n\nexport function stat(db: Database, path: string): WorkspaceStatResult {\n const { name } = canonicalizePath(path);\n const node = resolveInode(db, path);\n if (node === null) {\n throw createWorkspaceError(\"ENOENT\", `no such path: ${path}`, path);\n }\n\n const isDirectory = node.type === \"dir\";\n const isFile = node.type === \"file\";\n const size = isFile\n ? (db.scalar<number>(\n \"SELECT COALESCE(SUM(size), 0) FROM vfs_chunks WHERE inode = ?\",\n node.inode,\n ) ?? 0)\n : 0;\n\n return {\n name,\n mode: node.mode,\n mtime: node.mtime,\n size,\n isFile,\n isDirectory,\n };\n}\n","import type { Database } from \"../storage.js\";\n\n// Stage a chunk directly into vfs_blobs + vfs_blob_bytes without\n// creating a node or a manifest. The receiver-side push path uses\n// this to land bytes the sender shipped via pushObjects so a\n// subsequent applyChanges call can find them by hash.\n//\n// Idempotent: a second call with the same hash refreshes\n// last_seen so the bytes don't get reaped by an interleaved gc.\n// vfs_blob_bytes uses DO NOTHING on conflict — bytes are\n// content-addressed so the stored value is always identical.\n//\n// Callers are expected to have verified that hash === sha256(bytes)\n// before calling. The function trusts the caller; a mismatched\n// pair would silently land under the wrong key.\nexport function stageBlob(db: Database, hash: Uint8Array, bytes: Uint8Array, now: number): void {\n db.run(\n \"INSERT INTO vfs_blobs (hash, size, last_seen) VALUES (?, ?, ?) ON CONFLICT(hash) DO UPDATE SET last_seen = excluded.last_seen\",\n hash,\n bytes.byteLength,\n now,\n );\n db.run(\n \"INSERT INTO vfs_blob_bytes (hash, bytes) VALUES (?, ?) ON CONFLICT(hash) DO NOTHING\",\n hash,\n bytes,\n );\n}\n","import { createHash } from \"node:crypto\";\n\nimport type { Database } from \"../storage.js\";\n\n// A manifest names the ordered chunk list for a single file. Two\n// files whose bytes chunk identically share one manifest row, which\n// is what lets the sync wire say \"this file is the same as the one\n// I just sent you\" by hash alone.\n//\n// Encoding is JSON for now — readable, debuggable, and structurally\n// identical to casync's `.caidx`. Phase 4 swaps the encoding to the\n// `.caidx` byte layout without a schema change.\n\nexport interface ManifestChunk {\n hash: Uint8Array;\n size: number;\n}\n\nexport const MANIFEST_VERSION = 1;\n\ninterface EncodedManifest {\n version: number;\n chunks: { hash: string; size: number }[];\n}\n\nfunction toHex(bytes: Uint8Array): string {\n let out = \"\";\n for (let i = 0; i < bytes.byteLength; i++) {\n out += bytes[i].toString(16).padStart(2, \"0\");\n }\n return out;\n}\n\nfunction sha256(bytes: Uint8Array): Uint8Array {\n return new Uint8Array(createHash(\"sha256\").update(bytes).digest());\n}\n\n// Compute the manifest hash for a chunk list without touching the\n// DB. Used by the apply path to short-circuit when an upstream\n// entry already matches the local node — the manifest hash is\n// content-addressed so identical chunks always produce the same\n// hash.\nexport function computeManifestHash(chunks: ManifestChunk[]): Uint8Array {\n const encoded: EncodedManifest = {\n version: MANIFEST_VERSION,\n chunks: chunks.map((c) => ({ hash: toHex(c.hash), size: c.size })),\n };\n const bytes = new TextEncoder().encode(JSON.stringify(encoded));\n return sha256(bytes);\n}\n\n// Build a manifest row for the given chunk list. Idempotent: a\n// second call with the same chunks no-ops on the UNIQUE(hash). The\n// returned hash is what the caller writes onto\n// `vfs_nodes.manifest_hash`.\nexport function buildManifest(db: Database, chunks: ManifestChunk[], now: number): Uint8Array {\n const hash = computeManifestHash(chunks);\n const size = chunks.reduce((acc, c) => acc + c.size, 0);\n const encoded: EncodedManifest = {\n version: MANIFEST_VERSION,\n chunks: chunks.map((c) => ({ hash: toHex(c.hash), size: c.size })),\n };\n const bytes = new TextEncoder().encode(JSON.stringify(encoded));\n db.run(\n \"INSERT INTO vfs_manifests (hash, size, encoded, last_seen) VALUES (?, ?, ?, ?) ON CONFLICT(hash) DO UPDATE SET last_seen = excluded.last_seen\",\n hash,\n size,\n bytes,\n now,\n );\n return hash;\n}\n","import { createHash } from \"node:crypto\";\nimport { createWorkspaceError } from \"../errors.js\";\nimport { canonicalizePath } from \"../path.js\";\nimport { incrementRev } from \"../rev.js\";\nimport { ROOT_INODE } from \"../schema/index.js\";\nimport type { Database } from \"../storage.js\";\nimport { stageBlob } from \"../sync/blobs.js\";\nimport { buildManifest } from \"../sync/manifests.js\";\nimport { assertNotReadOnly } from \"./mount-guard.js\";\n\n// Fixed chunk size. Exported so tests can size inputs precisely\n// without hard-coding the magic number twice.\nexport const CHUNK_SIZE = 512 * 1024;\n\nexport type WriteFileContent = string | Uint8Array | ReadableStream<Uint8Array>;\n\nexport interface WriteFileOptions {\n mode?: number;\n}\n\n// Resolve directory-only paths (the parent of the target file). The\n// final segment is handled by the caller. Returns the parent inode or\n// throws ENOENT/ENOTDIR.\nfunction resolveParent(db: Database, parts: string[], canonical: string): number {\n let parentInode = ROOT_INODE;\n for (let i = 0; i < parts.length - 1; i++) {\n const name = parts[i];\n const child = db.one<{ child_inode: number }>(\n \"SELECT child_inode FROM vfs_dirents WHERE parent_inode = ? AND name = ?\",\n parentInode,\n name,\n );\n if (child === undefined) {\n throw createWorkspaceError(\"ENOENT\", `parent directory missing: ${canonical}`, canonical);\n }\n const next = db.one<{ inode: number; type: \"file\" | \"dir\" }>(\n \"SELECT inode, type FROM vfs_nodes WHERE inode = ?\",\n child.child_inode,\n );\n if (next === undefined) {\n throw createWorkspaceError(\"ENOENT\", `dangling dirent: ${canonical}`, canonical);\n }\n if (next.type !== \"dir\") {\n throw createWorkspaceError(\n \"ENOTDIR\",\n `parent path segment is not a directory: ${canonical}`,\n canonical,\n );\n }\n parentInode = next.inode;\n }\n return parentInode;\n}\n\nasync function materialize(content: string | Uint8Array): Promise<Uint8Array> {\n if (typeof content === \"string\") {\n return new TextEncoder().encode(content);\n }\n return content;\n}\n\n// sha256 with a synchronous code path so writeFile can be called both\n// from async drivers (the FS API) and from sync drivers (the\n// VirtualProvider). node:crypto is available natively on Node and\n// polyfilled by workerd.\nfunction sha256(bytes: Uint8Array): Uint8Array {\n const hash = createHash(\"sha256\");\n hash.update(bytes);\n return new Uint8Array(hash.digest());\n}\n\ninterface PreparedChunk {\n hash: Uint8Array;\n bytes: Uint8Array;\n size: number;\n}\n\nexport function chunksOf(bytes: Uint8Array): PreparedChunk[] {\n const chunks: PreparedChunk[] = [];\n for (let offset = 0; offset < bytes.byteLength; offset += CHUNK_SIZE) {\n const end = Math.min(offset + CHUNK_SIZE, bytes.byteLength);\n // subarray (not slice) avoids an extra copy; sha256() takes its own\n // copy when needed.\n const slice = bytes.subarray(offset, end);\n const hash = sha256(slice);\n chunks.push({ hash, bytes: slice, size: slice.byteLength });\n }\n return chunks;\n}\n\nexport async function writeFile(\n db: Database,\n path: string,\n content: WriteFileContent,\n options: WriteFileOptions,\n now: () => number,\n): Promise<void> {\n if (content instanceof ReadableStream) {\n await writeFileStreaming(db, path, content, options, now);\n return;\n }\n const bytes = await materialize(content);\n writeFileSync(db, path, bytes, options, now);\n}\n\n// Streaming write path. Reads the source one source-chunk at a time,\n// re-windows into fixed CHUNK_SIZE pieces, hashes each window, and\n// stages it into vfs_blobs / vfs_blob_bytes as it goes. The final\n// inode / dirent / vfs_chunks / manifest writes happen in a single\n// short transaction once the source is drained, against a list of\n// {hash, size} entries that's O(file_size / CHUNK_SIZE) bytes — not\n// O(file_size).\n//\n// Failure mid-stream leaves blob rows behind; gc() reaps orphans on\n// its next pass since no node references them.\nasync function writeFileStreaming(\n db: Database,\n path: string,\n source: ReadableStream<Uint8Array>,\n options: WriteFileOptions,\n now: () => number,\n): Promise<void> {\n const { parts, path: canonical } = canonicalizePath(path);\n if (parts.length === 0) {\n throw createWorkspaceError(\"EISDIR\", \"cannot write to the root directory\", canonical);\n }\n // Reject before we stage any blob bytes so a read-only mount\n // doesn't grow orphan vfs_blobs rows that gc() then has to reap.\n assertNotReadOnly(db, canonical);\n const mode = (options.mode ?? 0o644) & 0o7777;\n const mtime = now();\n\n const chunkRefs: Array<{ hash: Uint8Array; size: number }> = [];\n // Carry-over buffer: bytes left over from the previous source chunk\n // that didn't fill a CHUNK_SIZE window.\n let carry: Uint8Array | undefined;\n\n const flush = (chunk: Uint8Array): void => {\n const hash = sha256(chunk);\n stageBlob(db, hash, chunk, mtime);\n chunkRefs.push({ hash, size: chunk.byteLength });\n };\n\n const reader = source.getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n if (value === undefined || value.byteLength === 0) continue;\n let input = value;\n if (carry !== undefined) {\n // Splice carry-over onto the front of this source chunk so\n // we can re-window cleanly.\n const merged = new Uint8Array(carry.byteLength + input.byteLength);\n merged.set(carry, 0);\n merged.set(input, carry.byteLength);\n input = merged;\n carry = undefined;\n }\n let offset = 0;\n while (input.byteLength - offset >= CHUNK_SIZE) {\n // Copy the window so the staged blob doesn't alias a\n // larger backing buffer.\n const window = input.slice(offset, offset + CHUNK_SIZE);\n flush(window);\n offset += CHUNK_SIZE;\n }\n if (offset < input.byteLength) {\n carry = input.slice(offset);\n }\n }\n } finally {\n reader.releaseLock();\n }\n if (carry !== undefined && carry.byteLength > 0) {\n flush(carry);\n }\n\n // Wire up the inode against the staged blobs in one short\n // transaction. From this point on the SQL is the same shape as the\n // synchronous path — only the chunk-bytes step is skipped because\n // stageBlob already landed them above.\n db.transactionSync(() => {\n const parentInode = resolveParent(db, parts, canonical);\n const leafName = parts[parts.length - 1];\n const existing = db.one<{ child_inode: number }>(\n \"SELECT child_inode FROM vfs_dirents WHERE parent_inode = ? AND name = ?\",\n parentInode,\n leafName,\n );\n let inode: number;\n if (existing !== undefined) {\n const node = db.one<{ type: \"file\" | \"dir\" }>(\n \"SELECT type FROM vfs_nodes WHERE inode = ?\",\n existing.child_inode,\n );\n if (node?.type === \"dir\") {\n throw createWorkspaceError(\"EISDIR\", `path is a directory: ${canonical}`, canonical);\n }\n inode = existing.child_inode;\n db.run(\"DELETE FROM vfs_chunks WHERE inode = ?\", inode);\n } else {\n db.run(\n \"INSERT INTO vfs_nodes (type, mode, mtime, rev) VALUES ('file', ?, ?, 0)\",\n mode,\n mtime,\n );\n const allocated = db.scalar<number>(\"SELECT last_insert_rowid()\");\n if (allocated === undefined) {\n throw createWorkspaceError(\"EIO\", \"failed to allocate inode\");\n }\n inode = allocated;\n db.run(\n \"INSERT INTO vfs_dirents (parent_inode, name, child_inode) VALUES (?, ?, ?)\",\n parentInode,\n leafName,\n inode,\n );\n }\n for (let idx = 0; idx < chunkRefs.length; idx++) {\n const ref = chunkRefs[idx];\n db.run(\n \"INSERT INTO vfs_chunks (inode, idx, hash, size) VALUES (?, ?, ?, ?)\",\n inode,\n idx,\n ref.hash,\n ref.size,\n );\n }\n const manifestHash = buildManifest(db, chunkRefs, mtime);\n const rev = incrementRev(db);\n db.run(\n \"UPDATE vfs_nodes SET mode = ?, mtime = ?, rev = ?, manifest_hash = ? WHERE inode = ?\",\n mode,\n mtime,\n rev,\n manifestHash,\n inode,\n );\n });\n}\n\n// Synchronous entry point used by the VirtualProvider. Identical SQL\n// to the async path; differs only in that the bytes have already been\n// materialized.\nexport function writeFileSync(\n db: Database,\n path: string,\n bytes: Uint8Array,\n options: WriteFileOptions,\n now: () => number,\n): void {\n const { parts, path: canonical } = canonicalizePath(path);\n if (parts.length === 0) {\n throw createWorkspaceError(\"EISDIR\", \"cannot write to the root directory\", canonical);\n }\n assertNotReadOnly(db, canonical);\n const mode = (options.mode ?? 0o644) & 0o7777;\n const chunks = chunksOf(bytes);\n const mtime = now();\n\n db.transactionSync(() => {\n const parentInode = resolveParent(db, parts, canonical);\n const leafName = parts[parts.length - 1];\n const existing = db.one<{ child_inode: number }>(\n \"SELECT child_inode FROM vfs_dirents WHERE parent_inode = ? AND name = ?\",\n parentInode,\n leafName,\n );\n\n let inode: number;\n if (existing !== undefined) {\n const node = db.one<{ type: \"file\" | \"dir\" }>(\n \"SELECT type FROM vfs_nodes WHERE inode = ?\",\n existing.child_inode,\n );\n if (node?.type === \"dir\") {\n throw createWorkspaceError(\"EISDIR\", `path is a directory: ${canonical}`, canonical);\n }\n inode = existing.child_inode;\n // Replace the chunk list. Orphaned blobs (if any) are cleaned up\n // by a later gc() pass.\n db.run(\"DELETE FROM vfs_chunks WHERE inode = ?\", inode);\n } else {\n db.run(\n \"INSERT INTO vfs_nodes (type, mode, mtime, rev) VALUES ('file', ?, ?, 0)\",\n mode,\n mtime,\n );\n const allocated = db.scalar<number>(\"SELECT last_insert_rowid()\");\n if (allocated === undefined) {\n throw createWorkspaceError(\"EIO\", \"failed to allocate inode\");\n }\n inode = allocated;\n db.run(\n \"INSERT INTO vfs_dirents (parent_inode, name, child_inode) VALUES (?, ?, ?)\",\n parentInode,\n leafName,\n inode,\n );\n }\n\n // Upsert blobs and write the new chunk list.\n for (let idx = 0; idx < chunks.length; idx++) {\n const chunk = chunks[idx];\n db.run(\n \"INSERT INTO vfs_blobs (hash, size, last_seen) VALUES (?, ?, ?) ON CONFLICT(hash) DO UPDATE SET last_seen = excluded.last_seen\",\n chunk.hash,\n chunk.size,\n mtime,\n );\n db.run(\n \"INSERT INTO vfs_blob_bytes (hash, bytes) VALUES (?, ?) ON CONFLICT(hash) DO NOTHING\",\n chunk.hash,\n chunk.bytes,\n );\n db.run(\n \"INSERT INTO vfs_chunks (inode, idx, hash, size) VALUES (?, ?, ?, ?)\",\n inode,\n idx,\n chunk.hash,\n chunk.size,\n );\n }\n\n const manifestHash = buildManifest(db, chunks, mtime);\n const rev = incrementRev(db);\n db.run(\n \"UPDATE vfs_nodes SET mode = ?, mtime = ?, rev = ?, manifest_hash = ? WHERE inode = ?\",\n mode,\n mtime,\n rev,\n manifestHash,\n inode,\n );\n });\n}\n","// WorkspaceFilesystem — class wrapper that binds a Database and a\n// clock to the free fs/* functions.\n//\n// Every method here is a thin forward to the matching free\n// function. The class exists so callers (host-side Workspace,\n// in-container tools, tests) get a single instance to thread\n// through their code rather than passing (db, now) pairs into\n// every call.\n//\n// Free functions remain exported for internal callers — the\n// apply paths in sync/* operate on a Database directly, and the\n// in-package tests skip the class wrapper when they only need a\n// single op.\n\nimport type { Database } from \"../storage.js\";\n\nimport { find, type WorkspaceFoundEntry } from \"./find.js\";\nimport { type GrepOptions, grep, type WorkspaceGrepMatch } from \"./grep.js\";\nimport { ls } from \"./ls.js\";\nimport { type MkdirOptions, mkdir } from \"./mkdir.js\";\nimport { readdir, type WorkspaceDirentResult } from \"./readdir.js\";\nimport { type ReadFileOptions, readFile } from \"./readFile.js\";\nimport { type RmOptions, rm } from \"./rm.js\";\nimport { stat, type WorkspaceStatResult } from \"./stat.js\";\nimport { type WriteFileContent, type WriteFileOptions, writeFile } from \"./writeFile.js\";\n\nexport interface WorkspaceFilesystemOptions {\n // Clock used for mtime / last_seen. Defaults to Date.now.\n // Override for deterministic tests.\n now?: () => number;\n}\n\nexport class WorkspaceFilesystem {\n readonly db: Database;\n readonly now: () => number;\n\n constructor(db: Database, options: WorkspaceFilesystemOptions = {}) {\n this.db = db;\n this.now = options.now ?? Date.now;\n }\n\n // --- Reads -------------------------------------------------------\n\n readFile(path: string): Promise<ReadableStream<Uint8Array>>;\n readFile(path: string, encoding: \"utf8\"): Promise<string>;\n readFile(path: string, options: ReadFileOptions): Promise<string | ReadableStream<Uint8Array>>;\n readFile(\n path: string,\n optionsOrEncoding?: \"utf8\" | ReadFileOptions,\n ): Promise<string | ReadableStream<Uint8Array>> {\n // Forward through the free function's overload set. The\n // individual overloads above let callers see the precise\n // return type for each input shape.\n // Cast through the union overload of the free function;\n // the class's overloads above carry the precise return type\n // for each input shape back to the caller.\n return readFile(this.db, path, optionsOrEncoding as ReadFileOptions, this.now);\n }\n\n async stat(path: string): Promise<WorkspaceStatResult> {\n return stat(this.db, path);\n }\n\n async readdir(path: string): Promise<WorkspaceDirentResult[]> {\n return readdir(this.db, path);\n }\n\n async find(directory: string, pattern?: string): Promise<WorkspaceFoundEntry[]> {\n return find(this.db, directory, pattern);\n }\n\n async ls(prefix: string): Promise<string[]> {\n return ls(this.db, prefix);\n }\n\n grep(pattern: string, path: string, options: GrepOptions = {}): Promise<WorkspaceGrepMatch[]> {\n return grep(this.db, pattern, path, options);\n }\n\n // --- Mutations ---------------------------------------------------\n\n writeFile(\n path: string,\n content: WriteFileContent,\n options: WriteFileOptions = {},\n ): Promise<void> {\n return writeFile(this.db, path, content, options, this.now);\n }\n\n async mkdir(path: string, options: MkdirOptions = {}): Promise<void> {\n mkdir(this.db, path, options, this.now);\n }\n\n async rm(path: string, options: RmOptions = {}): Promise<void> {\n rm(this.db, path, options);\n }\n}\n","import { createWorkspaceError } from \"../errors.js\";\nimport type { Database } from \"../storage.js\";\nimport { resolveInode } from \"./resolve.js\";\n\n// Return the stored target of a symlink. Does not follow the link.\n// Mirrors POSIX semantics: ENOENT for a missing path, EINVAL when\n// the path resolves to something that isn't a symlink.\nexport function readlink(db: Database, path: string): string {\n const node = resolveInode(db, path, { followSymlinks: false });\n if (node === null) {\n throw createWorkspaceError(\"ENOENT\", `no such path: ${path}`, path);\n }\n if (node.type !== \"symlink\" || node.linkTarget === undefined) {\n throw createWorkspaceError(\"EINVAL\", `not a symlink: ${path}`, path);\n }\n return node.linkTarget;\n}\n","import { createWorkspaceError } from \"../errors.js\";\nimport { canonicalizePath } from \"../path.js\";\nimport { incrementRev } from \"../rev.js\";\nimport { ROOT_INODE } from \"../schema/index.js\";\nimport type { Database } from \"../storage.js\";\n\n// Create a symlink node. The target is stored as-is — it can be a\n// relative or absolute path, dangling or live. resolveInode follows\n// it transparently when callers walk through this entry.\nexport function symlink(db: Database, target: string, path: string, now: () => number): void {\n const { parts, path: canonical } = canonicalizePath(path);\n if (parts.length === 0) {\n throw createWorkspaceError(\"EEXIST\", \"cannot symlink onto root\", canonical);\n }\n\n db.transactionSync(() => {\n // Walk to the parent dirent. Intermediate segments must be real\n // directories; we don't auto-create them.\n let parentInode = ROOT_INODE;\n for (let i = 0; i < parts.length - 1; i++) {\n const child = db.one<{ child_inode: number }>(\n \"SELECT child_inode FROM vfs_dirents WHERE parent_inode = ? AND name = ?\",\n parentInode,\n parts[i],\n );\n if (child === undefined) {\n throw createWorkspaceError(\"ENOENT\", `parent directory missing: ${canonical}`, canonical);\n }\n const next = db.one<{ inode: number; type: \"file\" | \"dir\" | \"symlink\" }>(\n \"SELECT inode, type FROM vfs_nodes WHERE inode = ?\",\n child.child_inode,\n );\n if (next === undefined || next.type !== \"dir\") {\n throw createWorkspaceError(\n \"ENOTDIR\",\n `parent path segment is not a directory: ${canonical}`,\n canonical,\n );\n }\n parentInode = next.inode;\n }\n\n const leafName = parts[parts.length - 1];\n const existing = db.one<{ child_inode: number }>(\n \"SELECT child_inode FROM vfs_dirents WHERE parent_inode = ? AND name = ?\",\n parentInode,\n leafName,\n );\n if (existing !== undefined) {\n throw createWorkspaceError(\"EEXIST\", `path exists: ${canonical}`, canonical);\n }\n\n const rev = incrementRev(db);\n const mtime = now();\n db.run(\n \"INSERT INTO vfs_nodes (type, mode, mtime, rev, link_target) VALUES ('symlink', ?, ?, ?, ?)\",\n 0o777,\n mtime,\n rev,\n target,\n );\n const inode = db.scalar<number>(\"SELECT last_insert_rowid()\");\n if (inode === undefined) {\n throw createWorkspaceError(\"EIO\", \"failed to allocate inode\");\n }\n db.run(\n \"INSERT INTO vfs_dirents (parent_inode, name, child_inode) VALUES (?, ?, ?)\",\n parentInode,\n leafName,\n inode,\n );\n });\n}\n","// Path segment matcher for the container-side ignore list. The\n// container uses this to drop paths from coalesceChanges before\n// they hit the wire; the DO's Workspace.fs surface uses the same\n// helper to make ignored paths invisible to API consumers.\n//\n// Matching is whole-segment: \"node_modules\" matches the segment\n// node_modules anywhere in the path but does not match\n// node_modules_old or my_node_modules. Patterns are plain strings,\n// not globs; we can extend to globs later if a real case demands it.\n\nexport const DEFAULT_IGNORE = [\"node_modules\"];\n\nexport function isIgnored(path: string, patterns: string[]): boolean {\n if (patterns.length === 0) return false;\n // canonicalizePath strips the trailing slash and leaves a leading\n // \"/\" for non-root paths; split skips the empty leading segment.\n const segments = path.split(\"/\").filter((s) => s.length > 0);\n for (const segment of segments) {\n for (const p of patterns) {\n if (segment === p) return true;\n }\n }\n return false;\n}\n","import { ROOT_INODE } from \"../schema/index.js\";\nimport type { Database } from \"../storage.js\";\nimport { type ChangeEntry, materialiseChange } from \"./changes.js\";\nimport { isIgnored } from \"./ignore.js\";\n\n// Walk vfs_dirents from `inode` up to ROOT_INODE, gathering the path\n// segments along the way. Returns null when the inode is unreachable\n// (orphan after a partially-applied rm; should not happen inside a\n// healthy DB but the caller treats null as \"skip this entry\").\nfunction pathOf(db: Database, inode: number): string | null {\n if (inode === ROOT_INODE) return \"/\";\n const segments: string[] = [];\n let current = inode;\n // Bound the walk: a million levels deep is well past any real FS;\n // anything beyond that is corruption and should not loop forever.\n for (let i = 0; i < 1_000_000; i++) {\n const row = db.one<{ parent_inode: number; name: string }>(\n \"SELECT parent_inode, name FROM vfs_dirents WHERE child_inode = ?\",\n current,\n );\n if (row === undefined) return null;\n segments.push(row.name);\n if (row.parent_inode === ROOT_INODE) {\n segments.reverse();\n return `/${segments.join(\"/\")}`;\n }\n current = row.parent_inode;\n }\n return null;\n}\n\n// Yield one ChangeEntry per path touched since `sinceRev`. Per-path\n// coalescing: five rewrites of the same path between watermarks\n// produce one entry (the latest state wins). Tombstoned paths get a\n// delete entry unless they have been recreated, in which case the\n// live entry wins.\n//\n// Entries are emitted in ascending rev order. pullOnce relies on\n// this so it can advance fetchRev per committed batch — if entry N\n// has rev R, every entry already emitted has rev <= R, so\n// checkpointing fetchRev at R is safe.\n//\n// Streaming, not buffering at the wire: the per-path coalesce step\n// holds at most one slot per dirty path in memory (sorted by rev),\n// which is the same bound the live + tombstone scans already pay.\nexport interface CoalesceOptions {\n // Path-segment patterns to drop before yielding. The wire never\n // carries entries under an ignored segment.\n ignore?: string[];\n}\n\nexport async function* coalesceChanges(\n db: Database,\n sinceRev: number,\n options: CoalesceOptions = {},\n): AsyncIterable<ChangeEntry> {\n const ignore = options.ignore ?? [];\n\n // Build the per-path candidate set in two passes, keeping the\n // highest rev seen for each path. A live mutation that landed\n // after a tombstone wins; a tombstone that landed after a write\n // wins; the highest rev is the rev we stamp on the wire and the\n // rev pullOnce checkpoints to.\n type Candidate = { path: string; rev: number };\n const candidates = new Map<string, Candidate>();\n\n // Live mutations: every mkdir / writeFile / symlink bumps\n // vfs_nodes.rev. The by_rev index makes this a range scan.\n const touched = db.all<{ inode: number; rev: number }>(\n \"SELECT inode, rev FROM vfs_nodes WHERE rev > ? ORDER BY rev\",\n sinceRev,\n );\n for (const { inode, rev } of touched) {\n const path = pathOf(db, inode);\n if (path === null) continue;\n if (isIgnored(path, ignore)) continue;\n const prior = candidates.get(path);\n if (prior === undefined || rev > prior.rev) {\n candidates.set(path, { path, rev });\n }\n }\n\n // Tombstones: each rm appends a row to vfs_changes with the\n // post-bump rev. The highest rev per path wins (a path can be\n // deleted-recreated-deleted; we want the last rm's rev).\n const tombs = db.all<{ path: string; rev: number }>(\n \"SELECT path, MAX(rev) AS rev FROM vfs_changes WHERE rev > ? AND op = 'delete' GROUP BY path\",\n sinceRev,\n );\n for (const { path, rev } of tombs) {\n if (isIgnored(path, ignore)) continue;\n const prior = candidates.get(path);\n if (prior === undefined || rev > prior.rev) {\n candidates.set(path, { path, rev });\n }\n }\n\n // Sort by rev ascending so pullOnce can checkpoint per batch.\n // Ties on rev (same transactionSync touching multiple paths)\n // break on path so the wire order is deterministic.\n const ordered = Array.from(candidates.values()).sort((a, b) => {\n if (a.rev !== b.rev) return a.rev - b.rev;\n return a.path < b.path ? -1 : a.path > b.path ? 1 : 0;\n });\n\n for (const { path } of ordered) {\n const entry = materialiseChange(db, path);\n if (entry !== null) yield entry;\n }\n}\n","import type { Database } from \"../storage.js\";\n\n// Watermarks owned by the DO. The container's appliedPushRev lives\n// in-memory on the container side; we don't store it here.\n//\n// pushRev — last DO-side rev successfully pushed to the container.\n// fetchRev — last container-side rev the DO has fetched and applied.\n//\n// initializeSchema() seeds both at 0 in _vfs_watermark. The schema\n// table is the durability surface; readers and writers always go\n// through this module so the SQL stays in one place.\nexport type WatermarkKey = \"pushRev\" | \"fetchRev\";\n\nexport function readWatermark(db: Database, key: WatermarkKey): number {\n return db.scalar<number>(\"SELECT v FROM _vfs_watermark WHERE k = ?\", key) ?? 0;\n}\n\nexport function writeWatermark(db: Database, key: WatermarkKey, value: number): void {\n db.run(\n \"INSERT INTO _vfs_watermark (k, v) VALUES (?, ?) ON CONFLICT(k) DO UPDATE SET v = excluded.v\",\n key,\n value,\n );\n}\n\n// The latest rev stamped on any DO-side mutation. coalesceChanges\n// reads this implicitly via vfs_nodes.rev; the sync layer exposes it\n// to callers that want to record \"what cursor should I pass back as\n// sinceRev next time\".\nexport function currentRev(db: Database): number {\n return db.scalar<number>(\"SELECT v FROM vfs_meta WHERE k = 'rev'\") ?? 0;\n}\n","// Directory watcher backed by polling vfs_meta.rev.\n//\n// On each tick, coalesceChanges yields every path touched since\n// the watcher last looked. We filter by the watched directory and\n// recursive flag, then emit one 'change' event per path with\n// fs.watch-compatible (eventType, filename) arguments.\n//\n// The polling cadence is the provider's watchIntervalMs (default\n// 100 ms). That's already what node's fs.watch uses internally on\n// platforms without inotify, and it's slow enough that the SQL\n// scan stays in the noise even with many watchers active.\n//\n// Coverage: there is no watch.test.ts here because watch is only\n// reachable through SQLiteWorkspaceProvider.watch /\n// watchAsyncIterable; the test surface is provider.watch.test.ts,\n// which exercises both the EventEmitter and the AsyncIterable\n// adapters end-to-end.\n\nimport { EventEmitter } from \"node:events\";\n\nimport { canonicalizePath } from \"../path.js\";\nimport type { Database } from \"../storage.js\";\nimport { coalesceChanges } from \"../sync/coalesce.js\";\nimport { currentRev } from \"../sync/watermarks.js\";\n\nexport interface WatchOptions {\n // Recurse into subdirectories. When false (default) the watcher\n // only fires for direct children of `path`.\n recursive?: boolean;\n // AbortSignal that closes the watcher when triggered.\n signal?: AbortSignal;\n // Override the poll interval. Default comes from the provider.\n interval?: number;\n}\n\nexport interface WatchEvent {\n eventType: \"rename\" | \"change\";\n filename: string;\n}\n\nexport interface WatchHandle extends EventEmitter {\n close(): void;\n}\n\nexport function createWatcher(\n db: Database,\n path: string,\n options: WatchOptions,\n defaultInterval: number,\n): WatchHandle {\n const { path: canonical } = canonicalizePath(path);\n const prefix = canonical === \"/\" ? \"/\" : `${canonical}/`;\n const recursive = options.recursive === true;\n const interval = options.interval ?? defaultInterval;\n\n const emitter = new EventEmitter() as WatchHandle;\n let cursor = currentRev(db);\n let closed = false;\n\n const tick = async () => {\n if (closed) return;\n try {\n const seen = new Set<string>();\n for await (const entry of coalesceChanges(db, cursor)) {\n // Filter to entries inside the watched scope.\n if (!isInScope(entry.path, canonical, prefix, recursive)) continue;\n if (seen.has(entry.path)) continue;\n seen.add(entry.path);\n const filename = relativeName(entry.path, canonical);\n const eventType: \"rename\" | \"change\" = entry.kind === \"delete\" ? \"rename\" : \"change\";\n emitter.emit(\"change\", eventType, filename);\n }\n cursor = currentRev(db);\n } catch (error) {\n emitter.emit(\"error\", error);\n }\n };\n\n const handle = setInterval(() => void tick(), interval);\n handle.unref?.();\n\n emitter.close = () => {\n if (closed) return;\n closed = true;\n clearInterval(handle);\n emitter.emit(\"close\");\n };\n\n if (options.signal !== undefined) {\n if (options.signal.aborted) {\n emitter.close();\n } else {\n options.signal.addEventListener(\"abort\", () => emitter.close(), {\n once: true,\n });\n }\n }\n\n return emitter;\n}\n\nfunction isInScope(\n entryPath: string,\n watchedPath: string,\n prefix: string,\n recursive: boolean,\n): boolean {\n if (entryPath === watchedPath) return true;\n if (!entryPath.startsWith(prefix)) return false;\n if (recursive) return true;\n // Non-recursive: only direct children. No extra '/' in the\n // remainder past the prefix.\n const remainder = entryPath.slice(prefix.length);\n return !remainder.includes(\"/\");\n}\n\nfunction relativeName(entryPath: string, watchedPath: string): string {\n if (entryPath === watchedPath) return \"\";\n const prefix = watchedPath === \"/\" ? \"/\" : `${watchedPath}/`;\n return entryPath.startsWith(prefix) ? entryPath.slice(prefix.length) : entryPath;\n}\n\n// Adapter from EventEmitter-based watcher to AsyncIterable for\n// for-await consumers. Mirrors @platformatic/vfs's VFSWatchAsyncIterable.\nexport function createWatchAsyncIterable(watcher: WatchHandle): AsyncIterable<WatchEvent> & {\n return(): Promise<{ value: undefined; done: true }>;\n} {\n const pending: WatchEvent[] = [];\n const waiters: ((result: IteratorResult<WatchEvent>) => void)[] = [];\n let done = false;\n\n watcher.on(\"change\", (eventType: \"rename\" | \"change\", filename: string) => {\n const event: WatchEvent = { eventType, filename };\n const next = waiters.shift();\n if (next) next({ value: event, done: false });\n else pending.push(event);\n });\n watcher.on(\"close\", () => {\n done = true;\n while (waiters.length > 0) {\n const next = waiters.shift();\n if (next) next({ value: undefined as never, done: true });\n }\n });\n\n return {\n [Symbol.asyncIterator]() {\n return this as unknown as AsyncIterator<WatchEvent>;\n },\n next(): Promise<IteratorResult<WatchEvent>> {\n const buffered = pending.shift();\n if (buffered) return Promise.resolve({ value: buffered, done: false });\n if (done) return Promise.resolve({ value: undefined as never, done: true });\n return new Promise((resolve) => waiters.push(resolve));\n },\n async return() {\n watcher.close();\n return { value: undefined, done: true as const };\n },\n } as unknown as AsyncIterable<WatchEvent> & {\n return(): Promise<{ value: undefined; done: true }>;\n };\n}\n","// SQLiteWorkspaceProvider — a @platformatic/vfs VirtualProvider backed\n// by the dofs SQLite store.\n//\n// Every method on VirtualProvider is declared. Methods we already have\n// synchronous building blocks for delegate to the existing fs/ helpers;\n// the rest throw ENOSYS so the gaps are visible at the call site.\n// Subsequent commits fill in the stubs (file descriptors, positional\n// I/O, truncate, symlinks, watch).\n\nimport { createWorkspaceError } from \"./errors.js\";\nimport type { MkdirOptions } from \"./fs/mkdir.js\";\nimport { mkdir as mkdirImpl } from \"./fs/mkdir.js\";\nimport { readdir as readdirImpl } from \"./fs/readdir.js\";\nimport { readlink as readlinkImpl } from \"./fs/readlink.js\";\nimport { resolveInode } from \"./fs/resolve.js\";\nimport { rm as rmImpl } from \"./fs/rm.js\";\nimport { stat as statImpl } from \"./fs/stat.js\";\nimport { symlink as symlinkImpl } from \"./fs/symlink.js\";\nimport {\n createWatchAsyncIterable,\n createWatcher,\n type WatchEvent,\n type WatchHandle,\n type WatchOptions,\n} from \"./fs/watch.js\";\nimport { writeFileSync as writeFileSyncImpl } from \"./fs/writeFile.js\";\nimport { canonicalizePath } from \"./path.js\";\nimport type { Database } from \"./storage.js\";\n\nexport interface SQLiteWorkspaceProviderOptions {\n // Wall-clock source. Defaults to Date.now so production callers\n // don't need to thread one through; tests pin it.\n now?: () => number;\n // Poll interval for watch() in milliseconds. Defaults to 100 ms\n // to match node's fs.watch on most filesystems; tests can lower\n // it to keep durations short.\n watchIntervalMs?: number;\n}\n\ninterface VirtualStatsLike {\n dev: number;\n mode: number;\n nlink: number;\n uid: number;\n gid: number;\n rdev: number;\n blksize: number;\n ino: number;\n size: number;\n blocks: number;\n atimeMs: number;\n mtimeMs: number;\n ctimeMs: number;\n birthtimeMs: number;\n atime: Date;\n mtime: Date;\n ctime: Date;\n birthtime: Date;\n isFile(): boolean;\n isDirectory(): boolean;\n isSymbolicLink(): boolean;\n isBlockDevice(): boolean;\n isCharacterDevice(): boolean;\n isFIFO(): boolean;\n isSocket(): boolean;\n}\n\ninterface VirtualDirentLike {\n name: string;\n parentPath: string;\n path: string;\n isFile(): boolean;\n isDirectory(): boolean;\n isSymbolicLink(): boolean;\n isBlockDevice(): boolean;\n isCharacterDevice(): boolean;\n isFIFO(): boolean;\n isSocket(): boolean;\n}\n\ninterface FdState {\n path: string;\n position: number;\n readable: boolean;\n writable: boolean;\n // append mode pins every writeSync to current EOF rather than\n // honouring an explicit position argument.\n append: boolean;\n}\n\nexport class SQLiteWorkspaceProvider {\n readonly db: Database;\n readonly now: () => number;\n\n // Capability flags consulted by @platformatic/vfs callers.\n readonly readonly = false;\n readonly supportsSymlinks = true;\n readonly supportsWatch = true;\n\n // Fd table. Start at 3 — 0/1/2 are reserved by convention even\n // though we don't expose them — so consumers that pass them around\n // can't accidentally collide with stdio mental models.\n #fds = new Map<number, FdState>();\n #nextFd = 3;\n\n readonly watchIntervalMs: number;\n\n constructor(db: Database, options: SQLiteWorkspaceProviderOptions = {}) {\n this.db = db;\n this.now = options.now ?? Date.now;\n this.watchIntervalMs = options.watchIntervalMs ?? 100;\n }\n\n // -- Essential primitives ------------------------------------------\n\n open(path: string, flags?: string, mode?: number): Promise<number> {\n return Promise.resolve(this.openSync(path, flags, mode));\n }\n\n openSync(path: string, flags: string = \"r\", _mode?: number): number {\n const { read, write, truncate, append, create, exclusive } = parseFlags(flags);\n const existing = resolveInode(this.db, path);\n\n if (existing === null) {\n if (!create) {\n throw createWorkspaceError(\"ENOENT\", `no such file: ${path}`, path);\n }\n writeFileSyncImpl(this.db, path, new Uint8Array(), {}, this.now);\n } else {\n if (existing.type !== \"file\") {\n throw createWorkspaceError(\"EISDIR\", `path is a directory: ${path}`, path);\n }\n if (exclusive) {\n throw createWorkspaceError(\"EEXIST\", `path exists: ${path}`, path);\n }\n if (truncate) {\n writeFileSyncImpl(this.db, path, new Uint8Array(), {}, this.now);\n }\n }\n\n const stat = statImpl(this.db, path);\n const fd = this.#nextFd++;\n this.#fds.set(fd, {\n path,\n position: append ? stat.size : 0,\n readable: read,\n writable: write,\n append,\n });\n return fd;\n }\n\n stat(path: string, options?: { bigint?: boolean }): Promise<VirtualStatsLike> {\n return Promise.resolve(this.statSync(path, options));\n }\n\n statSync(path: string, _options?: { bigint?: boolean }): VirtualStatsLike {\n const s = statImpl(this.db, path);\n const node = resolveInode(this.db, path);\n const ino = node?.inode ?? 0;\n return wrapStats({\n mode: s.mode,\n size: s.size,\n mtimeMs: s.mtime,\n ino,\n isFile: s.isFile,\n isDirectory: s.isDirectory,\n isSymbolicLink: false,\n });\n }\n\n lstat(path: string, options?: { bigint?: boolean }): Promise<VirtualStatsLike> {\n return Promise.resolve(this.lstatSync(path, options));\n }\n\n lstatSync(path: string, _options?: { bigint?: boolean }): VirtualStatsLike {\n const node = resolveInode(this.db, path, { followSymlinks: false });\n if (node === null) {\n throw createWorkspaceError(\"ENOENT\", `no such path: ${path}`, path);\n }\n const isSymlink = node.type === \"symlink\";\n const size = isSymlink\n ? (node.linkTarget ?? \"\").length\n : node.type === \"file\"\n ? (this.db.scalar<number>(\n \"SELECT COALESCE(SUM(size), 0) FROM vfs_chunks WHERE inode = ?\",\n node.inode,\n ) ?? 0)\n : 0;\n return wrapStats({\n mode: node.mode,\n size,\n mtimeMs: node.mtime,\n ino: node.inode,\n isFile: node.type === \"file\",\n isDirectory: node.type === \"dir\",\n isSymbolicLink: isSymlink,\n });\n }\n\n readdir(\n path: string,\n options?: { withFileTypes?: boolean },\n ): Promise<string[] | VirtualDirentLike[]> {\n return Promise.resolve(this.readdirSync(path, options));\n }\n\n readdirSync(path: string, options?: { withFileTypes?: boolean }): string[] | VirtualDirentLike[] {\n const entries = readdirImpl(this.db, path);\n if (options?.withFileTypes === true) {\n return entries.map((entry) => wrapDirent(entry));\n }\n return entries.map((entry) => entry.name);\n }\n\n mkdir(path: string, options?: MkdirOptions): Promise<string | undefined> {\n return Promise.resolve(this.mkdirSync(path, options));\n }\n\n mkdirSync(path: string, options?: MkdirOptions): string | undefined {\n mkdirImpl(this.db, path, options ?? {}, this.now);\n return undefined;\n }\n\n rmdir(path: string): Promise<void> {\n this.rmdirSync(path);\n return Promise.resolve();\n }\n\n rmdirSync(path: string): void {\n rmImpl(this.db, path, {});\n }\n\n unlink(path: string): Promise<void> {\n this.unlinkSync(path);\n return Promise.resolve();\n }\n\n unlinkSync(path: string): void {\n rmImpl(this.db, path, {});\n }\n\n rename(oldPath: string, newPath: string): Promise<void> {\n this.renameSync(oldPath, newPath);\n return Promise.resolve();\n }\n\n renameSync(oldPath: string, newPath: string): void {\n // The FS module doesn't expose rename as a standalone operation\n // yet; we lean on the existing schema-level pieces here. When\n // rename grows up (cross-directory, overwriting an existing file,\n // ...) it should move into fs/rename.ts with its own tests.\n const node = resolveInode(this.db, oldPath);\n if (node === null) {\n throw createWorkspaceError(\"ENOENT\", `no such path: ${oldPath}`, oldPath);\n }\n const { parts, path: newCanonical } = canonicalizePath(newPath);\n if (parts.length === 0) {\n throw createWorkspaceError(\"EINVAL\", \"cannot rename onto root\", newCanonical);\n }\n const newName = parts[parts.length - 1];\n const newParentPath = parts.length === 1 ? \"/\" : `/${parts.slice(0, -1).join(\"/\")}`;\n const newParent = resolveInode(this.db, newParentPath);\n if (newParent === null || newParent.type !== \"dir\") {\n throw createWorkspaceError(\n \"ENOENT\",\n `parent directory missing: ${newCanonical}`,\n newCanonical,\n );\n }\n this.db.transactionSync(() => {\n // If the destination already exists, displace it before linking\n // the source dirent. POSIX rename(2) is atomic and overwrites a\n // regular file or empty directory at the target. We follow the\n // same semantics: an existing file at newPath is unlinked, and\n // its inode (and chunks / blobs) are reaped via the existing\n // gc() safety window.\n const existing = this.db.one<{ child_inode: number; type: string }>(\n `SELECT d.child_inode AS child_inode, n.type AS type\n FROM vfs_dirents d JOIN vfs_nodes n ON n.inode = d.child_inode\n WHERE d.parent_inode = ? AND d.name = ?`,\n newParent.inode,\n newName,\n );\n if (existing !== undefined && existing.child_inode !== node.inode) {\n // Refuse to overwrite a non-empty directory or replace a\n // directory with a file (Linux rename semantics).\n if (existing.type === \"dir\") {\n const childCount = this.db.scalar<number>(\n \"SELECT COUNT(*) FROM vfs_dirents WHERE parent_inode = ?\",\n existing.child_inode,\n );\n if ((childCount ?? 0) > 0) {\n throw createWorkspaceError(\"ENOTEMPTY\", `not empty: ${newCanonical}`, newCanonical);\n }\n }\n // Unlink the displaced inode. vfs_chunks / vfs_blob_bytes\n // referenced by file chunks become orphaned and gc() reaps\n // them after the safety window.\n this.db.run(\"DELETE FROM vfs_dirents WHERE child_inode = ?\", existing.child_inode);\n this.db.run(\"DELETE FROM vfs_chunks WHERE inode = ?\", existing.child_inode);\n this.db.run(\"DELETE FROM vfs_nodes WHERE inode = ?\", existing.child_inode);\n }\n this.db.run(\"DELETE FROM vfs_dirents WHERE child_inode = ?\", node.inode);\n this.db.run(\n \"INSERT INTO vfs_dirents (parent_inode, name, child_inode) VALUES (?, ?, ?)\",\n newParent.inode,\n newName,\n node.inode,\n );\n });\n }\n\n // -- Default implementations ---------------------------------------\n\n readFile(\n path: string,\n options?: BufferEncoding | { encoding?: BufferEncoding | null } | null,\n ): Promise<Buffer | string> {\n return Promise.resolve(this.readFileSync(path, options));\n }\n\n readFileSync(\n path: string,\n options?: BufferEncoding | { encoding?: BufferEncoding | null } | null,\n ): Buffer | string {\n const node = resolveInode(this.db, path);\n if (node === null) {\n throw createWorkspaceError(\"ENOENT\", `no such file: ${path}`, path);\n }\n if (node.type !== \"file\") {\n throw createWorkspaceError(\"EISDIR\", `path is a directory: ${path}`, path);\n }\n const chunks = this.db.all<{ hash: Uint8Array; size: number }>(\n \"SELECT hash, size FROM vfs_chunks WHERE inode = ? ORDER BY idx\",\n node.inode,\n );\n let total = 0;\n for (const c of chunks) total += c.size;\n const out = Buffer.alloc(total);\n let offset = 0;\n for (const chunk of chunks) {\n const row = this.db.one<{ bytes: Uint8Array }>(\n \"SELECT bytes FROM vfs_blob_bytes WHERE hash = ?\",\n chunk.hash,\n );\n if (row === undefined) {\n throw createWorkspaceError(\"EIO\", `missing blob bytes for ${path}`, path);\n }\n out.set(row.bytes, offset);\n offset += row.bytes.byteLength;\n }\n const encoding = typeof options === \"string\" ? options : options?.encoding;\n return encoding ? out.toString(encoding) : out;\n }\n\n writeFile(\n path: string,\n data: string | Buffer,\n options?: { encoding?: BufferEncoding; mode?: number } | BufferEncoding,\n ): Promise<void> {\n this.writeFileSync(path, data, options);\n return Promise.resolve();\n }\n\n writeFileSync(\n path: string,\n data: string | Buffer,\n options?: { encoding?: BufferEncoding; mode?: number } | BufferEncoding,\n ): void {\n const mode = typeof options === \"string\" ? undefined : options?.mode;\n const bytes =\n typeof data === \"string\"\n ? new TextEncoder().encode(data)\n : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);\n writeFileSyncImpl(this.db, path, bytes, { mode }, this.now);\n }\n\n appendFile(\n _path: string,\n _data: string | Buffer,\n _options?: { encoding?: BufferEncoding; mode?: number } | BufferEncoding,\n ): Promise<void> {\n return Promise.reject(notImplemented(\"appendFile\"));\n }\n\n appendFileSync(\n _path: string,\n _data: string | Buffer,\n _options?: { encoding?: BufferEncoding; mode?: number } | BufferEncoding,\n ): void {\n throw notImplemented(\"appendFileSync\");\n }\n\n exists(path: string): Promise<boolean> {\n return Promise.resolve(this.existsSync(path));\n }\n\n existsSync(path: string): boolean {\n try {\n return resolveInode(this.db, path) !== null;\n } catch {\n return false;\n }\n }\n\n copyFile(_src: string, _dest: string, _mode?: number): Promise<void> {\n return Promise.reject(notImplemented(\"copyFile\"));\n }\n\n copyFileSync(_src: string, _dest: string, _mode?: number): void {\n throw notImplemented(\"copyFileSync\");\n }\n\n internalModuleStat(_path: string): number {\n // Used by node:vfs module-resolution hooks. The wsd driver doesn't\n // need it; if this provider is ever mounted via `vfs.mount()` we'll\n // need to return 0 for files, 1 for dirs, -1 for not-found.\n throw notImplemented(\"internalModuleStat\");\n }\n\n realpath(path: string, _options?: { encoding?: BufferEncoding }): Promise<string> {\n return Promise.resolve(this.realpathSync(path));\n }\n\n realpathSync(path: string, _options?: { encoding?: BufferEncoding }): string {\n const { path: canonical } = canonicalizePath(path);\n if (resolveInode(this.db, canonical) === null) {\n throw createWorkspaceError(\"ENOENT\", `no such path: ${canonical}`, canonical);\n }\n return canonical;\n }\n\n access(path: string, _mode?: number): Promise<void> {\n this.accessSync(path);\n return Promise.resolve();\n }\n\n accessSync(path: string, _mode?: number): void {\n if (resolveInode(this.db, path) === null) {\n throw createWorkspaceError(\"ENOENT\", `no such path: ${path}`, path);\n }\n }\n\n // -- File descriptors ----------------------------------------------\n\n closeSync(fd: number): void {\n if (!this.#fds.delete(fd)) {\n throw createWorkspaceError(\"EBADF\", `unknown fd ${fd}`);\n }\n }\n\n readSync(\n fd: number,\n buffer: Buffer | Uint8Array,\n offset: number,\n length: number,\n position: number | null,\n ): number {\n const state = this.#fdOrThrow(fd);\n if (!state.readable) {\n throw createWorkspaceError(\"EBADF\", `fd ${fd} is not readable`);\n }\n const startAt = position ?? state.position;\n const bytes = readFileBytesSync(this.db, state.path);\n if (startAt >= bytes.byteLength) {\n return 0;\n }\n const end = Math.min(startAt + length, bytes.byteLength);\n const n = end - startAt;\n const view =\n buffer instanceof Buffer\n ? buffer\n : Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength);\n view.set(bytes.subarray(startAt, end), offset);\n if (position === null || position === undefined) {\n state.position += n;\n }\n return n;\n }\n\n writeSync(\n fd: number,\n buffer: Buffer | Uint8Array,\n offset: number = 0,\n length: number = buffer.byteLength - offset,\n position: number | null = null,\n ): number {\n const state = this.#fdOrThrow(fd);\n if (!state.writable) {\n throw createWorkspaceError(\"EBADF\", `fd ${fd} is not writable`);\n }\n const existing = readFileBytesSync(this.db, state.path);\n const startAt = state.append ? existing.byteLength : (position ?? state.position);\n const next = spliceBytes(existing, startAt, buffer, offset, length);\n writeFileSyncImpl(this.db, state.path, next, {}, this.now);\n if (position === null || position === undefined) {\n state.position = startAt + length;\n }\n return length;\n }\n\n fstatSync(fd: number, _options?: { bigint?: boolean }): VirtualStatsLike {\n const state = this.#fdOrThrow(fd);\n return this.statSync(state.path);\n }\n\n truncateSync(path: string, len: number): void {\n const node = resolveInode(this.db, path);\n if (node === null) {\n throw createWorkspaceError(\"ENOENT\", `no such path: ${path}`, path);\n }\n if (node.type !== \"file\") {\n throw createWorkspaceError(\"EISDIR\", `path is a directory: ${path}`, path);\n }\n const existing = readFileBytesSync(this.db, path);\n if (existing.byteLength === len) {\n return;\n }\n let next: Uint8Array;\n if (len < existing.byteLength) {\n next = existing.subarray(0, len);\n } else {\n next = new Uint8Array(len);\n next.set(existing, 0);\n }\n writeFileSyncImpl(this.db, path, next, {}, this.now);\n }\n\n ftruncateSync(fd: number, len: number): void {\n const state = this.#fdOrThrow(fd);\n this.truncateSync(state.path, len);\n }\n\n #fdOrThrow(fd: number): FdState {\n const state = this.#fds.get(fd);\n if (state === undefined) {\n throw createWorkspaceError(\"EBADF\", `unknown fd ${fd}`);\n }\n return state;\n }\n\n // -- Symlinks ------------------------------------------------------\n\n readlink(path: string, _options?: { encoding?: BufferEncoding }): Promise<string> {\n return Promise.resolve(this.readlinkSync(path));\n }\n\n readlinkSync(path: string, _options?: { encoding?: BufferEncoding }): string {\n return readlinkImpl(this.db, path);\n }\n\n symlink(target: string, path: string, _type?: string): Promise<void> {\n this.symlinkSync(target, path);\n return Promise.resolve();\n }\n\n symlinkSync(target: string, path: string, _type?: string): void {\n symlinkImpl(this.db, target, path, this.now);\n }\n\n // -- Watch ----------------------------------------------------------\n //\n // The watcher polls vfs_meta.rev on a timer. Each tick\n // coalesceChanges yields every path touched since the last\n // observed rev; we filter by the watched directory (and\n // recursive flag) and emit one 'change' event per path. Cheap\n // because coalesceChanges is one indexed range scan on\n // vfs_nodes.rev plus a path walk per touched inode.\n //\n // Event types follow node's fs.watch convention:\n // - 'rename' for deletes (path went away)\n // - 'change' for everything else (file/dir/symlink mutation)\n // We don't distinguish first-time creation from in-place edit\n // — the cost is a per-watcher state map that's bigger than\n // the signal is worth. Callers that need rename-vs-change\n // semantics can stat the path themselves.\n\n watch(path: string, options: WatchOptions = {}): WatchHandle {\n return createWatcher(this.db, path, options, this.watchIntervalMs);\n }\n\n watchAsync(path: string, options: WatchOptions = {}): AsyncIterable<WatchEvent> {\n return createWatchAsyncIterable(this.watch(path, options));\n }\n\n // watchFile / unwatchFile fire on stat changes at a single path\n // (not the directory under it). Different semantics from watch();\n // editors typically use watch() instead. Leave as ENOSYS until a\n // real call site shows up.\n watchFile(\n _path: string,\n _options?: unknown,\n _listener?: (curr: VirtualStatsLike, prev: VirtualStatsLike) => void,\n ): unknown {\n throw notImplemented(\"watchFile\");\n }\n\n unwatchFile(\n _path: string,\n _listener?: (curr: VirtualStatsLike, prev: VirtualStatsLike) => void,\n ): void {\n throw notImplemented(\"unwatchFile\");\n }\n}\n\nfunction notImplemented(method: string) {\n return createWorkspaceError(\"ENOSYS\", `SQLiteWorkspaceProvider.${method} is not implemented yet`);\n}\n\n// -- VirtualStats / VirtualDirent shim ------------------------------\n//\n// @platformatic/vfs callers (and FUSE drivers built on top) consult\n// the full Node-style stat shape. Most fields don't map onto our\n// content-addressed store, so they get sensible constants. The fields\n// that do map — mode, size, mtime, ino — are populated for real.\n\ninterface StatsInputs {\n mode: number;\n size: number;\n mtimeMs: number;\n ino: number;\n isFile: boolean;\n isDirectory: boolean;\n isSymbolicLink: boolean;\n}\n\n// POSIX mode-bit constants. Linux FUSE rejects a stat whose mode\n// has no S_IF* bits set with EIO — it can't decide whether\n// the inode is a regular file, a directory, or a symlink.\nconst S_IFREG = 0o100000;\nconst S_IFDIR = 0o040000;\nconst S_IFLNK = 0o120000;\n\nfunction fileTypeBits(input: StatsInputs): number {\n if (input.isDirectory) return S_IFDIR;\n if (input.isSymbolicLink) return S_IFLNK;\n if (input.isFile) return S_IFREG;\n return 0;\n}\n\nfunction wrapStats(input: StatsInputs): VirtualStatsLike {\n const mtime = new Date(input.mtimeMs);\n return {\n dev: 0,\n mode: (input.mode & 0o7777) | fileTypeBits(input),\n nlink: 1,\n uid: 0,\n gid: 0,\n rdev: 0,\n blksize: 4096,\n ino: input.ino,\n size: input.size,\n blocks: Math.ceil(input.size / 512),\n atimeMs: input.mtimeMs,\n mtimeMs: input.mtimeMs,\n ctimeMs: input.mtimeMs,\n birthtimeMs: input.mtimeMs,\n atime: mtime,\n mtime,\n ctime: mtime,\n birthtime: mtime,\n isFile: () => input.isFile,\n isDirectory: () => input.isDirectory,\n isSymbolicLink: () => input.isSymbolicLink,\n isBlockDevice: () => false,\n isCharacterDevice: () => false,\n isFIFO: () => false,\n isSocket: () => false,\n };\n}\n\ninterface DirentInput {\n name: string;\n parentPath: string;\n isFile: boolean;\n isDirectory: boolean;\n}\n\nfunction wrapDirent(input: DirentInput): VirtualDirentLike {\n const fullPath =\n input.parentPath === \"/\" ? `/${input.name}` : `${input.parentPath}/${input.name}`;\n return {\n name: input.name,\n parentPath: input.parentPath,\n path: fullPath,\n isFile: () => input.isFile,\n isDirectory: () => input.isDirectory,\n isSymbolicLink: () => false,\n isBlockDevice: () => false,\n isCharacterDevice: () => false,\n isFIFO: () => false,\n isSocket: () => false,\n };\n}\n\ninterface ParsedFlags {\n read: boolean;\n write: boolean;\n create: boolean;\n truncate: boolean;\n append: boolean;\n exclusive: boolean;\n}\n\n// Translate Node's fs flag strings into the boolean flag set the fd\n// table uses. Mirrors the documented behaviour of fs.open(flags) at\n// https://nodejs.org/api/fs.html#file-system-flags.\nfunction parseFlags(flags: string): ParsedFlags {\n switch (flags) {\n case \"r\":\n return {\n read: true,\n write: false,\n create: false,\n truncate: false,\n append: false,\n exclusive: false,\n };\n case \"r+\":\n return {\n read: true,\n write: true,\n create: false,\n truncate: false,\n append: false,\n exclusive: false,\n };\n case \"w\":\n return {\n read: false,\n write: true,\n create: true,\n truncate: true,\n append: false,\n exclusive: false,\n };\n case \"w+\":\n return {\n read: true,\n write: true,\n create: true,\n truncate: true,\n append: false,\n exclusive: false,\n };\n case \"wx\":\n return {\n read: false,\n write: true,\n create: true,\n truncate: false,\n append: false,\n exclusive: true,\n };\n case \"wx+\":\n return {\n read: true,\n write: true,\n create: true,\n truncate: false,\n append: false,\n exclusive: true,\n };\n case \"a\":\n return {\n read: false,\n write: true,\n create: true,\n truncate: false,\n append: true,\n exclusive: false,\n };\n case \"a+\":\n return {\n read: true,\n write: true,\n create: true,\n truncate: false,\n append: true,\n exclusive: false,\n };\n case \"ax\":\n return {\n read: false,\n write: true,\n create: true,\n truncate: false,\n append: true,\n exclusive: true,\n };\n case \"ax+\":\n return {\n read: true,\n write: true,\n create: true,\n truncate: false,\n append: true,\n exclusive: true,\n };\n default:\n throw createWorkspaceError(\"EINVAL\", `unsupported fs flag: ${flags}`);\n }\n}\n\n// Pull a file's full content out of the chunk store into one buffer.\n// Used by the fd-positional code paths because the simplest correct\n// model for writeSync/truncate is \"read whole file, splice, write\n// whole file\"; the content-addressed write path keeps untouched\n// chunks deduped so this only costs the changed chunks on the wire.\nfunction readFileBytesSync(db: Database, path: string): Uint8Array {\n const node = resolveInode(db, path);\n if (node === null) {\n throw createWorkspaceError(\"ENOENT\", `no such file: ${path}`, path);\n }\n if (node.type !== \"file\") {\n throw createWorkspaceError(\"EISDIR\", `path is a directory: ${path}`, path);\n }\n const chunks = db.all<{ hash: Uint8Array; size: number }>(\n \"SELECT hash, size FROM vfs_chunks WHERE inode = ? ORDER BY idx\",\n node.inode,\n );\n let total = 0;\n for (const c of chunks) total += c.size;\n const out = new Uint8Array(total);\n let pos = 0;\n for (const chunk of chunks) {\n const row = db.one<{ bytes: Uint8Array }>(\n \"SELECT bytes FROM vfs_blob_bytes WHERE hash = ?\",\n chunk.hash,\n );\n if (row === undefined) {\n throw createWorkspaceError(\"EIO\", `missing blob bytes for ${path}`, path);\n }\n out.set(row.bytes, pos);\n pos += row.bytes.byteLength;\n }\n return out;\n}\n\n// Splice `length` bytes from `src[srcOffset..]` into a copy of `dst`\n// at `at`. The result is at least as long as max(dst.length, at + length).\n// Bytes in `[dst.length, at)` are zero-filled (writing past EOF).\nfunction spliceBytes(\n dst: Uint8Array,\n at: number,\n src: Uint8Array | Buffer,\n srcOffset: number,\n length: number,\n): Uint8Array {\n const newLength = Math.max(dst.byteLength, at + length);\n const out = new Uint8Array(newLength);\n out.set(dst, 0);\n const srcView = src.subarray(srcOffset, srcOffset + length);\n out.set(srcView, at);\n return out;\n}\n","import type { DurableObjectStorageLike, SQLStorageLike } from \"./types.js\";\n\nexport class Database {\n readonly sql: SQLStorageLike;\n readonly transactionSync: <T>(closure: () => T) => T;\n // Depth counter so reentrant transactionSync() calls work. The\n // outer call uses the storage adapter's transactionSync (or\n // BEGIN/COMMIT under the hood); nested calls use SAVEPOINTs\n // through sql.exec directly. SQLite forbids a real BEGIN inside\n // an active transaction.\n #txDepth = 0;\n\n constructor(storage: DurableObjectStorageLike) {\n this.sql = storage.sql;\n this.transactionSync = <T>(closure: () => T): T => {\n if (this.#txDepth > 0) {\n // Reentrant call: use a savepoint. SQLite's RELEASE on a\n // savepoint inside an outer transaction commits the inner\n // work without ending the outer one.\n const sp = `_t${this.#txDepth}`;\n this.sql.exec(`SAVEPOINT ${sp}`);\n this.#txDepth++;\n try {\n const result = closure();\n this.sql.exec(`RELEASE ${sp}`);\n return result;\n } catch (error) {\n this.sql.exec(`ROLLBACK TO ${sp}`);\n this.sql.exec(`RELEASE ${sp}`);\n throw error;\n } finally {\n this.#txDepth--;\n }\n }\n // Outer call: hand off to the storage adapter so the DO\n // runtime's transaction semantics apply.\n this.#txDepth++;\n try {\n if (storage.transactionSync !== undefined) {\n return storage.transactionSync(closure);\n }\n if (storage.transaction !== undefined) {\n const result = storage.transaction(closure);\n if (\n result !== undefined &&\n result !== null &&\n typeof result === \"object\" &&\n \"then\" in result\n ) {\n throw new Error(\"Durable Object storage adapter requires synchronous transactions\");\n }\n return result;\n }\n return closure();\n } finally {\n this.#txDepth--;\n }\n };\n }\n\n run(query: string, ...bindings: unknown[]): void {\n this.sql.exec(query, ...bindings);\n }\n\n all<Row extends object>(query: string, ...bindings: unknown[]): Row[] {\n const rows = this.sql.exec<Row>(query, ...bindings).toArray();\n return rows.map((row) => normalizeRow(row as Record<string, unknown>)) as Row[];\n }\n\n one<Row extends object>(query: string, ...bindings: unknown[]): Row | undefined {\n return this.all<Row>(query, ...bindings)[0];\n }\n\n scalar<T>(query: string, ...bindings: unknown[]): T | undefined {\n const row = this.one<Record<string, T>>(query, ...bindings);\n if (row === undefined) {\n return undefined;\n }\n\n const [value] = Object.values(row);\n return value;\n }\n}\n\n// Cloudflare's DO SqlStorage returns BLOB columns as ArrayBuffer,\n// whereas node:sqlite returns Uint8Array. Normalise to Uint8Array so\n// the rest of the code only has to handle one shape.\nfunction normalizeRow(row: Record<string, unknown>): Record<string, unknown> {\n // node:sqlite hands back rows with a null prototype; the DO SQL\n // flavour returns ArrayBuffer for BLOB columns. Re-key into a plain\n // {} so consumers get Object.prototype-shaped rows (capnweb's\n // serializer keys off Object.prototype to detect \"object\") and\n // convert any ArrayBuffer to Uint8Array in the same pass.\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(row)) {\n const value = row[key];\n out[key] = value instanceof ArrayBuffer ? new Uint8Array(value) : value;\n }\n return out;\n}\n","import { mkdir } from \"../fs/mkdir.js\";\nimport { readOnlyRootFor } from \"../fs/mount-guard.js\";\nimport { resolveInode } from \"../fs/resolve.js\";\nimport { rm } from \"../fs/rm.js\";\nimport { symlink } from \"../fs/symlink.js\";\nimport { writeFile, writeFileSync } from \"../fs/writeFile.js\";\nimport type { Database } from \"../storage.js\";\nimport type { ChangeEntry } from \"./changes.js\";\nimport { computeManifestHash } from \"./manifests.js\";\nimport { currentRev, readWatermark, writeWatermark } from \"./watermarks.js\";\n\n// One container-side change that landed under a read-only mount and\n// was therefore skipped rather than applied. Callers (the workspace\n// pull surface, the shell exec bracket) surface these so the user\n// learns the mount stayed authoritative.\nexport interface SkippedEntry {\n // Absolute VFS path the change targeted.\n path: string;\n // Mount root that owns the path (the one whose mode is\n // read-only). Lets callers group skipped entries by mount.\n mountRoot: string;\n // 'write' covers file / dir / symlink create-or-update; 'delete'\n // covers tombstones. The single field is enough for callers to\n // decide messaging.\n op: \"write\" | \"delete\";\n // Open shape: future skip reasons can join this union without\n // breaking callers that match on 'read-only' today.\n reason: \"read-only\";\n}\n\n// Return shape of applyChanges / applyChangesSync. Existing callers\n// that only wanted a count read `result.applied`; new callers can\n// surface `result.skipped`.\nexport interface ApplyResult {\n // Entries written through writeFile / mkdir / symlink / rm.\n applied: number;\n // Entries dropped because they targeted a read-only mount root.\n // Empty when no such mounts are registered or the stream stayed\n // clear of them.\n skipped: SkippedEntry[];\n}\n\nexport interface ApplyOptions {\n // Soft cap on bytes written per transactionSync batch. Default 64\n // MiB; matches docs/02_sync_protocol.md. The cap is advisory: a\n // single large file is always one batch.\n maxBytesPerBatch?: number;\n // Soft cap on entries per batch. Default 1024 paths.\n maxPathsPerBatch?: number;\n // After the stream drains, advance fetchRev to this value if it's\n // higher than the current persisted value. Callers pass the\n // sender's currentRev so the next pull resumes from the right\n // cursor. Never regresses the watermark.\n advanceFetchRev?: number;\n // Where the entries came from. 'local' (default) treats the apply\n // path like any other mutation: writeFile/mkdir/etc bump vfs_meta.rev\n // and the push loop later ships those new revs upstream. 'upstream'\n // means the entries came from a remote push or fetch; the apply\n // still bumps rev (so readers see fresh data) but we advance pushRev\n // to match, so the push loop knows everything in this range is\n // already on the wire. Without this flag, applying an upstream\n // entry would generate a push-back on the next tick and the two\n // sides would ping-pong forever.\n source?: \"local\" | \"upstream\";\n}\n\nconst DEFAULT_MAX_BYTES = 64 * 1024 * 1024;\nconst DEFAULT_MAX_PATHS = 1024;\n\nfunction hex(bytes: Uint8Array): string {\n let s = \"\";\n for (let i = 0; i < bytes.byteLength; i++) s += bytes[i].toString(16).padStart(2, \"0\");\n return s;\n}\n\n// Drive a ChangeEntry stream against `db`, batching writes so peak\n// memory stays bounded and a crash mid-apply leaves the DB in a\n// consistent state. Each batch runs inside a single transactionSync\n// from the underlying FS helpers — mkdir, writeFile, symlink,\n// rm all wrap their own transactionSync, so a batch is in practice\n// a sequence of independently-committed mutations rather than one\n// fat transaction. The bounded-batch contract still holds because\n// fetchRev only advances after the stream drains.\n//\n// `objects` is a hash-keyed map of chunk bytes the sender shipped\n// via pushObjects / fetchObjects. File entries reassemble their\n// chunks from this map; missing entries throw.\nexport async function applyChanges(\n db: Database,\n entries: Iterable<ChangeEntry> | AsyncIterable<ChangeEntry>,\n objects: Map<string, Uint8Array>,\n options: ApplyOptions = {},\n): Promise<ApplyResult> {\n // Snapshot rev before we touch anything. Used by the loopback-\n // suppression at the bottom to decide whether it's safe to\n // advance pushRev past the entries this apply produced.\n const revBeforeApply = currentRev(db);\n const maxBytes = options.maxBytesPerBatch ?? DEFAULT_MAX_BYTES;\n const maxPaths = options.maxPathsPerBatch ?? DEFAULT_MAX_PATHS;\n\n let bytesInBatch = 0;\n let pathsInBatch = 0;\n let applied = 0;\n const skipped: SkippedEntry[] = [];\n const flush = () => {\n bytesInBatch = 0;\n pathsInBatch = 0;\n };\n\n for await (const entry of entries) {\n // Idempotent skip: if the entry already matches the local\n // state, drop it on the floor. The check is what stops a\n // pull from bumping vfs_meta.rev for entries that are\n // already in place, which in turn stops the next push from\n // re-shipping them.\n if (options.source === \"upstream\" && entry.kind !== \"delete\") {\n if (alreadyApplied(db, entry)) continue;\n }\n // Read-only mount guard. Entries under a registered read-only\n // mount root are surfaced via the return value and not applied.\n // The owning workspace's surface (Workspace.pull, exec()) folds\n // these into its own return so callers see what stayed\n // authoritative on the mount.\n const blockingRoot = readOnlyRootFor(db, entry.path);\n if (blockingRoot !== undefined) {\n skipped.push({\n path: entry.path,\n mountRoot: blockingRoot,\n op: entry.kind === \"delete\" ? \"delete\" : \"write\",\n reason: \"read-only\",\n });\n continue;\n }\n if (entry.kind === \"delete\") {\n try {\n rm(db, entry.path, { recursive: true, force: true });\n } catch {\n // Already gone is fine — idempotent apply.\n }\n applied++;\n pathsInBatch++;\n if (pathsInBatch >= maxPaths) flush();\n continue;\n }\n if (entry.kind === \"dir\") {\n mkdir(db, entry.path, { mode: entry.mode, recursive: true }, () => entry.mtime);\n applied++;\n pathsInBatch++;\n if (pathsInBatch >= maxPaths) flush();\n continue;\n }\n if (entry.kind === \"symlink\") {\n symlink(db, entry.target, entry.path, () => entry.mtime);\n applied++;\n pathsInBatch++;\n if (pathsInBatch >= maxPaths) flush();\n continue;\n }\n // file: assemble chunk bytes. First check the in-memory map\n // (the streaming hand-off); fall back to vfs_blob_bytes (the\n // staged-via-pushObjects path).\n const parts: Uint8Array[] = [];\n let total = 0;\n for (const c of entry.chunks) {\n const k = hex(c.hash);\n let bytes = objects.get(k);\n if (bytes === undefined) {\n const row = db.one<{ bytes: Uint8Array }>(\n \"SELECT bytes FROM vfs_blob_bytes WHERE hash = ?\",\n c.hash,\n );\n bytes = row?.bytes;\n }\n if (bytes === undefined) {\n throw new Error(`applyChanges: missing object ${k} for ${entry.path}`);\n }\n parts.push(bytes);\n total += bytes.byteLength;\n }\n const buf = new Uint8Array(total);\n let off = 0;\n for (const p of parts) {\n buf.set(p, off);\n off += p.byteLength;\n }\n await writeFile(db, entry.path, buf, { mode: entry.mode }, () => entry.mtime);\n applied++;\n bytesInBatch += total;\n pathsInBatch++;\n if (bytesInBatch >= maxBytes || pathsInBatch >= maxPaths) flush();\n }\n\n // Advance fetchRev only after the stream drains so a crash\n // mid-apply leaves the watermark behind and the next pull\n // re-fetches anything not yet committed.\n if (options.advanceFetchRev !== undefined) {\n const current = readWatermark(db, \"fetchRev\");\n if (options.advanceFetchRev > current) {\n writeWatermark(db, \"fetchRev\", options.advanceFetchRev);\n }\n }\n\n // Loopback suppression: when this apply pass reflects entries\n // from upstream, the writeFile/mkdir/symlink/rm calls inside\n // bumped vfs_meta.rev. Without this advance, the next push tick\n // would see those rev bumps as fresh local changes and push them\n // back to upstream, which would apply them and bump again, and\n // so on.\n //\n // Subtle: we can only advance pushRev when it already covered\n // every rev that existed *before* this apply. If the caller had\n // unpushed local writes sitting between (existing, revBeforeApply],\n // advancing pushRev past them would strand them — the next\n // pushOnce would skip them as already-shipped. That was F1: a\n // pull whose entries were all idempotent-skipped still bumped\n // pushRev up to currentRev, masking local writes that hadn't\n // shipped yet.\n //\n // In the unsafe case we leave pushRev alone. The next pushOnce\n // drains both the unpushed locals and the apply's own bumps;\n // the receiver's alreadyApplied() check suppresses the latter.\n // One redundant round-trip per apply, bounded.\n if (options.source === \"upstream\") {\n const revAfter = currentRev(db);\n const existing = readWatermark(db, \"pushRev\");\n if (existing >= revBeforeApply && revAfter > existing) {\n writeWatermark(db, \"pushRev\", revAfter);\n }\n }\n\n return { applied, skipped };\n}\n\n// Synchronous variant of applyChanges. Same semantics; takes an\n// in-memory entry array instead of an iterable. Used on the push\n// receiver so the whole batch can run inside a single transactionSync\n// and a mid-stream failure rolls back every prior entry.\n//\n// Stays separate from applyChanges so the streaming pull path\n// (which can't hold a sync transaction across network I/O) keeps\n// its async semantics.\nexport function applyChangesSync(\n db: Database,\n entries: readonly ChangeEntry[],\n objects: Map<string, Uint8Array>,\n options: ApplyOptions = {},\n): ApplyResult {\n const revBeforeApply = currentRev(db);\n const maxBytes = options.maxBytesPerBatch ?? DEFAULT_MAX_BYTES;\n const maxPaths = options.maxPathsPerBatch ?? DEFAULT_MAX_PATHS;\n\n let bytesInBatch = 0;\n let pathsInBatch = 0;\n let applied = 0;\n const skipped: SkippedEntry[] = [];\n const flush = () => {\n bytesInBatch = 0;\n pathsInBatch = 0;\n };\n\n for (const entry of entries) {\n if (options.source === \"upstream\" && entry.kind !== \"delete\") {\n if (alreadyApplied(db, entry)) continue;\n }\n const blockingRoot = readOnlyRootFor(db, entry.path);\n if (blockingRoot !== undefined) {\n skipped.push({\n path: entry.path,\n mountRoot: blockingRoot,\n op: entry.kind === \"delete\" ? \"delete\" : \"write\",\n reason: \"read-only\",\n });\n continue;\n }\n if (entry.kind === \"delete\") {\n try {\n rm(db, entry.path, { recursive: true, force: true });\n } catch {\n // Already gone is fine — idempotent apply.\n }\n applied++;\n pathsInBatch++;\n if (pathsInBatch >= maxPaths) flush();\n continue;\n }\n if (entry.kind === \"dir\") {\n mkdir(db, entry.path, { mode: entry.mode, recursive: true }, () => entry.mtime);\n applied++;\n pathsInBatch++;\n if (pathsInBatch >= maxPaths) flush();\n continue;\n }\n if (entry.kind === \"symlink\") {\n symlink(db, entry.target, entry.path, () => entry.mtime);\n applied++;\n pathsInBatch++;\n if (pathsInBatch >= maxPaths) flush();\n continue;\n }\n const parts: Uint8Array[] = [];\n let total = 0;\n for (const c of entry.chunks) {\n const k = hex(c.hash);\n let bytes = objects.get(k);\n if (bytes === undefined) {\n const row = db.one<{ bytes: Uint8Array }>(\n \"SELECT bytes FROM vfs_blob_bytes WHERE hash = ?\",\n c.hash,\n );\n bytes = row?.bytes;\n }\n if (bytes === undefined) {\n throw new Error(`applyChanges: missing object ${k} for ${entry.path}`);\n }\n parts.push(bytes);\n total += bytes.byteLength;\n }\n const buf = new Uint8Array(total);\n let off = 0;\n for (const p of parts) {\n buf.set(p, off);\n off += p.byteLength;\n }\n writeFileSync(db, entry.path, buf, { mode: entry.mode }, () => entry.mtime);\n applied++;\n bytesInBatch += total;\n pathsInBatch++;\n if (bytesInBatch >= maxBytes || pathsInBatch >= maxPaths) flush();\n }\n\n if (options.advanceFetchRev !== undefined) {\n const current = readWatermark(db, \"fetchRev\");\n if (options.advanceFetchRev > current) {\n writeWatermark(db, \"fetchRev\", options.advanceFetchRev);\n }\n }\n\n if (options.source === \"upstream\") {\n const revAfter = currentRev(db);\n const existing = readWatermark(db, \"pushRev\");\n if (existing >= revBeforeApply && revAfter > existing) {\n writeWatermark(db, \"pushRev\", revAfter);\n }\n }\n\n return { applied, skipped };\n}\n\n// Compare an entry against the local node graph. Returns true when\n// the entry would be a no-op apply: the manifest hash (files),\n// mode + symlink target (symlinks), or mode (dirs) already matches.\n// We deliberately skip mtime comparison — mtime is metadata\n// the source decides on, and re-applying it would still bump the\n// local rev counter for nothing. Receivers see eventual mtime\n// drift between peers; the wire stays quiet.\nfunction alreadyApplied(db: Database, entry: Exclude<ChangeEntry, { kind: \"delete\" }>): boolean {\n const live = resolveInode(db, entry.path, { followSymlinks: false });\n if (live === null) return false;\n\n if (entry.kind === \"file\") {\n if (live.type !== \"file\") return false;\n const row = db.one<{ manifest_hash: Uint8Array | null }>(\n \"SELECT manifest_hash FROM vfs_nodes WHERE inode = ?\",\n live.inode,\n );\n if (!row?.manifest_hash) return false;\n const wanted = computeManifestHash(entry.chunks);\n return uint8Equal(row.manifest_hash, wanted);\n }\n if (entry.kind === \"dir\") {\n return live.type === \"dir\" && (live.mode & 0o7777) === (entry.mode & 0o7777);\n }\n // symlink\n return (\n live.type === \"symlink\" &&\n live.linkTarget === entry.target &&\n (live.mode & 0o7777) === (entry.mode & 0o7777)\n );\n}\n\nfunction uint8Equal(a: Uint8Array, b: Uint8Array): boolean {\n if (a.byteLength !== b.byteLength) return false;\n for (let i = 0; i < a.byteLength; i++) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n}\n","// Cross-side invariant: every fetchChanges and push response carries\n// the container's current appliedPushRev. The DO asserts\n// appliedPushRev >= pushRev on every response.\n//\n// The two sides never share a single clock, but echoing the largest\n// applied DO rev makes the \"container is caught up with the DO's\n// pushes\" invariant inspectable on the wire instead of load-bearing\n// in-process state. A regression in the suppress-dirty-tracking\n// apply path trips the assertion immediately rather than corrupting\n// data silently.\n//\n// Throwing an Error is the right escalation: a violation means the\n// protocol is broken; the connection should tear down and rebuild\n// rather than soldiering on with stale state.\n\nexport function assertAppliedPushRev(appliedPushRev: number, pushRev: number): void {\n if (appliedPushRev < pushRev) {\n throw new Error(\n `cross-side invariant violated: appliedPushRev (${appliedPushRev}) < pushRev (${pushRev})`,\n );\n }\n}\n","// Periodic heartbeat for a SyncRPC transport.\n//\n// Two responsibilities, served by the same timer:\n//\n// - Detect a dead peer. A WebSocket can be silently broken\n// (unclean RST, host OOM with no FIN frame) without the close\n// event firing for tens of seconds. Calling a cheap RPC on a\n// timer surfaces the failure in O(interval) instead.\n// - Keep middleboxes warm. CF edge, NAT, customer middleboxes have\n// idle disconnect timers in the 100–600s range. Application\n// traffic resets them.\n//\n// `ping` is whatever the caller wants to use as a liveness probe.\n// SyncRPC.watermarks() is the natural choice — three SQL scalars,\n// no side effects, exercises the wire end-to-end. `onFailure` fires\n// exactly once if ping rejects; the heartbeat then stops on its own\n// so a dead transport doesn't accumulate failures.\n\nexport interface HeartbeatOptions {\n intervalMs: number;\n ping: () => Promise<unknown>;\n onFailure: (error: Error) => void;\n}\n\nexport function startHeartbeat(options: HeartbeatOptions): () => void {\n const { intervalMs, ping, onFailure } = options;\n let stopped = false;\n const timer = setInterval(() => {\n if (stopped) return;\n ping().catch((error) => {\n if (stopped) return;\n stopped = true;\n clearInterval(timer);\n onFailure(error instanceof Error ? error : new Error(String(error)));\n });\n }, intervalMs);\n return () => {\n if (stopped) return;\n stopped = true;\n clearInterval(timer);\n };\n}\n","// CloudflareContainerBackend — backs Workspace with a wsd instance\n// running inside a Cloudflare Container.\n//\n// The backend drives container lifecycle through an IWorkspaceContainerAPI\n// abstraction. Same-DO and cross-DO callers look identical from\n// here; whether ctx.container is reached directly or through an\n// RPC stub is a concern of the IWorkspaceContainerAPI implementation.\n//\n// Same-DO shape (one DO owns both the container and the Workspace):\n//\n// class WsdContainer extends DurableObject<Env> {\n// #backend = new CloudflareContainerBackend({\n// container: () => this.ws,\n// workspace: { binding: \"WsdContainer\", id: this.ctx.id.toString() },\n// });\n// #workspace = new Workspace({ backends: [this.#backend] });\n//\n// override fetch(req: Request): Promise<Response> {\n// return this.#backend.handleFetch(req);\n// }\n// }\n//\n// Cross-DO shape (Agent DO holds the Workspace, a pool member DO\n// owns the container):\n//\n// class AgentDO extends DurableObject<Env> {\n// #backend = new CloudflareContainerBackend({\n// container: async () => {\n// const memberId = await pickPoolMember(this.env, this.ctx.id);\n// return this.env.WsdHost.get(this.env.WsdHost.idFromString(memberId));\n// },\n// workspace: { binding: \"AgentDO\", id: this.ctx.id.toString() },\n// });\n// }\n//\n// The factory runs once per connect(), so a redial after a session\n// drop re-picks the pool member; mid-session container churn is the\n// pool's problem, not the backend's.\n//\n// Failure model: connect() does the bootstrap once and throws on\n// any failure. The Workspace's ready() retries by re-entering\n// connect() on the next call. On a mid-session WebSocket drop the\n// backend resolves `BackendHandle.closed`, which the Workspace\n// listens for and uses to drop its cached handle so the next call\n// rebuilds against a fresh session.\n\nimport type { WorkspaceRPC } from \"@cloudflare/workspace-rpc\";\nimport { newWebSocketRpcSession, type RpcStub } from \"capnweb\";\n\nimport type { BackendHandle, WorkspaceBackend } from \"../backend.js\";\nimport { startHeartbeat } from \"../heartbeat.js\";\nimport type { IWorkspaceContainerAPI, WorkspaceRef } from \"./container-host.js\";\n\n// What the backend's `container` factory returns: anything with\n// a getWorkspaceContainer() method — the shape withWorkspaceContainer\n// installs. Same-DO callers pass `this`; cross-DO callers pass a\n// DO stub whose target was extended with withWorkspaceContainer\n// (Workers RPC exposes the method as a pipelined callable).\nexport interface ContainerHostHolder {\n getWorkspaceContainer(): IWorkspaceContainerAPI | Promise<IWorkspaceContainerAPI>;\n}\n\nexport interface CloudflareContainerBackendOptions {\n // Resolves the container host to drive on each connect(). Called\n // anew per dial so a pool-backed factory can re-pick. Returning\n // a Promise is supported for pickers that consult external state\n // (KV, a coordinator DO, etc.).\n //\n // The returned value exposes getWorkspaceContainer() — the same\n // shape withWorkspaceContainer installs. Pass `this` (same-DO)\n // or a DO stub (cross-DO); the backend calls the method itself.\n container: () => ContainerHostHolder | Promise<ContainerHostHolder>;\n\n // Identifies the Workspace-owning DO. Fixed for the lifetime of\n // the backend: the backend lives inside this DO and the /ws\n // upgrade always lands here. Plain {binding, id} data so it\n // survives the Workers RPC hop to a cross-DO container host.\n workspace: WorkspaceRef;\n\n // Hostname wsd will dial back. Defaults to \"workspace.internal\".\n // Override for tests or to avoid collisions with other backends\n // sharing the same container host.\n egressHost?: string;\n\n // TCP port wsd listens on inside the container. Default 8080,\n // matching the Dockerfile shipped with examples/wsd-container.\n containerPort?: number;\n\n // Environment variables passed to container.start(). Merged onto\n // the defaults (PORT, MOUNT_POINT). Caller-supplied values win.\n containerEnv?: Record<string, string>;\n\n // Total time the backend waits for: container port to open,\n // /connect POST to return, /ws upgrade to arrive. Default 30s.\n connectTimeoutMs?: number;\n\n // Period for the application-level heartbeat — a watermarks()\n // RPC on a timer. Two jobs: detect a silently-dead peer faster\n // than waiting for the next real RPC, and keep middlebox idle\n // timers warm. Default 20_000ms. Set 0 to disable.\n heartbeatIntervalMs?: number;\n}\n\nconst DEFAULT_EGRESS_HOST = \"workspace.internal\";\nconst DEFAULT_CONTAINER_PORT = 8080;\nconst DEFAULT_CONNECT_TIMEOUT_MS = 30_000;\nconst DEFAULT_HEARTBEAT_INTERVAL_MS = 20_000;\n\nexport class CloudflareContainerBackend implements WorkspaceBackend {\n readonly id = \"cloudflare-container\";\n\n readonly #options: Required<\n Omit<CloudflareContainerBackendOptions, \"container\" | \"workspace\" | \"containerEnv\">\n > &\n Pick<CloudflareContainerBackendOptions, \"container\" | \"workspace\" | \"containerEnv\">;\n\n // State for the in-flight /ws upgrade. handleFetch() resolves\n // #pendingUpgrade; connect() awaits it.\n #pendingUpgrade: Promise<WebSocket> | undefined;\n #resolveUpgrade: ((ws: WebSocket) => void) | undefined;\n #rejectUpgrade: ((err: unknown) => void) | undefined;\n\n // Cached after the first successful connect(). Cleared on close()\n // or when the underlying WebSocket reports `close` / `error`.\n #handle: BackendHandle | undefined;\n\n constructor(options: CloudflareContainerBackendOptions) {\n this.#options = {\n container: options.container,\n workspace: options.workspace,\n containerEnv: options.containerEnv,\n egressHost: options.egressHost ?? DEFAULT_EGRESS_HOST,\n containerPort: options.containerPort ?? DEFAULT_CONTAINER_PORT,\n connectTimeoutMs: options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS,\n heartbeatIntervalMs: options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS,\n };\n }\n\n async connect(): Promise<BackendHandle> {\n if (this.#handle) return this.#handle;\n\n const deadline = Date.now() + this.#options.connectTimeoutMs;\n const holder = await this.#options.container();\n const host = await holder.getWorkspaceContainer();\n\n await host.start({\n PORT: String(this.#options.containerPort),\n MOUNT_POINT: \"/workspace\",\n ...this.#options.containerEnv,\n });\n await host.interceptOutboundHttp(this.#options.egressHost, this.#options.workspace);\n\n // Arm the upgrade promise before posting /connect — wsd\n // dials back as soon as /health on the egress answers, so\n // the upgrade can arrive before the POST resolves.\n this.#armUpgrade();\n\n await this.#waitForPort(host, deadline);\n await this.#postConnect(host, deadline);\n const ws = await this.#waitForUpgrade(deadline);\n\n const stub = newWebSocketRpcSession(\n ws as unknown as globalThis.WebSocket,\n ) as RpcStub<WorkspaceRPC>;\n\n // `closed` resolves on the first 'close' event from the underlying\n // WebSocket. The Workspace listens for it and drops its cached\n // handle so the next ready() call rebuilds against a fresh\n // session.\n let stopHeartbeat: (() => void) | undefined;\n const closed = new Promise<void>((resolve) => {\n let fired = false;\n const onClose = () => {\n if (fired) return;\n fired = true;\n stopHeartbeat?.();\n resolve();\n this.#handle = undefined;\n };\n ws.addEventListener(\"close\", onClose, { once: true });\n // Some runtimes fire 'error' without a follow-up 'close' on\n // abrupt teardown; treat error as close too.\n ws.addEventListener(\"error\", onClose, { once: true });\n // capnweb's RPC layer can notice the session is broken (an\n // abort frame, a malformed message) before the underlying\n // WebSocket fires close. onRpcBroken closes that gap so the\n // next ready() rebuilds against a fresh transport instead of\n // waiting on a heartbeat or the next real RPC to discover\n // the wedged session.\n (stub as unknown as { onRpcBroken: (cb: (err: unknown) => void) => void }).onRpcBroken(\n onClose,\n );\n });\n\n if (this.#options.heartbeatIntervalMs > 0) {\n stopHeartbeat = startHeartbeat({\n intervalMs: this.#options.heartbeatIntervalMs,\n ping: () => (stub as unknown as WorkspaceRPC).sync.watermarks(),\n onFailure: () => {\n try {\n ws.close();\n } catch {\n // already closed; idempotent\n }\n },\n });\n }\n\n const handle: BackendHandle = {\n rpc: stub as unknown as WorkspaceRPC,\n closed,\n close: async () => {\n stopHeartbeat?.();\n // Dispose the root stub first. Per capnweb's docs, this is\n // the documented way to shut a session down — it lets the\n // RPC layer send a clean abort frame to the peer before\n // the socket dies. Falling through to ws.close() is\n // belt-and-braces for runtimes where the dispose path\n // doesn't (yet) close the transport.\n try {\n (stub as unknown as Disposable)[Symbol.dispose]?.();\n } catch {\n // already disposed; idempotent\n }\n try {\n ws.close();\n } catch {\n // already closed; idempotent\n }\n this.#handle = undefined;\n },\n };\n this.#handle = handle;\n return handle;\n }\n\n // Routes a /ws upgrade Request into the in-flight connect().\n // Returns the 101 response that the WorkspaceProxy fetch handler\n // forwards back to the container.\n async handleFetch(req: Request): Promise<Response> {\n const url = new URL(req.url);\n if (url.pathname !== \"/ws\") {\n return new Response(\"not found\", { status: 404 });\n }\n if (req.headers.get(\"upgrade\") !== \"websocket\") {\n return new Response(\"expected websocket upgrade\", { status: 426 });\n }\n\n const pair = new WebSocketPair();\n const [client, server] = [pair[0], pair[1]];\n server.accept();\n\n if (this.#resolveUpgrade) {\n this.#resolveUpgrade(server);\n } else {\n // No connect() in flight — close the socket immediately.\n // The remote will redial on its next attempt; we don't\n // hold orphaned sockets that nothing will reap.\n server.close(1011, \"no pending connect\");\n return new Response(\"no pending connect\", { status: 409 });\n }\n\n return new Response(null, { status: 101, webSocket: client });\n }\n\n // --- internals --------------------------------------------------\n\n #armUpgrade(): void {\n this.#pendingUpgrade = new Promise<WebSocket>((resolve, reject) => {\n this.#resolveUpgrade = resolve;\n this.#rejectUpgrade = reject;\n });\n // Swallow unhandled-rejection noise if connect() throws\n // before anyone awaits the promise.\n this.#pendingUpgrade.catch(() => {});\n }\n\n #clearUpgrade(): void {\n this.#pendingUpgrade = undefined;\n this.#resolveUpgrade = undefined;\n this.#rejectUpgrade = undefined;\n }\n\n async #waitForPort(host: IWorkspaceContainerAPI, deadline: number): Promise<void> {\n let lastError: unknown;\n while (Date.now() < deadline) {\n try {\n const res = await host\n .port(this.#options.containerPort)\n .fetch(\"http://container/health\", { method: \"HEAD\" });\n void res.body?.cancel();\n return;\n } catch (error) {\n lastError = error;\n await sleep(250);\n }\n }\n this.#rejectUpgrade?.(new Error(\"port did not open\"));\n this.#clearUpgrade();\n throw new Error(\n `CloudflareContainerBackend: container port ${this.#options.containerPort} did not open: ${describeError(lastError)}`,\n );\n }\n\n async #postConnect(host: IWorkspaceContainerAPI, deadline: number): Promise<void> {\n const remaining = Math.max(0, deadline - Date.now());\n let res: Response;\n try {\n res = await host.port(this.#options.containerPort).fetch(\"http://container/connect\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({\n url: `http://${this.#options.egressHost}`,\n healthTimeoutMs: remaining,\n }),\n });\n } catch (error) {\n this.#rejectUpgrade?.(error);\n this.#clearUpgrade();\n throw new Error(`CloudflareContainerBackend: POST /connect failed: ${describeError(error)}`);\n }\n if (!res.ok) {\n const body = await res.text().catch(() => \"\");\n this.#rejectUpgrade?.(new Error(`/connect ${res.status}`));\n this.#clearUpgrade();\n throw new Error(`CloudflareContainerBackend: POST /connect returned ${res.status}: ${body}`);\n }\n }\n\n async #waitForUpgrade(deadline: number): Promise<WebSocket> {\n const upgrade = this.#pendingUpgrade;\n if (!upgrade) throw new Error(\"CloudflareContainerBackend: upgrade promise missing\");\n\n const remaining = Math.max(0, deadline - Date.now());\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n const ws = await Promise.race([\n upgrade,\n new Promise<never>((_, reject) => {\n timer = setTimeout(\n () =>\n reject(\n new Error(\n `CloudflareContainerBackend: /ws upgrade did not arrive within ${this.#options.connectTimeoutMs}ms`,\n ),\n ),\n remaining,\n );\n }),\n ]);\n return ws;\n } finally {\n if (timer) clearTimeout(timer);\n this.#clearUpgrade();\n }\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction describeError(error: unknown): string {\n if (error instanceof Error) return error.message;\n return String(error);\n}\n","// IWorkspaceContainerAPI — the seam CloudflareContainerBackend\n// drives instead of talking to a Container binding directly. Two\n// reasons it exists:\n//\n// 1. Same-DO vs cross-DO. A container-pool deployment wants the\n// DO that owns the Workspace (e.g. an Agent DO) to be separate\n// from the DO that owns the Container binding (a pool member\n// that can be re-leased between sessions). The pool member's\n// ctx.container isn't reachable from the Agent's isolate, but\n// an RpcTarget stub satisfying IWorkspaceContainerAPI is.\n//\n// 2. Testability. The interface is narrower than `Container` —\n// three methods — and fakes don't need to mimic the full\n// runtime surface.\n//\n// Consumers don't implement this interface directly. They mix\n// `withWorkspaceContainer(Base)` into their DO class, which adds a\n// single `ws` accessor returning a `WorkspaceContainerAPI` —\n// an RpcTarget so it works the same in-isolate and across RPC.\n\nimport { RpcTarget } from \"cloudflare:workers\";\n\n// Identifies the Durable Object that owns the Workspace and answers\n// the /ws upgrade. Plain data so it can travel over Workers RPC.\nexport interface WorkspaceRef {\n // Binding name in the host Worker's env that resolves to the\n // DurableObjectNamespace for the Workspace-owning DO class.\n binding: string;\n // Stringified DurableObjectId of the specific Workspace owner.\n id: string;\n}\n\n// Driver surface CloudflareContainerBackend talks to. Implemented\n// by WorkspaceContainerAPI below; exposed on consumer DOs through\n// the `ws` accessor that withWorkspaceContainer installs.\nexport interface IWorkspaceContainerAPI {\n // Idempotent start. Returns once the runtime has accepted the\n // start command; readiness is verified by the backend polling\n // /health via port().\n start(env: Record<string, string>): Promise<void>;\n\n // Wire `host` → workspace inside the container's egress table.\n // Called once per backend connect(). The implementation\n // constructs the loopback Fetcher locally from {binding, id},\n // because Fetchers can't survive a Workers RPC hop.\n interceptOutboundHttp(host: string, workspace: WorkspaceRef): Promise<void>;\n\n // Return a Fetcher bound to the named TCP port inside the\n // container. The backend uses this for the /health poll and the\n // POST /connect handshake — call `.fetch(request)` on the result.\n // Across RPC, the Workers runtime auto-stubs the Fetcher so a\n // chained `.fetch(...)` pipelines into one round-trip.\n port(port: number): Fetcher;\n}\n\n// Concrete implementation. Extends RpcTarget so it travels intact\n// across a Workers RPC boundary; in-isolate callers see plain\n// method calls. Constructed by withWorkspaceContainer's `ws`\n// getter — consumers don't instantiate this directly.\nexport class WorkspaceContainerAPI extends RpcTarget implements IWorkspaceContainerAPI {\n readonly #container: NonNullable<DurableObjectState[\"container\"]>;\n readonly #ctx: DurableObjectState;\n\n constructor(ctx: DurableObjectState) {\n super();\n if (!ctx.container) {\n throw new Error(\"WorkspaceContainerAPI: DO is not container-enabled (check wrangler.jsonc)\");\n }\n this.#container = ctx.container;\n this.#ctx = ctx;\n }\n\n async start(env: Record<string, string>) {\n if (this.#container.running) return;\n this.#container.start({ enableInternet: true, env });\n }\n\n async interceptOutboundHttp(host: string, ref: WorkspaceRef) {\n // ctx.exports.WorkspaceProxy is bound by name in the\n // consumer's Worker (they re-export WorkspaceProxy from this\n // package). The cast keeps us independent of the consumer's\n // worker-configuration.d.ts.\n // ctx.exports is present at runtime but not on the public\n // DurableObjectState type; cast through unknown to reach it.\n const exports = (this.#ctx as unknown as { exports: Record<string, unknown> }).exports as {\n WorkspaceProxy: (opts: { props: WorkspaceRef }) => Fetcher;\n };\n await this.#container.interceptOutboundHttp(host, exports.WorkspaceProxy({ props: ref }));\n }\n\n port(port: number) {\n return this.#container.getTcpPort(port);\n }\n}\n\n// TS requires a mixin class's constructor to take a single rest\n// parameter, so we widen here. DurableObject's runtime signature\n// is still (ctx, env); the rest tuple just makes TS happy. We\n// don't constrain the instance shape because DurableObject's\n// `ctx` is protected — visible inside the mixin's class body via\n// `extends Base`, but not as a public structural property.\n// biome-ignore lint/suspicious/noExplicitAny: mixin constructor shape requires any[]\ntype DOCtor = new (...args: any[]) => object;\n\n// Mixin: add a single `getWorkspaceContainer()` method to a DO\n// class. Returns a fresh WorkspaceContainerAPI bound to this DO's\n// ctx. One name added to the consumer's class — nothing to\n// forward to super, nothing else to override. A method (not a\n// getter) so it crosses Workers RPC as a callable, and the\n// long-form name keeps it from colliding with anything the\n// consumer's base class might already expose.\n//\n// Same-DO usage (Agent owns the container):\n//\n// export class Agent extends withWorkspaceContainer(\n// class extends DurableObject<Env> {},\n// ) {\n// #backend = new CloudflareContainerBackend({\n// container: () => this,\n// workspace: { binding: \"Agent\", id: this.ctx.id.toString() },\n// });\n// }\n//\n// Cross-DO usage (pool member owns the container):\n//\n// export class WsdHost extends withWorkspaceContainer(\n// class extends DurableObject<Env> {},\n// ) {}\n//\n// #backend = new CloudflareContainerBackend({\n// container: () => this.env.WsdHost.get(memberId),\n// workspace: { binding: \"Agent\", id: this.ctx.id.toString() },\n// });\n//\n// Constructor type the mixin returns. Written explicitly so\n// rolldown-plugin-dts can emit a stable .d.ts (anonymous returned\n// classes with method declarations trip its TS transformer).\nexport type WithWorkspaceContainerCtor<TBase extends DOCtor> = TBase &\n (new (\n // biome-ignore lint/suspicious/noExplicitAny: mirror mixin constructor shape\n ...args: any[]\n ) => InstanceType<TBase> & { getWorkspaceContainer(): WorkspaceContainerAPI });\n\nexport function withWorkspaceContainer<TBase extends DOCtor>(\n Base: TBase,\n): WithWorkspaceContainerCtor<TBase> {\n class WithWorkspaceContainer extends Base {\n getWorkspaceContainer(): WorkspaceContainerAPI {\n return new WorkspaceContainerAPI((this as unknown as { ctx: DurableObjectState }).ctx);\n }\n }\n return WithWorkspaceContainer as WithWorkspaceContainerCtor<TBase>;\n}\n","// Client-side adapter: turn a WebSocket URL into a typed SyncRPC\n// stub. Capnweb's newWebSocketRpcSession does the actual dial; we\n// own the connection lifecycle and expose a close() for clean\n// teardown.\nimport { newWebSocketRpcSession } from \"capnweb\";\n// Open a SyncRPC session against `url`. The first call to any\n// method on the returned stub queues until the WebSocket reaches\n// readyState OPEN; capnweb's transport handles that.\nexport function createSyncClient(options) {\n const WS = options.WebSocketImpl ?? WebSocket;\n const ws = new WS(options.url);\n // The WebSocket cast crosses two type boundaries: the runtime\n // ws (node `ws` package or global) is structurally compatible\n // with capnweb's expected globalThis.WebSocket but TS can't\n // bridge the nominal types. The RpcStub cast names the remote\n // interface so the Proxy returned downstream is strongly typed.\n const stub = newWebSocketRpcSession(ws);\n // capnweb's RpcStub is a Proxy that exposes the remote interface\n // as if it were local. We wrap it so callers see SyncClient\n // (= SyncRPC + close).\n const onEvent = options.onRPCEvent;\n return new Proxy(stub, {\n get(target, prop, receiver) {\n if (prop === \"close\") {\n return async () => {\n // Dispose the root stub first — per capnweb's docs this\n // is the documented way to close a session and lets the\n // RPC layer send a clean abort frame before the transport\n // dies. ws.close() below is belt-and-braces.\n try {\n target[Symbol.dispose]?.();\n }\n catch {\n // already disposed; idempotent\n }\n await new Promise((resolve) => {\n const w = ws;\n if (w.readyState >= 2) {\n resolve();\n return;\n }\n ws.addEventListener(\"close\", () => resolve(), {\n once: true,\n });\n w.close();\n // Belt-and-braces: if `close` never fires (the socket\n // was already torn down) the timeout breaks the await.\n setTimeout(resolve, 200);\n });\n };\n }\n const value = Reflect.get(target, prop, receiver);\n if (onEvent === undefined || typeof prop !== \"string\")\n return value;\n // Capnweb's Proxy returns a callable RpcPromise/RpcStub for\n // every string property. Wrap the call to time it and fire\n // onRPCEvent. The wrapped value still behaves like an\n // RpcPromise (thenable + property-access for pipelining) for\n // calls that return synchronously-pipelined values; we only\n // measure the awaited terminal call.\n return (...args) => {\n const start = Date.now();\n const result = value(...args);\n // For non-thenable returns (streams), fire the event\n // immediately with ok=true. The caller may still throw\n // while reading the stream; observability for that path\n // belongs to the caller.\n if (result && typeof result.then === \"function\") {\n return result.then((v) => {\n onEvent({ rpc: prop, durationMs: Date.now() - start, ok: true });\n return v;\n }, (err) => {\n onEvent({\n rpc: prop,\n durationMs: Date.now() - start,\n ok: false,\n code: err?.code,\n });\n throw err;\n });\n }\n onEvent({ rpc: prop, durationMs: Date.now() - start, ok: true });\n return result;\n };\n },\n // The Proxy is structurally a SyncRPC stub + the close()\n // override; TS can't infer that from the get-handler shape,\n // so route through unknown to land on SyncClient.\n });\n}\n// Open a WorkspaceRPC session. Same transport as createSyncClient,\n// different stub shape: callers reach the sync half via `.sync`\n// and the shell half via `.shell`.\n//\n// onRPCEvent isn't wired here yet — the composite stub's\n// property-access path is `stub.sync.push(...)` which capnweb\n// surfaces as a two-step proxy traversal; the per-call timing\n// shim from createSyncClient doesn't compose cleanly. Add when a\n// caller actually needs it.\nexport function createWorkspaceClient(options) {\n const WS = options.WebSocketImpl ?? WebSocket;\n const ws = new WS(options.url);\n // Same WebSocket / RpcStub cast pattern as createSyncClient —\n // see comment there. WorkspaceRPC adds the composite shape so\n // callers can pipeline `.sync.push(...)` and `.shell.exec(...)`.\n const stub = newWebSocketRpcSession(ws);\n return new Proxy(stub, {\n get(target, prop, receiver) {\n if (prop === \"close\") {\n return async () => {\n // Same dispose-before-close pattern as createSyncClient.\n try {\n target[Symbol.dispose]?.();\n }\n catch {\n // already disposed; idempotent\n }\n await new Promise((resolve) => {\n const w = ws;\n if (w.readyState >= 2) {\n resolve();\n return;\n }\n ws.addEventListener(\"close\", () => resolve(), {\n once: true,\n });\n w.close();\n setTimeout(resolve, 200);\n });\n };\n }\n return Reflect.get(target, prop, receiver);\n },\n // As in createSyncClient, the Proxy is structurally a\n // WorkspaceRPC stub + close(); route through unknown so TS\n // accepts the WorkspaceClient landing type.\n });\n}\n","// TestBackend — the simplest possible backend. Takes a URL\n// pointing at an already-running wsd instance and constructs\n// a SyncRPC client against it.\n//\n// No subprocesses, no Docker calls, no file IO. The package\n// runs unchanged under workerd — TestBackend is just URL\n// plumbing. The test harness outside the package is what\n// stands up the wsd container and exposes its port.\n\nimport { createWorkspaceClient } from \"@cloudflare/workspace-rpc/client\";\n\nimport type { BackendHandle, WorkspaceBackend } from \"../backend.js\";\n\nexport interface TestBackendOptions {\n // URL pointing at the wsd HTTP server. ws://, wss://,\n // http://, and https:// are all accepted; the http(s)\n // schemes are normalised to ws(s) when constructing the\n // capnweb WebSocket session.\n url: string;\n}\n\nexport class TestBackend implements WorkspaceBackend {\n readonly id = \"test\";\n readonly #url: string;\n\n constructor(options: TestBackendOptions) {\n this.#url = options.url;\n }\n\n async connect(): Promise<BackendHandle> {\n const wsUrl = toWebSocketUrl(this.#url);\n // Probe /health before constructing the RPC stub. capnweb's\n // WebSocket session queues calls until the upgrade succeeds,\n // so we'd otherwise discover a misconfigured URL only on the\n // first RPC. The probe surfaces \"harness forgot to start the\n // container\" up front.\n await probeHealth(this.#url);\n const client = createWorkspaceClient({ url: `${wsUrl}/ws` });\n return {\n rpc: client,\n close: async () => {\n await client.close();\n },\n };\n }\n}\n\nfunction toWebSocketUrl(input: string): string {\n if (input.startsWith(\"ws://\") || input.startsWith(\"wss://\")) {\n return stripTrailingSlash(input);\n }\n if (input.startsWith(\"http://\")) {\n return stripTrailingSlash(`ws://${input.slice(\"http://\".length)}`);\n }\n if (input.startsWith(\"https://\")) {\n return stripTrailingSlash(`wss://${input.slice(\"https://\".length)}`);\n }\n throw new Error(`TestBackend: unsupported URL scheme in ${input}`);\n}\n\nfunction stripTrailingSlash(url: string): string {\n return url.endsWith(\"/\") ? url.slice(0, -1) : url;\n}\n\nasync function probeHealth(url: string): Promise<void> {\n const healthUrl = `${stripTrailingSlash(toHttpUrl(url))}/health`;\n let response: Response;\n try {\n response = await fetch(healthUrl);\n } catch (cause) {\n throw new Error(\n `TestBackend: ${healthUrl} is not reachable. ` +\n `Is the wsd container running? (${cause instanceof Error ? cause.message : String(cause)})`,\n );\n }\n if (!response.ok) {\n throw new Error(`TestBackend: ${healthUrl} returned ${response.status} ${response.statusText}`);\n }\n}\n\nfunction toHttpUrl(input: string): string {\n if (input.startsWith(\"ws://\")) return `http://${input.slice(\"ws://\".length)}`;\n if (input.startsWith(\"wss://\")) return `https://${input.slice(\"wss://\".length)}`;\n return input;\n}\n","// R2Bucket — eager, streaming mount over a Cloudflare R2 binding.\n//\n// Indexing pages through R2 `list()` honouring `prefix`, then issues\n// one `get()` per key and pipes the returned ReadableStream directly\n// into `MountWriteAPI.writeFile`. No object body is ever buffered.\n// Bounded concurrency keeps the fan-out predictable.\n//\n// Read-only in this milestone. put / delete proxies join later when\n// the write-back path lands.\n//\n// Indexed exactly once per workspace store. After the first\n// successful materialize() — even on an empty bucket — the mount is\n// marked indexed=1 in _vfs_mounts and subsequent workspace boots\n// over the same store skip it. New R2 objects landing after the\n// first index are not picked up automatically; the workspace must\n// be torn down and rebuilt over a fresh store.\n\nimport type { EagerMount, MountWriteAPI } from \"../types.js\";\n\n// Duck-typed slice of the workers `R2Bucket` interface so callers\n// can hand us either the real binding or a test fake without\n// pulling `@cloudflare/workers-types` into this package's dep\n// list.\nexport interface R2BucketBinding {\n list(options?: { prefix?: string; cursor?: string; limit?: number }): Promise<{\n objects: Array<{ key: string; size: number }>;\n truncated: boolean;\n cursor?: string;\n }>;\n get(key: string): Promise<{ body: ReadableStream<Uint8Array>; size: number } | null>;\n}\n\nexport interface R2BucketOptions {\n // Stripped from R2 keys when computing relative paths inside the\n // mount root. Trailing slash optional; both \"skills\" and\n // \"skills/\" behave identically. Objects outside the prefix are\n // not surfaced.\n prefix?: string;\n // \"read-only\" (default) rejects writes through Workspace.fs with\n // EROFS. \"read-write\" is wired in a later milestone.\n mode?: \"read-only\" | \"read-write\";\n // Page size used for R2 `list()` calls. Default 1000 (R2's\n // documented maximum). Exposed mainly for tests that want to\n // exercise pagination without huge fixtures.\n listLimit?: number;\n // Bounded concurrency for the per-object `get()` fan-out. Default\n // 8 so we don't hammer R2 with one in-flight request per file in\n // a bucket of thousands.\n concurrency?: number;\n // Forwarded to the EagerMount.maxBytes cap honoured by the\n // indexer. Provider-level convenience so callers can express the\n // cap on the same options bag as `prefix`.\n maxBytes?: number;\n // Same idea for entry count.\n maxEntries?: number;\n}\n\nfunction normalisePrefix(prefix: string | undefined): string {\n if (!prefix) return \"\";\n return prefix.endsWith(\"/\") ? prefix : `${prefix}/`;\n}\n\nasync function* paginate(\n bucket: R2BucketBinding,\n prefix: string,\n limit: number,\n): AsyncGenerator<{ key: string; size: number }> {\n let cursor: string | undefined;\n while (true) {\n const page = await bucket.list({\n prefix: prefix.length > 0 ? prefix : undefined,\n cursor,\n limit,\n });\n for (const obj of page.objects) yield obj;\n if (!page.truncated || !page.cursor) return;\n cursor = page.cursor;\n }\n}\n\n// Run `tasks` with at most `concurrency` in flight. Errors abort the\n// remaining tasks by rejecting the returned promise; in-flight calls\n// run to completion (no AbortController to thread through).\nasync function runBounded<T>(tasks: Array<() => Promise<T>>, concurrency: number): Promise<void> {\n if (concurrency < 1) concurrency = 1;\n let next = 0;\n const workers: Promise<void>[] = [];\n let firstError: unknown;\n for (let i = 0; i < Math.min(concurrency, tasks.length); i++) {\n workers.push(\n (async () => {\n while (true) {\n if (firstError !== undefined) return;\n const idx = next++;\n if (idx >= tasks.length) return;\n try {\n await tasks[idx]();\n } catch (error) {\n if (firstError === undefined) firstError = error;\n return;\n }\n }\n })(),\n );\n }\n await Promise.all(workers);\n if (firstError !== undefined) throw firstError;\n}\n\nexport function R2Bucket(bucket: R2BucketBinding, options: R2BucketOptions = {}): EagerMount {\n const prefix = normalisePrefix(options.prefix);\n const mode = options.mode ?? \"read-only\";\n const listLimit = options.listLimit ?? 1000;\n const concurrency = options.concurrency ?? 8;\n\n return {\n kind: \"r2\",\n mode,\n strategy: \"eager\",\n maxBytes: options.maxBytes,\n maxEntries: options.maxEntries,\n async materialize(api: MountWriteAPI): Promise<void> {\n // First, collect every key. We could interleave the get()\n // fan-out with list() pages, but materialize() runs once at\n // cold start and the list is cheap relative to the gets; a\n // simple two-phase pass keeps the code obvious.\n const keys: Array<{ key: string; size: number }> = [];\n for await (const obj of paginate(bucket, prefix, listLimit)) {\n keys.push(obj);\n }\n\n const tasks = keys.map((entry) => async () => {\n const got = await bucket.get(entry.key);\n if (got === null) {\n throw new Error(`R2Bucket: object disappeared mid-materialize: ${entry.key}`);\n }\n const relKey = prefix.length > 0 ? entry.key.slice(prefix.length) : entry.key;\n if (relKey.length === 0) return; // skip a key equal to the prefix itself\n // Pipe the R2 body straight into the streaming writeFile.\n await api.writeFile(`${api.root}/${relKey}`, got.body);\n });\n await runBounded(tasks, concurrency);\n },\n };\n}\n","// WorkspaceProxy — the WorkerEntrypoint a container DO hands to\n// ctx.container.interceptOutboundHttp(...) so wsd can dial back\n// into the DO.\n//\n// Why this exists as a separate class rather than passing the DO\n// itself: `interceptOutboundHttp` requires a runtime-wrapped\n// binding Fetcher (has `getSubrequestChannel` plumbing the\n// platform needs). Plain DO stubs fail that check; only loopback\n// bindings produced by `ctx.exports.<ClassName>(...)` for a\n// top-level-exported WorkerEntrypoint pass.\n//\n// Usage shape in user code:\n//\n// // Worker entry point — re-export the class so the runtime can\n// // wrap it into a loopback binding.\n// export { WorkspaceProxy } from \"@cloudflare/workspace\";\n//\n// class WsdContainer extends DurableObject<Env> {\n// constructor(ctx: DurableObjectState, env: Env) {\n// super(ctx, env);\n// this.#backend = new CloudflareContainerBackend({\n// container: () => ctx.container!,\n// egress: ctx.exports.WorkspaceProxy({\n// props: {\n// // Binding name in env that points back at this DO.\n// binding: \"WSD\",\n// // The DO instance the upgrade should route to.\n// id: ctx.id.toString(),\n// },\n// }),\n// });\n// }\n//\n// override async fetch(req: Request): Promise<Response> {\n// // The DO answers /health (port-readiness poll from the\n// // backend) and /ws (capnweb upgrade) on its own fetch().\n// const url = new URL(req.url);\n// if (url.pathname === \"/health\") return new Response(\"ok\\n\");\n// if (url.pathname === \"/ws\") return this.#backend.handleFetch(req);\n// return new Response(\"not found\", { status: 404 });\n// }\n// }\n//\n// `binding` is a string because props travel through structured\n// clone and DurableObjectNamespace references aren't clonable. The\n// proxy looks up `env[binding]` at fetch time and falls back to a\n// clear error if the name doesn't resolve. The DO class doesn't\n// need to live in @cloudflare/workspace — the proxy works for any\n// DO that implements a fetch() handler answering /health and /ws.\n\nimport { WorkerEntrypoint } from \"cloudflare:workers\";\n\nexport interface WorkspaceProxyProps {\n // Name of a DurableObjectNamespace binding in env. The proxy\n // resolves `env[binding]` at request time.\n binding: string;\n // Stringified DurableObjectId — typically `ctx.id.toString()`\n // from inside the owning DO's constructor.\n id: string;\n}\n\nexport class WorkspaceProxy extends WorkerEntrypoint<unknown, WorkspaceProxyProps> {\n override async fetch(request: Request): Promise<Response> {\n const url = new URL(request.url);\n\n if (url.pathname === \"/health\") {\n return new Response(\"ok\\n\", {\n headers: { \"content-type\": \"text/plain; charset=utf-8\" },\n });\n }\n\n if (url.pathname === \"/ws\") {\n const { binding, id } = this.ctx.props;\n const ns = (this.env as Record<string, unknown>)[binding] as\n | DurableObjectNamespace\n | undefined;\n if (!ns) {\n return new Response(`WorkspaceProxy: env.${binding} is not a DurableObjectNamespace`, {\n status: 500,\n });\n }\n const stub = ns.get(ns.idFromString(id));\n return stub.fetch(request);\n }\n\n return new Response(\"not found\", { status: 404 });\n }\n}\n","// Host-side WorkspaceShell facade.\n//\n// Wraps the ShellRPC half of a WorkspaceRPC stub. The docs/05\n// contract: one entry point — exec() — that returns a detached\n// handle. Callers either await `result()` (run-and-wait) or\n// consume the ReadableStream directly (run-and-stream), or drop\n// the handle entirely (fire-and-forget).\n//\n// Every exec() call brackets the spawn with the docs/05 sync\n// frames:\n// - pushOnce(db, rpc.sync) runs *before* the spawn so any\n// host-side writes since the last push are visible to the\n// command.\n// - pullOnce(db, rpc.sync) runs *after* the stream drains (i.e.\n// after the exit event), so anything the command produced is\n// visible to subsequent Workspace.fs reads.\n// The pushed / pulled counts land in ExecResult.\n//\n// Pull only fires when the caller awaits handle.result(). A\n// caller that consumes the stream directly gets the push but not\n// the pull — docs/05 puts the pull after the exit event, which\n// only result() observes. If you need the pull in that flow,\n// drive the stream yourself then call Workspace.pull() explicitly.\n//\n// get() (reattach) is intentionally not bracketed. Reattaching\n// to an already-running exec doesn't represent a new push frame.\n// The result() of a reattached handle reports pushed = 0 and the\n// pulled count from a pull that runs after its own drain — best-\n// effort, can be 0 if nothing landed in wsd between reattach and\n// drain.\n\nimport type { ApplyResult, SkippedEntry } from \"@cloudflare/dofs\";\nimport type { ExecEvent, ShellRPC } from \"@cloudflare/workspace-rpc\";\n\nexport type ExecEncoding = \"utf8\" | undefined;\n\n// The payload type for stdout/stderr chunks: Uint8Array by\n// default, string when the caller passes encoding: \"utf8\".\ntype Chunk<E extends ExecEncoding> = E extends \"utf8\" ? string : Uint8Array;\n\nexport type WorkspaceExecEvent<E extends ExecEncoding = undefined> =\n | { id: string; seq: number; name: \"stdout\"; value: Chunk<E> }\n | { id: string; seq: number; name: \"stderr\"; value: Chunk<E> }\n | { id: string; seq: number; name: \"exit\"; value: number };\n\nexport interface ExecResult<E extends ExecEncoding = undefined> {\n exitCode: number;\n stdout: Chunk<E>;\n stderr: Chunk<E>;\n // VFS sync stats from the docs/05 bracket.\n // pushed — entries shipped by the pre-exec pushOnce.\n // pulled — entries the post-drain pullOnce applied locally.\n // skipped — entries the post-drain pullOnce did NOT apply\n // because they targeted a read-only mount root.\n // Empty when no read-only mounts are registered or\n // the container stayed clear of them.\n // pulled / skipped are populated only when handle.result() is\n // awaited. Consuming the stream directly leaves both at their\n // empty values; pushed is observed before the stream is returned\n // so it reflects the real push count either way.\n pushed: number;\n pulled: number;\n skipped: SkippedEntry[];\n}\n\nexport type KillSignal = \"SIGTERM\" | \"SIGKILL\" | \"SIGINT\" | \"SIGHUP\";\n\n// ExecHandle is a ReadableStream<WorkspaceExecEvent> with three\n// extras tacked on. Implemented as the wire stream + extra own\n// properties (id / result / kill) rather than a subclass for two\n// reasons:\n//\n// 1. The wire stream comes back from capnweb already built;\n// subclassing means a pump-through layer that copies every\n// chunk for no behavioural gain.\n// 2. pipeThrough (used for the utf8 transform) returns a plain\n// ReadableStream, so the subclass identity gets lost on the\n// first transform anyway.\nexport interface ExecHandle<E extends ExecEncoding = undefined>\n extends ReadableStream<WorkspaceExecEvent<E>> {\n readonly id: string;\n result(): Promise<ExecResult<E>>;\n kill(signal?: KillSignal): Promise<void>;\n}\n\nexport interface ExecOptions<E extends ExecEncoding = undefined> {\n // Stable id. If omitted the runner mints a UUID. Reusing an id\n // while a previous run is still active throws EEXEC_BUSY.\n id?: string;\n // Absolute path inside the container. Defaults to the\n // workspace root.\n cwd?: string;\n // Encoding for stdout/stderr value payloads. Default is\n // Uint8Array; \"utf8\" decodes per-chunk through a stream-mode\n // TextDecoder so multi-byte boundaries survive.\n encoding?: E;\n // Per-call timeout in milliseconds. Past this duration the\n // container sends SIGTERM (then SIGKILL after a short grace).\n // Omit to use the runner's default (typically 320_000). Pass 0\n // to disable the timeout for this call.\n timeoutMs?: number;\n}\n\nexport interface GetExecOptions<E extends ExecEncoding = undefined> {\n encoding?: E;\n // \"tail\" yields only events produced after this call. A\n // number resumes from that seq+1. Omit to receive every\n // event from the start of the run (replays the whole log).\n resume?: \"tail\" | \"full\" | number;\n}\n\n// Push/pull bracket plumbing. WorkspaceShell doesn't know about\n// the local Database or the SyncRPC wire — the host wires both\n// behind a Sync object that exposes the entry counts.\n// Workspace itself satisfies this interface (push() / pull() are\n// public methods); tests pass a plain { push, pull } object.\n// pull() returns the dofs ApplyResult so the shell can surface\n// skipped read-only entries on ExecResult.\nexport interface Sync {\n push(): Promise<number>;\n pull(): Promise<ApplyResult>;\n}\n\nexport class WorkspaceShell {\n readonly #shell: ShellRPC;\n readonly #sync: Sync;\n\n constructor(shell: ShellRPC, sync: Sync) {\n this.#shell = shell;\n this.#sync = sync;\n }\n\n exec(command: string): Promise<ExecHandle<undefined>>;\n exec(command: string, options: ExecOptions<undefined>): Promise<ExecHandle<undefined>>;\n exec(command: string, options: ExecOptions<\"utf8\">): Promise<ExecHandle<\"utf8\">>;\n async exec<E extends ExecEncoding>(\n command: string,\n options: ExecOptions<E> = {},\n ): Promise<ExecHandle<E>> {\n // Pre-exec push: ship anything the host wrote since the last\n // push so the spawned command sees it. Failures non-fatal per\n // docs/05 — the command still runs; pushed reports 0.\n let pushed = 0;\n try {\n pushed = await this.#sync.push();\n } catch {\n // pushed stays 0\n }\n const envelope = await this.#shell.exec({\n command,\n id: options.id,\n cwd: options.cwd,\n timeoutMs: options.timeoutMs,\n });\n // Dispose the result envelope when the event stream finishes\n // draining. Without this, capnweb's exports table holds onto\n // the envelope for the life of the session — one entry per\n // exec call — because we hand the inner stream off to the\n // caller and can't `using` the envelope ourselves.\n const events = disposeOnDone(envelope.events, () => maybeDispose(envelope));\n return wrapHandle<E>(this.#shell, this.#sync, envelope.id, events, options.encoding, pushed);\n }\n\n get(id: string): Promise<ExecHandle<undefined>>;\n get(id: string, options: GetExecOptions<undefined>): Promise<ExecHandle<undefined>>;\n get(id: string, options: GetExecOptions<\"utf8\">): Promise<ExecHandle<\"utf8\">>;\n async get<E extends ExecEncoding>(\n id: string,\n options: GetExecOptions<E> = {},\n ): Promise<ExecHandle<E>> {\n const after = resumeToAfter(options.resume);\n const envelope = await this.#shell.getExec({ id, after });\n const events = disposeOnDone(envelope.events, () => maybeDispose(envelope));\n // Reattach doesn't own the original push frame: pushed = 0.\n // The post-drain pull still fires, scoped to whatever lands\n // between reattach and the next drain.\n return wrapHandle<E>(this.#shell, this.#sync, id, events, options.encoding, 0);\n }\n}\n\nfunction resumeToAfter(resume: \"tail\" | \"full\" | number | undefined): number | \"tail\" | undefined {\n if (resume === undefined || resume === \"full\") return undefined;\n if (resume === \"tail\") return \"tail\";\n return resume;\n}\n\n// Stitch the runtime extras (id, result, kill) onto a fresh\n// ReadableStream that pipes from the wire stream and applies any\n// encoding conversion in flight.\n//\n// The wire stream is tee'd so kill() can observe the exit event\n// independently of whatever the caller does with the handle. kill()\n// sends the signal then awaits the exit event so callers can rely on\n// \"resolved ⇒ child has exited\" without having to drain the stream\n// or await result() themselves.\nfunction wrapHandle<E extends ExecEncoding>(\n shell: ShellRPC,\n sync: Sync,\n id: string,\n wireEvents: ReadableStream<ExecEvent>,\n encoding: E | undefined,\n pushed: number,\n): ExecHandle<E> {\n const [forUser, forWatcher] = wireEvents.tee();\n const exited = watchForExit(forWatcher);\n const stream = pipeEvents<E>(forUser, encoding);\n const handle = stream as ExecHandle<E>;\n Object.defineProperties(handle, {\n id: { value: id, enumerable: false, writable: false },\n result: {\n value: () => drainToResult<E>(stream, encoding, sync, pushed),\n enumerable: false,\n writable: false,\n },\n kill: {\n value: async (signal?: KillSignal) => {\n await shell.killExec({ id, signal });\n await exited;\n },\n enumerable: false,\n writable: false,\n },\n });\n return handle;\n}\n\n// Drain the watcher branch in the background, resolving once the\n// first exit event is observed (or the stream closes / errors\n// without one). Errors are swallowed so kill() doesn't reject on a\n// torn-down wire — the caller's own branch will surface any real\n// stream error.\nfunction watchForExit(events: ReadableStream<ExecEvent>): Promise<void> {\n return (async () => {\n const reader = events.getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) return;\n if (value.name === \"exit\") return;\n }\n } catch {\n // Swallow — surfaced via the user-facing branch instead.\n } finally {\n reader.releaseLock();\n }\n })();\n}\n\nfunction pipeEvents<E extends ExecEncoding>(\n source: ReadableStream<ExecEvent>,\n encoding: E | undefined,\n): ReadableStream<WorkspaceExecEvent<E>> {\n if (encoding !== \"utf8\") {\n // Identity pipe — the wire shape already matches.\n return source as unknown as ReadableStream<WorkspaceExecEvent<E>>;\n }\n // Per-stream TextDecoders preserve multi-byte boundaries\n // across chunk splits.\n const stdoutDec = new TextDecoder(\"utf-8\", { fatal: false });\n const stderrDec = new TextDecoder(\"utf-8\", { fatal: false });\n return source.pipeThrough(\n new TransformStream<ExecEvent, WorkspaceExecEvent<E>>({\n transform(event, controller) {\n if (event.name === \"stdout\") {\n controller.enqueue({\n id: event.id,\n seq: event.seq,\n name: \"stdout\",\n value: stdoutDec.decode(event.value, { stream: true }) as Chunk<E>,\n });\n } else if (event.name === \"stderr\") {\n controller.enqueue({\n id: event.id,\n seq: event.seq,\n name: \"stderr\",\n value: stderrDec.decode(event.value, { stream: true }) as Chunk<E>,\n });\n } else {\n controller.enqueue(event as WorkspaceExecEvent<E>);\n }\n },\n flush(_controller) {\n // Flush any trailing bytes the streaming decoder\n // held back. These are dropped on the floor today\n // — they'd land in an event with no seq attached.\n // In practice the child terminates its output with\n // a newline; partial multi-byte sequences at EOF\n // are rare. Note for follow-up if real callers see\n // truncation.\n stdoutDec.decode();\n stderrDec.decode();\n },\n }),\n );\n}\n\nasync function drainToResult<E extends ExecEncoding>(\n stream: ReadableStream<WorkspaceExecEvent<E>>,\n encoding: E | undefined,\n sync: Sync,\n pushed: number,\n): Promise<ExecResult<E>> {\n const reader = stream.getReader();\n const stdoutParts: Array<Chunk<E>> = [];\n const stderrParts: Array<Chunk<E>> = [];\n let exitCode = -1;\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n if (value.name === \"stdout\") stdoutParts.push(value.value);\n else if (value.name === \"stderr\") stderrParts.push(value.value);\n else exitCode = value.value;\n }\n } finally {\n reader.releaseLock();\n }\n // Post-drain pull: apply anything wsd produced during the exec.\n // Failures non-fatal per docs/05 (\"failed pushes/pulls do not\n // abort the command\"); pulled / skipped stay at their empty\n // values in that case.\n let pulled = 0;\n let skipped: SkippedEntry[] = [];\n try {\n const result = await sync.pull();\n pulled = result.applied;\n skipped = result.skipped;\n } catch {\n // pulled / skipped stay empty\n }\n return {\n exitCode,\n stdout: joinParts<E>(stdoutParts, encoding),\n stderr: joinParts<E>(stderrParts, encoding),\n pushed,\n pulled,\n skipped,\n };\n}\n\nfunction joinParts<E extends ExecEncoding>(\n parts: Array<Chunk<E>>,\n encoding: E | undefined,\n): Chunk<E> {\n if (parts.length === 0) {\n return (encoding === \"utf8\" ? \"\" : new Uint8Array(0)) as Chunk<E>;\n }\n if (typeof parts[0] === \"string\") {\n return (parts as string[]).join(\"\") as Chunk<E>;\n }\n const arrays = parts as Uint8Array[];\n const total = arrays.reduce((acc, a) => acc + a.byteLength, 0);\n const out = new Uint8Array(total);\n let offset = 0;\n for (const a of arrays) {\n out.set(a, offset);\n offset += a.byteLength;\n }\n return out as Chunk<E>;\n}\n\n// Pipe `stream` through an identity TransformStream that fires `onDone`\n// exactly once when the stream finishes — clean end, cancel, or\n// error. Used to release a capnweb result envelope as soon as the\n// event stream it carried is drained, without having to keep the\n// envelope reference alive across wrapHandle().\nfunction disposeOnDone<T>(stream: ReadableStream<T>, onDone: () => void): ReadableStream<T> {\n let fired = false;\n const fire = () => {\n if (fired) return;\n fired = true;\n try {\n onDone();\n } catch {\n // ignore — disposer errors are not actionable here\n }\n };\n return stream.pipeThrough(\n new TransformStream<T, T>({\n transform(chunk, controller) {\n controller.enqueue(chunk);\n },\n flush() {\n fire();\n },\n cancel() {\n fire();\n },\n }),\n );\n}\n\n// Best-effort dispose of a capnweb result envelope. Real envelopes\n// expose [Symbol.dispose]; test fakes return plain objects, so the\n// symbol may be absent.\nfunction maybeDispose(value: unknown): void {\n const d = (value as { [Symbol.dispose]?: () => void } | null | undefined)?.[Symbol.dispose];\n if (typeof d === \"function\") d.call(value);\n}\n","// Stub leak tracking. Gated on the CAPNWEB_TRACK_STUBS env flag so\n// production paths pay nothing. Every RpcTarget we own opts in by\n// calling `trackStub(this)` in its constructor; capnweb invokes\n// `[Symbol.dispose]` when the last remote stub for that target is\n// disposed, which is where we decrement.\n//\n// The point is *measurement*, not enforcement: snapshot() returns the\n// per-class live count so a soak script can assert \"after a quiet\n// point, every counter is zero.\" Anything non-zero is a leak —\n// either we forgot to dispose a result envelope on the caller side,\n// or the disposal contract broke.\n// Read the flag at module init. Both Node (process.env) and\n// workerd (globalThis override) are tolerated; runtimes without\n// either surface (notably workerd, where env vars only land on\n// the binding object) can call enableStubTracking() programmatically.\nlet trackingEnabled = readFlag();\nfunction readFlag() {\n try {\n const env = globalThis.process\n ?.env;\n if (env?.CAPNWEB_TRACK_STUBS === \"1\")\n return true;\n }\n catch {\n // ignore — process isn't available in this runtime\n }\n const override = globalThis.CAPNWEB_TRACK_STUBS;\n return override === \"1\" || override === true;\n}\n// Force tracking on. Useful for workerd-based tests where the env\n// var doesn't reach process.env or globalThis. Idempotent.\nexport function enableStubTracking() {\n trackingEnabled = true;\n}\nconst counters = new Map();\nexport function trackStub(target) {\n if (!trackingEnabled)\n return;\n const name = target.constructor?.name ?? \"anonymous\";\n counters.set(name, (counters.get(name) ?? 0) + 1);\n}\nexport function untrackStub(target) {\n if (!trackingEnabled)\n return;\n const name = target.constructor?.name ?? \"anonymous\";\n const current = counters.get(name) ?? 0;\n if (current <= 1)\n counters.delete(name);\n else\n counters.set(name, current - 1);\n}\n// Snapshot the current live counts. Empty object when tracking is\n// disabled or when nothing is live. Callers should not mutate the\n// returned object — it's a fresh copy on every call.\nexport function stubSnapshot() {\n const out = {};\n for (const [name, count] of counters)\n out[name] = count;\n return out;\n}\n// True iff tracking is on (env flag, globalThis override, or\n// enableStubTracking() was called). Useful for skipping diagnostic\n// surfaces when tracking is off.\nexport function isStubTrackingEnabled() {\n return trackingEnabled;\n}\n","// WorkspaceStub — wraps a host-side Workspace as an RpcTarget so it\n// can be handed across the Workers RPC boundary.\n//\n// Construct it via `workspace.stub()` — the Workspace class owns\n// the lifecycle and the WorkspaceStub just delegates.\n//\n// Usage shape:\n//\n// // Inside a DO that owns the live wsd connection:\n// class WsdContainer extends DurableObject {\n// #workspace = new Workspace({ backends: [...] });\n// async getWorkspace(): Promise<WorkspaceStub> {\n// await this.#workspace.ready();\n// return this.#workspace.stub();\n// }\n// }\n//\n// // From a Worker (or another DO):\n// const ws = await env.WSD.get(id).getWorkspace();\n// await ws.fs.writeFile(\"/foo\", bytes);\n// const handle = await ws.shell.exec(\"ls /workspace\");\n// const { exitCode, stdout, stderr } = await handle.result();\n//\n// All the SyncRPC streaming (push / pushObjects / fetchObjects /\n// fetchChanges) happens on the capnweb wire inside the DO. What\n// crosses the Workers-RPC boundary here is only the high-level\n// value-shaped facade — writeFile / readFile / stat / exec —\n// because Workers RPC doesn't carry non-byte ReadableStreams or\n// capnweb stubs.\n//\n// Streaming exec is intentionally absent from this surface for\n// now. Workers RPC only carries ReadableStream<Uint8Array>, so a\n// streamed exec would have to frame events as bytes (SSE, length-\n// prefixed JSON, etc.) — punted until we have a concrete caller\n// that needs it. Today exec() returns a handle whose only method\n// is result(), matching the run-and-wait half of WorkspaceShell.\n//\n// RpcTarget comes from capnweb rather than `cloudflare:workers`.\n// Per capnweb's docs, that import is an alias for the workerd\n// builtin when running under workerd, so the runtime behaviour is\n// identical; the difference is that capnweb's export resolves\n// under both workerd and node (tests, type-only consumers), while\n// `cloudflare:workers` only resolves under workerd.\n\nimport type {\n GrepOptions,\n MkdirOptions,\n ReadFileOptions,\n RmOptions,\n WorkspaceDirentResult,\n WorkspaceFoundEntry,\n WorkspaceGrepMatch,\n WorkspaceStatResult,\n WriteFileContent,\n WriteFileOptions,\n} from \"@cloudflare/dofs\";\nimport { trackStub, untrackStub } from \"@cloudflare/workspace-rpc/debug\";\nimport { RpcTarget } from \"capnweb\";\n\nimport type { ExecResult } from \"./shell.js\";\nimport type { Workspace } from \"./workspace.js\";\n\nexport interface WorkspaceExecOptions {\n cwd?: string;\n // \"utf8\" decodes stdout/stderr chunks through a streaming\n // TextDecoder so multi-byte boundaries survive. Default leaves\n // bytes as Uint8Array.\n encoding?: \"utf8\";\n}\n\nexport interface WorkspaceExecResult<E extends \"utf8\" | undefined = undefined> {\n exitCode: number;\n stdout: E extends \"utf8\" ? string : Uint8Array;\n stderr: E extends \"utf8\" ? string : Uint8Array;\n}\n\n// Filesystem half. A direct proxy onto Workspace.fs — every\n// public WorkspaceFilesystem method is mirrored verbatim so the\n// remote surface matches the in-process surface one-for-one.\n//\n// All argument and return types are already JSRPC-compatible:\n// strings, plain objects, Uint8Array, and a single byte-shaped\n// ReadableStream<Uint8Array> on readFile. writeFile's\n// WriteFileContent union includes ReadableStream<Uint8Array> for\n// the same reason.\nexport class WorkspaceFilesystemStub extends RpcTarget {\n readonly #ws: Workspace;\n\n constructor(ws: Workspace) {\n super();\n this.#ws = ws;\n trackStub(this);\n }\n\n [Symbol.dispose](): void {\n untrackStub(this);\n }\n\n // --- Reads -------------------------------------------------------\n\n readFile(path: string): Promise<ReadableStream<Uint8Array>>;\n readFile(path: string, encoding: \"utf8\"): Promise<string>;\n readFile(path: string, options: ReadFileOptions): Promise<string | ReadableStream<Uint8Array>>;\n readFile(\n path: string,\n optionsOrEncoding?: \"utf8\" | ReadFileOptions,\n ): Promise<string | ReadableStream<Uint8Array>> {\n return this.#ws.fs.readFile(path, optionsOrEncoding as ReadFileOptions);\n }\n\n stat(path: string): Promise<WorkspaceStatResult> {\n return this.#ws.fs.stat(path);\n }\n\n readdir(path: string): Promise<WorkspaceDirentResult[]> {\n return this.#ws.fs.readdir(path);\n }\n\n find(directory: string, pattern?: string): Promise<WorkspaceFoundEntry[]> {\n return this.#ws.fs.find(directory, pattern);\n }\n\n ls(prefix: string): Promise<string[]> {\n return this.#ws.fs.ls(prefix);\n }\n\n grep(pattern: string, path: string, options: GrepOptions = {}): Promise<WorkspaceGrepMatch[]> {\n return this.#ws.fs.grep(pattern, path, options);\n }\n\n // --- Mutations ---------------------------------------------------\n\n writeFile(\n path: string,\n content: WriteFileContent,\n options: WriteFileOptions = {},\n ): Promise<void> {\n return this.#ws.fs.writeFile(path, content, options);\n }\n\n mkdir(path: string, options: MkdirOptions = {}): Promise<void> {\n return this.#ws.fs.mkdir(path, options);\n }\n\n rm(path: string, options: RmOptions = {}): Promise<void> {\n return this.#ws.fs.rm(path, options);\n }\n}\n\n// Exec handle returned from WorkspaceShellStub.exec. Holds the\n// underlying ExecHandle on the DO side and exposes only the\n// run-and-wait half of its API — result() — because Workers RPC\n// can't carry the non-byte event stream that ExecHandle is.\n//\n// kill() and event streaming are deliberately omitted for now;\n// they'd need a byte-framed transport (SSE, length-prefixed\n// JSON) and we don't have a caller for that yet. When that lands\n// it goes here as a new method, not as a replacement for this\n// one.\nexport class WorkspaceExecHandleStub<E extends \"utf8\" | undefined = undefined> extends RpcTarget {\n readonly #pending: Promise<ExecResult<E>>;\n\n constructor(pending: Promise<ExecResult<E>>) {\n super();\n this.#pending = pending;\n trackStub(this);\n }\n\n [Symbol.dispose](): void {\n untrackStub(this);\n }\n\n async result(): Promise<WorkspaceExecResult<E>> {\n const result = await this.#pending;\n return {\n exitCode: result.exitCode,\n // joinParts in shell.ts returns string for \"utf8\",\n // Uint8Array otherwise — exactly the\n // WorkspaceExecResult shape.\n stdout: result.stdout as WorkspaceExecResult<E>[\"stdout\"],\n stderr: result.stderr as WorkspaceExecResult<E>[\"stderr\"],\n };\n }\n}\n\n// Shell half. exec() returns an RpcTarget handle whose only\n// method today is result(). Streaming exec lands as a separate\n// method when a concrete caller needs it; see the note at the\n// top of this file.\nexport class WorkspaceShellStub extends RpcTarget {\n readonly #ws: Workspace;\n\n constructor(ws: Workspace) {\n super();\n this.#ws = ws;\n trackStub(this);\n }\n\n [Symbol.dispose](): void {\n untrackStub(this);\n }\n\n exec(command: string): Promise<WorkspaceExecHandleStub<undefined>>;\n exec(\n command: string,\n options: WorkspaceExecOptions & { encoding: \"utf8\" },\n ): Promise<WorkspaceExecHandleStub<\"utf8\">>;\n exec(command: string, options: WorkspaceExecOptions): Promise<WorkspaceExecHandleStub<undefined>>;\n async exec(\n command: string,\n options: WorkspaceExecOptions = {},\n ): Promise<WorkspaceExecHandleStub<\"utf8\" | undefined>> {\n // Kick off the exec eagerly so the caller's first round trip\n // (the one that built this stub) already has the spawn in\n // flight. result() awaits the handle's own result() when the\n // caller asks.\n const pending: Promise<ExecResult<\"utf8\" | undefined>> =\n options.encoding === \"utf8\"\n ? this.#ws.shell\n .exec(command, { cwd: options.cwd, encoding: \"utf8\" })\n .then((handle) => handle.result())\n : this.#ws.shell.exec(command, { cwd: options.cwd }).then((handle) => handle.result());\n return new WorkspaceExecHandleStub<\"utf8\" | undefined>(pending);\n }\n}\n\n// Top-level wrapper. Two sub-RpcTargets let callers use promise\n// pipelining: `stub.fs.writeFile(...)` is one round trip, not two.\n//\n// Construct via `workspace.stub()` rather than directly — the\n// Workspace owns the lifecycle and the stub just delegates.\n//\n// Note the name collision: the *type* `WorkspaceRPC` is also\n// exported by @cloudflare/workspace-rpc as the wire contract\n// between wsd and the DO. WorkspaceStub here is a different thing\n// (the Workers-RPC value carried between the DO and a Worker), so\n// the name doesn't clash.\nexport class WorkspaceStub extends RpcTarget {\n // Getters rather than instance properties so Workers RPC\n // exposes them through the stub proxy. Plain readonly fields\n // set in the constructor land as private isolate state and the\n // proxy reports \"method not implemented\".\n readonly #fs: WorkspaceFilesystemStub;\n readonly #shell: WorkspaceShellStub;\n\n constructor(ws: Workspace) {\n super();\n this.#fs = new WorkspaceFilesystemStub(ws);\n this.#shell = new WorkspaceShellStub(ws);\n trackStub(this);\n }\n\n // Cascade disposal to the sub-stubs. Workers-RPC exposes them as\n // getters off this object, so the caller can't reach them as\n // independent stubs; their lifetime is bounded by ours. Without\n // this, the per-iteration leak observed in the DO↔Worker stub\n // soak (WorkspaceFilesystemStub +1, WorkspaceShellStub +1 every\n // getWorkspace() call) never collapses.\n [Symbol.dispose](): void {\n this.#fs[Symbol.dispose]();\n this.#shell[Symbol.dispose]();\n untrackStub(this);\n }\n\n get fs(): WorkspaceFilesystemStub {\n return this.#fs;\n }\n\n get shell(): WorkspaceShellStub {\n return this.#shell;\n }\n}\n","// Bidirectional sync driver. Pairs a local Database with a remote\n// SyncRPC stub and runs pull + push ticks against the wire.\n//\n// Both sides of the prototype use the same driver: the container\n// (wsd) drives an upstream DO stub, and once the DO has a real\n// runtime it'll drive a container stub the same way.\n//\n// The driver doesn't own a timer. The caller decides when to call\n// `pullOnce()` and `pushOnce()` \\u2014 a polling loop in production,\n// a manual `tick()` in tests so convergence is deterministic.\n\nimport {\n type ApplyResult,\n applyChanges,\n assertAppliedPushRev,\n type ChangeEntry,\n coalesceChanges,\n currentRev,\n type Database,\n readWatermark,\n type SkippedEntry,\n stageBlob,\n writeWatermark,\n} from \"@cloudflare/dofs\";\n\nimport type { SyncRPC } from \"./interface.js\";\n\nfunction hex(bytes: Uint8Array): string {\n let s = \"\";\n for (let i = 0; i < bytes.byteLength; i++) s += bytes[i].toString(16).padStart(2, \"0\");\n return s;\n}\n\n// Pipe `stream` through an identity TransformStream that fires `onDone`\n// exactly once when the stream finishes — clean end, cancel, or error.\n// Used to release a capnweb result envelope as soon as the stream it\n// carried is drained, without having to keep the envelope reference\n// alive across the consume site. `onDone` errors are swallowed: the\n// stream's content is the load-bearing thing, not the cleanup.\nfunction disposeOnDone<T>(stream: ReadableStream<T>, onDone: () => void): ReadableStream<T> {\n let fired = false;\n const fire = () => {\n if (fired) return;\n fired = true;\n try {\n onDone();\n } catch {\n // ignore — disposer errors are not actionable here\n }\n };\n return stream.pipeThrough(\n new TransformStream<T, T>({\n transform(chunk, controller) {\n controller.enqueue(chunk);\n },\n flush() {\n fire();\n },\n cancel() {\n fire();\n },\n }),\n );\n}\n\n// Best-effort dispose of a capnweb result envelope. Real envelopes\n// expose [Symbol.dispose]; the test fakes return plain objects, so\n// the symbol may be absent.\nfunction maybeDispose(value: unknown): void {\n const d = (value as { [Symbol.dispose]?: () => void } | null | undefined)?.[Symbol.dispose];\n if (typeof d === \"function\") d.call(value);\n}\n\n// Soft cap on entries processed per batch in pullOnce. Each batch\n// runs hasObjects + fetchObjects + applyChanges against the entries\n// it just buffered, then releases them before reading the next.\n// Peak memory in pullOnce is O(PULL_BATCH_SIZE), not O(stream).\nconst PULL_BATCH_SIZE = 256;\n\n// Pull every entry the remote has produced since the last successful\n// pull, apply locally, advance fetchRev. Returns an `ApplyResult`\n// folded across every batch so callers see both the applied count\n// (decide whether to tick again) and any entries skipped because\n// they targeted a read-only mount root (surface to the user).\n//\n// Bytes the receiver already holds (vfs_blobs.hash present) are\n// skipped on the wire; the hasObjects probe is what makes that\n// dedup work without per-chunk round-trips.\n//\n// The entry stream is drained in batches of PULL_BATCH_SIZE so peak\n// memory stays bounded on a large tree. fetchRev still advances\n// once at the end of the whole stream — per-batch advance would\n// require the wire to carry a rev cursor per entry. Crash safety is\n// idempotent re-apply: the receiver's alreadyApplied() check inside\n// applyChanges drops a re-fetched batch on the floor.\nexport async function pullOnce(db: Database, remote: SyncRPC): Promise<ApplyResult> {\n const sinceRev = readWatermark(db, \"fetchRev\");\n const localPushRev = readWatermark(db, \"pushRev\");\n // fetchChanges hands back the remote's currentRev (cursor we\n // advance fetchRev to), its appliedPushRev (cross-side invariant\n // check on the pull path), and the entry stream itself. One\n // round-trip instead of the previous currentRev() + fetchChanges()\n // pair.\n // The fetchChanges return is a capnweb result envelope wrapping a\n // stream stub; without explicit disposal it sits in the exports\n // table until the session ends. We can't bind it to `using`\n // because the stream inside outlives this scope (we hand it off\n // to the reader loop below), so wrap the stream in a transform\n // that disposes the envelope when the stream finishes draining.\n const fetchResult = await remote.fetchChanges({ sinceRev });\n const { currentRev: remoteRev, appliedPushRev } = fetchResult;\n // Run the cross-side invariant check before touching the stream.\n // Symmetric to the push response check: the remote must have\n // applied at least everything we claimed to push. A drop here\n // means apply lost state on the receiver; tear down and rebuild\n // rather than corrupt watermarks.\n assertAppliedPushRev(appliedPushRev, localPushRev);\n const stream = disposeOnDone(fetchResult.stream, () => maybeDispose(fetchResult));\n if (remoteRev <= sinceRev) {\n // Drain the (empty) stream so the remote's iterator is\n // released; cancel is the right surface for that.\n await stream.cancel().catch(() => {});\n return { applied: 0, skipped: [] };\n }\n\n const reader = stream.getReader();\n let totalApplied = 0;\n const totalSkipped: SkippedEntry[] = [];\n let streamDone = false;\n try {\n while (!streamDone) {\n // Read up to PULL_BATCH_SIZE entries before processing the batch.\n const batch: ChangeEntry[] = [];\n const wantedHashes: Uint8Array[] = [];\n const seenHash = new Set<string>();\n let batchMaxRev = 0;\n while (batch.length < PULL_BATCH_SIZE) {\n const { value, done } = await reader.read();\n if (done) {\n streamDone = true;\n break;\n }\n batch.push(value);\n if (value.rev > batchMaxRev) batchMaxRev = value.rev;\n if (value.kind === \"file\") {\n for (const c of value.chunks) {\n const k = hex(c.hash);\n if (!seenHash.has(k)) {\n seenHash.add(k);\n wantedHashes.push(c.hash);\n }\n }\n }\n }\n if (batch.length === 0) break;\n\n // Probe + fetch missing chunk bytes for just this batch. Bytes\n // the receiver already holds (or the remote doesn't have) are\n // skipped, so the per-batch network cost is bounded.\n if (wantedHashes.length > 0) {\n const haveSubset = await remote.hasObjects(wantedHashes);\n const remoteHasLocally = new Set<string>();\n for (const h of haveSubset) remoteHasLocally.add(hex(h));\n const missing = wantedHashes.filter((h) => {\n const k = hex(h);\n if (!remoteHasLocally.has(k)) return false;\n const row = db.one<{ hash: Uint8Array }>(\"SELECT hash FROM vfs_blobs WHERE hash = ?\", h);\n return row === undefined;\n });\n if (missing.length > 0) {\n // Bare ReadableStream return — no envelope to dispose,\n // capnweb releases the stream stub when the stream itself\n // closes. The reader-loop below drains to completion.\n const bytesStream = await remote.fetchObjects(missing);\n const bytesReader = bytesStream.getReader();\n try {\n while (true) {\n const { value, done } = await bytesReader.read();\n if (done) break;\n stageBlob(db, value.hash, value.bytes, Date.now());\n }\n } finally {\n bytesReader.releaseLock();\n }\n }\n }\n\n // Advance fetchRev per committed batch. Because coalesceChanges\n // emits in ascending rev order, every entry already applied\n // (this batch + all previous ones) has rev <= batchMaxRev, so\n // it's safe to checkpoint here. A crash after this commit\n // means the next pull resumes from batchMaxRev and re-fetches\n // only entries from later batches — bounded by PULL_BATCH_SIZE\n // instead of the whole stream.\n const batchResult = await applyChanges(db, batch, new Map(), {\n source: \"upstream\",\n advanceFetchRev: batchMaxRev,\n });\n totalApplied += batchResult.applied;\n if (batchResult.skipped.length > 0) {\n for (const s of batchResult.skipped) totalSkipped.push(s);\n }\n }\n } finally {\n reader.releaseLock();\n }\n\n // Nudge fetchRev to the remote's currentRev captured at the start.\n // The per-batch advances above bring fetchRev up to the max rev\n // any entry carried, but if the rev window contained entries that\n // were all filtered out (e.g. ignored paths), the stream is empty\n // and the per-batch path never fires. Advancing to remoteRev here\n // is still safe because we captured it before draining the stream\n // and never regress.\n const current = readWatermark(db, \"fetchRev\");\n if (remoteRev > current) {\n writeWatermark(db, \"fetchRev\", remoteRev);\n }\n return { applied: totalApplied, skipped: totalSkipped };\n}\n\n// Push every entry the local store has produced since the last\n// successful push. The wire shape mirrors pullOnce in reverse:\n// stage bytes the remote lacks, then push the entry stream.\nexport async function pushOnce(db: Database, remote: SyncRPC): Promise<number> {\n const sincePush = readWatermark(db, \"pushRev\");\n const localRev = currentRev(db);\n if (localRev <= sincePush) return 0;\n\n const entries: ChangeEntry[] = [];\n const wantedHashes: Uint8Array[] = [];\n const seenHash = new Set<string>();\n for await (const e of coalesceChanges(db, sincePush)) {\n entries.push(e);\n if (e.kind === \"file\") {\n for (const c of e.chunks) {\n const k = hex(c.hash);\n if (!seenHash.has(k)) {\n seenHash.add(k);\n wantedHashes.push(c.hash);\n }\n }\n }\n }\n if (entries.length === 0) return 0;\n\n // Probe the remote for the chunks it already holds; ship the\n // complement.\n const remoteHas = new Set<string>();\n if (wantedHashes.length > 0) {\n const have = await remote.hasObjects(wantedHashes);\n for (const h of have) remoteHas.add(hex(h));\n }\n const missing = wantedHashes.filter((h) => !remoteHas.has(hex(h)));\n\n if (missing.length > 0) {\n const local = (function* () {\n for (const h of missing) {\n const row = db.one<{ bytes: Uint8Array }>(\n \"SELECT bytes FROM vfs_blob_bytes WHERE hash = ?\",\n h,\n );\n if (row === undefined) {\n throw new Error(`pushOnce: missing local blob ${hex(h)}`);\n }\n yield { hash: h, bytes: row.bytes };\n }\n })();\n const bytesStream = new ReadableStream<{ hash: Uint8Array; bytes: Uint8Array }>({\n pull(controller) {\n const next = local.next();\n if (next.done) controller.close();\n else controller.enqueue(next.value);\n },\n });\n await remote.pushObjects(bytesStream);\n }\n\n const entryStream = new ReadableStream<ChangeEntry>({\n start(controller) {\n for (const e of entries) controller.enqueue(e);\n controller.close();\n },\n });\n const response = await remote.push({ senderRev: localRev, changes: entryStream });\n\n // Cross-side invariant: the receiver must echo back at least\n // the rev we just claimed to push. A drift means the apply\n // path lost data, or a stale receiver is serving an old\n // snapshot. Tear down loudly rather than corrupt watermarks.\n assertAppliedPushRev(response.appliedPushRev, localRev);\n\n // Local pushRev advances to the rev we observed at the start of\n // this round. Anything written after that gets caught next tick.\n writeWatermark(db, \"pushRev\", localRev);\n return entries.length;\n}\n\n// One full tick: pull, then push. The order matters \\u2014 pulling\n// first lets the loopback-suppression in applyChanges absorb\n// remote writes before we look at our own dirty set, so we don't\n// re-push entries that just came in.\nexport async function tick(\n db: Database,\n remote: SyncRPC,\n): Promise<{ pulled: ApplyResult; pushed: number }> {\n const pulled = await pullOnce(db, remote);\n const pushed = await pushOnce(db, remote);\n return { pulled, pushed };\n}\n\n// Reconcile local watermarks against the remote's view of the world.\n// Called on (re)connect, before any push or pull tick.\n//\n// The asymmetry that makes this necessary: the DO's watermarks live\n// in durable storage and survive its incarnations, but today's wsd\n// runs against a process-lifetime DB so a container restart wipes\n// the container-side state. Without a check, pushOnce's early-return\n// (`localRev <= sincePush`) skips talking to the container entirely\n// when the DO has no new writes — so the next exec runs against an\n// empty FUSE mount.\n//\n// The fix is mechanical: ask the remote what it has, and reset our\n// cursors to 0 wherever the remote is behind us. The rev-0 baseline\n// path in fetchChanges / pushOnce then re-ships everything\n// incrementally on the next tick.\n//\n// Returns the changes made so callers can log them.\nexport async function reconcileWatermarks(\n db: Database,\n remote: SyncRPC,\n): Promise<{ fetchRevReset: boolean; pushRevReset: boolean }> {\n const remoteWatermarks = await remote.watermarks();\n const localFetchRev = readWatermark(db, \"fetchRev\");\n const localPushRev = readWatermark(db, \"pushRev\");\n\n let fetchRevReset = false;\n let pushRevReset = false;\n\n // If the remote's currentRev is below our fetchRev, the remote's\n // log is shorter than we remember — it lost state since we last\n // pulled. Re-baseline from 0.\n if (remoteWatermarks.currentRev < localFetchRev) {\n writeWatermark(db, \"fetchRev\", 0);\n fetchRevReset = true;\n }\n\n // The remote's pushRev is what it last applied from us (when the\n // remote acts as a sync peer it advances pushRev to the senderRev\n // on every push). If that's below our local pushRev, the remote\n // hasn't seen what we claimed to ship — reset and re-push.\n if (remoteWatermarks.pushRev < localPushRev) {\n writeWatermark(db, \"pushRev\", 0);\n pushRevReset = true;\n }\n\n return { fetchRevReset, pushRevReset };\n}\n","// Mount indexer.\n//\n// Drives each registered Mount's materialize() exactly once per DO\n// lifetime. Persists progress to _vfs_mounts so a subsequent\n// Workspace over the same store does not re-run. Failures roll back\n// the subtree under the mount root and leave _vfs_mounts.indexed = 0\n// so the next pass retries from scratch.\n//\n// dofs's read-only mount guard reads _vfs_mounts.mode to reject\n// writes under registered read-only roots. The indexer must therefore\n// keep the row in 'read-write' while it materialises (otherwise its\n// own mkdir / writeFile calls hit the guard) and flip to the\n// registered mode only after materialize() succeeds. The cache in\n// @cloudflare/dofs is invalidated after every transition so the next\n// check picks up the new mode.\n\nimport type { Database, WorkspaceFilesystem } from \"@cloudflare/dofs\";\nimport { invalidateReadOnlyMountCache, ROOT_INODE } from \"@cloudflare/dofs\";\n\nimport type { Mount, MountWriteAPI } from \"./types.js\";\n\nexport interface IndexerOptions {\n db: Database;\n fs: WorkspaceFilesystem;\n // The resolved registry keyed by absolute mount root.\n mounts: Map<string, Mount>;\n}\n\n// Materialize every registered mount whose _vfs_mounts row is not\n// yet marked indexed. Calls are deduped through the per-workspace\n// `indexPromise` cached in MountIndex below; this free function is\n// the workhorse.\nasync function runIndex(opts: IndexerOptions): Promise<void> {\n const { db, fs, mounts } = opts;\n // Snapshot the indexed flag per root so we don't re-run something\n // a previous attach already finished.\n const status = new Map<string, boolean>();\n for (const root of mounts.keys()) {\n const row = db.one<{ indexed: number }>(\"SELECT indexed FROM _vfs_mounts WHERE root = ?\", root);\n status.set(root, row?.indexed === 1);\n }\n\n // Run every pending mount in parallel. Per-mount failures clear\n // their own subtree; the overall index call rejects with the first\n // failure once every pending mount has settled.\n const pending = [...mounts.entries()].filter(([root]) => status.get(root) !== true);\n if (pending.length === 0) return;\n\n const results = await Promise.allSettled(\n pending.map(async ([root, mount]) => {\n // Record the mount as registered-but-not-indexed before we\n // start writing into the subtree. mode='read-write' for the\n // duration of materialize() so the indexer's own mkdir /\n // writeFile calls bypass the dofs read-only guard; we flip\n // to mount.mode at the end. If materialize() crashes the row\n // stays with indexed=0 and the next pass retries from\n // scratch.\n db.run(\n \"INSERT INTO _vfs_mounts (root, kind, mode, indexed) VALUES (?, ?, 'read-write', 0)\\n\" +\n \" ON CONFLICT(root) DO UPDATE SET kind = excluded.kind, mode = 'read-write', indexed = 0\",\n root,\n mount.kind,\n );\n invalidateReadOnlyMountCache(db);\n\n const api = createWriteAPI({ fs, root, mount });\n try {\n // Pre-create the mount root so an empty mount (one whose\n // materialize() produces zero entries) still has a\n // resolvable inode. Without this, only non-empty mounts get\n // a root — as a side effect of the first writeFile's parent\n // mkdir chain — and readdir(root) on an empty mount rejects\n // with ENOENT. The mkdir is inside the try block so a crash\n // mid-materialize rolls it back via the existing fs.rm path\n // below.\n await fs.mkdir(root, { recursive: true });\n await mount.materialize(api);\n // Stamp every node reachable from the mount root with\n // mount_root = <root>. The dofs guard reads the path → root\n // mapping from _vfs_mounts, but the provenance column also\n // exists on vfs_nodes so downstream tools (sync drop\n // reasoning, future write-back, GC of mount subtrees) can\n // tell which mount owns which inode without re-resolving\n // the path. Stamped inside the try block so the rollback\n // path wipes the stamps along with the rows.\n stampMountProvenance(db, root);\n } catch (error) {\n // Roll back anything the partial materialize landed under\n // this mount's root. We use fs.rm with recursive+force so a\n // half-created subtree leaves no orphan rows behind. The\n // row is still mode='read-write' at this point, so the rm\n // is not blocked by its own guard.\n try {\n await fs.rm(root, { recursive: true, force: true });\n } catch {\n // Best effort. If the subtree doesn't exist we still want\n // the original error to surface.\n }\n throw error;\n }\n // Flip to the registered mode and mark indexed in a single\n // UPDATE, then invalidate the guard cache so subsequent\n // writes through Workspace.fs (and any pull) see the final\n // mode.\n db.run(\"UPDATE _vfs_mounts SET mode = ?, indexed = 1 WHERE root = ?\", mount.mode, root);\n invalidateReadOnlyMountCache(db);\n }),\n );\n\n const failures = results.filter((r): r is PromiseRejectedResult => r.status === \"rejected\");\n if (failures.length > 0) {\n // Surface the first failure; preserve the original error so\n // tests / callers see the underlying message.\n throw failures[0].reason;\n }\n}\n\n// Walk vfs_dirents from ROOT_INODE down to the mount root's inode.\n// Returns undefined when any segment is missing or hits a non-dir.\n// Used by the indexer to find the root inode to stamp; we don't\n// reuse resolveInode because it isn't exported from @cloudflare/dofs\n// and we don't want to take a dep on its internal symlink-following\n// behaviour for an indexer-only walk.\nfunction findInodeAt(db: Database, absPath: string): number | undefined {\n if (absPath === \"/\") return ROOT_INODE;\n const parts = absPath.split(\"/\").filter((p) => p.length > 0);\n let current = ROOT_INODE;\n for (const name of parts) {\n const row = db.one<{ child_inode: number; type: \"file\" | \"dir\" | \"symlink\" }>(\n \"SELECT d.child_inode AS child_inode, n.type AS type\\n\" +\n \" FROM vfs_dirents d JOIN vfs_nodes n ON n.inode = d.child_inode\\n\" +\n \" WHERE d.parent_inode = ? AND d.name = ?\",\n current,\n name,\n );\n if (row === undefined) return undefined;\n current = row.child_inode;\n }\n return current;\n}\n\n// Stamp vfs_nodes.mount_root = <root> on every inode reachable from\n// the mount root's inode (the root itself included). The walk is\n// iterative BFS over vfs_dirents so it doesn't blow the stack on\n// deep trees.\nfunction stampMountProvenance(db: Database, root: string): void {\n const rootInode = findInodeAt(db, root);\n if (rootInode === undefined) {\n // The indexer always mkdir's the root before calling this, so\n // a missing inode here means materialize() somehow tore it\n // down. Skip silently — the caller's rollback path will\n // handle the cleanup if the index pass throws.\n return;\n }\n const inodes: number[] = [rootInode];\n const queue: number[] = [rootInode];\n while (queue.length > 0) {\n const parent = queue.shift() as number;\n const children = db.all<{ child_inode: number }>(\n \"SELECT child_inode FROM vfs_dirents WHERE parent_inode = ?\",\n parent,\n );\n for (const c of children) {\n inodes.push(c.child_inode);\n queue.push(c.child_inode);\n }\n }\n // One UPDATE per inode keeps the SQL simple and avoids building a\n // large IN-list parameter. The subtree size is bounded by the\n // materialised mount and we're inside the indexer's serialized\n // run, so this is fine for the scales we care about.\n for (const inode of inodes) {\n db.run(\"UPDATE vfs_nodes SET mount_root = ? WHERE inode = ?\", root, inode);\n }\n}\n\ninterface WriteAPIOptions {\n fs: WorkspaceFilesystem;\n root: string;\n mount: Mount;\n}\n\nfunction createWriteAPI(opts: WriteAPIOptions): MountWriteAPI {\n const { fs, root, mount } = opts;\n const maxBytes = mount.maxBytes;\n const maxEntries = mount.maxEntries;\n let bytesWritten = 0;\n let entriesWritten = 0;\n\n function checkPath(absPath: string): void {\n if (!absPath.startsWith(`${root}/`) && absPath !== root) {\n throw new Error(`mount ${root}: writeFile/mkdir target ${absPath} is outside the mount root`);\n }\n }\n\n return {\n root,\n async writeFile(\n absPath: string,\n source: ReadableStream<Uint8Array>,\n mode?: number,\n ): Promise<void> {\n checkPath(absPath);\n if (maxEntries !== undefined && entriesWritten + 1 > maxEntries) {\n throw new Error(`mount ${root}: maxEntries=${maxEntries} exceeded`);\n }\n entriesWritten += 1;\n\n // Ensure the parent directory chain exists. mkdir on an\n // existing path is EEXIST, which is fine here because the\n // recursive flag swallows that for the already-a-directory\n // case.\n const lastSlash = absPath.lastIndexOf(\"/\");\n if (lastSlash > 0) {\n await fs.mkdir(absPath.slice(0, lastSlash), { recursive: true });\n }\n\n // When a byte cap is set, tee the source stream so we can\n // count bytes without buffering. The tee keeps the streaming\n // contract: bytes still flow chunk-by-chunk into writeFile.\n let toWrite: ReadableStream<Uint8Array> = source;\n if (maxBytes !== undefined) {\n const [counted, forwarded] = source.tee();\n toWrite = forwarded;\n // Drain the counted side concurrently; if the cap is\n // exceeded mid-stream, cancel the forwarded side to short\n // circuit the write.\n const cancelForwarded = (reason: unknown): void => {\n forwarded.cancel(reason).catch(() => {});\n };\n const counter = (async () => {\n const reader = counted.getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n if (value === undefined) continue;\n bytesWritten += value.byteLength;\n if (bytesWritten > maxBytes) {\n const err = new Error(\n `mount ${root}: maxBytes=${maxBytes} exceeded (saw ${bytesWritten})`,\n );\n cancelForwarded(err);\n throw err;\n }\n }\n } finally {\n reader.releaseLock();\n }\n })();\n // Await both sides. If the counter rejects, surface that;\n // otherwise let writeFile propagate.\n const writePromise = fs.writeFile(absPath, toWrite, { mode });\n const [counterResult, writeResult] = await Promise.allSettled([counter, writePromise]);\n if (counterResult.status === \"rejected\") throw counterResult.reason;\n if (writeResult.status === \"rejected\") throw writeResult.reason;\n return;\n }\n await fs.writeFile(absPath, toWrite, { mode });\n },\n\n async mkdir(absPath: string, mode?: number): Promise<void> {\n checkPath(absPath);\n if (maxEntries !== undefined && entriesWritten + 1 > maxEntries) {\n throw new Error(`mount ${root}: maxEntries=${maxEntries} exceeded`);\n }\n entriesWritten += 1;\n await fs.mkdir(absPath, { recursive: true, mode });\n },\n };\n}\n\n// Singleton wrapper that the Workspace holds. The class keeps the\n// in-flight promise so concurrent ensureIndexed() callers share one\n// run.\nexport class MountIndex {\n readonly #db: Database;\n readonly #fs: WorkspaceFilesystem;\n readonly #mounts: Map<string, Mount>;\n #inFlight: Promise<void> | undefined;\n #done = false;\n\n constructor(opts: IndexerOptions) {\n this.#db = opts.db;\n this.#fs = opts.fs;\n this.#mounts = opts.mounts;\n }\n\n ensureIndexed(): Promise<void> {\n if (this.#done) return Promise.resolve();\n if (this.#inFlight) return this.#inFlight;\n if (this.#mounts.size === 0) {\n this.#done = true;\n return Promise.resolve();\n }\n this.#inFlight = runIndex({ db: this.#db, fs: this.#fs, mounts: this.#mounts })\n .then(() => {\n this.#done = true;\n })\n .finally(() => {\n this.#inFlight = undefined;\n });\n return this.#inFlight;\n }\n}\n","// Mount registry.\n//\n// Validates roots, resolves factories to Mount instances, and exposes\n// the resulting map by mount root. The registry is purely in-memory;\n// _vfs_mounts persistence is the indexer's job (see index.ts).\n\nimport type { Mount, MountContext, MountFactory } from \"./types.js\";\n\nexport type MountValue = Mount | MountFactory;\n\nexport interface RegistryOptions {\n // Source for the MountContext.sessionId field. Defaults to \"\" when\n // the caller didn't supply one.\n sessionId?: string;\n // Builder for the VFS handle handed to factories. Lazy so we don't\n // construct a SQLiteWorkspaceProvider for callers with no factory\n // mounts.\n vfs: () => MountContext[\"vfs\"];\n}\n\nfunction validateRoot(root: string): void {\n if (root.length === 0 || !root.startsWith(\"/\")) {\n throw new Error(`mount root must be absolute (starts with '/'): ${JSON.stringify(root)}`);\n }\n if (root.length > 1 && root.endsWith(\"/\")) {\n throw new Error(`mount root must not have a trailing slash: ${JSON.stringify(root)}`);\n }\n}\n\nfunction rejectNesting(roots: string[]): void {\n // O(n^2) is fine; we expect a handful of mounts per workspace.\n for (let i = 0; i < roots.length; i++) {\n for (let j = 0; j < roots.length; j++) {\n if (i === j) continue;\n const a = roots[i];\n const b = roots[j];\n // a is an ancestor of b when b starts with `${a}/`. Compare\n // with the trailing slash so /workspace/a is not flagged as\n // an ancestor of /workspace/another.\n if (b.startsWith(`${a}/`)) {\n throw new Error(`mount roots must not nest: ${a} contains ${b}`);\n }\n }\n }\n}\n\nexport function buildMountRegistry(\n mounts: Record<string, MountValue> | undefined,\n options: RegistryOptions,\n): Map<string, Mount> {\n const out = new Map<string, Mount>();\n if (mounts === undefined) return out;\n const roots = Object.keys(mounts);\n for (const root of roots) validateRoot(root);\n rejectNesting(roots);\n\n const sessionId = options.sessionId ?? \"\";\n // Build the VFS handle only once even when multiple factories ask\n // for it.\n let vfsCached: MountContext[\"vfs\"] | undefined;\n const vfs = (): MountContext[\"vfs\"] => {\n if (vfsCached === undefined) vfsCached = options.vfs();\n return vfsCached;\n };\n\n for (const root of roots) {\n const value = mounts[root];\n if (typeof value === \"function\") {\n const ctx: MountContext = { sessionId, root, vfs: vfs() };\n out.set(root, value(ctx));\n } else {\n out.set(root, value);\n }\n }\n return out;\n}\n","// Host-side Workspace facade.\n//\n// Runs inside a Cloudflare Worker / Durable Object. Owns a local\n// dofs Database (the host store) and a SyncRPC connection\n// to wsd. Filesystem operations on Workspace.fs mutate the local\n// store directly via the WorkspaceFilesystem class from\n// @cloudflare/dofs; sync between the host store and wsd\n// is driven explicitly via Workspace.push() / Workspace.pull().\n// The shell-side pre-exec push / post-exec pull bracket lives\n// on Workspace.shell.exec.\n\nimport {\n type ApplyResult,\n Database,\n type DurableObjectStorageLike,\n initializeSchema,\n SQLiteWorkspaceProvider,\n WorkspaceFilesystem,\n} from \"@cloudflare/dofs\";\nimport { pullOnce, pushOnce, reconcileWatermarks } from \"@cloudflare/workspace-rpc/driver\";\n\nimport type { BackendHandle, WorkspaceBackend } from \"./backend.js\";\nimport { MountIndex } from \"./mounts/index.js\";\nimport { buildMountRegistry, type MountValue } from \"./mounts/registry.js\";\nimport type { Mount } from \"./mounts/types.js\";\nimport { WorkspaceShell } from \"./shell.js\";\nimport { WorkspaceStub } from \"./stub.js\";\n\nexport interface WorkspaceOptions {\n // Local store backing this Workspace. In a Durable Object, pass\n // `ctx.storage`; in tests, pass a SQLiteTestStorage from\n // @cloudflare/dofs/testing. The constructor opens a\n // Database against it and runs initializeSchema (idempotent).\n storage: DurableObjectStorageLike;\n\n // Backends are tried in declared order. The first one whose\n // connect() resolves wins; the rest are not consulted.\n backends: WorkspaceBackend[];\n\n // Clock used for mtime / last_seen on local FS writes. Defaults\n // to Date.now. Override for deterministic tests.\n now?: () => number;\n\n // Identifier for this workspace / session. Forwarded to mount\n // factories via MountContext.sessionId. Optional; defaults to \"\".\n sessionId?: string;\n\n // Mounts to register against the workspace. Keys are absolute\n // mount roots (no trailing slash, no nesting). Values are either\n // bare Mount objects or factories that take a MountContext and\n // return one. Factories are called once at construction.\n mounts?: Record<string, MountValue>;\n\n // Bounded retry policy for ready(). When omitted, ready() runs\n // the backend list once and surfaces the first failure — the\n // shipped behaviour before retries existed. When set, a transient\n // failure on every backend triggers a wait + retry, up to\n // `attempts` total tries with exponential backoff. The delay\n // starts at `initialDelayMs` and doubles each round, capped at\n // `maxDelayMs`.\n reconnect?: ReconnectOptions;\n}\n\nexport interface ReconnectOptions {\n // Total connect() attempts across the backend list. 1 means\n // no retry (one pass). Default 1.\n attempts: number;\n // First backoff delay in ms. Doubles each round up to maxDelayMs.\n initialDelayMs: number;\n // Cap on the per-attempt backoff delay.\n maxDelayMs: number;\n}\n\nexport class Workspace {\n readonly #db: Database;\n readonly #fs: WorkspaceFilesystem;\n /**\n * Lazily-constructed dofs provider. Built on first `provider()`\n * call; cached so repeated callers share the same instance.\n */\n #provider: SQLiteWorkspaceProvider | undefined;\n readonly #backends: WorkspaceBackend[];\n readonly #reconnect: ReconnectOptions;\n readonly #now: () => number;\n readonly #mounts: Map<string, Mount>;\n readonly #mountIndex: MountIndex;\n #handle: BackendHandle | undefined;\n #shell: WorkspaceShell | undefined;\n #readyPromise: Promise<void> | undefined;\n // FIFO that serializes mutating entry points (push, pull, and the\n // shell exec bracket which goes through them). Reads bypass the\n // queue entirely — they hit the local store directly through\n // Workspace.fs. The queue is a single tail-promise: each new caller\n // chains its work onto the tail and updates it. See docs/02 \"Concurrent\n // mutators\".\n #mutationTail: Promise<unknown> = Promise.resolve();\n\n constructor(options: WorkspaceOptions) {\n if (options.backends.length === 0) {\n throw new Error(\"Workspace requires at least one backend\");\n }\n this.#now = options.now ?? Date.now;\n this.#db = new Database(options.storage);\n initializeSchema(this.#db, this.#now);\n this.#fs = new WorkspaceFilesystem(this.#db, { now: this.#now });\n this.#backends = options.backends.slice();\n this.#reconnect = options.reconnect ?? { attempts: 1, initialDelayMs: 0, maxDelayMs: 0 };\n this.#mounts = buildMountRegistry(options.mounts, {\n sessionId: options.sessionId,\n vfs: () => this.provider(),\n });\n this.#mountIndex = new MountIndex({\n db: this.#db,\n fs: this.#fs,\n mounts: this.#mounts,\n });\n }\n\n // Force every registered mount to materialize. Idempotent; safe to\n // call from multiple places (ready(), tests, future fs/shell\n // entry points). Concurrent callers share one materialize() pass\n // per mount.\n ensureMountsIndexed(): Promise<void> {\n return this.#mountIndex.ensureIndexed();\n }\n\n // Resolved mount registry, keyed by absolute mount root. Returned\n // as a defensive copy so callers can't mutate the internal map.\n mounts(): Map<string, Mount> {\n return new Map(this.#mounts);\n }\n\n // Local store. Exposed for tests / diagnostics and for the\n // sync helpers that take a Database directly.\n get db(): Database {\n return this.#db;\n }\n\n // Filesystem facade — the documented Workspace.fs surface from\n // docs/04. Available immediately; doesn't need ready() because\n // reads and writes hit the local store, not the wire.\n //\n // Read-only mount enforcement lives at the data layer in\n // @cloudflare/dofs: writeFile / mkdir / rm consult the registered\n // mount roots and reject EROFS without needing a workspace-side\n // wrapper. The same check fires on the apply path used by\n // pullOnce, so container-side writes under a read-only mount are\n // also rejected (and surfaced via Workspace.pull's skipped[]).\n get fs(): WorkspaceFilesystem {\n return this.#fs;\n }\n\n /**\n * Underlying dofs `SQLiteWorkspaceProvider` over the local store.\n *\n * This is the `@platformatic/vfs`-shaped provider — a node:fs\n * surface with full symlink support. Callers that want a\n * `VirtualFileSystem` (e.g. to hand to isomorphic-git) wrap it\n * themselves to keep `@platformatic/vfs` out of this package's\n * dependency tree:\n *\n * ```ts\n * import { create, VirtualProvider } from \"@platformatic/vfs\";\n * import type { SQLiteWorkspaceProvider } from \"@cloudflare/dofs\";\n *\n * class Glue extends VirtualProvider {\n * constructor(private inner: SQLiteWorkspaceProvider) { super(); }\n * override get readonly() { return this.inner.readonly; }\n * override get supportsSymlinks() { return this.inner.supportsSymlinks; }\n * override get supportsWatch() { return this.inner.supportsWatch; }\n * }\n * // Forward every node:fs method to `inner` via a\n * // `for (const name of [...]) Object.defineProperty(...)` loop.\n * const vfs = create(new Glue(workspace.provider()));\n * ```\n *\n * Available immediately; doesn't need `ready()` because the\n * provider only reads/writes the local store, not the wire.\n */\n provider(): SQLiteWorkspaceProvider {\n if (!this.#provider) {\n this.#provider = new SQLiteWorkspaceProvider(this.#db, { now: this.#now });\n }\n return this.#provider;\n }\n\n // Shell facade. Throws if called before ready() resolves.\n // exec() brackets the spawn with push() / pull(); see shell.ts.\n get shell(): WorkspaceShell {\n if (!this.#shell) {\n throw new Error(\"Workspace not connected — await ready() first\");\n }\n return this.#shell;\n }\n\n // Walk the backends in declared order. Caches the first\n // successful BackendHandle so subsequent .shell / .close calls\n // reuse it. ready() is idempotent; multiple callers share\n // the same in-flight connection attempt.\n ready(): Promise<void> {\n if (this.#readyPromise) return this.#readyPromise;\n this.#readyPromise = (async () => {\n await this.#connect();\n // Index after the backend is wired so reads of mounted paths\n // are populated before the first push() inside an exec()\n // bracket can ship them.\n await this.#mountIndex.ensureIndexed();\n })();\n return this.#readyPromise;\n }\n\n // Wrap this workspace in a WorkspaceStub so it can be handed\n // across the Workers-RPC boundary (e.g. returned from a DO RPC\n // method). The stub is a lazy RpcTarget — it doesn't own any\n // resources itself; it just delegates back to this workspace.\n // Throws if called before ready() resolves, because the inner\n // .shell getter does.\n stub(): WorkspaceStub {\n // Touch .shell so the not-connected error surfaces here\n // rather than on the first RPC method call.\n void this.shell;\n return new WorkspaceStub(this);\n }\n\n // Sync the local store with the connected backend.\n //\n // push() ships everything the host has written since the last\n // push to wsd; pull() applies everything wsd has produced since\n // the last pull. Both are explicit — the package doesn't run a\n // background loop. WorkspaceShell brackets exec() automatically;\n // call these directly for FS-only flows that need to hand off to\n // the container via a tool other than exec.\n //\n // push() returns the number of entries shipped to the remote so\n // a polling loop can decide whether to tick again. pull() returns\n // the dofs ApplyResult { applied, skipped } — `applied` is the\n // number of entries written into the local store, `skipped`\n // surfaces container-side writes the apply path rejected because\n // they targeted a read-only mount root. Callers that don't care\n // about read-only enforcement read `applied`; the shell exec\n // bracket folds `skipped` into its ExecResult so users see what\n // stayed authoritative on the mount.\n push(): Promise<number> {\n return this.#serialize(async () => {\n await this.ready();\n if (!this.#handle) throw new Error(\"Workspace not connected\");\n return pushOnce(this.#db, this.#handle.rpc.sync);\n });\n }\n\n pull(): Promise<ApplyResult> {\n return this.#serialize(async () => {\n await this.ready();\n if (!this.#handle) throw new Error(\"Workspace not connected\");\n return pullOnce(this.#db, this.#handle.rpc.sync);\n });\n }\n\n // Tail-promise FIFO. Each call chains onto the existing tail so\n // it can't start until every queued mutation ahead of it has\n // resolved (or rejected). Rejections are not contagious: we swallow\n // the rejection here so a failing mutation doesn't poison the rest\n // of the queue — the caller still sees the original rejection via\n // the returned promise.\n #serialize<T>(fn: () => Promise<T>): Promise<T> {\n const run = this.#mutationTail.then(fn, fn);\n this.#mutationTail = run.then(\n () => undefined,\n () => undefined,\n );\n return run;\n }\n\n async close(): Promise<void> {\n if (this.#handle) {\n try {\n await this.#handle.close();\n } finally {\n this.#handle = undefined;\n this.#shell = undefined;\n this.#readyPromise = undefined;\n }\n }\n }\n\n async #connect(): Promise<void> {\n const { attempts, initialDelayMs, maxDelayMs } = this.#reconnect;\n let delay = initialDelayMs;\n let lastError: Error | undefined;\n for (let attempt = 1; attempt <= attempts; attempt++) {\n try {\n await this.#connectOnce();\n return;\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n if (attempt === attempts) break;\n await sleep(delay);\n delay = Math.min(delay * 2 || 1, maxDelayMs);\n }\n }\n // Throwing the last attempt's error preserves the per-backend\n // summary the pass produced — the caller still sees which\n // backend failed and why.\n throw lastError;\n }\n\n async #connectOnce(): Promise<void> {\n const errors: Array<{ id: string; error: unknown }> = [];\n for (const backend of this.#backends) {\n try {\n const handle = await backend.connect();\n // Reconcile watermarks before publishing the handle. If the\n // remote restarted between our pushes / fetches it has lost\n // state we thought it had; reset the local cursors so the\n // next tick rebaselines. Done eagerly on connect because\n // pushOnce's localRev <= sincePush early-return otherwise\n // hides the mismatch.\n await reconcileWatermarks(this.#db, handle.rpc.sync);\n this.#handle = handle;\n // Workspace satisfies the Sync interface in shell.ts via\n // its public push() / pull() methods.\n this.#shell = new WorkspaceShell(handle.rpc.shell, this);\n // Tear down our caches if the transport drops mid-session.\n // Backends without a `closed` promise (in-process fakes) opt\n // out by omitting it; we only react when it's wired.\n if (handle.closed) {\n handle.closed\n .catch(() => {})\n .then(() => {\n // Only clear if this handle is still the current one.\n // A close() that already ran will have nulled #handle,\n // and a subsequent ready() may have installed a new one.\n if (this.#handle === handle) {\n this.#handle = undefined;\n this.#shell = undefined;\n this.#readyPromise = undefined;\n }\n });\n }\n return;\n } catch (error) {\n errors.push({ id: backend.id, error });\n }\n }\n const summary = errors\n .map(\n ({ id, error }) => ` - ${id}: ${error instanceof Error ? error.message : String(error)}`,\n )\n .join(\"\\n\");\n throw new Error(`Workspace: no backend reachable\\n${summary}`);\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n if (ms <= 0) return Promise.resolve();\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"],"mappings":"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"}