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.
Execute as another user
await client.executeAsUser(
'SELECT * FROM app.messages LIMIT 1',
'alice',
);Source behavior:
- wraps SQL as
EXECUTE AS USER 'username' ( <statement> ) - strips trailing semicolons in inner SQL
- escapes
'in username - requires one non-empty statement and a non-empty username
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.