Skip to content

TypeScript / JavaScript SDK

Package: @heliosdb/client (REST/Realtime, Supabase-compatible) or pg / postgres / node-postgres (direct PG wire) Repo: heliosdb-sdks Compatible with: Path B (PostgreSQL wire) works with every HeliosDB edition (Nano, Lite, Full); Path A targets Nano’s REST, auth and realtime surface


UVP

Two valid paths from Node.js / TypeScript into HeliosDB Nano: the @heliosdb/client package for a Supabase-compatible fluent API (db.from('table').select()) backed by REST + WebSocket, or the standard pg / postgres / Drizzle / Prisma stack for direct PostgreSQL-wire access. Both work against the same data; pick by use case. No proprietary driver required. This page shows hands-on snippets for each path — connect, CRUD, vector search — and links to a full end-to-end app tutorial.


Path A — REST + Realtime (@heliosdb/client)

Idiomatic for Next.js / Astro / SvelteKit apps that already know Supabase.

Install

Terminal window
# @heliosdb/client is not published on npm; build it from source
git clone https://github.com/HeliosDatabase/HeliosDB-SDKs.git
(cd HeliosDB-SDKs/sdks/typescript && npm install && npm run build)
npm install ./HeliosDB-SDKs/sdks/typescript

Connect

import { createClient } from "@heliosdb/client";
const db = createClient(
"http://localhost:8080",
"anon-key" // any string before login
);

Auth

// Signup
const { data: signupData } = await db.auth.signUp({
email: "alice@example.com",
password: "s3cret",
});
// Login (returns a session attached to the client)
const { data: { session } } = await db.auth.signInWithPassword({
email: "alice@example.com",
password: "s3cret",
});
console.log("access_token:", session.access_token);

CRUD

// Insert
const { data: post } = await db
.from("posts")
.insert({ title: "Hello Nano", body: "First post" })
.select()
.single();
// Read with filter chain
const { data: recent } = await db
.from("posts")
.select("id, title, body, created_at")
.ilike("title", "%nano%")
.gte("created_at", "2026-04-01")
.order("created_at", { ascending: false })
.range(0, 9); // first 10 rows
// Update
await db.from("posts")
.update({ body: "edited" })
.eq("id", post.id);
// Delete
await db.from("posts").delete().eq("id", post.id);

Nano’s REST filters support eq, neq, gt, gte, lt, lte, like, ilike, is and in, with not. negation; cs, cd, ov, full-text and or/and filters are planned. The client’s chain methods for the planned operators fail against the current release.

const results = await db.vectorSearch("docs", [0.1, 0.2, 0.3, /* ...384 dims... */], {
topK: 10,
metric: "cosine", // "cosine" | "l2" | "ip"
filter: { category: "tech" },
});
for (const hit of results) {
console.log(hit.id, hit.score, hit.row.title);
}

Realtime

const channel = db
.channel("posts-changes")
.on(
"postgres_changes",
{ event: "*", schema: "public", table: "posts" },
(payload) => {
console.log(payload.eventType, payload.new ?? payload.old);
}
)
.subscribe();
// channel.unsubscribe() to stop

Opens ws://localhost:8080/realtime/v1/websocket and emits the same event shape Supabase does. Known issue: realtime events are not delivered in the current release; tracked by the Nano team. Realtime subscriptions are not filtered by row-level security: a subscriber receives every change on the tables it subscribes to. Secured realtime is planned.


Path B — Direct PG Wire (postgres / pg / Drizzle / Prisma)

Idiomatic for backend services with heavy SQL, transactions, or ORM usage.

postgres (porsager/postgres)

Terminal window
npm install postgres
import postgres from "postgres";
const sql = postgres("postgresql://postgres:s3cret@127.0.0.1:5432/postgres");
// Tagged-template parameters (auto-escaped)
const users = await sql`SELECT id, name FROM users WHERE age > ${18}`;
await sql`
INSERT INTO users (name, age) VALUES (${"Alice"}, ${30})
`;
await sql.end();

pg (node-postgres)

Terminal window
npm install pg
import { Pool } from "pg";
const pool = new Pool({
connectionString: "postgresql://postgres:s3cret@127.0.0.1:5432/postgres",
});
const { rows } = await pool.query(
"SELECT id, title FROM posts WHERE author = $1",
["alice@example.com"],
);
console.log(rows);
await pool.end();

Drizzle ORM

import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
import { pgTable, serial, text, timestamp } from "drizzle-orm/pg-core";
const posts = pgTable("posts", {
id: serial("id").primaryKey(),
title: text("title").notNull(),
body: text("body"),
createdAt: timestamp("created_at").defaultNow(),
});
const pool = new Pool({ connectionString: "postgresql://postgres:s3cret@127.0.0.1:5432/postgres" });
const db = drizzle(pool);
const [row] = await db.insert(posts).values({ title: "Hi", body: "..." }).returning();
const all = await db.select().from(posts).limit(50);

Drizzle (and Prisma) work without modification: the fixes recorded in the changelog address every Drizzle edge case (quoted identifiers, mixed-qualifier GROUP BY, parameterized LIMIT/OFFSET, timestamp wire format).

Vector Search via PG Wire

// Numbers only, so the literal cannot carry SQL.
function vecLit(v: number[]): string {
return "'[" + v.map(x => Number(x).toFixed(6)).join(",") + "]'";
}
const qVec = [/* 384 dims */];
const { rows } = await pool.query(
`SELECT id, title, embedding <=> ${vecLit(qVec)}::vector AS distance
FROM docs
ORDER BY distance
LIMIT $1`,
[5],
);

The query vector is inlined as a '[...]'::vector literal. In the current release a ::vector cast on a bound parameter ($1::vector) fails; only inline literals work (#33, see Known Limitations).


When to Pick Which

NeedUse
Frontend / serverless functionPath A (@heliosdb/client)
Realtime subscriptionsKnown issue (tracked)
Drop-in Supabase migrationPath A
Drizzle / Prisma / TypeORMPath B (pg / postgres)
Bulk import (COPY FROM)Path B
Heavy transactional workloadPath B
Multi-tenant RLSNeither in the stock server: enforce per-user access in your service layer (SQL CREATE POLICY and RLS for REST and wire clients are planned)

You can mix both in one repo — REST/Realtime for the API edge, Drizzle for batch jobs and migrations.


Next Steps