@kalamdb/orm
v0.5.1-beta.2
Published
Drizzle ORM driver and schema generator for KalamDB
Maintainers
Readme
@kalamdb/orm
Drizzle ORM driver, KalamDB table helpers, live table helpers, and schema generator for KalamDB.
Use this package with @kalamdb/client when you want Drizzle-style queries in a browser app, admin UI, or Node service. Use @kalamdb/consumer separately for topic workers and agents. Use the same generated table definitions with @kalamdb/react when a React UI needs typed live-query hooks and component wrappers.
Install
npm i @kalamdb/client @kalamdb/orm drizzle-ormDriver quick start
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 rows = await db.select().from(messages).limit(20);The driver normalizes KalamDB temporal wire values for Drizzle columns: timestamp(..., { mode: 'date' }), date(..., { mode: 'date' }), and time(...) are converted from the numeric wire representation when needed.
Table helpers
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(),
body: text('body'),
metadata: jsonb('metadata'),
attachment: file('attachment'),
raw_bytes: bytes('raw_bytes'),
embedding: embedding('embedding', 384),
created_at: timestamp('created_at', { mode: 'date' }).notNull(),
});file(), bytes(), and embedding() are KalamDB-specific Drizzle custom columns. file() maps values to FileRef | null, bytes() maps to Uint8Array | null, and embedding() maps to number[] | null.
Use the table-kind helpers when the table kind matters to live views or agents:
kTable.shared('app.docs', columns);
kTable.user('app.messages', columns);
kTable.stream('app.events', columns);
kTable.system('system.users', columns);Pass { systemColumns: true } when your app needs typed _seq/_deleted fields for ordering, resume checks, or diagnostics. Streams only receive _seq; shared and user tables receive _seq and _deleted.
Consumer And ORM Coverage
The package test suite includes dedicated generated-schema plus runConsumer() scenarios for chat, AI chat memory, job dispatch, support CRM, and commerce fulfillment domains. Each scenario combines shared, user, and stream table definitions with topic consumption and Drizzle builders through kalamDriver() or executeAsUser().
Generate schema.ts
kalamdb-orm \
--url http://localhost:2900 \
--user admin \
--password AdminPass123! \
--namespace app \
--include-system-columns \
--out src/db/schema.tsGenerator options:
--namespace <name>: limit output to one or more namespaces. Repeat it or pass comma-separated names.--include-system: includesystemanddbatables.--include-system-columns [all|_seq,_deleted]: add KalamDB hidden system columns to generated table types.--bigint-mode <string|bigint|number>: choose how generatedBIGINTcolumns are emitted. Default isstringto preserve Int64 precision.--no-type-aliases: skip generated$inferSelectand$inferInsertaliases.
The generator introspects SHOW TABLES, uses DESCRIBE when column metadata is incomplete, preserves primary keys and non-null columns, and emits imports only for builders used by the generated schema. Generated schemas include ${tableName}Config, $inferSelect, and $inferInsert exports next to each table, so browser apps and agents can import schema.generated.ts directly without a wrapper file.
Watch schema.ts in local development
Add a generator script to your app:
{
"scripts": {
"schema:gen": "kalamdb-orm --url http://localhost:2900 --user admin --password AdminPass123! --namespace app --out src/db/schema.ts"
}
}Then let the Kalam CLI rerun that script whenever system.tables changes:
# Watch one namespace
kalam --watch-schema --namespace app --run "npm run schema:gen" --run-on-start
# Watch several namespaces
kalam --watch-schema --namespace chat --namespace billing --run "npm run schema:gen"
# Watch one table only
kalam --watch-schema --table app.messages --run "npm run schema:gen"--watch-schema polls every 5 seconds by default. Override that with --interval 2s, --interval 500ms, or another supported duration when you need faster feedback.
KalamDB datatype mapping
| KalamDB type | Generated Drizzle helper | Wire/read note |
|---|---|---|
| BOOLEAN | boolean() | boolean |
| INT | integer() | number |
| SMALLINT | smallint() | number |
| BIGINT | text() by default | Int64 is transported as a string; use --bigint-mode bigint or number if desired |
| DOUBLE | doublePrecision() | number |
| FLOAT | real() | number |
| TEXT | text() | string |
| TIMESTAMP / DATETIME | timestamp(..., { mode: 'date' }) | numeric timestamp is normalized to Date |
| DATE | date(..., { mode: 'date' }) | date-day wire value is normalized to Date |
| TIME | time() | microseconds since midnight normalize to HH:mm:ss[.fraction] |
| JSON | jsonb() | plain JSON |
| BYTES | bytes() | Uint8Array |
| EMBEDDING(n) | embedding(name, n) | number[] |
| UUID | uuid() | string UUID |
| DECIMAL(p,s) | numeric() | exact decimal string |
| FILE | file() | FileRef | null |
Live table helpers
import { liveTable } from '@kalamdb/orm';
import { messages } from './schema';
const stop = await liveTable(client, messages, (rows) => {
console.log(rows);
}, { lastRows: 50 });
await stop();liveTable() reuses the same @kalamdb/client connection and normalizes timestamp/date/time fields according to the Drizzle table definition.
liveTable() accepts the same row-oriented live options as @kalamdb/client.live(), including lastRows, from, limit, getKey, and onCheckpoint when you want to persist a resume cursor. Raw event streams stay in @kalamdb/client.liveEvents().
React Typed Mode
@kalamdb/react can compile the same Drizzle table descriptors into live query controllers:
import { LiveQueries } from '@kalamdb/react';
import { asc, eq } from 'drizzle-orm';
import { messages, typing } from './schema.generated';
<LiveQueries
queries={{
messages: {
table: messages,
where: (table) => eq(table.conversationId, conversationId),
orderBy: (table) => asc(table.createdAt),
deps: [conversationId],
},
typing: {
table: typing,
where: (table) => eq(table.conversationId, conversationId),
deps: [conversationId],
},
}}
>
{({ messages, typing }) => <ChatView messages={messages.rows} typing={typing.rows} />}
</LiveQueries>This keeps schema ownership in the ORM package and avoids a separate React-specific schema layer.
Execute as a user
Agents and service workers can compile a Drizzle builder and run it through KalamDB's EXECUTE AS USER path:
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.
License
Licensed under the Apache License, Version 2.0 (Apache-2.0). See the packaged LICENSE.txt and NOTICE files.
