Docs

TypeScript SDK

@alienplatform/sdk is what the code you deploy imports at runtime: typed access to linked resources, plus commands, event handlers, and background tasks for Workers.

src/index.ts
import { command, kv } from "@alienplatform/sdk"
import { z } from "zod"

const SummaryInput = z.object({ accountId: z.string() })

command("get-summary", SummaryInput, async ({ accountId }) => {
  const entry = await kv("cache").getJson<{ total: number }>(`summary:${accountId}`)
  return { accountId, total: entry?.value.total ?? 0 }
})

This page documents version 3.3.41. All @alienplatform/* packages release together with the same version number.

Packages

PackageUse it for
@alienplatform/coreDeclaring the stack in alien.ts (import * as alien from "@alienplatform/core"). See Stack config.
@alienplatform/sdkRuntime code: bindings, command(), event handlers, waitUntil(), AI.
@alienplatform/bindingskey() and remote bindings (Bindings) from your own backend. The SDK re-exports the other factories from here.
@alienplatform/commandsSending commands from your backend (CommandsClient) and receiving them in a Container or Daemon (createCommandReceiver).
@alienplatform/testingDeploying a stack from a test. See Testing.
@alienplatform/platform-apiCalling the Alien REST API from your backend. See Platform API client.

Install

npm install @alienplatform/sdk
RequirementSupported
RuntimeNode.js 18 or newer, Bun
Native addonmacOS (arm64, x64), Linux (arm64, x64, glibc)
No addonWindows, musl Linux such as Alpine (the SDK throws an unsupported-platform error)

The bindings ship as a native addon that wraps the same Rust core as the Python and Rust SDKs. Each binary is an optional dependency, @alienplatform/bindings-<target>. If the addon is missing, check that optional dependencies were installed, or install the matching package.

Bindings

A binding is a typed client for a resource linked to the current workload. Call the factory with the resource ID from alien.ts:

import { storage } from "@alienplatform/sdk"

const files = storage("files") // matches new alien.Storage("files")

Factories do no I/O. The first method call reads the binding from the ALIEN_<NAME>_BINDING environment variable that Alien injects for each link, and throws BindingNotConfiguredError if the resource isn't linked to this workload.

FactoryPackageMethods
storage(name)sdkget(path), put(path, data, { condition?: "absent", attributes? }), delete(path), list(prefix?), head(path), copy(from, to), signedUrl({ method, path, expiresIn })
kv(name)sdkget(key), getText(key), getJson<T>(key), set(key, value, { ttl?, ifVersion? }), setJson(key, value, options?), delete(key, { ifVersion? }), exists(key), scan(prefix, limit?, cursor?)
queue(name)sdksend(message), sendText(text), sendBatch(messages), sendBatchText(messages), receive(max), ack(receipt), ackBatch(receipts), renewLease(receipt, seconds), nack(receipt), purge()
vault(name)sdkget(name), getJson<T>(name), put(name, value), putJson(name, value), delete(name), list()
postgres(name)sdkconnection() returns { connectionString, host, port, database, username, password, sslmode, ssl }
container(name)sdkgetInternalUrl(), getInternalUrls(), getPublicUrl()
sandbox(name)sdkcreate(options?), get(id), getOrCreate(idOrOptions, options?), list(), runCommand(id, command, options), startJob, pollJob, cancelJob, readFile, writeFiles, preview(id, port), updateEgress, pause, resume, terminate, capabilities
ai(name)sdkchat.completions.create(params), responses.create(params), getAvailableModels()
key(name)bindingsencrypt(plaintext, { context? }), decrypt(ciphertext, { context? })

Each resource page lists the options, limits, and platform mapping: Storage, KV, Queue, Vault, Postgres, Container, Sandbox, AI, Key.

The following code uses several bindings from one workload:

import { kv, postgres, queue, storage, vault } from "@alienplatform/sdk"
import { key } from "@alienplatform/bindings"

const files = storage("files")
const path = "reports/2026-10.json"
await files.put(path, Buffer.from("{}"), { condition: "absent" })
const { url } = await files.signedUrl({ method: "GET", path, expiresIn: 300 })

await kv("cache").setJson("user:42", { plan: "pro" }, { ttl: 60 })
await queue("jobs").send({ type: "reindex" })

const token = await vault("secrets").get("upstream-token")
const db = await postgres("db").connection()
const ciphertext = await key("records").encrypt(Buffer.from("private"))

AI

ai(name) returns an OpenAI-compatible client for an AI resource. An embedded gateway signs requests with the workload's cloud identity, for Amazon Bedrock, Vertex AI, or Azure AI Foundry. The code holds no API keys, and prompts stay in the customer's cloud.

import { ai } from "@alienplatform/sdk"

const llm = ai("llm")
const [model] = await llm.getAvailableModels()
const reply = await llm.chat.completions.create({
  model: model.id,
  messages: [{ role: "user", content: "Summarize this ticket." }],
})
console.log(reply.choices[0].message.content)

Pass stream: true to get an async iterable of chunks. To use another OpenAI-compatible client, call getAiConnection(name), which returns { baseURL, apiKey? }.

Worker handlers

A Worker's entry module registers handlers as a side effect and default-exports an HTTP app (any object with a fetch method, such as a Hono app). Export nothing if the Worker has no HTTP endpoint.

src/index.ts
import {
  kv,
  onCronEvent,
  onQueueMessage,
  onStorageEvent,
  queue,
  storage,
  waitUntil,
} from "@alienplatform/sdk"
import { Hono } from "hono"

const app = new Hono()
app.get("/health", c => c.json({ status: "ok" }))

const uploadsFilter = { prefix: "incoming/" }

onStorageEvent("uploads", async event => {
  if (event.eventType !== "created") return
  const object = await storage("uploads").get(event.objectKey)
  await queue("jobs").send({ key: event.objectKey, size: object.meta.size })
}, uploadsFilter)

onQueueMessage<{ key: string }>("jobs", async message => {
  waitUntil(kv("cache").set(`seen:${message.payload.key}`, "1", { ttl: 3600 }))
})

onCronEvent("nightly", async event => {
  console.log("ran at", event.timestamp)
})

export default app

You declare the trigger itself (which bucket, queue, or schedule invokes the Worker) on the Worker in alien.ts. See Worker.

FunctionSignatureNotes
commandcommand(name, handler) or command(name, schema, handler)schema is any Standard Schema v1 validator (zod, valibot). The handler receives (params, { commandId, attempt, deadline? }). Delivery is at least once: key retries on commandId to stay idempotent. attempt counts deliveries.
onStorageEventonStorageEvent(bucket, handler, { prefix? })Event fields: bucketName, objectKey, eventType (created, deleted, copied, metadataUpdated, restored, tierChanged, unknown), timestamp, size?, etag?, contentType?.
onQueueMessageonQueueMessage<T>(queue, handler)Message fields: id, source, receiptHandle, payload: T, attemptCount, timestamp, attributes?.
onQueueBatchonQueueBatch<T>(queue, handler)The handler receives { source, deliveries } and returns one { deliveryId, disposition: "success" | "retry" } per delivery.
onCronEventonCronEvent(scheduleName, handler)Event fields: scheduleName, timestamp.
waitUntilwaitUntil(promise)Keeps work running after the response is sent. The runtime waits for registered tasks before it shuts the Worker down.

Pass "*" as the bucket, queue, or schedule name to match all of them. Event handlers run only in Workers; Containers and Daemons don't receive events. Every on* function returns a function that removes the handler.

Commands from your backend

@alienplatform/commands sends commands to a deployment and receives them in workloads that aren't Workers. It uses fetch only and has no native code.

import { CommandsClient } from "@alienplatform/commands"

const commands = await CommandsClient.forDeployment({
  deploymentId: "dep_0123456789abcdef",
  apiKey: process.env.ALIEN_API_KEY!,
})

const input = { accountId: "acct_1" }
const summary = await commands.invoke<{ total: number }>("get-summary", input, {
  timeoutMs: 30_000,
})

await commands.target("indexer").invoke("reindex", {})

invoke options: timeoutMs, deadline, idempotencyKey, targetResourceId, pollIntervalMs, maxPollIntervalMs, pollBackoff. Use target(resourceId) when more than one resource handles the same command name.

A Container or Daemon receives commands with a receiver, which reads its configuration from the environment variables Alien injects:

import { createCommandReceiver } from "@alienplatform/commands"
import { z } from "zod"

const ReindexInput = z.object({ full: z.boolean().default(false) })

const receiver = createCommandReceiver()

receiver.command("reindex", ReindexInput, async ({ full }) => {
  return { started: true, full }
})

await receiver.run()

Remote bindings

Bindings from @alienplatform/bindings lets your backend use a resource inside a customer's environment without running code there. The resource must be added with { remoteAccess: true } in alien.ts. See Remote bindings.

import { Bindings } from "@alienplatform/bindings"

const bindings = await Bindings.forRemoteCustomer({
  project: "acme",
  externalId: "customer-1",
  token: process.env.ALIEN_API_KEY!,
})
const reports = await bindings.storage("files").list("reports/")
await bindings.queue("jobs").send({ type: "reindex" })

externalId is your own ID for the customer (its external ID). Bindings.forRemoteDeployment({ deploymentId, token }) targets one deployment instead of a customer. A remote Bindings exposes storage, queue (send only), kv, key, sandbox, and ai(), which returns a short-lived lease with the provider endpoint and credentials.

Errors

Binding errors extend AlienError and carry a machine-readable code, a retryable flag, and a context object, plus internal, httpStatusCode, and hint when the backend supplies them. Branch on code, and show the message or hint to people rather than parsing it. The SDK exports AlienError, BindingNotConfiguredError, BindingNotFoundError, InvalidBindingConfigError, AiTransportError, and AiUpstreamError.

Next

On this page