Transport API
Consumer topology helpers and author-facing transport contracts for document delivery, timeout recovery, and typed closure.
Most applications use a topology-specific high-level helper. Browser apps pair createWebWorkerClientOptions() with serveWebWorkerRuntime(), while Electron renderers use createElectronClientOptions(). Use the raw transport factories when binding a custom filesystem or host, and use @taucad/runtime/transport only when authoring a new transport.
import { createRuntimeClient } from '@taucad/runtime/client';
import { createWebWorkerClientOptions } from '@taucad/runtime/transport/web';
const clientOptions = createWebWorkerClientOptions({
createWorker: () => new Worker(new URL('./runtime.worker.ts', import.meta.url), { type: 'module' }),
});
const client = createRuntimeClient(clientOptions);The low-level Node transport requires the application-owned worker entry URL; createNodeClient() remains the zero-configuration in-process helper for command-line and server workloads.
import { nodeWorkerTransport } from '@taucad/runtime/transport/node';
const transport = nodeWorkerTransport({
url: new URL('./runtime.worker.ts', import.meta.url),
});Exports by Subpath
Concrete topology imports stay isolated per environment:
| Subpath | Exports |
|---|---|
/transport/in-process | inProcessTransport |
/transport/web | webWorkerTransport, webWorkerHost, webWorkerClient, createWebWorkerClientOptions, WebWorkerLike, WebWorkerTransportOptions, WebWorkerHostOptions |
/transport/node | nodeWorkerTransport, nodeWorkerHost, nodeWorkerClient, NodeWorkerLike, NodeWorkerClientOptions, NodeWorkerHostOptions |
/transport/websocket | webSocketTransport, webSocketClient, webSocketClientDescribe, WebSocketTransportOptions |
/transport/websocket-host | webSocketHost, WebSocketHostOptions, WebSocketHostHandle, closeCauseFor, isOriginAllowed |
Each *Transport factory is the wired plugin consumers pass to createRuntimeClient; the *Client / *Host factories are the standalone halves for custom host compositions.
Remote Hosts over WebSockets
webSocketTransport dials a kernel host in another process or on another machine. The client half is browser-safe; the Node server half ships separately at @taucad/runtime/transport/websocket-host so ws and node:http never reach a browser graph.
import { createRuntimeWorker, defineRuntime } from '@taucad/runtime/worker';
import { fromNodeFs } from '@taucad/runtime/filesystem/node';
import { webSocketHost } from '@taucad/runtime/transport/websocket-host';
const runtime = defineRuntime({});
const host = webSocketHost({
worker: () => createRuntimeWorker({ runtime }),
fileSystem: fromNodeFs('/srv/projects/demo'),
allowedOrigins: ['https://app.example.com'],
host: '0.0.0.0', // defaults to 127.0.0.1, which a remote browser cannot reach
port: 8080,
});
await host.ready;import { createRuntimeClient } from '@taucad/runtime/client';
import { webSocketTransport } from '@taucad/runtime/transport/websocket';
const client = createRuntimeClient({
transport: webSocketTransport({ url: 'ws://127.0.0.1:8080' }),
});The host serves one kernel worker per connection, so two clients never collide. The descriptor is { id: 'web-socket', wire: 'remote', memory: { geometryDelivery: 'copy', abortSignal: 'wire-notify' }, fileSystem: 'host-local' } — a socket carries neither transferables nor a SharedArrayBuffer. WebSocketHostHandle exposes ready, address(), and close().
Pass a fileSystem on the client instead (and start the host without one) to keep the project on the consumer's side. The transport then opens a second socket, /fs, on which the consumer is the bridge server: the remote kernel reads, writes and watches through it, and the descriptor reports fileSystem: 'bridged'. There is no multiplexer; the two sockets are correlated by a transport-private session id.
Both inline adapters such as fromMemoryFs() and channel handles returned by fromFileSystemBridge() are supported. A channel handle is materialized into a fresh bridge for each connection attempt, so a failed initialization never reuses a transferred MessagePort. Closing the runtime client closes the /runtime socket, /fs socket, and bridge together.
allowedOrigins is an exact-match allowlist enforced at the HTTP upgrade; a request with no Origin header (any Node client) is admitted, so the default [] denies every browser. The host also pings idle peers and drops the ones that stop answering. Remote hosts are bound to the same build — the wire hello carries protocolVersion and a mismatch is rejected at connect.
Sharing an HTTP server, authorizing an upgrade, bounding a frame
Pass server to attach to an HTTP (or HTTPS) server you already run, and pathPrefix to move both routes under a path. Matching is exact — ${pathPrefix}/runtime and ${pathPrefix}/fs — and on a server the host does not own every other upgrade is ignored, so another WebSocketServer on the same server keeps its own paths in either registration order. A host that owns its server still answers an unknown path with a raw 404.
authorize runs after the origin check and before the upgrade completes. Returning false or throwing refuses the upgrade with a raw 401. There is no client→server hello frame. Node hosts can send an authorization header; browser deployments should authenticate the browser upgrade with its existing cookie/session and use a short-lived non-secret session URL. Do not put durable bearer credentials in browser WebSocket URLs. The client's ordinary search params are preserved on both sockets.
import { createServer } from 'node:http';
import { createRuntimeWorker, defineRuntime } from '@taucad/runtime/worker';
import { webSocketHost } from '@taucad/runtime/transport/websocket-host';
const runtime = defineRuntime({});
const server = createServer(); // shared with the rest of the application
const pairedTokens = new Set(['a-short-lived-session-token']);
const host = webSocketHost({
worker: () => createRuntimeWorker({ runtime }),
server,
pathPrefix: '/rt', // serves /rt/runtime and /rt/fs
authorize: (request) =>
pairedTokens.has(new URL(request.url ?? '/', 'http://localhost').searchParams.get('token') ?? ''),
maxPayload: 32 * 1024 * 1024,
});
await host.ready;maxPayload is the per-frame ceiling in bytes, defaulting to ws's 100 MiB. There is no chunking or streaming sub-protocol, so one frame carries one whole message — a readFile result over the ceiling is never delivered and the socket closes with 1009, which the client settles as wire-failure. Raise it for large binary assets, or keep the default.
Author Subpath
@taucad/runtime/transport exports defineRuntimeTransport, definePassthroughTransport, and runtimeDocumentProtocolSchemas. Use the shared validators at both channel boundaries. Known message fields are validated; additive unknown payload fields are ignored. The client verifies the hello protocolVersion before initialization.
| Role | Types |
|---|---|
| Client | TransportPlugin, RuntimeTransportClient, RuntimeTransportCloseResult, RuntimeTransportTimeoutRecovery, RuntimeTransportFacet, TransportDescriptor |
| Host | RuntimeTransportHost, TransportClientReady, TransportHostReady, TransportHelloPayload, RuntimeInitializePayload, RuntimeInitializeMemoryHandle, HostInitializeBindings, HostInitializeBindingsCore, HostBinaryDeliveryBinding, EncodedBinary |
| Channel | Channel, ChannelServerHandle, Port, RpcProtocol, WithTransferables |
| Projections | TransportPluginId, TransportId, TransportProtocol, TransportBindingsExtra, TransportClientOptions, TransportHostOptions, RuntimeFromTransport |
Prop
Type
Prop
Type
Binary Delivery
resolveBinary materializes document view artifacts and export files into owned bytes before delivering them to consumers. Host bindings expose binaryDelivery.publishBytes(key, bytes) and acknowledge(key). Pooled delivery must copy before acknowledging ownership. Inline delivery must also preserve consumer ownership. Terminal notifications settle only after materialization and preceding operation logs and telemetry.
initialize assembles transport-private memory and filesystem bindings. A concurrent or successful second initialization rejects with RuntimeAlreadyInitializedError; a failed first attempt may be retried. Keep native abort-slot layouts and transferable lists inside the transport implementation.
Timeout Recovery
operationTimeoutRecovery is the current transport capability name. Its kind: 'terminable' branch terminates exactly this client's isolated host and settles closure with the timeout cause. Same-isolate transports report { kind: 'unsupported' } because synchronous work can block their deadline timer.
Prop
Type
The runtime owns document operation correlation and cancellation. Do not infer termination capability from a diagnostic descriptor or the presence of SharedArrayBuffer.
Typed Closure
closed resolves once with the first terminal cause and never rejects. close() is idempotent and releases exactly the host owned by that materialized client. createRuntimeClient observes closure from construction onward, including failures before or during open().
Prop
Type
| Cause | Meaning |
|---|---|
requested | Consumer closed or terminated the client. |
operation-timeout | Deadline recovery terminated this isolated host. |
host-exit | Host exited independently; phase distinguishes boot from session. |
wire-failure | Channel failed; the result retains the original error. |
A later requested close cannot overwrite a previously observed exit, wire failure, or timeout.