Skip to Content
Drizzle ORM & Generator

Drizzle ORM & Generator

@kalamdb/orm is the Drizzle package for KalamDB. Use it when you want generated tables, typed queries, or table-level live helpers. It runs on top of @kalamdb/client.

Install it with the client and Drizzle:

BASH
npm i @kalamdb/client @kalamdb/orm drizzle-orm

Driver quick start

Copy this after you have a generated schema.ts file.

TS
import { Auth, createClient } from '@kalamdb/client';import { kalamDriver } from '@kalamdb/orm';import { drizzle } from 'drizzle-orm/pg-proxy';import { messages } from './schema'; const client = createClient({  url: 'http://localhost:2900',  authProvider: async () => Auth.basic('admin', 'AdminPass123!'),}); const db = drizzle(kalamDriver(client));const recent = await db.select().from(messages).limit(20);

The driver uses KalamDB SQL over HTTP and normalizes temporal values for Drizzle columns. timestamp(..., { mode: 'date' }), date(..., { mode: 'date' }), and time(...) can read KalamDB’s numeric wire values without app-side parsing.

Pass a full KalamDBClient to kalamDriver() so FILE uploads can route to queryWithFiles() when needed.

Table helpers

Use kTable instead of raw pgTable so the schema retains KalamDB table type metadata.

TS
import { bytes, embedding, file, kTable } from '@kalamdb/orm';import { bigint, jsonb, text, timestamp, uuid } from 'drizzle-orm/pg-core'; export const docs = kTable.shared('app.docs', {  id: bigint('id', { mode: 'bigint' }).primaryKey(),  owner_id: uuid('owner_id').notNull(),  title: text('title').notNull(),  metadata: jsonb('metadata'),  attachment: file('attachment'),  raw_bytes: bytes('raw_bytes'),  doc_embedding: embedding('doc_embedding', 384),  created_at: timestamp('created_at', { mode: 'date' }).notNull(),});

Available table helpers:

HelperUse for
kTable.shared(name, columns, options?)WITH (TYPE = 'SHARED') tables
kTable.user(name, columns, options?)per-user isolated tables
kTable.stream(name, columns, options?)stream tables
kTable.system(name, columns, options?)system metadata tables
kSystemColumns([...])_seq, _deleted, and _commit_seq typed hidden columns

KalamDB-specific columns:

HelperRuntime value
file(name)`FileRef
bytes(name)`Uint8Array
embedding(name, dimensions)`number[]

Generate schema.ts

Generate a Drizzle schema from a running KalamDB server:

BASH
npx kalamdb-orm \  --url http://localhost:2900 \  --user admin \  --password AdminPass123! \  --namespace app \  --include-system-columns \  --out src/db/schema.ts

The generator reads SHOW TABLES, calls DESCRIBE <namespace.table> when column metadata is incomplete, and emits only the imports needed by the generated tables. It preserves primary keys, not-null constraints, system-column options, comments, table config constants such as chat_messagesConfig, and $inferSelect / $inferInsert aliases.

Options:

OptionDescription
--namespace <name>Limit output to one or more namespaces. Repeat it or pass comma-separated names.
--include-systemInclude system and dba tables.
--include-system-columns <mode>Add typed hidden columns to generated table definitions. Use all, _seq, or _deleted.
--bigint-mode <mode>Choose how generated BIGINT columns are emitted. Accepted values are string, bigint, or number; default is string.
--no-type-aliasesSkip generated $inferSelect and $inferInsert aliases.

BIGINT defaults to text() because KalamDB transports Int64 values as strings to preserve precision. Choose --bigint-mode bigint when you want Drizzle to coerce values to native bigint, or --bigint-mode number only when values fit safely in JavaScript numbers.

Keep schema.ts fresh during local development

Add a generator script to your app so the command stays short:

JSON
{  "scripts": {    "schema:gen": "kalamdb-orm --url http://localhost:2900 --user admin --password AdminPass123! --namespace app --out src/db/schema.ts"  }}

In a kalam.toml project, kalam dev watches schema.sql and reruns kalam schema gen when [dev].generate_types = true:

BASH
kalam dev

See Local Development. The legacy kalam --watch-schema server-polling flag is deprecated.

Exact KalamDB datatype mapping

KalamDB typeGenerated Drizzle helperRead behavior
BOOLEANboolean()boolean
INTinteger()number
SMALLINTsmallint()number
BIGINTtext() by defaultstring for Int64 precision
DOUBLEdoublePrecision()number
FLOATreal()number
TEXTtext()string
TIMESTAMPtimestamp(..., { mode: 'date' })Date when using date mode
DATETIMEtimestamp(..., { mode: 'date' })Date when using date mode
DATEdate(..., { mode: 'date' })UTC-date Date
TIMEtime()HH:mm:ss[.fraction] string
JSONjsonb()JSON value
BYTESbytes()Uint8Array
EMBEDDING(n)embedding(name, n)number[]
UUIDuuid()UUID string
DECIMAL(p,s)numeric()exact decimal string
FILEfile()`FileRef

Upsert and returning

Drizzle returning() and onConflictDoUpdate() compile to KalamDB SQL and run through the same driver:

TS
import { sql } from 'drizzle-orm'; const inserted = await db  .insert(users)  .values({ id: 1, name: 'Nader', age: 3 })  .returning(); const updated = await db  .insert(users)  .values({ id: 1, name: 'Nader Updated', age: 5 })  .onConflictDoUpdate({    target: users.id,    set: { name: sql`excluded.name`, age: sql`excluded.age` },  })  .returning();

FILE uploads through Drizzle

You do not wrap every query. Normal select, insert, update, and delete calls go through db directly. Only FILE bytes need multipart SQL, and the driver handles that when upload values appear in .values() or .set():

TS
import { kalamFile } from '@kalamdb/orm';import { attachments } from './schema'; await db.insert(attachments).values({  id: 'att_1',  file_data: kalamFile('upload', selectedFile),}); await db  .insert(context_files)  .values({    path: 'notes/readme.md',    file_ref: kalamFile('upload', uploadFile),    updated_at: new Date(),  })  .onConflictDoUpdate({    target: context_files.path,    set: {      file_ref: kalamFile('upload', uploadFile),      updated_at: new Date(),    },  });

You can also pass a File or Blob directly to a generated file() column; the driver uses the filename (or upload for plain blobs) as the multipart field name.

kalamDriver() compiles the Drizzle builder, normalizes quoted identifiers and DEFAULT columns for KalamDB, replaces upload params with FILE("name"), and calls client.queryWithFiles() when bytes are present.

For raw SQL strings (no Drizzle builder), use queryWithFiles() from @kalamdb/orm or @kalamdb/client. Use compileQuery() when you need normalized SQL and params without executing.

See FILE Columns & Uploads for download URLs, FileRef, and progress callbacks.

Execute as a user

Agents and service workers can compile a Drizzle builder and run it through KalamDB’s EXECUTE AS USER path:

TS
import { executeAsUser } from '@kalamdb/orm'; await executeAsUser(  client,  db.insert(messages).values({    room: 'main',    role: 'assistant',    author: 'KalamDB Copilot',    sender_username: 'alice',    content: 'Done.',  }),  'alice',);

Only pass a user id that your service account is authorized to impersonate.

Live table helpers

Use liveTable() for React state, dashboards, and admin UI screens that want the current materialized row set.

TS
import { liveTable } from '@kalamdb/orm';import { messages } from './schema'; const stop = await liveTable(client, messages, (rows) => {  setRows(rows);}, {  lastRows: 100,}); await stop();

liveTable() reuses the same @kalamdb/client connection and normalizes timestamp/date/time fields from the Drizzle table metadata. It accepts the same row-oriented options as client.live(), including lastRows, from, limit, getKey, and onCheckpoint. Use client.liveEvents() directly when you need low-level change events.

UI and example fit

Use this package in UI code or service code that wants typed tables. Worker code should import @kalamdb/consumer only when it needs topic consumption.

Copy This Project Shape

TEXT
src/  db/    schema.ts      # generated by kalamdb-orm    client.ts      # createClient() + drizzle(kalamDriver(client))  app/    messages.ts    # Drizzle queries against generated tables
Last updated on