Capabilities & Layers
Make dependencies visible and provide them by scope
VibeLang models a dependency as a capability in a compiler-tracked requirement channel. A Context class defines and accesses a service; a Layer decides how to construct it.
Define a Capability
import { Context } from "vibelang/context"
abstract class Mailer extends Context {
abstract send(message: Message): Result<Receipt, MailError>
}The class is both the service contract and its nominal key. Two Context subclasses with identical methods remain distinct capabilities.
Use a Capability
function welcome(user: User): Result<Receipt, MailError> {
const mailer = Mailer.context()
const clock = Clock.context()
const message = Message.welcome(user, clock.now())
return mailer.send(message)
}The inherited context() call returns the instance and adds the class to the enclosing function's inferred requirements. welcome requires Mailer | Clock.
Requirement and Result Composition
function register(
input: Signup,
): Result<User, CreateUserError | MailError> {
const users = Users.context()
const user = users.create(input).unwrap()
welcome(user).unwrap()
return user
}The compiler propagates Users, Mailer, and Clock through the ordinary calls. .unwrap() propagates the Error variants; these are separate from the requirement channel.
Build and Provide a Layer
import { Layer } from "vibelang/provider"
const Production = Layer.merge(
Layer.succeed(Clock, SystemClock),
Layer.succeed(Mailer, new SesMailer(credentials)),
Layer.succeed(Users, new PostgresUsers(databaseUrl)),
)
Layer.provide(Production, async () => {
const user = (await registerAsync(input)).unwrap()
return user.id
})A layer is conceptually Layer<Provides, InitError, Requires>. Acquisition may return a Result and require other capabilities. A missing dependency is a compile error when the closure is known.
Test With Layers
const Test = Layer.merge(
Layer.succeed(Clock, TestClock.at("2026-08-20T12:00:00Z")),
Layer.succeed(Mailer, RecordingMailer.make()),
Layer.succeed(Users, InMemoryUsers.empty()),
)
Layer.provide(Test, () => {
const user = register(fixture).unwrap()
expect(RecordingMailer.sent).toHaveLength(1)
return user
})No module replacement or global clock patch is needed.
Platform Capabilities
function loadConfig(): Result<Config, FileError | InvalidConfig> {
const fs = FileSystem.context()
const text = fs.readText("app.json").unwrap()
return Config.parse(text)
}Node, Bun, Deno, native, WASI, and tests can provide different implementations of the same platform contracts.
Open Lifecycle Work
The layer API is accepted, while acquisition/disposal ordering, sharing, nested overrides, cycles, and finalizer failure policy remain under design. Source semantics do not depend on whether the backend uses hidden parameters or a scoped environment.