Trellis is a distributed system where services and apps communicate over NATS. This page covers the core concepts you need to understand before writing a service or app.
Contracts
Every service and app in Trellis declares a contract — a source definition that describes what it does, what it needs, and what access it requires.
A contract declares:
- RPCs the service handles (request/reply operations)
- events the service publishes or subscribes to
- resources the service needs Trellis to provision (KV buckets, streams)
- dependencies on other contracts via
uses - capabilities required to call each operation
Contracts are the source of truth for authorization. Trellis derives NATS subject permissions directly from the active contract set — if an operation is not in the contract, the service cannot access it.
Contract kinds
| Kind | Purpose |
|---|---|
service | A backend process connected to NATS with a session key |
app | A browser client that connects via websocket |
cli | A command-line tool |
Contract identity
Each contract has a stable id like graph@v1 or trellis.auth@v1. The @vN suffix is a major version — breaking changes require a new version. Within a deployment, only one active version of each contract id can exist.
Contracts are content-addressed by a SHA-256 digest of their canonical JSON form. The digest is what Trellis uses internally to identify a specific contract revision.
Generated contract artifacts
The canonical exchange artifact is still trellis.contract.v1 JSON, but it is generated from contract source during build and release. In normal authoring workflows you edit the contract module, not a checked-in sidecar manifest file.
Communication
All communication between services goes through NATS. There are two patterns:
RPCs (request/reply)
RPCs are synchronous operations. A caller sends a request and waits for a response.
- Subject convention:
rpc.v1.<LogicalName>(e.g.,rpc.v1.User.Find) - Callers use logical method names (e.g.,
"User.Find"), not raw subjects - Timeout-bounded (default 5 seconds)
- Both sides use
Result<T, E>— errors are values, not exceptions
Events (JetStream pub/sub)
Events announce state changes. Publishers fire and forget; consumers receive events through JetStream durable consumers with at-least-once delivery.
- Subject convention:
events.v1.<Domain>.<Action>[.<tokens>] - Handlers must be idempotent
- Subject tokens enable filtered subscriptions (e.g., subscribe to
events.v1.Document.Uploaded.pdf.*for only PDF uploads)
Cross-contract dependencies (uses)
A contract declares which other contracts it depends on through uses:
uses: {
core: core.use({
rpc: { call: ["Trellis.Catalog"] },
}),
auth: auth.use({
events: { subscribe: ["Auth.Connect"] },
}),
}, If a dependency is not declared in uses, Trellis will not grant that cross-contract access at install time.
Authentication and authorization
Session keys
Every client (service, app, or CLI) has a local Ed25519 session key. The private seed never leaves the client. Authentication works by proving ownership of the session key.
- Services receive their identity when an operator installs the service contract against the service’s public key
- Browser apps store the session key in IndexedDB via WebCrypto (non-extractable)
- CLI tools use a local seed file
The auth callout flow
When a client connects to NATS, it connects as the sentinel user on the AUTH account. This triggers NATS auth callout, which hands the connection to the Trellis auth service. Trellis validates the client’s session key proof and issues a scoped NATS JWT that grants only the permissions declared by the client’s installed contract.
Capabilities
Contracts declare per-operation capability requirements:
"MyService.List": {
capabilities: { call: ["admin"] },
}, Capabilities use the pattern <domain>.<action> (e.g., users.read, partners.write). Some operations require the service capability, meaning only registered services can call them. The admin capability grants administrative access.
Capabilities are assigned to users and services at the deployment level. Authorization changes take effect immediately because Trellis derives NATS permissions from the active contracts and current grants.
Resources
Services can declare cloud-managed resources in their contract. Trellis provisions them during install or upgrade.
KV buckets
NATS KV buckets for persistent state:
resources: {
kv: {
items: {
purpose: "Store item records",
history: 1,
ttlMs: 0,
},
},
}, The key (items) is a logical alias. Trellis assigns the physical bucket name at install time. connectService resolves bindings automatically and exposes them as service.kv.items, which can be opened directly with a schema.
Streams
JetStream streams for event-driven architectures, work queues, and audit trails. Like KV, stream names in the contract are logical aliases resolved at install time.
Jobs
@qlever-llc/trellis-jobs provides a job queue with retry, progress tracking, and dead-letter handling. Jobs use a stream-first architecture — JetStream is the source of truth, and KV provides a read-optimized projection for queries.
Job states: pending → active → completed / failed / retry / cancelled / expired / dead
Services process their own jobs via their own JetStream consumer. A central jobs service handles global queries, janitor cleanup, and the KV projection.
Packages
| Package | Use for |
|---|---|
@qlever-llc/trellis-contracts | defineContract(...), shared protocol schemas |
@qlever-llc/trellis-server | TrellisServer, health schemas, server types |
@qlever-llc/trellis-server/deno | Deno service runtime adapter |
@qlever-llc/trellis-server/node | Node service runtime adapter |
@qlever-llc/trellis | createClient(...), runtime client for RPC/events |
@qlever-llc/trellis-svelte | Browser auth, NATS, and Trellis integration for Svelte apps |
@qlever-llc/trellis-auth | Session key management, signing, bind flow helpers |
@qlever-llc/trellis-result | Result<T, E> for explicit error handling |
@qlever-llc/trellis-telemetry | Shared tracing helpers |
@qlever-llc/trellis-jobs | Job creation, processing, retry, progress tracking |
@qlever-llc/trellis-sdk-* | Generated SDK packages with typed DTOs for Trellis-owned contracts |
@qlever-llc/trellis-sdk-* packages (e.g., @qlever-llc/trellis-sdk-auth, @qlever-llc/trellis-sdk-core) are generated from contract definitions as part of the contract build pipeline. Import them for typed request/response types and use(...) declarations.
On the Rust side, the equivalent building blocks are generated Rust SDK crates plus a generated crate for the app, service, or CLI you are building. That generated crate is the preferred ergonomic entrypoint because it exposes only the owned surface and uses aliases declared by the local contract.