How to Contribute to Cloudflare Computer Runtime Types: A Developer's Guide
To contribute to Cloudflare Computer's runtime types, modify the TypeScript interfaces in packages/computer/src/runtime/types.ts, update the router logic in runtime.ts, and add tests covering serialization and edge cases.
The Cloudflare Computer repository provides a programmable execution environment through its Workspace Runtime API. The runtime types define the contract between callers (such as Workers) and the backends that execute commands or modules. This guide walks through the architecture, key files, and step-by-step process for contributing to these runtime types based on the actual source code in cloudflare/computer.
Understanding the Runtime Type Architecture
The Workspace Runtime is built around a layered type system that separates the public API from backend implementations. Before making changes, you need to understand how these interfaces interact.
Core Type Definitions
All public type definitions live in packages/computer/src/runtime/types.ts. The primary interfaces include:
| Interface | Purpose | Location |
|---|---|---|
WorkspaceRuntime |
Entry point that routes exec, getExec, killExec, and disposeExec calls |
runtime.ts (implementation), types.ts (interface) |
WorkspaceRuntimeExecHandle |
A ReadableStream of events with result(), kill(), and [Symbol.dispose] methods |
types.ts |
WorkspaceRuntimeEvent |
Low-level event shape for stdout, stderr, or exit with generic encoding support |
types.ts |
WorkspaceRuntimeResult |
Final aggregate containing status, exit code, captured output, and sync statistics | types.ts |
WorkspaceModuleBackend |
Backend protocol interface that backends implement for module execution | types.ts |
The generic E parameter on WorkspaceRuntimeEvent allows the same interface to represent either UTF-8 strings or raw Uint8Array buffers depending on the encoding option passed to exec().
Runtime Implementation Layer
The packages/computer/src/runtime/runtime.ts file contains the concrete WorkspaceRuntime class. This class performs three critical functions:
- Validates options passed to each operation
- Selects the correct backend based on the
backendoption (e.g.,'container','worker-javascript') - Wraps returned handles in a streaming-or-result API that guarantees exclusive use
Two helper functions in this file handle the complex stream transformations:
transformModuleEvents— converts raw byte streams into UTF-8 strings whenencoding: 'utf8'is specifieddrainModuleResult— aggregates streams into a completeWorkspaceRuntimeResult
Step-by-Step: Contributing New Runtime Types
Follow this five-step process when adding new capabilities or fixing bugs in Cloudflare Computer's runtime types.
Step 1: Add or Modify Types in types.ts
Begin by editing packages/computer/src/runtime/types.ts. Follow the established naming convention: prefix all public types with WorkspaceRuntime.
// Example: Adding a new exec option for signal handling
export interface WorkspaceRuntimeExecOptions {
backend: string;
encoding?: 'utf8' | 'bytes';
cwd?: string;
env?: Record<string, string>;
stdin?: ReadableStream<Uint8Array> | string;
timeout?: number;
signal?: AbortSignal; // New field
}
Maintain backward compatibility where possible. New fields should be optional with sensible defaults.
Step 2: Update the Runtime Router
If your change introduces a new operation or requires different validation, modify the WorkspaceRuntime class in runtime.ts:
// In packages/computer/src/runtime/runtime.ts
async exec(
command: string,
options: WorkspaceRuntimeExecOptions
): Promise<WorkspaceRuntimeExecHandle> {
// Validate new signal option
if (options.signal?.aborted) {
throw new Error('Exec aborted before start');
}
const backend = this.selectBackend(options.backend);
const handle = await backend.exec(command, options);
return new WorkspaceRuntimeExecHandleImpl(handle, options.encoding);
}
Step 3: Adjust Streaming Logic
New event kinds require updates to transformModuleEvents or drainModuleResult. For example, adding a resize event for terminal emulation:
// In runtime.ts - extending event transformation
function transformModuleEvents(
stream: ReadableStream<WorkspaceModuleEvent>,
encoding: 'utf8' | 'bytes'
): ReadableStream<WorkspaceRuntimeEvent> {
return new TransformStream({
transform(event, controller) {
switch (event.name) {
case 'stdout':
case 'stderr':
case 'exit':
// Existing handling
break;
case 'resize': // New event type
controller.enqueue({ name: 'resize', value: event.value });
break;
}
}
});
}
Step 4: Write Comprehensive Tests
Add tests in packages/computer/src/runtime/runtime.test.ts or create new test files in the same directory. Cover:
- Serialization — round-trip of options through backend boundaries
- Error handling — invalid IDs, timeout scenarios, backend failures
- Edge cases — empty IDs, oversized IDs, malformed UTF-8 sequences
// Example test structure
describe('WorkspaceRuntimeExecOptions', () => {
it('validates signal option', async () => {
const controller = new AbortController();
controller.abort();
await expect(ws.runtime.exec('echo', {
backend: 'container',
signal: controller.signal
})).rejects.toThrow('Exec aborted before start');
});
it('handles empty exec ID', async () => {
await expect(ws.runtime.getExec('')).rejects.toThrow(/invalid.*id/i);
});
});
Step 5: Document the Public API
Update two documentation locations:
packages/computer/README.md— package-level usage examples for consumersdocs/README.md— design specification explaining architectural decisions and intended evolution
Include method signatures, parameter descriptions, and version information when relevant.
Working with Backend Protocols
The runtime abstracts over multiple backend implementations through the WorkspaceModuleBackend interface. When contributing types that affect backend communication, coordinate changes across:
| Component | File | Responsibility |
|---|---|---|
| Backend handle factory | types.ts |
backendHandle type definition |
| Concrete backend | Backend-specific files | Implements WorkspaceModuleBackend and returns WorkspaceModuleBackendHandle |
| Runtime router | runtime.ts |
Calls exec, getExec, killExec, disposeExec on the backend handle |
Practical Code Examples
Running a Command with UTF-8 Output
import { Workspace } from '@cloudflare/computer';
const ws = new Workspace(/* configuration */);
const handle = await ws.runtime.exec('echo "hello world"', {
backend: 'container',
encoding: 'utf8',
});
const result = await handle.result();
console.log(result.stdout); // → hello world
Streaming Real-Time Output
const handle = await ws.runtime.exec('yes', {
backend: 'container',
encoding: 'bytes' // Receive raw Uint8Array
});
for await (const event of handle) {
if (event.name === 'stdout') {
console.log(new TextDecoder().decode(event.value));
}
if (event.name === 'exit') break;
}
// Clean up resources
handle[Symbol.dispose]();
Resuming a Previous Execution
const execId = 'my-long-running-task';
const handle = await ws.runtime.getExec(execId, {
backend: 'worker-javascript',
resume: 'tail' // Start from most recent output
});
Cleaning Up Remote Resources
await ws.runtime.disposeExec(execId, {
backend: 'container'
});
Key Files Reference
| File | Purpose | Lines to Watch |
|---|---|---|
packages/computer/src/runtime/types.ts |
All public type definitions | Interface declarations, generics with E parameter |
packages/computer/src/runtime/runtime.ts |
Runtime router and stream wrappers | WorkspaceRuntime class, transformModuleEvents, drainModuleResult |
packages/computer/src/runtime/runtime.test.ts |
Test suite for runtime API | Serialization, error, and edge case coverage |
packages/computer/README.md |
Package documentation | Usage examples, API overview |
docs/README.md |
Design specification | Architectural rationale, evolution roadmap |
Summary
- Cloudflare Computer runtime types are defined in
packages/computer/src/runtime/types.tswith theWorkspaceRuntime*naming convention - The runtime router in
runtime.tsvalidates options, selects backends, and wraps handles with streaming-or-result semantics - Stream transformation helpers (
transformModuleEvents,drainModuleResult) handle encoding conversions and result aggregation - Contributions require type changes, router updates, streaming logic adjustments, comprehensive tests, and documentation updates
- Backend protocols implement
WorkspaceModuleBackendand are selected by thebackendoption string
Frequently Asked Questions
What encoding options does WorkspaceRuntimeExecHandle support?
The encoding option accepts either 'utf8' for string output or 'bytes' (default) for raw Uint8Array. This is controlled by the generic E parameter on WorkspaceRuntimeEvent<E>. When 'utf8' is specified, transformModuleEvents automatically decodes byte streams using TextDecoder.
How do I add a new backend to the runtime?
Define your backend's options interface in types.ts, implement the WorkspaceModuleBackend interface, then register it in the runtime's backend selection logic. The backend must return a WorkspaceModuleBackendHandle with exec, getExec, killExec, and disposeExec methods that match the runtime's expected signatures.
Why does WorkspaceRuntimeExecHandle guarantee exclusive use of streaming or result()?
This design prevents race conditions between consuming the ReadableStream and awaiting the result() promise. Once you call result(), the stream is drained and consumed. Attempting to iterate the stream after calling result() will yield no events. This invariant is enforced by the implementation in runtime.ts.
Where should I add tests for new runtime types?
Add tests in packages/computer/src/runtime/runtime.test.ts or create additional *.test.ts files in the same directory. Test coverage should include: valid and invalid option serialization, backend error propagation, timeout behavior, and edge cases like empty or oversized IDs.
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 →