§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.
§7.4.1 Result and Option
Section titled “§7.4.1 Result and Option”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".
§7.4.2 ValidationError
Section titled “§7.4.2 ValidationError”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;}§7.4.3 HttpResult
Section titled “§7.4.3 HttpResult”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:
| Export | Role |
|---|---|
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 Ack →
msg.ack() and Retry → log the reason + msg.retry().
| Export | Role |
|---|---|
QueueResult | the verdict type and its Ack / Retry(reason) constructor namespace |
§7.4.4 Agent state
Section titled “§7.4.4 Agent state”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).
| Export | Role |
|---|---|
DurableObjectStorage, DurableObjectState | the storage and state interfaces an agent class consumes |
KVNamespace | the 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) |
InMemoryStorage | an 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, DurableObjectNamespace | a 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 };| Export | Role |
|---|---|
ServiceBinding | the 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 |
§7.4.6 Non-exports: brands and Effect
Section titled “§7.4.6 Non-exports: brands and Effect”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.
Effectis realised asPromise(§7.3.2); there is noEffectruntime value.Effect.purelowers away at emission and<-lowers toawait, so neither needs runtime support.
§7.4.7 Prelude actors (v0.45)
Section titled “§7.4.7 Prelude actors (v0.45)”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 anyalg ≠ HS256, enforcesexp/nbf(rejecting malformed NumericDate claims), and returns thesubclaim — plus, since v0.53, the full verifiedclaimsobject 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 asha256=<hex>prefix. When a timestamp is configured it MUST be a finite number withintoleranceseconds of now; a malformed hex signature, an absent header, or a stale/non-numeric timestamp returnsfalse. Returns a boolean — the seam mapsfalseto 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 exposingsent: F[](the frames written to it) andclosed: 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.