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

JavaScript / TypeScript SDK

✓ Shipped

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

// 🔍 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.

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<WriteResponse>
Append a new record. Every call creates a new nucleotide — nothing is ever overwritten. The returned WriteResponse contains record_id, content_hash, parent_hash, and timestamp_hlc.
.writeBatch(records[])→ Promise<WriteResponse[]>
Write multiple records in parallel. Same semantics as .write() for each item.
.get(contentHash)→ Promise<RecordView>
Fetch one record by its content hash (hex string). Throws SapixError on 404.
.latest(limit?)→ Promise<SaqlQueryResult>
The most recent limit records (default 20).
.query(body)→ Promise<SaqlQueryResult>
Execute a structured SaQL query. 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>
Current chain head hash and total record count for this agent.
.status()→ Promise<AgentStatusResponse>
Agent status — record count and chain head in one call.
.deleteRecord(contentHash)→ Promise<void>
Soft-delete (tombstone) a record by content hash. The record stays in the strand for audit but is excluded from .latest() results.
.restore(contentHash)→ Promise<void>
Restore a previously soft-deleted record.

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.

TypeScript
// 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:00Z

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, edgeType, weight?)→ Promise<void>
Create a directed edge with weight 1.0. Shorthand for addEdge.
db.graph.addEdge(edge)→ Promise<void>
Create a directed edge with explicit src, dst, edge_type, and weight.
db.graph.edges(agentId)→ Promise<GraphEdge[]>
All outbound edges from an agent.
db.graph.inboundEdges(agentId)→ Promise<GraphEdge[]>
All inbound edges to an agent.
db.graph.traverse(agentId, options?)→ Promise<TraversalResult>
Walk the graph from a starting agent. Options: depth (default 1, max 3), edge_type (filter). Returns { nodes, edges }.
db.graph.removeEdge(src, edgeType, dst)→ Promise<void>
Delete a directed edge.
TypeScript
// 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 traversed

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: "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?)→ 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.