WorkspaceStub vs WorkspaceRuntimeStub vs WorkspaceFilesystemStub in Cloudflare Computer: Functional Differences Explained
Cloudflare Computer splits the remote view of a Workspace into three specialized stub objects: WorkspaceStub serves as the entry point container, while WorkspaceFilesystemStub and WorkspaceRuntimeStub each proxy a specific slice of the filesystem or runtime API across the Workers RPC boundary.
The @cloudflare/computer package implements a clean separation of concerns for remote workspace access. When a Worker or Durable Object needs to interact with a workspace hosted in another Durable Object, it doesn't receive the full Workspace instance directly. Instead, it receives type-safe stub objects that forward method calls across the RPC layer. Understanding the functional differences between these stubs is essential for building correct distributed applications on the platform.
WorkspaceStub: The Top-Level Facade
WorkspaceStub acts as the entry point and container for all remote workspace operations. It holds a reference to the host-side Workspace instance and exposes specialized sub-stubs as properties.
The class is defined in packages/computer/src/stub.ts with a constructor that stores the Workspace reference and registers the stub for leak tracking. You obtain a WorkspaceStub by calling Workspace.stub() — see the implementation in packages/computer/src/workspace.ts at line 519.
// Acquire from a Durable Object that owns the workspace
const ws: WorkspaceStub = await env.WSD.get(id).getWorkspace();
// Access sub-stubs through properties
const fs: WorkspaceFilesystemStub = ws.fs;
const runtime: WorkspaceRuntimeStub = ws.runtime;
// Also available: ws.assets, ws.artifacts
Key responsibilities:
- Provides a single entry point for workers to obtain any workspace capability
- Exposes sub-stubs as lazy-loaded properties
- Handles top-level lifecycle tracking via
trackStub(this)/untrackStub(this)(lines 96-101)
WorkspaceFilesystemStub: Filesystem Operations
WorkspaceFilesystemStub is a dedicated RPC proxy for the Workspace.fs API. It mirrors every public method of the in-process filesystem interface while preserving JSRPC-compatible types.
The implementation begins at line 90 in packages/computer/src/stub.ts. Each method follows a consistent pattern: wrap the underlying call in withSpan for observability, then forward arguments unchanged.
Available methods include:
readFile(path, encoding?)— returnsUint8Arrayor stringwriteFile(path, data)— acceptsstring,Uint8Array, orReadableStreamstat(path),mkdir(path, options),rm(path, options)
// Write binary data
await ws.fs.writeFile('/data.bin', new Uint8Array([0x00, 0x01, 0x02]));
// Read as UTF-8 string
const text = await ws.fs.readFile('/config.json', 'utf8');
// Stream large files
const stream = await ws.fs.createReadStream('/large.log');
The stub ensures type preservation across the RPC boundary: Uint8Array and ReadableStream objects serialize correctly through the Workers RPC layer. All operations are traced via withSpan for debugging.
WorkspaceRuntimeStub: Process Execution
WorkspaceRuntimeStub proxies runtime command execution through the Workspace.runtime API. Unlike the filesystem stub's many small methods, this stub focuses on a single high-impact operation: spawning processes.
The class definition starts at line 74 in packages/computer/src/stub.ts. Its core method is:
exec(command: string, options?: ExecOptions): Promise<WorkspaceRuntimeExecHandleStub>
The returned WorkspaceRuntimeExecHandleStub (lines 80-140) provides three critical capabilities:
| Method | Purpose |
|---|---|
result() |
Await completion; returns { exitCode, stdout, stderr } |
stream() |
Access live stdout/stderr as a ReadableStream<Uint8Array> |
kill() |
Terminate the running process |
// Start a command
const handle = await ws.runtime.exec('npm install');
// Option 1: Wait for completion
const { exitCode, stdout } = await handle.result();
if (exitCode !== 0) {
throw new Error(`npm failed: ${stdout}`);
}
// Option 2: Stream output for real-time logs
const stream = handle.stream();
const reader = stream.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) break;
process.stderr.write(new TextDecoder().decode(value));
}
// Option 3: Timeout-based cleanup
setTimeout(() => handle.kill(), 30000);
The exec() implementation first ensures the workspace is ready, then forwards to this.#ws.runtime.exec with full tracing.
Key Functional Differences
| Aspect | WorkspaceStub | WorkspaceFilesystemStub | WorkspaceRuntimeStub |
|---|---|---|---|
| Primary role | Container/entry point | Filesystem proxy | Process execution proxy |
| Number of methods | Few properties (fs, runtime, assets, artifacts) | Many methods (read, write, stat, mkdir, rm, etc.) | Few methods (exec, plus handle methods) |
| Return values | Sub-stub instances | Primitive types, buffers, streams | Exec handle stub with streaming interface |
| Streaming support | Indirect (via sub-stubs) | File streams | Live process stdout/stderr |
| Lifecycle disposal | Tracks own reference | Disposes filesystem proxy only | Disposes exec handle when finished |
How Stubs Relate: A Complete Example
import { WorkspaceStub } from '@cloudflare/computer';
// 1. Obtain the top-level stub
const ws: WorkspaceStub = await env.WORKSPACE_DO.get(id).getWorkspace();
// 2. Filesystem operations through WorkspaceFilesystemStub
await ws.fs.mkdir('/project', { recursive: true });
await ws.fs.writeFile('/project/package.json', JSON.stringify({
name: "my-project",
scripts: { build: "tsc" }
}));
// 3. Runtime operations through WorkspaceRuntimeStub
const build = await ws.runtime.exec('npm run build', {
cwd: '/project',
env: { NODE_ENV: 'production' }
});
// 4. Stream build output in real-time
const reader = build.stream().getReader();
while (true) {
const { value, done } = await reader.read();
if (done) break;
console.log(new TextDecoder().decode(value));
}
// 5. Get final result
const { exitCode, stdout } = await build.result();
if (exitCode !== 0) {
console.error('Build failed:', stdout);
}
Summary
-
WorkspaceStub is the root container that workers receive when accessing a remote workspace; it exposes
fsandruntimeproperties but implements no capability logic itself. -
WorkspaceFilesystemStub provides fine-grained filesystem access with methods like
readFile,writeFile, andstat; it preserves binary types across RPC. -
WorkspaceRuntimeStub enables process execution with streaming output support; its
exec()method returns a handle for async result retrieval, live streaming, or process termination.
All three stubs participate in leak tracking through trackStub/untrackStub calls and use withSpan wrappers for distributed tracing. The stub architecture lets Cloudflare Computer maintain type safety across the Workers ↔ Durable Object boundary while keeping each RPC surface focused and minimal.
Frequently Asked Questions
Can I use WorkspaceFilesystemStub or WorkspaceRuntimeStub directly without WorkspaceStub?
No — these stubs are only accessible as properties of a WorkspaceStub. The fs and runtime properties are instantiated lazily when accessed. See the property definitions in packages/computer/src/stub.ts lines 74-90.
What happens if I don't call kill() on a runtime exec handle?
The process continues running until it completes naturally. However, all stubs track their lifecycle through trackStub, and unreferenced stubs are eventually garbage collected. For explicit cleanup, call handle.kill() or let the handle fall out of scope.
Are streaming methods like stream() backpressure-safe?
Yes — the ReadableStream returned by handle.stream() is a standard Web Streams implementation that respects backpressure. The underlying RPC layer in Workers handles flow control automatically.
How do I know which encoding to use with readFile()?
Omit the encoding parameter to receive Uint8Array, or specify 'utf8' for strings. This matches Node.js fs conventions. Binary files should use the default; text files typically use 'utf8'.
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 →