Edge Functions
Run your own code next to your database as a sandboxed WebAssembly module. A function has no ambient credentials — it reaches the database, the network, and your secrets only through capabilities that stay inside your project's security boundary.
Write a function in TypeScript
The quickest way to author a function is TypeScript (or JavaScript) with
the @kethosbase/functions SDK. You write a
handler; the CLI bundles it, compiles it to WebAssembly,
and deploys it — you never touch the raw module format.
npm install @kethosbase/functions
// hello.ts
import { serve, db, log } from "@kethosbase/functions";
serve(async (req) => {
log("request", req.method, req.path);
// RLS-bound query — returns only rows the caller may see.
const rows = db.query("select $1::text as name", [req.json().name ?? "world"]);
return {
status: 200,
headers: { "content-type": "application/json" },
body: { hello: rows[0].name },
};
});
SDK API
Everything a function can do is on the object it imports — there are no
ambient globals, no fetch() to arbitrary hosts, no filesystem.
| Export | Signature | What it does |
|---|---|---|
serve |
serve(handler: (req) => Response | Promise<Response>) |
Registers your handler. req has method,
path, query, headers, a raw
body (Uint8Array), and helpers
req.text() / req.json(). Return
{ status?, headers?, body? } — body may be a
string, a Uint8Array, or an object (JSON-encoded for you). |
db.query |
db.query(sql: string, args?: unknown[]): Row[] |
Runs a parameterized SQL statement and returns the rows. Bound to the caller's RLS identity (below); throws on a SQL error. |
secret |
secret(name: string): string |
Returns a project secret's value (below). Throws if it is not set. |
fetch |
fetch(url, init?): Promise<{ status, headers, body, text(), json() }> |
An outbound HTTP call, subject to the egress allowlist (below). |
log |
log(...args: unknown[]): void |
Appends a line to the invocation log. |
Deploy with the CLI
Log in and link the project once, then deploy the file. The CLI bundles your entrypoint with the SDK, compiles it to a WebAssembly module, validates it, and uploads it:
npm install -g @kethosbase/cli
kethosbase login
kethosbase link # selects the project in this directory
kethosbase functions deploy hello.ts
# → Deployed function hello (sha256 …, 961236 bytes).
The function name defaults to the file's base name (override with
--name); re-deploying the same name replaces it. Use
--dry-run -o hello.wasm to produce and inspect the module
without uploading. You can also manage functions from the project dashboard,
which uploads a compiled .wasm directly.
Invoke a function
A deployed function answers on its own path under
/functions/v1/. It receives the incoming method, path, query,
headers, and body, and returns whatever status, headers, and body it writes.
Authentication is the same as the data API — send
Authorization: Bearer <key-or-token>:
curl -X POST "https://<ref>.kethosbase.com/functions/v1/hello" \
-H "Authorization: Bearer <key>" \
-H "Content-Type: application/json" \
-d '{"name": "world"}'
The caller's credentials authenticate the request but are not
handed to the module — the Authorization and Cookie
headers are stripped before the function runs. Instead, the caller's identity
is carried into the database capability (below), so a function reads and
writes exactly what that caller could.
The sandbox
Each function is a WebAssembly module run on an in-house runtime (wazero, WASI). It has no filesystem, no sockets, and no clock beyond WASI, and holds no connection string, service key, or other credential. Anything a function needs from the outside world it must ask for through a capability that the platform mediates and audits.
The runtime speaks a simple contract: the request is delivered as JSON on
stdin, and the function writes a JSON response envelope
({ status, headers, body }) to stdout. The
@kethosbase/functions SDK (above) speaks
that contract for you. Any toolchain that compiles to WASI can also produce a
function directly — for example Go
(GOOS=wasip1 GOARCH=wasm go build), whose module imports only the
kethosbase host functions and WASI.
Capabilities
| Capability | What it does | Boundary |
|---|---|---|
| Database | Run parameterized SQL and read the rows back. | Runs under the caller's identity with RLS enforced
— an authenticated caller keeps their user identity (so
auth.uid() works); everything else runs as anon.
Never service_role, so writes need an RLS
policy for the identity the function runs as. |
| Outbound HTTP | Make an HTTP request to an external service. | Deny-by-default: only hosts on your project's allowlist are reachable. Guarded against SSRF (the resolved IP is checked at dial time, private / loopback / link-local ranges are blocked, redirects are not followed), metered as egress, response capped at 1 MiB, 10-second per-request timeout. |
| Secrets | Read a named project secret at runtime. | Values are sealed at rest and decrypted only transiently inside the call. The management API can list secret names but never returns a value. |
| Logging | Append lines to the invocation log. | Capped per invocation. |
Limits
| Wall-clock timeout | 30 s (a runaway loop is cancelled → 504) |
| Memory | 64 MiB linear memory |
| Request body | 1 MiB |
| Module size | 8 MiB (.wasm) |
| Concurrency | bounded per instance |
A runtime error answers 502 (function_error);
exceeding the timeout answers 504
(function_timeout).
Management API
The CLI and the dashboard sit on top of the project management API (owner or admin session), which you can also call directly. Functions, their secrets, and their egress allowlist are all managed here; a deploy uploads the compiled module, and re-deploying the same name replaces it.
# deploy (or replace) a compiled module
POST /v1/projects/{ref}/functions/{name} # body: the .wasm module
DELETE /v1/projects/{ref}/functions/{name}
# secrets the function can read at runtime
GET /v1/projects/{ref}/functions/secrets
PUT /v1/projects/{ref}/functions/secrets/{name}
DELETE /v1/projects/{ref}/functions/secrets/{name}
# the outbound-HTTP allowlist (deny-by-default)
GET /v1/projects/{ref}/functions/egress-rules
PUT /v1/projects/{ref}/functions/egress-rules/{host}
DELETE /v1/projects/{ref}/functions/egress-rules/{host}