Querying & DML
Raw SQL execution
const res = await client.query('SELECT * FROM app.messages LIMIT 10');console.log(res.status, res.results?.[0]?.named_rows);With parameters:
const res = await client.query( 'SELECT * FROM app.messages WHERE conversation_id = $1', ['conv_42'],);From client.ts, params are sent through queryWithParams(sql, JSON.stringify(params)).
Vector search queries
Vector search uses the same query() and helper APIs as any other SQL workflow.
const matches = await client.queryAll( `SELECT d.id, d.title FROM rag.documents AS d JOIN rag.documents_vectors AS v ON v.id = d.id ORDER BY COSINE_DISTANCE(v.doc_embedding, $1) LIMIT 5`, [JSON.stringify(queryEmbedding)],);Typical SQL setup:
CREATE TABLE rag.documents_vectors ( id BIGINT PRIMARY KEY, doc_embedding EMBEDDING(384)) WITH (TYPE = 'USER'); ALTER TABLE rag.documents_vectors CREATE INDEX doc_embedding USING COSINE;See Vector Search for the full schema and retrieval pattern.
Row helpers (named_rows)
query() returns a raw QueryResponse. Results are exposed via results?.[0]?.named_rows as plain named-column maps from the server.
For ergonomic access, use:
queryOne(sql, params?)→RowData | nullqueryAll(sql, params?)→RowData[]queryRows<T>(sql, tableName, params?)→KalamRow<T>[]
queryOne() and queryAll() are intentionally not generic. They return RowData with KalamCellValue wrappers. Use queryRows<T>() when you want typed application objects plus FILE-aware row helpers.
const one = await client.queryOne('SELECT id, name FROM app.users WHERE id = $1', ['u1']);console.log(one?.id.asString(), one?.name.asString()); const many = await client.queryAll('SELECT id, active FROM app.users');for (const row of many) { console.log(row.id.asString(), row.active.asBool());}For FILE-aware row wrappers (.file() / .downloadUrl()):
interface MessageRow { id: string; sender: string; content: string;} const rows = await client.queryRows<MessageRow>( 'SELECT id, sender, content, avatar FROM app.messages', 'app.messages',);console.log(rows[0]?.data.sender);The helper methods above wrap each value as KalamCellValue (.asString(), .asInt(), .asFile(), etc.).
See Cell Values for the full API.
executeAsUser
await client.executeAsUser( 'SELECT * FROM app.messages LIMIT 1', 'alice',);Source behavior:
- wraps SQL as
EXECUTE AS '<user_id>' ( <statement> ) - strips trailing semicolons in inner SQL
- escapes
'in the target user id - requires one non-empty statement and a non-empty user id
- is valid for USER and STREAM tables; SHARED tables use their table policy directly
- KalamDB authorizes cross-user targets with its role matrix: system can target any role, DBA can target DBA/service/user, service can target service/user, and regular users can only target themselves
Convenience DML methods
await client.insert('app.messages', { id: 1, sender: 'alice', content: 'hello',}); await client.update('app.messages', 1, { content: 'updated' });await client.delete('app.messages', 1);Important caveat
update() builds SQL string values directly (with quote escaping for strings). For complex/untrusted inputs, prefer query() with parameter placeholders where possible.
Type shape notes from generated declarations
Generated SDK types mark some fields optional:
interface QueryResponse { status: 'success' | 'error'; results?: QueryResult[]; took?: number; error?: ErrorDetail;} interface QueryResult { schema?: SchemaField[]; rows?: unknown[][]; named_rows?: Record<string, unknown>[]; row_count: number; message?: string;}Handle missing results/schema defensively in generic tooling; prefer named_rows when present.