Provisioning & recipes
provision() is the high-level way to boot a worker-hosted VM and bring a catalog app up ready to run — installing it from the signed catalog, warming a snapshot, and handing you run/serve helpers. It carries no app-specific code: everything node needs to run is data in the node app's recipe.
Provision an app
import { Catalog, provision } from "@userland-run/nano-sdk";
const node = await provision(new Catalog(), "node@25.4.0", {
wasm: await (await fetch("/nano/nano.wasm")).arrayBuffer(),
swUrl: "/nano-sw.js",
workerFactory: () =>
new Worker(new URL("@userland-run/nano-sdk/worker", import.meta.url), { type: "module" }),
});The VM runs in a Web Worker, so the UI thread never blocks. provision installs the app (and any recipe deps + extraApps) from the catalog, configures the recipe's warmup, and prewarms the snapshot off-thread.
Run and serve
// one-shot: run a file, stream stdout await node.run("index.js", { onStdout: (c) => term.write(c) }); // inline code await node.evalCode("console.log(process.version)"); // long-running server, surfaced to an iframe const srv = await node.serve("server.js", { port: 3000, extraFiles }); iframe.src = await srv.url(); // /sw/3000/ srv.stop();
run/evalCode use the recipe's run templates to build the script; serve also wires the in-VM port to the service-worker preview bridge. extraFiles are injected per run (re-applied after each warm restore).
What a recipe is
An app recipe is app-specific provisioning data carried in the app's signed catalog manifest, so a generic runner needs zero per-app code. The node recipe lives in the catalog (catalog/recipes/node/recipe.toml); the SDK and your app stay node-agnostic.
# catalog/recipes/node/recipe.toml (excerpt)
[recipe]
benignExitCodes = [134]
outputFilters = ["WorkerThreadsTaskRunner", "Native stack trace"]
[recipe.warmup]
elfPath = "/usr/bin/node"
argv = ["node", "/launcher.js"]
env = { UV_THREADPOOL_SIZE = "0" }
[recipe.run]
fileScript = "process.mainModule.require('${file}')"The layering
- nano core — the RISC-V VM + a generic snapshot/restore primitive. Knows nothing about node.
- SDK — generic transport, catalog install, serve bridge, and the provision() runner. Node-agnostic.
- catalog — the node app + its recipe (the only place node-specifics live, as signed data).
- your app — provision('node') and call run/serve. No setup code.