The data and the api
Couchbase
Couchbase is somebody else's software, so the component says which image runs it and which driver speaks to it. It declares no code of ours:
kind = "online-service"
drivers.couchbase = { kind = "registry", package = "bluetext-driver-couchbase", version = "0.3" }
[interfaces.data]
kind = "other"
driver = "couchbase"
serves-api-configured-state = true
[interfaces.data.profiles.data]
credential-bundle = "admin"
Three lines are worth reading closely.
drivers.couchbase names the crate that speaks to it. A consumer's client
types and methods come from that crate, so the driver is the contract: model
code calling this link is writing Couchbase calls. The driver sits on the
interface and cannot be changed by a variant, because that would change the API
the model compiled against. That is why the component is called couchbase and
not store — nothing else can ever be behind the name.
profiles.data is a named way of consuming the interface, and
credential-bundle = "admin" says which credentials that way needs. A second
consumer wanting weaker rights gets a second profile rather than sharing this
one.
serves-api-configured-state says the server holds state that an api call
has to establish before it is usable. For Couchbase that is the bucket, declared
in a separate file:
[pre-consumer-apply]
buckets = [{ name = "todos", ram-mb = 256 }]
The deploy applies this before any consumer of the interface starts, so nothing
races an empty server. Separately, the variant carries a [bootstrap] block
naming a driver function that runs once against a fresh container — for
Couchbase, creating the administrator that the bucket call then authenticates
as.
The model
Business logic lives in parts, which belong to the system rather than to any component. A part declares what it reaches and which other parts it builds on:
# src/models/parts/todos-core.toml
links.store = "couchbase/data"
parts = []
Its Rust lives beside it in src/models/, and the platform generates a
links::store module from that link, so the part's code gets a typed Couchbase
handle without naming a host.
A component then binds the parts it needs — the api's variant says models = ["todos-core"]. The separation is what lets the same logic serve an HTTP
surface and a command-line client: the part does not know either exists.
The api
The component says what it serves and in which languages clients are generated:
kind = "online-service"
[interfaces.todos]
kind = "bluetext-api"
languages = ["rust", "ts"]
base-path = "/api"
kind = "bluetext-api" is what makes this a surface the platform generates
clients for, and languages says which. The TypeScript client is what the page
imports and the Rust one what the command-line client imports, both from these
declarations, so removing an endpoint breaks both consumers at compile time.
base-path is the prefix the surface answers at, and the one consumers resolve
to through the gateway.
The endpoints themselves are a file, one table per endpoint:
part = "todos-core"
caller = { admit = "admit_anyone" }
auth-refusal = "todos-auth-refusal-wire"
timeout = "3s"
[list-todos]
method = "GET"
path = "/todos"
response = "todo-list-wire"
refusal = "todo-refusal-wire"
[add-todo]
method = "POST"
path = "/todos"
request = "new-todo-wire"
response = "todo-wire"
refusal = "todo-refusal-wire"
The keys above the first endpoint apply to the whole surface: part binds the
logic that serves it, caller states who is admitted, auth-refusal the shape
returned when admission fails, and timeout the deadline for every call.
Each endpoint names a request, a response and a refusal, and those name
wire shapes declared in shapes/todos.toml. A refusal is required: an endpoint
declares how it fails as part of its contract, so a failure a caller can expect
is a typed answer rather than a 500.
The variant says what the api is made from:
kind = "source"
toolchain = "cargo"
base-image = "gcr.io/distroless/cc"
models = ["todos-core"]
serves.todos = { port = 8080 }
kind = "source" means the platform builds an image from our code, using
base-image as the base and toolchain to build; models binds the parts; and
serves.todos gives the interface its port. A component wrapping somebody
else's software says kind = "prebuilt" and image instead, which is what
Couchbase, Curity, Postgres and Kong each do.