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.

Gated per project. Functions are off by default on every project. Ask us to enable them for your project and we flip the per-project switch; deploy and invoke both stay closed until then.

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.

ExportSignatureWhat 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

ANY  /functions/v1/{name}

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

CapabilityWhat it doesBoundary
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 timeout30 s (a runaway loop is cancelled → 504)
Memory64 MiB linear memory
Request body1 MiB
Module size8 MiB (.wasm)
Concurrencybounded 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}
Because a function's database access is bound to Row-Level Security and never elevated, a function is a safe place to run logic on behalf of a signed-in user — it cannot read or write anything that user couldn't. To act with broader privilege, write an explicit RLS policy for the role the function runs as.