How a Bynk program is shaped
Bynk is architecture-first: the large-scale structure of a program — its modules, boundaries, state, and dependencies — is expressed in the language itself, not left to folder conventions. This page is the mental model, end to end.
The architecture is written in the language — modules, boundaries, and dependencies — then mapped onto a deployment target.
Text equivalent: a commons of pure types and functions is brought into a
context with uses. A context holds services (from http / on call), agents
(key, state, handlers), capabilities (interfaces injected with given), and
providers (provides, which implement a capability). A context reaches another
context’s services with consumes. Compiling targets either bundle (one
TypeScript tree, direct in-process calls) or workers (one Worker per context;
agents become Durable Objects; cross-context calls go over Service Bindings,
validated at the boundary).
The two kinds of module
Section titled “The two kinds of module”Every Bynk declaration lives in one of two top-level units:
commons— pure, stateless building blocks: types and functions. Acommonscompiles to plain TypeScript types and functions. It has no state, no effects, and no dependencies on contexts.context— a deployable boundary. Contexts hold services, agents, capabilities, and handlers. A context is the unit Bynk deploys: on theworkerstarget, each context becomes its own Cloudflare Worker.
A useful way to think about it: commons is your domain vocabulary; context is
a running, deployable piece of your system.
What lives in a context
Section titled “What lives in a context”- Services group request handlers. A handler is an
on call(an internal entry point) or anfrom http(an HTTP endpoint). - Agents own state, keyed by identity. See The agent model.
- Capabilities are dependencies a handler asks for with
given— an interface the context needs but does not itself implement. - Providers (
provides) supply an implementation of a capability.
How the pieces connect
Section titled “How the pieces connect”Two relationships wire a program together:
usesbrings acommonsinto scope — how a context (or another commons) reaches shared types and functions.consumesdeclares a dependency on another context’s services. The consumer calls them by qualified name (optionally through anasalias). This is the only way one context calls into another, so the dependency graph between contexts is always written down and checkable.
Capabilities and consumes together mean dependencies are explicit at two
levels: what a handler needs (capabilities, injected) and what a context depends
on (other contexts, consumed). Neither is implicit, so the architecture cannot
quietly acquire a dependency nobody declared.
Effects thread through
Section titled “Effects thread through”Anything that touches state, a capability, or another context is effectful, and
its type says so: handlers return Effect[T]. Effectful results are sequenced
with <-. Pure code (a commons function) cannot perform effects, and the
compiler enforces the separation. The result is that the effectful parts of a
program are visible in the types, and the pure core stays pure.
From source to deployment
Section titled “From source to deployment”Compilation is a fixed pipeline:
The compile pipeline. Source is lexed, parsed, name-resolved, and
type-checked, then emitted as TypeScript; a final tsc --strict pass verifies
the emitted code, so a successful build is type-correct end to end.
The source layout is the structure: a unit’s file path must match its qualified name, so the tree on disk mirrors the architecture. Compilation then maps that structure onto the target:
- On
bundle, everything becomes one TypeScript tree and contexts call each other directly. - On
workers, each context becomes a Worker, agents become Durable Objects, and cross-context calls go over Service Bindings with validation at the boundary.
See Target Cloudflare Workers for the target details, and Why compile to TypeScript for why the runtime is what it is.