# How to Implement Filesystem Primitives Against SQLite Storage in Durable Objects

> Learn to implement filesystem primitives with SQLite storage in Durable Objects. The dofs package offers a production-ready virtual filesystem with Node.js-style mkdir writeFile and readFile.

- Repository: [Cloudflare/computer](https://github.com/cloudflare/computer)
- Tags: how-to-guide
- Published: 2026-09-04

---

**The `@cloudflare/dofs` package provides a production-ready, SQLite-backed virtual filesystem that runs entirely inside Cloudflare Durable Objects, exposing synchronous Node.js-style primitives like `mkdir`, `writeFile`, and `readFile` through a transactional storage layer.**

Durable Objects (DOs) provide stateful coordination for serverless applications, but they lack a native filesystem interface. By leveraging the `@cloudflare/dofs` library—extracted from the Cloudflare Workers runtime—you can mount a POSIX-like filesystem inside any DO using SQLite as the storage engine. This implementation stores file metadata, directory entries, and blob contents in dedicated `vfs_*` tables while exposing low-level primitives that handle path canonicalization, revision tracking, and cache invalidation automatically.

## Architecture of the SQLite Filesystem Layer

The implementation follows a strict three-tier architecture that separates storage concerns from filesystem semantics.

### Database Wrapper

At the lowest level, the `Database` class in [`src/storage.ts`](https://github.com/cloudflare/computer/blob/main/src/storage.ts) wraps the DO’s `DurableObjectStorage` interface. It provides **synchronous transaction support** via `transactionSync()`, which uses SQLite SAVEPOINTs for nested calls, and convenience methods like `run()`, `one()`, `all()`, and `scalar()` for SQL execution. The wrapper also maintains an `inTransaction` flag consumed by the path resolution cache.

### SQLite Schema

The schema lives in [`src/schema/core.ts`](https://github.com/cloudflare/computer/blob/main/src/schema/core.ts) and [`src/schema/sync.ts`](https://github.com/cloudflare/computer/blob/main/src/schema/sync.ts), defining tables such as `vfs_nodes` (inode metadata), `vfs_dirents` (directory entries), and `vfs_blob_bytes` (content-addressed file data). The `initializeSchema()` function creates these tables on first boot, while `incrementRev()` generates monotonic revision numbers required by the sync protocol. Every mutating primitive writes this revision to `vfs_nodes.rev`, enabling tombstone tracking for distributed consistency.

### Filesystem Primitives

Each primitive resides under `src/fs/` and follows an identical workflow:
1. **Canonicalize the path** via `canonicalizePath` ([`src/path.ts`](https://github.com/cloudflare/computer/blob/main/src/path.ts))
2. **Enter a transaction** using `db.transactionSync()`
3. **Modify `vfs_*` tables** (insert rows for directories, write blobs for files)
4. **Update the revision** via `incrementRev()`
5. **Invalidate caches** via `invalidateResolveExact` ([`src/fs/resolveCache.ts`](https://github.com/cloudflare/computer/blob/main/src/fs/resolveCache.ts))
6. **Guard read-only mounts** using `assertNotReadOnly` ([`src/fs/mount-guard.ts`](https://github.com/cloudflare/computer/blob/main/src/fs/mount-guard.ts))

## Setting Up Filesystem Primitives in a Durable Object

To use the filesystem, instantiate the `Database` wrapper inside your DO constructor and initialize the schema before performing any operations.

```typescript
// src/workspace-do.ts
import { Database, initializeSchema } from "@cloudflare/dofs";
import { mkdir, writeFile, readFile, readdir } from "@cloudflare/dofs/src/fs";

export class WorkspaceDO extends DurableObject {
  private readonly db: Database;

  constructor(state: DurableObjectState, env: Env) {
    super(state, env);
    this.db = new Database(state.storage);
    // Initialize vfs_* tables on first run
    initializeSchema(this.db, Date.now);
  }

  async addSampleFile() {
    const now = Date.now;
    mkdir(this.db, "/example", { recursive: true }, now);
    writeFile(
      this.db, 
      "/example/hello.txt", 
      Buffer.from("👋 Hello!"), 
      {}, 
      now
    );
  }

  async getSampleFile(): Promise<string> {
    const buf = readFile(this.db, "/example/hello.txt");
    return Buffer.from(buf).toString("utf-8");
  }

  async listRoot(): Promise<string[]> {
    const entries = readdir(this.db, "/");
    return entries.map(e => e.name);
  }
}

```

**Critical implementation details:**
- All primitives are **synchronous** but run inside an atomic transaction that auto-rolls back on error
- Pass `Date.now` (or a custom clock) to supply `mtime` and `ctime` timestamps
- The `Database` instance must persist for the lifetime of the DO to maintain cache consistency

## Common Filesystem Operations

### Creating Nested Directories

The `mkdir` primitive in [`src/fs/mkdir.ts`](https://github.com/cloudflare/computer/blob/main/src/fs/mkdir.ts) supports recursive creation and POSIX mode bits.

```typescript
import { mkdir } from "@cloudflare/dofs/src/fs/mkdir";

// Creates /a/b/c, creating any missing intermediate directories
mkdir(db, "/a/b/c", { recursive: true, mode: 0o755 }, Date.now);

```

Under the hood, this inserts a row into `vfs_nodes` with `type: 'directory'` and adds corresponding entries to `vfs_dirents` for each parent-child relationship.

### Writing Files with Blob Storage

`writeFile` (located in [`src/fs/writeFile.ts`](https://github.com/cloudflare/computer/blob/main/src/fs/writeFile.ts)) handles content addressing automatically. It inserts byte arrays into `vfs_blob_bytes` and updates the inode’s `rev` column.

```typescript
import { writeFile } from "@cloudflare/dofs/src/fs/writeFile";

const data = new Uint8Array([72, 101, 108, 108, 111]); // "Hello"
writeFile(db, "/a/b/c/greeting.txt", data, {}, Date.now);

```

### Reading Files and Listing Directories

`readFile` ([`src/fs/readFile.ts`](https://github.com/cloudflare/computer/blob/main/src/fs/readFile.ts)) returns a `Uint8Array` via the content-addressed blob cache, while `readdir` ([`src/fs/readdir.ts`](https://github.com/cloudflare/computer/blob/main/src/fs/readdir.ts)) paginates through `vfs_dirents`.

```typescript
import { readFile } from "@cloudflare/dofs/src/fs/readFile";
import { readdir } from "@cloudflare/dofs/src/fs/readdir";

const content = readFile(db, "/a/b/c/greeting.txt");
console.log(new TextDecoder().decode(content)); // → Hello

const entries = readdir(db, "/a/b/c");
console.log(entries.map(e => e.name));

```

The `entries` array contains objects with `name`, `type`, `size`, and `mtime` properties sourced from the joined `vfs_nodes` and `vfs_dirents` tables.

## Advanced Features and Constraints

### Transactional Consistency

Every primitive operates within `db.transactionSync()`, ensuring that path resolution, inode creation, and revision bumping are atomic. If a primitive throws, SQLite automatically rolls back to the SAVEPOINT established at the transaction boundary.

### Cache Invalidation

When directory entries change, `invalidateResolveExact` ([`src/fs/resolveCache.ts`](https://github.com/cloudflare/computer/blob/main/src/fs/resolveCache.ts)) drops stale path-to-inode mappings. This prevents stale reads when a file is moved or deleted between operations.

### Read-Only Mount Guards

For FUSE integrations where the filesystem is mounted read-only, `assertNotReadOnly` and `assertNotInReadOnlyMount` ([`src/fs/mount-guard.ts`](https://github.com/cloudflare/computer/blob/main/src/fs/mount-guard.ts)) throw before any mutating SQL is executed, protecting against accidental writes in the `computerd` daemon context.

## Summary

- **`@cloudflare/dofs`** provides a synchronous, SQLite-backed filesystem API for Durable Objects through three layers: storage wrapper, schema management, and primitive implementations.
- **Initialize once** using `initializeSchema()` in the DO constructor before calling any filesystem methods.
- **All operations are transactional** via `Database.transactionSync()`, ensuring atomicity across metadata and blob updates.
- **Primitives are low-level** (`mkdir`, `writeFile`, `readFile`, `readdir`, etc.) and live in `src/fs/*`, designed to be composed into higher-level APIs like the `SQLiteWorkspaceProvider`.
- **Revision tracking** via `incrementRev()` and cache invalidation via `invalidateResolveExact` keep distributed views consistent.

## Frequently Asked Questions

### How does the filesystem handle concurrent writes from multiple DO instances?

Each Durable Object is single-threaded, but the SQLite transaction model in [`src/storage.ts`](https://github.com/cloudflare/computer/blob/main/src/storage.ts) ensures atomicity for concurrent requests hitting the same DO. The `transactionSync()` method uses SQLite SAVEPOINTs to handle nested calls, and the `incrementRev()` function provides monotonic versioning that the sync protocol uses to resolve conflicts across distributed instances.

### Can I use standard Node.js `fs` promises with this implementation?

Not directly. The primitives in `@cloudflare/dofs` are synchronous and operate on the `Database` class rather than file descriptors. However, the `SQLiteWorkspaceProvider` in the `@platformatic/vfs` adapter wraps these primitives into a Node-compatible `fs` interface that the `computerd` daemon mounts via FUSE, enabling standard `fs.promises` usage at a higher abstraction layer.

### What happens if a write operation fails midway?

Because every primitive runs inside `db.transactionSync()`, any thrown error triggers an automatic ROLLBACK to the SAVEPOINT established at the start of the operation. This ensures that partial writes—such as an inode insertion without the corresponding blob write—never leave the database in an inconsistent state. The `inTransaction` flag in [`src/storage.ts`](https://github.com/cloudflare/computer/blob/main/src/storage.ts) tracks nesting depth to handle complex operations like recursive `mkdir`.

### Where is file content actually stored in the SQLite database?

File contents are stored in the `vfs_blob_bytes` table defined in [`src/schema/core.ts`](https://github.com/cloudflare/computer/blob/main/src/schema/core.ts), using content-addressed storage keyed by hash. The `vfs_nodes` table stores metadata (size, permissions, revision) and references the blob via its hash, while `vfs_dirents` maintains the directory tree structure. This separation allows deduplication and efficient garbage collection via the `gc` primitive.