@kethosbase/client
v0.3.0
Published
Official Kethosbase JavaScript/TypeScript client — auth, database (REST), storage, and realtime. Zero third-party runtime dependencies.
Maintainers
Readme
@kethosbase/client
The official Kethosbase JavaScript/TypeScript client — auth, database (REST), storage, and realtime in one small package.
- Zero third-party runtime dependencies — nothing but the platform's
fetch. - Familiar, ergonomic API:
createClient,from(...).select().eq(),auth.signInWithPassword,storage.from(...).upload, realtime channels, … - TypeScript-first, browser and Node (18+).
Install
npm install @kethosbase/clientQuick start
import { createClient } from "@kethosbase/client";
const kb = createClient("https://<ref>.kethosbase.com", "kbp_your_publishable_key");
// Database
const { data, error } = await kb
.from("todos")
.select("*")
.eq("done", false)
.order("created_at", { ascending: false })
.limit(20);
// Auth
await kb.auth.signInWithPassword({ email, password });
const { data: { session } } = await kb.auth.getSession();
// Storage
await kb.storage.from("avatars").upload("me.png", file, { upsert: true });
const { data: signed } = await kb.storage.from("avatars").createSignedUrl("me.png", 60);Typed client
Pass a generated Database type to createClient<Database>() and from(...)
becomes schema-aware: table names are checked, and each query is typed to that
table's row and write shapes.
import { createClient } from "@kethosbase/client";
import type { Database } from "./database.types";
const kb = createClient<Database>("https://<ref>.kethosbase.com", "kbp_key");
const { data } = await kb.from("todos").select("*"); // data: Todo[] | null
const { data: one } = await kb.from("todos").select("*").eq("id", 1).single(); // Todo | null
await kb.from("todos").insert({ title: "buy milk" }); // payload checked against Insert
await kb.from("todos").update({ done: true }).eq("id", 1); // Partial<Update>
// @ts-expect-error — unknown column is rejected at compile time
await kb.from("todos").insert({ nope: true });Generate the Database type from your project's schema with the Kethosbase CLI:
npx @kethosbase/cli gen types > database.types.tsselect() and delete() resolve to Row[]; single()/maybeSingle() to
Row | null; insert()/upsert() accept Insert | Insert[]; update() takes
Partial<Update>. Calling createClient(...) without a type argument keeps
the original loosely-typed behavior, so existing code compiles unchanged.
Limitation: the returned row is always the full table row. The SDK does not yet narrow results from the
select("col1,col2")string or from embedded resource selects — that PostgREST select-string inference is out of scope for now. Use an explicit cast if you need a narrowed shape.
Realtime
Kethosbase realtime is a small, native protocol: one WebSocket per channel.
You subscribe to a channel by name, receive broadcasts (and database changes on
db:<table> channels), and publish broadcasts back. The client reconnects with
backoff and re-authenticates with the current session token automatically.
// Broadcast: a lightweight pub/sub channel any client can read and write.
const room = kb
.channel("room:1")
.on("broadcast", (payload) => console.log("got", payload))
.subscribe((status) => console.log(status)); // SUBSCRIBED | CLOSED | CHANNEL_ERROR | TIMED_OUT
await room.send({ msg: "hello", at: Date.now() }); // delivered to all subscribers (incl. you)
// Database changes (CDC): subscribe to a `db:<table>` channel.
const feed = kb
.channel("db:todos")
.on("change", (c) => console.log(c.action, c.record, c.old))
.subscribe();
// Clean up
await kb.removeChannel(room);
await kb.removeAllChannels();Database-change channels (
db:<table>) require aservice_rolekey — they are server-side only and are rejected foranon/publishable keys. Broadcast channels work with any key. The protocol is broadcast-first and live-only (messages are not persisted or replayed); presence and per-table filters are not part of v1.
On non-browser runtimes without a global WebSocket (Node < 22), pass one in:
import WebSocket from "ws";
const kb = createClient(url, key, { realtime: { WebSocket } });Supported
- Database:
select/insert/update/upsert/delete; filters (eq/neq/gt/gte/lt/lte/like/ilike/is/in);order/limit/range;single/maybeSingle;count: "exact";schema(name);rpc(fn, args). - Auth:
signUp,signInWithPassword,signInWithOtp+verifyOtp,signInAnonymously,signInWithOAuth,signOut({ scope }),getSession,getUser,updateUser,refreshSession,setSession,onAuthStateChange. Sessions persist inlocalStorage(browser) and are auto-recovered from the redirect URL fragment after magic-link / OAuth sign-in. The access token is refreshed automatically — proactively before it expires (auth.autoRefreshToken, defaulttrue) and reactively once on a 401 from a data/storage call when a session exists;TOKEN_REFRESHEDfires ononAuthStateChangeeither way. - Storage:
upload/update,download,list,remove,createSignedUrl,getPublicUrl. - Realtime:
channel(name)withon("broadcast"|"change"|"system", …),subscribe(onStatus),send(payload),unsubscribe();removeChannel/removeAllChannels. Auto-reconnect with backoff; token follows the session.
Not yet
- Chained filters on
rpc()results, and MFA helpers. - Realtime presence and per-table change filters (not part of the platform's v1 protocol).
These are planned; the wire endpoints already exist on the platform.
License
MIT © Kerythos AI LLC. See LICENSE.
