All on Cloudflare
The examples use Authentication from
Getting Started: the application’s request middleware.
Everything on one account, with large pushes hashed by dynamically loaded Workers so nothing leaves Cloudflare:
import * as Cloudflare from "alchemy/Cloudflare";import * as Git from "alchemy/Git";import * as GitHasher from "alchemy/Git/Hasher";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as HttpRouter from "effect/unstable/http/HttpRouter";import * as Http from "alchemy/Http";
export const GitObjects = Cloudflare.R2.Bucket("GitObjects");
const GitLive = Git.ApiLive.pipe( Layer.provide(Git.ApiHandlersLive), Layer.provide(Authentication.layer), Layer.provide(Git.ReposDurableObject), Layer.provide(Git.RegistryDurableObject), Layer.provide(GitHasher.HasherWorkerLoader()), Layer.provide(Git.BlobStoreR2(GitObjects)),);
export default class GitHost extends Cloudflare.Worker<GitHost>()( "Git", { main: import.meta.url, ...Git.GIT_WORKER_OPTIONS }, Effect.gen(function* () { const fetch = yield* HttpRouter.toHttpEffect(GitLive.pipe(Layer.provide(Http.Platform))); return { fetch }; }),) {}import * as Alchemy from "alchemy";import * as Cloudflare from "alchemy/Cloudflare";import * as Effect from "effect/Effect";import GitHost from "./src/git.ts";
export default Alchemy.Stack( "GitService", { providers: Cloudflare.providers(), state: Cloudflare.state() }, Effect.gen(function* () { const git = yield* GitHost; return { url: git.url.as<string>() }; }),);bun alchemy deploynpm run alchemy deploypnpm alchemy deployyarn alchemy deployWhat gets created
Section titled “What gets created”- An R2 bucket for packs, clone bundles, and spilled push bodies.
- A Worker with
nodejs_compat, a 300 s CPU limit, aworker_loaderbinding for the hasher, and the shared secret bound as a secret. - Two Durable Object namespaces on that Worker, one per repository and one for the registry, each with SQLite storage.
Nothing runs until a request arrives, and a repository that receives no traffic costs its storage.
Variations
Section titled “Variations”HasherInline is enough until pushes of tens of megabytes matter:
const GitLive = Git.ApiLive.pipe( Layer.provide(Git.ApiHandlersLive), Layer.provide(Authentication.layer), Layer.provide(Git.ReposDurableObject), Layer.provide(Git.RegistryDurableObject), Layer.provide(GitHasher.HasherWorkerLoader()), Layer.provide(Git.HasherInline), Layer.provide(Git.BlobStoreR2(GitObjects)),);A registry on D1 replicates name lookups instead of serving them from one region:
export const RepoIndex = Cloudflare.D1.Database("RepoIndex");
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(GitHasher.HasherWorkerLoader()), Layer.provide(Git.BlobStoreR2(GitObjects)),);A custom domain is a Worker option, so clone URLs read
https://git.example.com/acme/web.git. Custom domains &
routes covers it, and
the application example
serves a browser UI and Git on the same origin.