Skip to content

TypeScript / JavaScript SDK

TypeScript / JavaScript SDK

Package: @heliosdb/client (REST/Realtime, Supabase-compatible) or pg / postgres / node-postgres (direct PG wire) Repo: heliosdb-sdks Compatible with: all HeliosDB editions (Nano, Lite, Full)


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
npm install @heliosdb/client

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

The 20 PostgREST filter operators (eq, gt, gte, lt, lte, like, ilike, in, is, cs, cd, ov, fts, plfts, phfts, wfts, not, or, and, neq) all map to chain methods.

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. RLS applies on the WebSocket too — clients see only rows their JWT can read.


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 v3.14.5–v3.14.10 patches in CHANGELOG.md address every Drizzle edge case (quoted identifiers, mixed-qualifier GROUP BY, parameterized LIMIT/OFFSET, timestamp wire format).

Vector Search via PG Wire

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

The $1::vector cast tells the planner to coerce the text literal into a VECTOR(384) value.


When to Pick Which

NeedUse
Frontend / serverless functionPath A (@heliosdb/client)
Realtime subscriptionsPath A
Drop-in Supabase migrationPath A
Drizzle / Prisma / TypeORMPath B (pg / postgres)
Bulk import (COPY FROM)Path B
Heavy transactional workloadPath B
Multi-tenant RLS via JWTEither — both honor JWT claims

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


Next Steps