First unit — TypeScript
SDK 1.11.0 guide for Node ≥22.12. The example builds a simple process with an exposition and a cat.get handler. It requires Control Plane, an OIDC issuer, a link key and Dapr.
Scope and project
Set the administrator-provided registry in your user ~/.npmrc. Host, path and trailing slash must match between lines. Keep the token outside the project.
@seeraos:registry=https://registry.example.com/npm/
//registry.example.com/npm/:_authToken=YOUR_READ_TOKEN
{
"name": "cat-unit",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": { "start": "tsx src/main.ts" },
"dependencies": { "@seeraos/unit-sdk": "1.11.0" },
"devDependencies": { "tsx": "^4", "typescript": "^5", "@types/node": "^22" }
}
pnpm install
Unit process
Save src/main.ts. The cat identifier and data are synthetic. store identifies a persistent identity directory; the link key is single-use. Diagnostics also records waiting for the platform.
import { fileIdentityStore, runUnit } from "@seeraos/unit-sdk";
const document = JSON.stringify({
contract: "cat.v1",
unit: "shelter",
objects: { Cat: { identity: "id", fields: { id: { type: "string" }, name: { type: "string" } } } },
operations: { "cat.get": { object: "Cat", verb: "get", response: "Cat" } },
});
const stopping = new AbortController();
process.on("SIGTERM", () => stopping.abort());
const started = await runUnit({
documents: [document],
handlers: {
"cat.get": (request) =>
request.entityId === "01J9ZKCAT000000000000000A"
? { ok: true, value: { id: request.entityId, name: "Whiskers" } }
: {
ok: false,
error: { type: "notFound", code: "Cat.NotFound", description: "No such cat." },
},
},
linkKey: process.env.SEERAOS_LINK_KEY ?? "",
controlPlaneUrl: process.env.SEERAOS_CONTROL_PLANE_URL ?? "",
issuer: process.env.SEERAOS_ISSUER ?? "",
store: fileIdentityStore({ directory: process.env.SEERAOS_STATE_DIR ?? "./.seeraos" }),
port: 8080,
signal: stopping.signal,
// What the Unit notices about its own running. Leave it out and the process says nothing at all
// — including while it waits for a platform that has not finished coming up, which looks from
// outside like a process that has hung. See observability (source material outside the current package).
diagnostics: ({ level, code, message }) => {
process.stderr.write(`${level} ${code}: ${message}\n`);
},
});
if (!started.ok) {
process.stderr.write(`${started.error.code}: ${started.error.description}\n`);
process.exit(1);
}
pnpm start
Expected behaviour
The listener starts before enrollment and serves parity needed by Control Plane. runUnit returns a result: refusal has a code and description; success includes the instance, state, configuration, port and stop(). Success may precede instance configuration. Callbacks and lifecycle then proceed through configuration to active.
Unit.ListenFailed indicates a listener problem; the link key is not spent discovering an unavailable port. Preserve the same identity store across restarts. Configuration that cannot be applied holds serving rather than pretending readiness.
Event publication and calls to other units use instance identity: handlers may be supplied as a handlers: (unit) => ({ ... }) factory. This example publishes no event. Route names derive from the exposition rather than separate router configuration.
The source template with Node 24 pins SDK 1.8.0 and toolkit 0.15.0. Copying this guide is not an instruction to upgrade its pins to 1.11.0. Versions and troubleshooting help select the appropriate combination.