@platinum-dev/sdk
v0.4.0
Published
Platinum TypeScript SDK — typed client for hardware-isolated sandbox microVMs.
Readme
@platinum-dev/sdk
Typed TypeScript client for Platinum — hardware-isolated sandbox microVMs (one Cloud Hypervisor VM per sandbox, sub-second boots via a warm pool).
npm install @platinum-dev/sdk # or: bun add @platinum-dev/sdkESM-first; CJS require() is supported via a bundled dist/index.cjs. Node ≥ 18 or Bun.
Server-side runtimes only — never ship an API key to a browser.
Quickstart
import { Platinum } from '@platinum-dev/sdk';
const dn = new Platinum({
token: process.env.PT_TOKEN, // API key (pt_live_…) — mint in the dashboard
url: process.env.PT_API_URL, // e.g. https://api.platinum.dev
});
// Pick a template that exists on YOUR deployment first:
console.log(await dn.templates.list());
// Create → running in one round-trip (server holds the POST until ready).
const sbx = await dn.sandboxes.create(
{ template: 'pt-base', env: { GREETING: 'hi' } }, // template names + cpu/ram minimums vary per deployment
{ waitForRunning: true },
);
const r = await sbx.exec('uname -a'); // argv array or plain string
console.log(r.exit_code, r.stdout);
r.check(); // throws on non-zero exit
await sbx.delete();Expose a port in the same call (no second round-trip):
const sbx = await dn.sandboxes.create(
{ template: 'pt-base', expose: [{ port: 8080, public: true }] },
{ waitForRunning: true },
);
console.log(sbx.exposedUrl(8080)); // reachable preview URLBuild a custom image inline (cache-hit on repeat, built on first use):
import { Template, Platinum } from '@platinum-dev/sdk';
const image = Template.fromPythonImage('3.12-slim').pipInstall(['fastapi', 'uvicorn']).workdir('/app');
const sbx = await dn.sandboxes.create({ image }, { waitForRunning: true, waitTimeoutMs: 600_000 });Configuration
| Option / env | Default | Meaning |
|---|---|---|
| token / PT_TOKEN | — (required) | API key pt_live_…, org-scoped bearer token |
| url / PT_API_URL | http://127.0.0.1:3000 | Control-plane URL |
| timeoutMs | 60000 | Per-request timeout (file transfer calls override per call) |
| fetch | global fetch | Transport override (mocked tests) |
Errors
Everything the SDK throws extends PlatinumError (.status, .body, .code — the
API's machine-readable error code, also folded into the message):
import { NotFoundError, ConflictError, RateLimitError } from '@platinum-dev/sdk';
try { await sbx.exec('true'); }
catch (e) {
if (e instanceof NotFoundError) { /* sandbox gone */ }
if (e instanceof ConflictError) { /* e.g. e.code === 'sandbox_not_running' */ }
if (e instanceof RateLimitError) { /* back off e.retryAfterSeconds — the SDK never retries */ }
}Subclasses: ValidationError (400) · AuthenticationError (401) · ForbiddenError (403)
· NotFoundError (404) · ConflictError (409) · RateLimitError (429) · ServerError
(5xx) · PlatinumTimeoutError / PlatinumConnectionError (client-side, status === 0).
No automatic retries, ever — a 429 or a failed create is surfaced, never silently retried.
Surface
| Area | Methods |
|---|---|
| Sandboxes | create (inline image, inline expose, server-side wait) · get · connect · list · iterate (auto-pagination) · delete · rename |
| Lifecycle | stop/start (optional server-side wait) · pause/resume · kill · resize · fork · clone · snapshot · listSnapshots · deleteSnapshot · restore · restoreFromBackup · archive · backup · waitRunning · waitState · refresh |
| Execution | exec(argv \| string) · sh(script) · runCode(code, {lang}) — defaults to the sandbox's create-time language |
| Files (vsock) | files.read/write/delete/list/stat/mkdir/exists · find (glob) · grep (content) · replace (bulk sed) · watch (async iterator of change events) |
| Networking | expose(port, {public, ttlSeconds}) · unexpose · exposedUrl · revokeExposeToken · setEgressPolicy · addSshKeys |
| Observability | metrics() (live cpu/mem/disk) · usage() (billed usage) |
| Platform | templates.list/get/delete · Template builder · webhooks.* (create/list/update/delete/test/deliveries/retryDelivery) · volumes.* (create/list/get/delete/attach/detach) · regions.list · me() · health.check |
Watch for file changes:
for await (const ev of sbx.files.watch('/workspace', { maxSeconds: 60 })) {
if (ev.event === 'change') console.log(ev.data.type, ev.data.path);
}Not wrapped yet: interactive terminal (WebSocket), cross-host migrate (admin-only),
API-key management, audit-log queries, billing.
Limits worth knowing (honest edition)
files.writebodies must stay ≤ 16 MiB — larger writes currently truncate and fail through the vsock path. Split anything bigger into ≤ 16 MiB chunks: write sequential parts, then concatenate them in-guest withexec(cat part.* > out).exec/runCodeare buffered, not streaming: output arrives after the command exits. Default timeout 30 s — passtimeoutMsfor longer runs.runCodesource is capped at 1 MiB.- Template names and minimums are per-deployment.
pt-base(busybox — no git, pip, node orhttpdinside;nc/wgetavailable) exists on default installs; hosted deployments may only offer other templates with higher cpu/ram minimums. Calltemplates.list()first; a below-minimum create fails with a clear 400. - Background processes are reaped when their
execcall returns. To leave a server running (e.g. before hitting an exposed port), daemonize it:setsid sh -c '<server loop>' >/dev/null 2>&1 < /dev/null &— seeexamples/03-expose-service.ts.
Testing
bun test runs the offline unit suite (mocked fetch, no network). The live e2e is
opt-in: PT_E2E=1 PT_API_URL=… PT_TOKEN=… bun test test/sdk.test.ts.
Examples
Runnable scripts in examples/: create→exec→delete, expose + fetch a
live service.
PT_API_URL=… PT_TOKEN=… bun examples/01-create-exec-delete.tsLicense
MIT
