Skip to content

Part 3: A Copy per User

src/Api.ts
const userSandbox = Effect.fn("userSandbox")(function* (user: string) {
const [existing] = yield* sandboxes.list({ labels: { user } });
if (existing === undefined) {
return yield* sandboxes.create({ noEnv: true, autoStop: "10 minutes", labels: { user } });
}
const sandbox = yield* sandboxes.connect(existing.id);
yield* sandbox.resume({ autoStop: "10 minutes" });
return sandbox;
});

A user’s first request starts their sandbox from Golden, with the repository already cloned. The label finds it again on their next request.

src/Api.ts
if (request.method === "POST" && url.pathname === "/run") {
const result = yield* run(yield* request.text).pipe(Effect.scoped);
return yield* HttpServerResponse.json(result);
}
const match = /^\/users\/([a-z0-9-]+)\/files\/(.+)$/.exec(url.pathname);
if (match !== null) {
const sandbox = yield* userSandbox(match[1]!);
if (request.method === "PUT") yield* sandbox.files.write(`repo/${match[2]}`, yield* request.text);
return yield* HttpServerResponse.json({ content: yield* sandbox.files.readText(`repo/${match[2]}`) });
}

Each user edits their own copy of the repository. Golden never changes, so the next sandbox started from it doesn’t see their edits.

The sandbox stops after ten idle minutes and Boat saves its disk. resume on their next request brings it back with their files, for up to 14 days.

Terminal window
pnpm alchemy deploy
curl -X PUT "$URL/users/ana/files/notes.md" -d 'hello'
curl "$URL/users/ana/files/notes.md"

Part 4 changes the snapshot.