Skip to content

Use as a library

@nimbus-sh/core is the OS without the Worker: the durable SQLite filesystem, the shell with 60+ Unix commands, and the WASI runtime layer. It has no Cloudflare dependency. You hand it a SQLite and get back .fs and .exec.

Terminal window
import { Database } from 'bun:sqlite';
import { NimbusWorkspace } from '@nimbus-sh/core/workspace';
const db = new Database('workspace.sqlite');
const sql = {
exec(q, ...p) {
const st = db.query(q);
if (st.columnNames.length === 0) { db.run(q, ...p); return []; }
return st.all(...p);
},
};
const transactions = { storage: { transactionSync: (cb) => db.transaction(cb)() } };
const ws = await NimbusWorkspace.create({ sql, transactions, generation: 1 });
await ws.fs.writeFile('/home/user/hello.txt', 'hi\n');
await ws.exec('cat /home/user/hello.txt | wc -c'); // { stdout: '3\n', exitCode: 0 }

node:sqlite works the same way. The workspace behaves as a tenant in a database you own. It creates and touches only its own tables, and destroy() drops those tables without calling deleteAll(). transactionSync must be a real transaction. An implementation that only calls the callback turns every atomic write into a torn one.

The same class runs over ctx.storage.sql. The host must supply a generation that never repeats across instances. Pids derive from it, so a repeated generation gives a dead process live write authority. Persist a counter and bump it once per instance. A constant or Date.now() does not work:

Terminal window
import { DurableObject } from 'cloudflare:workers';
import { NimbusWorkspace } from '@nimbus-sh/core/workspace';
export class Workspace extends DurableObject {
private ws?: Promise<NimbusWorkspace>;
private workspace(): Promise<NimbusWorkspace> {
this.ws ??= (async () => {
const generation = ((await this.ctx.storage.get<number>('generation')) ?? 0) + 1;
await this.ctx.storage.put('generation', generation);
return NimbusWorkspace.create({
sql: this.ctx.storage.sql,
transactions: this.ctx,
generation,
});
})();
return this.ws;
}
async exec(command: string) {
return (await this.workspace()).exec(command);
}
}

Cold starts, hibernation wakes, and resets all re-instantiate the class. The counter bumps once per instance, and the code uses the bumped value only after the put resolves.

The wasm runtimes are separate npm packages, so a filesystem-only embedder never downloads a Python interpreter:

Terminal window
npm install @nimbus-sh/runtime-bash @nimbus-sh/runtime-cpython
Terminal window
import bash from '@nimbus-sh/runtime-bash';
import cpython from '@nimbus-sh/runtime-cpython';
import { localFacetHost } from '@nimbus-sh/core';
const ws = await NimbusWorkspace.create({
sql, transactions, generation: 1,
facets: localFacetHost(),
runtimes: [bash, cpython],
});
await ws.exec('bash -c "echo $((6*7))"'); // GNU bash 5.2, real BusyBox children
await ws.exec('python -c "print(6*7)"'); // CPython 3.13 with the real stdlib

@nimbus-sh/runtime-ruby (Ruby 3.3) and @nimbus-sh/runtime-clang (clang → wasm32-wasi) work the same way. Every package carries the same manifests and sha256-verified blobs the hosted product serves from R2.

localFacetHost() covers bun and node only. On workerd the CSP forbids request-time WebAssembly.instantiate. Wasm rides the Worker Loader module map instead, which is the machinery in @nimbus-sh/worker and @nimbus-sh/fabric. The shell, the coreutils, and the filesystem do not need it.

@nimbus-sh/fabric is the Cloudflare half as a standalone library, for building your own thing on Durable Objects rather than embedding Nimbus. It holds the resident-process fabric, warm Worker Loader pools, the multi-reason alarm multiplexer, instance-reset detection, the durable launch journal, and byte-accounted turn pacing. Its README has the measured platform limits: DO storage, CPU, facet, and RPC ceilings.

For the full hosted product shape (sessions, terminal, ports, auth), use create-nimbus-app instead of composing these by hand.