SapixDBSapixDB/Docs
Home
JavaScript & TypeScript SDK · v0.1

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
npm install sapixdb
bun
bun add sapixdb
yarn
yarn add sapixdb

Quick Start

TypeScript
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();

// 🔍 Filter
const shirts = await db.collection("products").find({ category: "apparel" });

// ⏱️  Time travel
const yesterday = await db
  .collection("orders")
  .asOf("2026-05-11T00:00:00Z")
  .latest();

TypeScript Generics

Pass your own interface to collection<T>() for full autocomplete and type safety on every read and write.

TypeScript
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<WriteResult>
Append a new record. Every call creates a new nucleotide — nothing is ever overwritten. The returned WriteResult contains id, hash, prev_hash, and timestamp.
.writeBatch(records[])Promise<WriteResult[]>
Write multiple records in parallel. Same semantics as .write() for each item.
.get(id)Promise<NucleotideRecord>
Fetch one record by its nucleotide ID. Throws SapixNotFoundError if not found.
.latest(options?)Promise<NucleotideRecord[]>
Fetch the current (most recent) version of every record in the collection.
.history(options?)Promise<NucleotideRecord[]>
Fetch the full append-only history — every version ever written.
.find(filter, options?)Promise<NucleotideRecord[]>
Find records matching a key/value filter (latest version only).
.findOne(filter)Promise<NucleotideRecord | null>
Returns the first matching record, or null. Never throws on empty result.
.asOf(timestamp)CollectionQuery
Scope all subsequent reads to a point in time. Returns a CollectionQuerywith the same .latest(), .find(), .findOne(), and .all() methods.

Time Travel Queries

TypeScript
// What was the order status 30 minutes ago?
const thirtyMinAgo = new Date(Date.now() - 30 * 60_000).toISOString();

const snapshot = await db
  .collection("orders")
  .asOf(thirtyMinAgo)
  .find({ customer_id: "cust_001" });

// The result shows the order as it was 30 minutes ago —
// before any status updates that happened since.

Graph 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, type, weight?)Promise<void>
The simplest way to create a relationship between two records.
db.graph.traverse(fromId, options?)Promise<TraverseResult>
Walk the graph from a starting node. Returns { nodes, edges }. Options: depth (default 1), direction"outbound" | "inbound" | "both".
db.graph.neighbors(id, direction?)Promise<NucleotideRecord[]>
Get direct neighbours of a node (depth 1 shortcut).
TypeScript
// Link records
await db.graph.relate(order.id, customer.id, "placed_by");
await db.graph.relate(product.id, category.id, "belongs_to");

// Traverse: find all orders placed by this customer (depth 2)
const { nodes, edges } = await db.graph.traverse(customer.id, {
  depth: 2,
  direction: "inbound",
});

Creating 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.

TypeScript — one-time setup script
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:

ZoneMutation policyUse for
immutable_coreNo mutations ever — requires dual-admin proposalFinancial ledgers, compliance archives
protectedMutations require explicit approvalCustomer PII, medical records
governedMutations auto-approve after a timeout (default)Orders, products, general business data
freeMutations apply immediately, no approval neededLogs, metrics, scratch agents

List existing agents

TypeScript
const { agents, total } = await db.listAgents();
console.log(agents); // ["customers", "orders", "shipments"]
console.log(total);  // 3

Write to a named agent after creation

TypeScript — online store agent writing to SapixDB
// 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.

TypeScript
// 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

TypeScript
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

TypeScript — store.ts
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: "[email protected]",
  });

  // 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: what did the order look like when it was placed?
  const placedAt = order.timestamp;
  const [original] = await db
    .collection("orders")
    .asOf(placedAt)
    .find({ customer_id: customer.id });

  console.log(original.data.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?)EventSource
Open an SSE stream for a single named agent. opts.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?)EventSource
Open an SSE stream for all agents. opts.agents restricts the stream to a subset of agent IDs.
TypeScript — per-agent stream
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();
TypeScript — backfill + field filter
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" },
);
TypeScript — global stream (React hook example)
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]);
}
Python and Go SDKs also available

pip install sapixdb and go get github.com/sapixdb/sapixdb-go — same realtime API, every language.