Skip to content

§7.4 The runtime library

Every emitted project ships a single runtime module, out/runtime.ts, generated by bynkc. The per-context and per-test modules import their shared values and types from it — at ./runtime.js for a top-level module, or ../runtime.js repeated by directory depth. This section is the normative contract: a conforming implementation MUST emit a runtime library providing these exports with these shapes and semantics. The module is headed // Generated by bynkc — do not edit by hand. and is identical across projects.

The TypeScript blocks here are the contract, not examples; the dynamic meaning of the constructs in §7.3 is defined against them.

Both are discriminated unions over a tag field — the same shape user sum types lower to (§7.3.1). Ok, Err, and Some are constructor functions; None is a constant.

export type Result<T, E> =
| { readonly tag: "Ok"; readonly value: T }
| { readonly tag: "Err"; readonly error: E };
export type Option<T> =
| { readonly tag: "Some"; readonly value: T }
| { readonly tag: "None" };

Ok/Err are shared with HttpResult (§7.4.3); a Bynk match discriminates them on tag and the ? operator (§7.3.2) inspects tag === "Err".

The error a refined or opaque .of constructor (§6.4) returns as the Err side of its Result:

export interface ValidationError {
readonly field: string;
readonly message: string;
readonly value: unknown;
}

The built-in HTTP-result sum (§5.7), a tag-discriminated union with a constructor namespace. Its variants track the common, modern HTTP status codes (RFC 9110) — success (Ok, Created, Accepted, NoContent), redirection carrying a Location URL (Found, SeeOther, PermanentRedirect, …), and the client/server failures (BadRequest, NotFound, TooManyRequests, ServerError, …). See the HTTP reference for the full table. The runtime maps each variant to an HTTP response:

ExportRole
HttpResult<T>the result type, and HttpResult the constructor namespace
httpResultToResponse(result, serialiseValue)maps a variant to a Response with the corresponding status (Ok → 200, Created → 201, NoContent → 204, BadRequest → 400, … ServerError → 500)
matchPath(pattern, path)matches a route pattern such as /orders/:id, returning the captured parameters or null

QueueResult (v0.44) is the analogous built-in for the queue protocol: a non-generic tag-discriminated sum with a constructor namespace, variants Ack (confirm the message) and Retry (redeliver, carrying a String reason). A queue handler returns Effect[QueueResult]; the runtime routes Ackmsg.ack() and Retry → log the reason + msg.retry().

ExportRole
QueueResultthe verdict type and its Ack / Retry(reason) constructor namespace

Agent classes consume a Durable-Object-shaped state surface. In bundle mode and under bynkc test this is backed in memory; in workers mode it is a real Durable Object (§7.3.3).

ExportRole
DurableObjectStorage, DurableObjectStatethe storage and state interfaces an agent class consumes
KVNamespacethe Worker KV namespace shape the bynk.cloudflare binding consumes — get/put (with expirationTtl options, v0.23)/delete/list (the cursor-page shape the drain follows)
InMemoryStoragean in-memory DurableObjectStorage, used in bundle mode and tests
makeTestState(name)builds an in-memory DurableObjectState
serialiseAgentKey(value)serialises an agent key to a stable string — semantically-equal keys (records compared by sorted fields) MUST serialise identically
StateRegistry<K>a serialised-key-to-state map with getOrCreate(key) and reset(); reset() clears all state so a fresh test sees a clean slate
DurableObjectStub, DurableObjectNamespacea minimal structural view of the Cloudflare Durable Object surface
callDurableObjectMethod(stub, method, args, deps)routes a workers-mode agent method call through the stub under the /_bynk/agent/<method> protocol
makeWorkersAgent(binding, key)a typed proxy over a Durable Object stub
makeAgent(registry, binding, key, constructBundle)the single construction helper: a present binding selects the workers path, an absent one the bundle registry path, so call sites are identical across targets
makeIntegrationDoNamespace(construct)an in-process Durable-Object namespace for multi-Worker integration tests

§7.4.5 The cross-Worker boundary protocol

Section titled “§7.4.5 The cross-Worker boundary protocol”

On the workers target a cross-context call is JSON over a Service Binding, validated at the boundary (§6.5).

export type JsonValue =
| null | boolean | number | string
| JsonValue[]
| { [k: string]: JsonValue };
export type BoundaryError =
| { readonly kind: "MalformedJson"; readonly details: string }
| { readonly kind: "StructuralMismatch"; readonly path: string;
readonly expected: string; readonly actual: string }
| { readonly kind: "RefinementViolation"; readonly path: string;
readonly violation: ValidationError }
| { readonly kind: "Transport"; readonly status: number; readonly details: string };
ExportRole
ServiceBindingthe fetch-shaped binding a consumer calls
callService(binding, servicePath, argsJson, deserialiseResult, callerContext)issues the call to /_bynk/call/<servicePath>, decodes the response, and raises a BoundaryError on transport or shape failure. v0.54: callerContext (the calling context’s qualified name) is stamped into the X-Bynk-Caller header so the callee’s by c: Caller handler reads a live CallerId; the args body is unchanged
boundaryError(error)wraps a BoundaryError as a throwable Error

Two parts of the surface have no runtime export, by design:

  • Brands are compile-time TypeScript intersections, erased after type-checking (§7.3.1); the runtime library carries no brand values.
  • Effect is realised as Promise (§7.3.2); there is no Effect runtime value. Effect.pure lowers away at emission and <- lowers to await, so neither needs runtime support.

The compiler provides four prelude actors — boundary contracts available without a declaration: Visitor (auth = None; identity ()), and the Internal actors Scheduler, Producer, and Caller that back the per-protocol defaults (cron, queue, and on call respectively). Caller yields the calling-context identity; the other prelude actors carry no identity payload (()) in this increment. Prelude actors have no runtime export — like brands, an actor is a compile-time contract; the zero-crypto schemes mint no runtime verification code (see §7.3).

§7.4.8 Authenticated-scheme verifiers (v0.47, v0.51)

Section titled “§7.4.8 Authenticated-scheme verifiers (v0.47, v0.51)”

The authenticated schemes do export runtime verification helpers, emitted into the per-Worker runtime module and called from the boundary seam (§7.3.4a):

  • verifyBearerJwtHs256 (Bearer, v0.47) — HS256-verifies a JWT with WebCrypto (crypto.subtle.verify, constant-time), rejects any alg ≠ HS256, enforces exp/nbf (rejecting malformed NumericDate claims), and returns the sub claim — plus, since v0.53, the full verified claims object for refinement-actor authorisation — on success; any failure is reported so the seam maps it to a 401. The authorisation predicate of a refinement actor is lowered inline over those claims (no new runtime export), a failed invariant mapping to 403.
  • verifySignatureHmacSha256 (Signature, v0.51) — recomputes HMAC-SHA256 over the raw request body (or <timestamp>.<body> when a timestamp is bound) with the sourced secret and constant-time-compares (crypto.subtle.verify) against the configured signature header, accepting a bare hex digest or a sha256=<hex> prefix. When a timestamp is configured it MUST be a finite number within tolerance seconds of now; a malformed hex signature, an absent header, or a stale/non-numeric timestamp returns false. Returns a boolean — the seam maps false to a 401.

Both source their secret from env (the same channel the Secrets capability reads); the secret is used only inside the verifier and never logged.

§7.4.9 Streams and connections (v0.100, v0.102+)

Section titled “§7.4.9 Streams and connections (v0.100, v0.102+)”

A Stream[T] has no runtime-library export: it lowers to a host AsyncIterable<T> emitted inline (§7.3.10), so the runtime module gains nothing for stream code. A streamed HTTP response is a standard Response whose body is the stream encoded as Server-Sent Events.

A Connection[F] is lowered against a runtime Connection<F> interface with send(frame: F): void (JSON-encodes and writes a frame) and close(): void (ends the socket). Two implementations satisfy it:

  • TestConnection<F> (bundle/test) — a capture-and-inspect channel exposing sent: F[] (the frames written to it) and closed: boolean, so a WebSocket service is fully testable under Node with no Durable Object.
  • the Workers binding — wraps a hibernatable Durable-Object WebSocket; a stored connection survives hibernation (re-associated via the platform’s serializeAttachment/getWebSockets) and is restored on rehydration, a platform-supplied guarantee the language relies on but does not implement.

Held connections are never serialised into the durable state record; on Workers a Map[K, Connection] lives in an in-memory side-table keyed alongside the durable state.