When to Use `getWorkspace()` Stub vs Constructing Workspace Directly in Cloudflare Computer

Use getWorkspace() to return a WorkspaceStub when crossing Durable Object boundaries via Workers RPC, and construct new Workspace() directly when operating inside the same Durable Object or in unit tests.

Choosing between the getWorkspace() stub and direct Workspace construction determines how your application handles RPC boundaries in Cloudflare Computer. The Workspace class in packages/computer/src/workspace.ts provides both patterns to support internal DO operations and external access patterns. Understanding when to marshal calls through a stub versus accessing the local SQLite store directly impacts performance, API availability, and resource isolation.

Understanding the Two Approaches

What is the getWorkspace() Stub?

When a Durable Object (DO) exposes its workspace via getWorkspace(), it returns a WorkspaceStub instance defined in packages/computer/src/stub.ts (lines 94-106). This stub implements RpcTarget from the capnweb library, enabling it to cross Workers RPC boundaries. The stub exposes only high-level facades—fs, runtime, git, assets, artifacts, and useThink—and marshals all calls to the host Workspace.

Direct Workspace Construction

Inside the owning Durable Object, you instantiate Workspace directly using the constructor in packages/computer/src/workspace.ts (lines 32-88). This creates a local SQLiteWorkspaceProvider and provides full access to internal methods like provider(), ready(), push(), and pull() that the stub deliberately conceals. You can optionally enable the Think filesystem mixin by passing useThink: true.

Decision Framework: When to Use Each Pattern

Cross-DO or Worker Communication — Use getWorkspace(). When a Worker or another Durable Object needs to access a workspace owned by a different DO, the stub acts as a thin RPC proxy. Only value-shaped data (JSON, Uint8Array, newline-delimited byte frames) crosses the boundary, keeping transport overhead minimal.

Inside the Same Durable Object — Use new Workspace(). The DO already holds direct access to the local SQLite storage handle via this.ctx.storage. Direct construction eliminates RPC marshalling overhead and exposes the complete API surface, including methods hidden by the stub.

Unit Testing — Use new Workspace(). Tests can provide a mock DurableObjectStorageLike such as @cloudflare/dofs/testing's SQLiteTestStorage. This runs purely in-memory without RPC or stub tracking overhead.

Think Helpers Without External DO — Use new Workspace({ useThink: true }). The useThink option mixes filesystem helpers (readFile, writeFile, rm, glob) directly onto the instance. These helpers are only available through direct construction; the stub does not expose them.

Resource Isolation — Use workspace.stub(). When exposing a workspace to external code but hiding internal implementation details, the stub isolates the DO's resources and tracks its own lifetime via trackStub/untrackStub to prevent memory leaks.

Implementation Details

How Workspace.stub() Works

In packages/computer/src/workspace.ts (lines 57-64), the stub() method instantiates WorkspaceStub from packages/computer/src/stub.ts. The stub wraps the host Workspace and implements RpcTarget, allowing it to be transferred over Workers RPC. Each stub tracks its lifecycle through trackStub and automatically disposes sub-stubs via [Symbol.dispose] when the caller finishes.

The Workspace Constructor

The constructor in packages/computer/src/workspace.ts (lines 32-88) builds the full workspace infrastructure. It initializes the SQLiteWorkspaceProvider, registers mounts (such as /tmp or custom backends), and optionally applies the Think mixin when useThink: true is passed in the options.

RPC Boundaries and Data Shapes

The stub enforces that only value-shaped data crosses RPC boundaries. Streaming interfaces are not transported over the stub; instead, operations return handles or buffers. This design keeps the transport layer cheap and predictable across the Cloudflare Workers RPC boundary.

Code Examples

Accessing a Workspace from a Worker

When calling from a Worker into a Durable Object that owns the workspace, use the stub pattern:

export default {
  async fetch(request, env) {
    const wsStub = await env.WSD.get(env.WSD.idFromName("my-workspace")).getWorkspace();

    await wsStub.fs.writeFile("/hello.txt", "Hello from the Worker!");

    const execHandle = await wsStub.runtime.exec("ls /workspace");
    const { stdout } = await execHandle.result();

    return new Response(`Workspace listing:\n${stdout}`);
  },
};

The wsStub variable is a WorkspaceStub. All filesystem and runtime calls travel over the Workers RPC boundary, with automatic cleanup of sub-stubs when operations complete.

Direct Construction Inside a Durable Object

Inside the DO that owns the workspace, construct directly for full API access:

import { Workspace } from "./workspace.js";

export class MyDO extends DurableObject {
  #ws = new Workspace({
    storage: this.ctx.storage,
    backends: [{ id: "default", type: "container", connect: createContainerBackend }],
    mounts: { "/tmp": { kind: "tmp" } },
    useThink: true,
  });

  async fetch(request) {
    await this.#ws.fs.writeFile("/data.txt", "local write");
    const txt = await this.#ws.readFile("/data.txt");
    return new Response(txt);
  }

  async getWorkspace() {
    await this.#ws.ready();
    return this.#ws.stub();
  }
}

Direct construction provides access to internal methods like this.#ws.provider() and Think helpers like this.#ws.readFile(). The getWorkspace() method returns a stub for external callers.

Unit Testing Without RPC

For tests, instantiate with mock storage to avoid RPC entirely:

import { Workspace } from "./workspace.js";
import { SQLiteTestStorage } from "@cloudflare/dofs/testing";

test("workspace basic ops", async () => {
  const ws = new Workspace({
    storage: new SQLiteTestStorage(),
    backends: [],
  });

  await ws.fs.mkdir("/tmp");
  await ws.fs.writeFile("/tmp/file.txt", "test");
  const content = await ws.fs.readFile("/tmp/file.txt", "utf8");
  expect(content).toBe("test");
});

This pattern runs purely in-process with no stub tracking or RPC overhead.

Summary

  • Use getWorkspace() (which returns WorkspaceStub) when crossing Workers RPC boundaries between Workers and Durable Objects or between different DOs.
  • Construct new Workspace() directly when operating inside the same Durable Object that owns the workspace.
  • Enable Think filesystem helpers by passing useThink: true to the constructor; these methods are unavailable through the stub.
  • The stub tracks its own lifecycle via trackStub/untrackStub to prevent resource leaks when exposing workspaces externally.
  • Unit tests should use direct construction with SQLiteTestStorage to avoid RPC complexity.

Frequently Asked Questions

Can I use Think helpers through a WorkspaceStub?

No. The Think-compatible filesystem helpers (readFile, writeFile, glob, etc.) are only available when you construct the Workspace directly with useThink: true in packages/computer/src/workspace.ts. These methods are mixed into the instance at construction time and are not exposed through the WorkspaceStub RPC interface.

Why does getWorkspace() return a stub instead of the full Workspace?

The stub isolates the Durable Object's internal resources and enforces the Workers RPC contract. According to the implementation in packages/computer/src/stub.ts, the stub exposes only value-shaped data transfer and prevents external callers from accessing internal methods like provider() or ready(). This design maintains security boundaries and enables automatic lifecycle tracking.

How do I clean up WorkspaceStub resources?

The WorkspaceStub implements [Symbol.dispose] which automatically cleans up sub-stubs (for filesystem, runtime, etc.) when the calling scope ends. The stub also registers itself via trackStub and removes itself via untrackStub to prevent memory leaks. You generally do not need manual cleanup when using standard await patterns with RPC.

Can I instantiate Workspace outside a Durable Object in production?

While technically possible, direct instantiation outside a DO context in production is not recommended because you lose the persistence guarantees of DurableObjectStorage. The Workspace class expects a storage backend that persists across requests. For serverless contexts, always access workspaces through getWorkspace() stubs bound to Durable Objects.

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 →