capsite/toybox: vats with code installed at runtime, on workerd
Each vat is a Durable Object that stores code by hash and runs it as a facet through the Worker Loader: an empty env, no fetch, storage that carries over upgrades. Two sockets with no configured address: whoever starts workerd hands them in (systemd socket activation), so the admin socket is the authority to install code. Tests run a real workerd: install, upgrade, isolation between vats, no fetch, the public port can't install, and code and state survive a crash. They spawn the package binary rather than the .bin shim, which forks workerd, so killing the shim orphaned it and the test run never ended. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
1d8a220bbc
commit
88739d9495
12 files changed
+401
No files matched your search
@@ -0,0 +1,72 @@
|
||||
/** @file toybox supervisor: one Vat per name; its app runs as a facet on installed code. */
|
||||
import { DurableObject, WorkerEntrypoint } from "cloudflare:workers";
|
||||
|
||||
const hex = (buf) => [...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
||||
|
||||
/**
|
||||
* Holds the vat's installed code; the app facet holds the app's state.
|
||||
*
|
||||
* Code is stored by hash so the facet can be rebuilt after hibernation or a
|
||||
* restart. The app gets an empty `env` and no `fetch`.
|
||||
*/
|
||||
export class Vat extends DurableObject {
|
||||
constructor(ctx, env) {
|
||||
super(ctx, env);
|
||||
ctx.storage.sql.exec("CREATE TABLE IF NOT EXISTS bundle (hash TEXT PRIMARY KEY, src TEXT)");
|
||||
ctx.storage.sql.exec("CREATE TABLE IF NOT EXISTS current (k INTEGER PRIMARY KEY CHECK (k = 0), hash TEXT)");
|
||||
}
|
||||
|
||||
/**
|
||||
* Install `src` and run the app on it; the app's storage carries over.
|
||||
*
|
||||
* @param {string} src an ES module exporting `class App extends DurableObject`
|
||||
* @returns {Promise<string>} the code's SHA-256, hex
|
||||
*/
|
||||
async start(src) {
|
||||
const hash = hex(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(src)));
|
||||
this.ctx.storage.sql.exec("INSERT OR IGNORE INTO bundle VALUES (?, ?)", hash, src);
|
||||
this.ctx.storage.sql.exec("INSERT OR REPLACE INTO current VALUES (0, ?)", hash);
|
||||
this.ctx.facets.abort("app", new Error("upgraded"));
|
||||
return hash;
|
||||
}
|
||||
|
||||
#app(hash, src) {
|
||||
return this.ctx.facets.get("app", () => {
|
||||
const worker = this.env.LOADER.get(hash, () => ({
|
||||
compatibilityDate: "2026-10-01",
|
||||
mainModule: "app.js",
|
||||
modules: { "app.js": src },
|
||||
env: {},
|
||||
globalOutbound: null,
|
||||
}));
|
||||
return { class: worker.getDurableObjectClass("App") };
|
||||
});
|
||||
}
|
||||
|
||||
fetch(request) {
|
||||
const [row] = this.ctx.storage.sql
|
||||
.exec("SELECT b.hash, b.src FROM current c JOIN bundle b USING (hash)")
|
||||
.toArray();
|
||||
if (!row) return new Response("no code installed\n", { status: 404 });
|
||||
return this.#app(row.hash, row.src).fetch(request);
|
||||
}
|
||||
}
|
||||
|
||||
const vatOf = (env, url) => env.VATS.getByName(new URL(url).pathname.split("/")[1]);
|
||||
|
||||
/** The http socket: `/<vat>/...` goes to that vat's app. */
|
||||
export class Public extends WorkerEntrypoint {
|
||||
fetch(request) {
|
||||
return vatOf(this.env, request.url).fetch(request);
|
||||
}
|
||||
}
|
||||
|
||||
/** The admin socket: `PUT /<vat>` with an ES module body installs and starts it. */
|
||||
export class Admin extends WorkerEntrypoint {
|
||||
async fetch(request) {
|
||||
if (request.method !== "PUT") return new Response("PUT code to /<vat>\n", { status: 405 });
|
||||
return new Response((await vatOf(this.env, request.url).start(await request.text())) + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
export default Public;
|
||||
Reference in new issue
Block a user