JavaScript / TypeScript SDK
Build SapixDB-powered apps in Node.js, Bun, Deno, or the browser. Zero dependencies — uses the platform fetch API. Full TypeScript support with generics.
Installation
npm install sapixdb
bun add sapixdb
yarn add sapixdb
Quick Start
import { SapixClient } from "sapixdb";
const db = new SapixClient({
url: "http://localhost:7475", // your SapixDB agent
agent: "my-app", // matches SAPIX_AGENT_ID
});
// ✅ Check connection
const alive = await db.ping(); // true / false, never throws
// ✍️ Write a record
const product = await db.collection("products").write({
name: "Classic T-Shirt",
price: 29.99,
stock: 100,
});
console.log(product.id); // "nuc_abc123"
console.log(product.hash); // "sha3:e7f2a1..." (cryptographic proof)
// 📖 Read latest records
const all = await db.collection("products").latest();
// 🔍 Structured SaQL query (filter, range, etc.)
const result = await db.collection("products").query({
type: "latest",
limit: 20,
});
// ⏱️ Time travel — records written before a point in time
const cutoff = Date.now() - 24 * 60 * 60_000; // 24h ago in ms
const snapshot = await db.collection("orders").query({
type: "time_range",
from_ts: 0,
to_ts: cutoff,
});TypeScript Generics
Pass your own interface to collection<T>() for full autocomplete and type safety on every read and write.
interface Product {
name: string;
price: number;
stock: number;
category?: string;
}
const products = db.collection<Product>("products");
// write() only accepts Partial<Product> — wrong fields fail at compile time
await products.write({ name: "Sneakers", price: 89.99, stock: 50 });
// latest() returns NucleotideRecord<Product>[]
const items = await products.latest();
const price = items[0].data.price; // typed as number ✓Collection API
Every read and write goes through db.collection(name). Collections are schema-free — you never declare them; they emerge from your first write.
.write(data)→ Promise<WriteResponse>WriteResponse contains record_id, content_hash, parent_hash, and timestamp_hlc..writeBatch(records[])→ Promise<WriteResponse[]>.write() for each item..get(contentHash)→ Promise<RecordView>SapixError on 404..latest(limit?)→ Promise<SaqlQueryResult>limit records (default 20)..query(body)→ Promise<SaqlQueryResult>body must have a "type" key: "latest", "hash", "time_range", or "chain_head". Use this for time-travel range scans and hash lookups..head()→ Promise<ChainHeadResponse>.status()→ Promise<AgentStatusResponse>.deleteRecord(contentHash)→ Promise<void>.latest() results..restore(contentHash)→ Promise<void>Time Travel Queries
Use .query() with type: "time_range" to read records written within a HLC timestamp window. Set to_tsto a past moment's Unix milliseconds to see the strand as it existed then.
// Records written in the 30 minutes before a specific moment
const cutoff = Date.now() - 30 * 60_000; // unix ms
const snapshot = await db.collection("orders").query({
type: "time_range",
from_ts: 0,
to_ts: cutoff,
});
// Returns records exactly as they existed at that point in time.
// For a single point-in-time REST query:
// GET /v1/strand/as-of?ts=2026-05-01T15:30:00ZGraph Relationships
Connect records with typed directed edges and traverse the graph to any depth. Useful for org charts, order→customer links, product→category trees, and access control graphs.
db.graph.relate(src, dst, edgeType, weight?)→ Promise<void>addEdge.db.graph.addEdge(edge)→ Promise<void>src, dst, edge_type, and weight.db.graph.edges(agentId)→ Promise<GraphEdge[]>db.graph.inboundEdges(agentId)→ Promise<GraphEdge[]>db.graph.traverse(agentId, options?)→ Promise<TraversalResult>depth (default 1, max 3), edge_type (filter). Returns { nodes, edges }.db.graph.removeEdge(src, edgeType, dst)→ Promise<void>// Link records
await db.graph.relate(order.id, customer.id, "placed_by");
await db.graph.relate(product.id, category.id, "belongs_to");
// Get all outbound edges from a node
const outbound = await db.graph.edges(order.id);
// Get all inbound edges to a node
const inbound = await db.graph.inboundEdges(customer.id);
// Traverse: walk up to 2 hops from a starting agent
const result = await db.graph.traverse(customer.id, { depth: 2 });
console.log(result.nodes); // nodes visited
console.log(result.edges); // edges traversedCreating and Managing Agents
Every collection in SapixDB is an agent — an independent strand with its own hash chain, Ed25519 keypair, and on-disk record store. Before you can write to a named agent like orders or customers, that agent must exist. You create it once; it persists across restarts.
The seedHexis a 64-character hex string (32 random bytes) that deterministically derives the agent's Ed25519 keypair. Store it securely — it is the agent's cryptographic identity.
import { SapixClient } from "sapixdb";
import { randomBytes } from "node:crypto"; // or use any CSPRNG
const db = new SapixClient({ url: "http://localhost:7475" });
// Generate a fresh seed for each agent — store these in your secrets manager
const ordersSeed = randomBytes(32).toString("hex"); // 64 hex chars
const customersSeed = randomBytes(32).toString("hex");
const shipmentsSeed = randomBytes(32).toString("hex");
// Create the agents (call this ONCE; re-running will return a 409 Conflict)
await db.createAgent("orders", ordersSeed, "governed");
await db.createAgent("customers", customersSeed, "governed");
await db.createAgent("shipments", shipmentsSeed, "governed");
console.log("Agents ready.");After creation, use db.collection(name) or db.agent(name) to write and query records on any of those agents.
Zone options
The zone parameter controls the mutation policy for the agent:
| Zone | Mutation policy | Use for |
|---|---|---|
immutable_core | No mutations ever — requires dual-admin proposal | Financial ledgers, compliance archives |
protected | Mutations require explicit approval | Customer PII, medical records |
governed | Mutations auto-approve after a timeout (default) | Orders, products, general business data |
free | Mutations apply immediately, no approval needed | Logs, metrics, scratch agents |
List existing agents
const { agents, total } = await db.listAgents();
console.log(agents); // ["customers", "orders", "shipments"]
console.log(total); // 3Write to a named agent after creation
// Your app's agent sends this event when an order is placed
await db.collection("orders").write({
order_number: "P-10042",
customer_id: "cust_alice",
product_id: "prod_headphones",
product_name: "Wireless Headphones",
category: "electronics",
shipping_carrier: "FedEx",
shipping_method: "express",
total_usd: 89.99,
event: "order_placed",
});
// SapixDB appends this as an immutable, signed, hash-chained record
// to the "orders" agent's strand. content_hash and parent_hash are
// computed and stored automatically.Agent Ingest
Use db.ingest() for automated pipelines — AI agents, webhooks, cron jobs. Identical to .write() but routes through the dedicated ingest endpoint, which supports optional dual-write to Supabase.
// Log every AI decision permanently and immutably
await db.ingest("ai_decisions", {
model: "gpt-4o",
action: "approve_loan",
confidence: 0.94,
applicant: "cust_001",
reasoning: "Credit score 780, DTI 28%",
});Error Handling
import { SapixError, SapixNetworkError, SapixNotFoundError } from "sapixdb";
try {
const record = await db.collection("orders").get("nuc_missing");
} catch (err) {
if (err instanceof SapixNotFoundError) {
// 404 — record does not exist
} else if (err instanceof SapixNetworkError) {
// Cannot reach SapixDB — check if it's running
} else if (err instanceof SapixError) {
console.log(err.status); // HTTP status code
console.log(err.message); // Error message from the server
}
}Full Example: Online Store
import { SapixClient } from "sapixdb";
const db = new SapixClient({ url: "http://localhost:7475", agent: "store" });
async function main() {
// 1. Add products
const shirt = await db.collection("products").write({
sku: "SHIRT-001", name: "Classic T-Shirt",
price: 29.99, stock: 200, category: "apparel",
});
// 2. Register customer
const customer = await db.collection("customers").write({
name: "Alice Johnson", email: "alice@example.com",
});
// 3. Place order
const order = await db.collection("orders").write({
customer_id: customer.id,
items: [{ product_id: shirt.id, qty: 2, unit_price: 29.99 }],
total: 59.98,
status: "placed",
});
// 4. Link in graph
await db.graph.relate(order.id, customer.id, "placed_by");
await db.graph.relate(order.id, shirt.id, "contains");
// 5. Ship (append — the "placed" version is preserved forever)
await db.collection("orders").write({
customer_id: customer.id,
status: "shipped",
tracking: "UPS-1Z999AA10123456784",
shipped_at: new Date().toISOString(),
});
// 6. Audit: read the strand up to the moment the order was placed
const history = await db.collection("orders").query({
type: "time_range",
from_ts: 0,
to_ts: order.timestamp_hlc,
});
const records = history.records ?? [];
console.log(records[records.length - 1]?.payload?.status); // "placed" — not "shipped"
}
main();Realtime Subscriptions (SSE)
Subscribe to live writes on any agent using Server-Sent Events. db.subscribeAgent() and db.subscribeGlobal() return an EventSource. Call .close() to unsubscribe. Works natively in browsers and Node.js 18+.
.subscribeAgent(agentId, onEvent, opts?)→ EventSourceopts.since replays records from an HLC timestamp before going live. opts.filter limits events to those whose JSON payload contains the named field..subscribeGlobal(onEvent, opts?)→ EventSourceopts.agents restricts the stream to a subset of agent IDs.import { SapixClient, type StreamEvent } from "sapixdb";
const db = new SapixClient({ url: "http://localhost:7475", agent: "my-app" });
const es = db.subscribeAgent("orders", (event: StreamEvent) => {
console.log("new record:", event.record_id, event.payload);
if ((event.payload as any)?.status === "shipped") {
sendShippingNotification(event.payload);
}
});
// Later — clean up
es.close();const lastSeenHlc = 1748304000000; // HLC timestamp — replay from here first
const es = db.subscribeAgent(
"transactions",
(event) => {
const payload = event.payload as { risk_score: number };
if (payload.risk_score >= 0.8) flagForReview(event);
},
{ since: lastSeenHlc, filter: "risk_score" },
);import { useEffect, useRef } from "react";
import { SapixClient, type StreamEvent } from "sapixdb";
function useLiveOrders(onWrite: (event: StreamEvent) => void) {
const esRef = useRef<EventSource | null>(null);
useEffect(() => {
const db = new SapixClient({ url: process.env.NEXT_PUBLIC_SAPIX_URL! });
esRef.current = db.subscribeGlobal(onWrite, {
agents: ["orders", "payments"],
});
return () => esRef.current?.close();
}, [onWrite]);
}pip install sapixdb and go get github.com/sapixdb/sapixdb-go — same realtime API, every language.