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
VirtualProviderinterface 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 invfs_chunkswith metadata invfs_nodesandvfs_dirents. - FUSE integration occurs through
NodeVirtualFileSystem, which the driver mounts and routes kernel calls through. - Change detection polls
vfs_meta.revto providefs.watchcompatibility 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →