Skip to content

Part 2: Start Sandboxes from It

src/Api.ts
import * as Boat from "@alchemy.run/boat";
import * as Cloudflare from "@alchemy.run/cloudflare";
import * as Effect from "effect/Effect";
import { Golden } from "./Golden.ts";
export default class Api extends Cloudflare.Worker<Api>()(
"Api",
{ main: import.meta.url },
Effect.gen(function* () {
const sandboxes = yield* Boat.CreateSandbox(Golden);
return {};
}).pipe(Effect.provide(Boat.CreateSandboxHttp)),
) {}

Every sandbox this client creates starts from Golden. At deploy, the Worker gets a Boat key of its own that creates sandboxes, works in them, and deletes only the ones it created.

src/Api.ts
Effect.gen(function* () {
const sandboxes = yield* Boat.CreateSandbox(Golden);
const run = Effect.fn("run")(function* (command: string) {
const sandbox = yield* Effect.acquireRelease(
sandboxes.create({ noEnv: true, autoStop: "5 minutes" }),
(sandbox) => Effect.ignore(sandbox.delete()),
);
return yield* sandbox.exec(command, { cwd: "/home/user/repo", timeout: "1 minute" });
});

The repository is already there: the sandbox starts from the snapshot’s disk, so nothing clones at request time. acquireRelease deletes the sandbox when the request’s scope closes, whatever happens.

src/Api.ts
return {};
return {
fetch: Effect.gen(function* () {
const request = yield* HttpServerRequest;
const url = new URL(request.url, "http://api");
if (request.method === "POST" && url.pathname === "/run") {
const result = yield* run(yield* request.text).pipe(Effect.scoped);
return yield* HttpServerResponse.json(result);
}
return HttpServerResponse.text("not found", { status: 404 });
}).pipe(Effect.orDie),
};
alchemy.run.ts
Effect.gen(function* () {
const golden = yield* Golden;
return { snapshot: golden.name };
const api = yield* Api;
return { snapshot: golden.name, url: api.url.as<string>() };
}),
Terminal window
pnpm alchemy deploy

Golden is unchanged, so this deploy only adds the Worker. Run a command in the repository:

Terminal window
curl -X POST "$URL/run" -d 'git log -1 --format=%s'

Part 3 gives each user a sandbox of their own.