Server Functions
Server routines are transactional handlers declared with CREATE FUNCTION or CREATE PROCEDURE. Functions return values through SELECT; procedures use CALL and OUT / INOUT parameters. They run in a sandboxed V8 isolate, share the same types and indexes as tables, and can read or write data on the request transaction.
User-defined functions can appear in SQL expressions. For built-in functions such as LOWER() and NOW(), see Built-in Functions.
You write one procedure contract. How it runs depends on the invocation origin:
| What you want | How it runs | ctx.http | ctx.source.kind |
|---|---|---|---|
| Call it from SQL or PGWire | CALL schema.name(...) | null | "call" |
| Call it over HTTP | POST /v1/functions/{schema}/{name} | present | "call" |
| Wake it when a topic has a new message | CREATE TRIGGER ... ON TOPIC ... EXECUTE PROCEDURE | null | "topic" |
| Run it on a cron or interval | CREATE SCHEDULE ... EXECUTE PROCEDURE | null | "schedule" |
The same procedure can be invoked from SQL and from HTTP. Topic handlers usually take the topic PAYLOAD. Table AFTER ROW triggers are not supported — route writes through a topic instead.
Typed arguments and returns use CREATE TYPE (composites, enums, table row types, topic payloads). Do not fall back to JSON when the shape is known.
Shared host context
Every origin gets the same ctx shape. Inline LANGUAGE JAVASCRIPT bodies and project-deployed TypeScript share it.
| Host | Purpose |
|---|---|
ctx.db.query / ctx.db.execute | Nested SQL on the same request transaction |
ctx.orm | Project TypeScript only: typed Drizzle handle |
ctx.functions.call(name, args) | Nested procedure call (same transaction, same origin) |
ctx.topics.publish(topic, payload) | Stage a topic publish; commit flushes it, rollback drops it |
ctx.log.info / debug / warn / error | Structured logs in system.procedure_logs |
ctx.actor / ctx.principal | Caller vs effective identity (SECURITY INVOKER vs DEFINER) |
ctx.source | Host-built origin metadata. Clients cannot send this. |
ctx.http | HTTP-root only. null for SQL CALL, topic triggers, and schedules. |
ctx.sleep(ms) | Pause, capped at 60s and the remaining deadline |
Clients must not send context, ctx, source, actor, or tx. The host builds those from the authenticated session (or the trigger / schedule principal).
CREATE PROCEDURE, DROP PROCEDURE, GRANT EXECUTE, and REVOKE EXECUTE require DBA or System. CALL requires EXECUTE on that routine (owner, DBA, and System always may). New procedures are not PUBLIC.
1. SQL procedures
Use SQL when the caller is already in a SQL session: CLI, Admin UI SQL Studio, PGWire, or POST /v1/api/sql with a CALL statement.
The function returns its declared result. Procedures return their declared output arguments. Calls support parameter binding; use $1, $2, … placeholders for application input. Nested ctx.db work and ctx.topics.publish share the request transaction:
Rules that matter at the SQL edge:
- New procedures have no
PUBLICexecute privilege. GrantEXECUTEdeliberately. - Owner, DBA, and System can always
CALL. SECURITY INVOKER(default) runs as the caller for RLS andCURRENT_USER.SECURITY DEFINERruns as the procedure owner for that frame.LANGUAGE JAVASCRIPTis executable immediately.LANGUAGE TYPESCRIPTis stored until a project deploy supplies compiled JavaScript.- Project-backed procedures omit
LANGUAGE/AS.kalam schema genscaffoldsfunctions/src/...andkalam deploy(orkalam dev) activates the module. inputis always a named object matching the SQL parameters ({ x }forinc(x INT)).CALLrejects arity mismatches, null forNOT NULL, invalid enum labels, and missing compositeNOT NULLfields (INVALID_ARGUMENTS).
PGWire uses the same CALL path as SQL HTTP. ctx.http is still null.
SQL and PLpgSQL routines
Routines support IN, OUT, INOUT, and VARIADIC arguments, defaults, overloads, and PostgreSQL CREATE, ALTER, DROP, and COMMENT operations. Use the argument types when identifying an overloaded routine, for example DROP FUNCTION app.add_one(INT).
CREATE PROCEDURE ... RETURNS is rejected. Use a function for a return value. SQL, PLpgSQL and JavaScript are executable routine languages. Inline TypeScript requires project compilation and deployment.
2. HTTP procedures
Every executable procedure is also available over REST. This is the same runtime as CALL, not a second controller contract.
The body is a named JSON object, a positional JSON array, or empty for a procedure with no arguments. Success is the SQL return value itself — not { "status": "success", "result": ... }.
On this origin only, ctx.http is present:
HTTP-root rules:
- Readable request metadata: method, path, selected headers, query parameters.
Authorization,Proxy-Authorization, andCookieare not readable.- Root procedures may set status, safe response headers, and content type.
- Nested procedures may read the root request. They cannot mutate the root response.
Connection,Transfer-Encoding,Content-Length, andHostresponse headers are rejected.- Unauthenticated access requires
GRANT EXECUTE ... TO anonymous.PUBLICdoes not include anonymous.
When the same procedure is invoked with SQL CALL, ctx.http is null. Branch on that if one handler serves both edges:
See HTTP API Reference for auth on /v1/functions/.... The same catalog is published as OpenAPI 3.1 at GET /v1/api/openapi.json.
3. Topic-trigger procedures (in-process consumers)
This is the in-database equivalent of a topic consumer. A table write (or an explicit ctx.topics.publish) appends a durable topic message. An enabled trigger wakes the procedure for that message, runs it as the trigger principal, and ACKs after a successful commit.
What happens on INSERT INTO chat.messages ...:
- The write path publishes one topic message (
payload = 'full'includes the row plus_table). - The trigger dispatcher consumes the next offset for group
trigger:{trigger_id}. - It invokes
chat.on_user_messageas a topic-origin root.ctx.source.kindis"topic". - On success, the attempt is recorded as
succeededinsystem.trigger_attemptsand the offset is ACKed. - On failure, the dispatcher retries with backoff, then writes status
dlq.
| Option | Default | Meaning |
|---|---|---|
principal | creating session | Identity used for RLS / CURRENT_USER inside the handler |
start | latest | latest or earliest, captured when the trigger is created |
retries | 5 | Attempts before DLQ |
retry_backoff | 1s | Delay between retries (250ms, 1s) |
concurrency | 1 | In-flight deliveries for this trigger |
Disabling or dropping a trigger keeps committed offsets. Nested ctx.functions.call from a topic root keeps ctx.source.kind = "topic" and sets ctx.parent to the trigger procedure.
Declare the handler argument as the topic payload type (payload chat.ai_inbox), not as untyped JSON. Full payloads include _table (namespace:table) so the handler can tell which source table produced the event.
Use a procedure trigger when the handler should share the database transaction, run next to the data, and ACK automatically after commit. Use an external consumer when the worker lives in another process.
4. Scheduled procedures
KalamDB extends its PostgreSQL-shaped dialect with CREATE SCHEDULE. Do not use MySQL CREATE EVENT.
DBA/System manage schedules. Targets must be existing zero-parameter procedures. Cron uses five fields and an IANA time zone (UTC by default). Intervals are at least one second. Overlaps and missed firings are skipped; there is no catch-up queue.
Scheduled calls use the normal transactional runtime, with ctx.http = null and ctx.source.kind = "schedule". Monitor system.schedules and system.procedure_logs filtered by origin = 'schedule'.
Project TypeScript (schema-first)
For application modules, declare a bodyless function or procedure in schema.sql and implement it in functions/src:
Then:
Existing implementation files are preserved on regeneration. .unimplemented() typechecks and builds, but those handlers are omitted from the compiled registry until you replace them — CALL then reports the procedure as not implemented.
kalam dev builds functions/ after the schema pipeline and activates the project module. Build failures print as errors and do not stop the app process. CLI details: CLI: Server Functions.
Catalog and logs
system.procedure_logs records outcomes (ok, error, log) without request bodies, arguments, results, or tokens.
Authoring options
| Style | When to use |
|---|---|
Inline LANGUAGE JAVASCRIPT AS $$ ... $$ | Small handlers, tests, one-file examples |
Bodyless CREATE FUNCTION / CREATE PROCEDURE + functions/src TypeScript | App modules, shared helpers, kalam schema gen / kalam deploy |
Inline LANGUAGE TYPESCRIPT | Stored for introspection; not executed until a project build compiles it |
Related
Typed SDK invocation
Generated TypeScript and Dart clients call invokeRoutine through the HTTP routine endpoint. See typed routine calls and JSON / KDOM encoding. JavaScript routines can also use ctx.db.queryView and ctx.db.queryBatches for native read views rather than materializing every row as a JavaScript object. Keep views within the routine lifetime.