typescript sdk
https://www.npmjs.com/package/@indexable/sdk
the node and bun package is generated from the rust sdk.
typescript adds await using, async iterators and generated zod schemas at
the language boundary.
the package is @indexable/sdk, node 22 or newer, or bun. it ships
generated declarations; no code generation runs in the consumer project.
boot a machine
set IX_TOKEN first, or run ix login. an omitted region uses IX_REGION, then us-west-1.
import { Client } from '@indexable/sdk'
const ix = new Client()
// no target: a new machine boots the base template.
const machine = await ix.machines().create({ name: 'sdk-example' })
try {
const result = await machine.execChecked(['uname', '-a'])
console.log(result.stdout.trim())
} finally {
await machine.delete()
} the handle returned by create carries every verb: exec, readFile, writeFile, snapshot, tailLogs, watch. delete the VM when you are done
with it, or skip the delete and it keeps running - a VM outlives the process
that made it. ix.machines().connect(id) reattaches later.
a stateful repl
Repl is the one piece of language-owned sugar: a long-lived interpreter
inside the VM, on one pty. state persists across exec calls, and independent
sessions are independent:
import { Client, Repl } from '@indexable/sdk'
const ix = new Client()
const machine = await ix.machines().create({})
try {
await using py = await Repl.open(machine, 'python')
await py.exec('import math')
await py.exec('x = 42')
const b = await py.exec('print(x * 2, math.pi)')
} finally {
await machine.delete()
} a second Repl.open(machine, 'python') is an independent session: it does not see x. await using is typescript and bun syntax; plain node 22 running
untranspiled javascript rejects it - call py.close() in a finally there
instead.
streams are async iterables everywhere: for await consumes machine.watch() and machine.tailLogs(), and dropping the iterator ends the stream. every call takes a
trailing AbortSignal, and aborting it cancels the rust future behind it.
validate external data
the same generator emits a zod schema for every record, and records are
plain JSON (integers are numbers, the stripe/openai convention). import
schemas only where data has crossed an untyped boundary:
import { MachineInfo } from '@indexable/sdk/schemas'
const machine = MachineInfo.parse(JSON.parse(payload)) values returned directly by Client are already typed and do not need to be
parsed again.