How SQLiteWorkspaceProvider Bridges Durable Object Storage to FUSE Mount Operations

The SQLiteWorkspaceProvider adapter translates @platformatic/vfs filesystem calls into SQLite queries on a Durable Object-backed database, enabling FUSE mounts of cloud-persistent storage.

The SQLiteWorkspaceProvider is the core architectural component in Cloudflare's computer repository that turns a Durable Object (DO)-backed SQLite database into a fully functional virtual filesystem. By implementing the @platformatic/vfs VirtualProvider interface, this adapter allows POSIX-like filesystem operations to persist in SQLite while exposing them through a FUSE mount point on the local machine.

Architecture Overview

The bridge consists of five coordinated layers that transform high-level filesystem calls into low-level SQLite operations:

Layer Responsibility Implementation Location
Durable Object storage Persists content-addressed blobs in SQLite tables packages/dofs/src/testing.ts (SQLiteTestStorage)
SQLiteWorkspaceProvider Implements VirtualProvider interface using DO storage packages/dofs/src/provider.ts
Prototype splice Injects VirtualProvider inheritance at runtime only in computerd packages/computerd/src/fuse/vfs.ts
NodeVirtualFileSystem Creates VFS instance with dofs-specific method extensions packages/computerd/src/fuse/vfs.ts
FUSE driver Mounts the VFS and translates kernel calls to provider methods packages/computerd/src/fuse/driver.ts

Durable Object (SQLiteTestStorage) → Database
      ↓
SQLiteWorkspaceProvider (implements VirtualProvider)
      ↓  (prototype splice)
NodeVirtualFileSystem (via @platformatic/vfs.create)
      ↓
FUSE driver (computerd) → mount point

How the Adapter Works

Provider Construction and Prototype Splicing

The createNodeVirtualFileSystem() function in packages/computerd/src/fuse/vfs.ts handles initialization. A critical implementation detail is the prototype splice—necessary because SQLiteWorkspaceProvider cannot statically depend on @platformatic/vfs (it targets Workers environments).

// packages/computerd/src/fuse/vfs.ts
export async function createNodeVirtualFileSystem(
  options: CreateOptions = {},
): Promise<NodeVfsHandle> {
  ensureVirtualProviderPrototype();                     // ← splice prototype
  const storage = new SQLiteTestStorage();              // DO-backed SQLite
  const db = new Database(storage);
  initializeSchema(db, () => Date.now());

  const provider = new SQLiteWorkspaceProvider(db);     // ← core adapter
  const vfs = create(provider as unknown as VirtualProvider, { moduleHooks: false });
  // expose extra dofs methods on the VFS instance …
  return { vfs, db, stopSync };
}

The ensureVirtualProviderPrototype() function (lines 21-33) dynamically sets:

Object.setPrototypeOf(SQLiteWorkspaceProvider.prototype, VirtualProvider.prototype)

This runtime inheritance allows the VFS creator to recognize the provider as a genuine VirtualProvider without bundling the VFS library into the Durable Object build.

Core Filesystem Primitives

Each VirtualProvider method in SQLiteWorkspaceProvider delegates to low-level helpers in packages/dofs/src/fs/ that operate directly on SQLite tables (vfs_nodes, vfs_dirents, vfs_chunks).

Opening files — The openSync() method (lines 35-70) resolves inodes, handles create flags, and manages file descriptor state:

// provider.ts – openSync()
const { read, write, truncate, append, create, exclusive } = parseFlags(flags);
const existing = resolveInode(this.db, path);
// … create handling …
const fd = this.#nextFd++;
this.#fds.set(fd, { path, position: append ? stat.size : 0, readable: read, writable: write, append });
return fd;

Reading files — readFileSync() (lines 66-98) checks pending write buffers first, then assembles stored chunks into a Buffer:

// provider.ts – readFileSync()
const node = resolveInode(this.db, path);
const buffered = getWriteBuffer(this.db, node.inode);
// … buffer handling …
const chunks = this.db.all(...);
// concatenate chunk bytes

Writing files — writeRangeSync() (lines 46-52) persists data through writeRangeSyncImpl:

// provider.ts – writeRangeSync()
const bytes = typeof data === "string"
  ? new TextEncoder().encode(data)
  : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
return writeRangeSyncImpl(this.db, path, bytes, offset, { mode }, this.now);

Renaming and linking — renameSync() (lines 21-34) flushes pending buffers before mutating directory entries:

// provider.ts – renameSync()
flushPendingUnderNode(this.db, oldPath, this.now);
flushPendingUnderNode(this.db, newPath, this.now);
// …
renameImpl(this.db, oldPath, newPath);

Watching for changes — The watch() implementation (lines 95-99) polls vfs_meta.rev for revision changes:

// provider.ts – watch()
return createWatcher(this.db, path, options, this.watchIntervalMs);

All methods return POSIX-compatible stat objects via wrapStats, providing mode, size, mtime, atime, and ctime fields that the FUSE driver expects.

Connecting to the FUSE Driver

The FUSE driver in packages/computerd/src/fuse/driver.ts receives the NodeVirtualFileSystem instance. Every kernel filesystem operation on the mounted path invokes the corresponding method on the provider, which executes SQLite queries against the Durable Object's database.

Practical Usage Examples

Mounting a FUSE Filesystem

import { createNodeVirtualFileSystem } from "@cloudflare/computerd/src/fuse/vfs";

// Build a VFS backed by an in-memory DO SQLite store
const { vfs, db, stopSync } = await createNodeVirtualFileSystem();

// Pass vfs to the FUSE driver (e.g., in computerd CLI)
await mountFuse(vfs, "/myfs");

// Perform regular Node fs calls on the mount point
import { promises as fs } from "fs";

await fs.writeFile("/myfs/hello.txt", "Hello, world!");
const content = await fs.readFile("/myfs/hello.txt", "utf8");
console.log(content); // → Hello, world!

The provider transparently translates writeFile into SQLite chunk inserts and readFile into chunk reassembly queries.

Watching for Changes

import { watch } from "fs";

const watcher = watch("/myfs", (eventType, filename) => {
  console.log(`${eventType} on ${filename}`);
});

// Trigger a change:
await fs.writeFile("/myfs/notes.txt", "quick note");

// Output (after poll interval):
// change on notes.txt

The polling-based watch implementation guarantees that changes from upstream synchronization also surface to local FUSE clients.

Key Implementation Files

File Role
[packages/dofs/src/provider.ts](https://github.com/cloudflare/computer/blob/main/packages/dofs/src/provider.ts) Core adapter implementing VirtualProvider on Database
[packages/computerd/src/fuse/vfs.ts](https://github.com/cloudflare/computer/blob/main/packages/computerd/src/fuse/vfs.ts) VFS factory with prototype splicing and method extensions
[packages/computerd/src/fuse/driver.ts](https://github.com/cloudflare/computer/blob/main/packages/computerd/src/fuse/driver.ts) FUSE driver consuming the virtual filesystem
packages/dofs/src/fs/ Low-level filesystem operation implementations
[packages/dofs/src/testing.ts](https://github.com/cloudflare/computer/blob/main/packages/dofs/src/testing.ts) SQLiteTestStorage for DO-backed SQLite

Summary

  • SQLiteWorkspaceProvider implements the VirtualProvider interface to expose Durable Object SQLite storage as a POSIX-compatible filesystem.
  • Prototype splicing (ensureVirtualProviderPrototype) bridges the Workers/Node.js boundary without polluting Durable Object builds.
  • Low-level SQLite operations in packages/dofs/src/fs/ handle file content as chunked blobs in vfs_chunks with metadata in vfs_nodes and vfs_dirents.
  • FUSE integration occurs through NodeVirtualFileSystem, which the driver mounts and routes kernel calls through.
  • Change detection polls vfs_meta.rev to provide fs.watch compatibility for local and remote modifications.

Frequently Asked Questions

How does SQLiteWorkspaceProvider handle file content storage?

The provider stores file content as content-addressed chunks in the vfs_chunks table, with metadata tracking chunk offsets and hashes in vfs_nodes. When reading, chunks are reassembled in order; when writing, new chunks are inserted and old overlapping ranges are invalidated. This deduplicates identical content across files automatically.

Why is prototype splicing necessary instead of normal class inheritance?

SQLiteWorkspaceProvider lives in packages/dofs and targets Cloudflare Workers, where @platformatic/vfs cannot be imported due to Node.js dependencies. The computerd package runs in Node.js and needs the provider to appear as a VirtualProvider. Splicing the prototype at runtime in computerd/src/fuse/vfs.ts satisfies both constraints: Workers builds stay clean, while Node.js builds gain full VFS compatibility.

Can multiple FUSE mounts share the same Durable Object storage?

Yes—multiple createNodeVirtualFileSystem() calls can instantiate separate SQLiteWorkspaceProvider instances backed by the same Database or synchronized storage. Each provider maintains independent file descriptor state, but the underlying SQLite tables provide a single source of truth. Concurrent access requires the provider's internal locking or external coordination.

What happens to unimplemented filesystem methods?

SQLiteWorkspaceProvider returns ENOSYS (function not implemented) for unsupported operations. This explicit failure mode prevents silent undefined behavior and makes compatibility gaps visible during development. Commonly stubbed methods include extended attribute operations and certain ioctl variants that lack SQLite equivalents.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →