# Transport API URL: /runtime/api/transport 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. ```typescript 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. ```typescript import { nodeWorkerTransport } from '@taucad/runtime/transport/node'; const transport = nodeWorkerTransport({ url: new URL('./runtime.worker.ts', import.meta.url), }); ``` ## Exports by Subpath [#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 [#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. ```typescript 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; ``` ```typescript 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 [#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. ```typescript 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 [#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` | **`RuntimeTransportClient`** — Runtime-facing transport handle returned by client factories (e.g. {@link webWorkerClient }). The {@link RuntimeClient } consumes this handle and never inspects the implementation. Generic over the wire protocol and the per-transport bindings extras the host side will produce. - **`id`** (`Id`, required) — Literal id (matches the plugin's `id`). - **`machines`** (`RuntimeTransportFacet | undefined`, optional) — Optional authenticated machines service carried beside, never over, the CAD channel. - **`closed`** (`Promise`, required) — Resolves once with the first terminal transport cause. Never rejects. - **`operationTimeoutRecovery`** (`RuntimeTransportTimeoutRecovery`, required) — Behavioral timeout recovery. Runtime code uses this union directly and never infers enforceability from the diagnostic descriptor. - **`describe`** (`() => TransportDescriptor`, required) — Human/diagnostic descriptor; never used to branch runtime behaviour. - **`open`** (`() => Promise>`, required) — Open the wire, spawn the host (if applicable), exchange hello. Idempotent: calling `open()` twice resolves the same channel. - **`initialize`** (`(input: RuntimeInitializePayload) => Promise`, required) — Send the runtime `initialize` call. The transport assembles the {@link RuntimeInitializeMemoryHandle} envelope from its own internal state (allocated SAB pools, FS bridge port, etc.) and chooses transferable vs copy semantics based on what its wire supports. The runtime never sees the wire-level transferables list. A second call while initialization is in flight or after it succeeds rejects with {@link RuntimeAlreadyInitializedError}; a failed first attempt may be retried. - **`resolveBinary`** (`(transport: BinaryContentDelivery) => Promise>`, required) — Copy one inline or pooled binary payload, acknowledging pooled ownership. - **`close`** (`() => Promise`, required) — Close the wire, terminate the host. After `close()` resolves the transport is unusable; callers must construct a new instance. **`RuntimeTransportHost`** — Host-facing transport handle returned by host factories (e.g. {@link webWorkerHost }). Used inside kernel-host scripts (web-worker entry, node-worker entry, Electron utility-process entry). - **`id`** (`Id`, required) - **`closed`** (`Promise`, required) - **`open`** (`() => Promise>`, required) — Open the host-side wire, advertise hello. After `open()` resolves the channel is wired and the host can register protocol handlers. - **`adoptInitialize`** (`(handle: RuntimeInitializeMemoryHandle) => HostInitializeBindings`, required) — Adopt the {@link RuntimeInitializeMemoryHandle} delivered in the `initialize` request. The host transport reconstructs internal SAB pools, mounts the bridged FS port if present, arms the abort signal slot, and contributes any per-transport extras into the returned {@link HostInitializeBindings}. - **`close`** (`(reason?: string) => Promise`, required) ## Binary Delivery [#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 [#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. **`RuntimeTransportTimeoutRecovery`** — Behavioral wall-clock timeout capability supplied by a transport. Isolated transports can terminate the host if it does not acknowledge cancellation. Same-isolate transports report `unsupported` because their deadline timer cannot run while synchronous work blocks the same event loop. - **`kind`** (`"terminable" | "unsupported"`, required) The runtime owns document operation correlation and cancellation. Do not infer termination capability from a diagnostic descriptor or the presence of `SharedArrayBuffer`. ## Typed Closure [#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()`. **`RuntimeTransportCloseResult`** — First terminal cause observed by a runtime transport client. The {@link RuntimeTransportClient.closed} promise resolves once with this value and never rejects. - **`cause`** (`"requested" | "operation-timeout" | "host-exit" | "wire-failure"`, required) | 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. ## Related [#related] * [Configure Operation Timeouts](/runtime/guides/render-timeouts) * [Framework Integrations API](/runtime/api/frameworks) * [Embedding in a Host](/runtime/guides/embedding-in-a-host) * [Worker Model](/runtime/concepts/worker-model)