typescript

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.