Overview
The examples use Authentication from
Getting Started: the application’s request middleware.
A git host is six decisions: what serves HTTP and who may call it, where refs and objects live, how names resolve, where bulk bytes go, what verifies a push, and which refs may move. Each is a Layer, and you make them in one graph:
const GitLive = Git.ApiLive.pipe( Layer.provide(Git.ApiHandlersLive), Layer.provide(Authentication.layer), Layer.provide(Git.ReposDurableObject), Layer.provide(Git.RegistryDurableObject), Layer.provide(Git.HasherInline), Layer.provide(Git.BlobStoreR2(GitObjects)),);One graph, one Effect.provide. Build it with Layer.* and provide it
once.
Two runtimes, one graph
Section titled “Two runtimes, one graph”A repository needs one place where a ref moves atomically. On Cloudflare that is a Durable Object, and the code that moves refs runs inside it. The graph does not know this. You write it once, the Worker builds its side of it at cold start, and each Durable Object builds its own on its first request:
GitLive ┌─────────────┴──────────────┐ Worker Repo Durable Object wire, REST, GitHub facade refs, objects, pull requests your middleware and policies push commit, jobs BlobStore: clone bundles BlobStore: packs, spilled pushesGit.BlobStoreR2(GitObjects) appears once and serves both sides: the
Worker streaming a clone bundle and the Durable Object writing a pack.
Your middleware and application policies run in the Worker request. They can
use the same user and database services.
The blocks
Section titled “The blocks”| Block | Decides | Ships with |
|---|---|---|
| HTTP routes | the HTTP surface: every plane as one HttpApi of routes you can replace, behind your middleware |
ApiHandlersLive, ApiLive, InternalApiLive |
| Repository | where refs and objects live, the jobs that keep a repository small | ReposDurableObject |
| Registry | how owner/name resolves, and listings |
RegistryDurableObject, RegistryD1 |
| Blob Store | where packs, bundles, and large pushes go | BlobStoreR2, BlobStoreS3 |
| Hasher | what verifies a push, and on which compute | HasherInline, HasherWorkerLoader, HasherLambda |
| Engine | repository operations and scoped prepare/commit | EngineLive |
| Auth | application middleware and policy functions | native Effect HTTP middleware |
Replacing a block
Section titled “Replacing a block”Every block is a Context.Service. Swap the Layer and nothing else in
the graph knows:
const GitLive = Git.ApiLive.pipe( Layer.provide(Git.ApiHandlersLive), Layer.provide(Authentication.layer), Layer.provide(Git.ReposDurableObject), Layer.provide(Git.RegistryDurableObject), Layer.provide(Git.RegistryD1(RepoIndex)), Layer.provide(Git.HasherInline), Layer.provide(Git.BlobStoreR2(GitObjects)),);Leave one out and the Worker does not compile. The graph still requires the service, and the compiler says which:
const GitLive = Git.ApiLive.pipe( Layer.provide(Git.ApiHandlersLive), Layer.provide(Authentication.layer), Layer.provide(Git.ReposDurableObject), Layer.provide(Git.RegistryDurableObject), Layer.provide(Git.HasherInline),);// Layer<Server, never, BlobStore>// ^ still required; providing it to the Worker is a type errorWriting your own is implementing the contract. Each block’s page shows it, and the shape is always the same:
const BlobStoreMine = Layer.effect( Git.BlobStore, Effect.gen(function* () { // return a Git.BlobStoreShape }),);Where next
Section titled “Where next”- HTTP routes — the three planes as one
HttpApi, and replacing a route. - Recipes — which lines change for which requirements.