how-it-works
serverless-functions
How It Works
The Handler Pattern
Every function exports a handler function. It receives the request data and returns a result:
function handler(data) {
return { message: "Hello, " + data.name };
}
Async functions are supported:
async function handler(url) {
const response = await fetch(url);
return await response.json();
}
Web Worker Isolation
Each deployed function runs in its own Deno Web Worker. Workers are:
- Memory-isolated — one function cannot access another's state
- Permission-restricted — only
netis allowed (no filesystem, no env, no subprocess) - Long-lived — the Worker stays in memory after deploy for fast warm invocations
- Terminable — delete a function and the Worker is terminated immediately
Deno Runtime Pod
├─ Main thread (HTTP server, routing)
├─ Worker: hello ← isolated
├─ Worker: process-data ← isolated
└─ Worker: notify-slack ← isolated
Permissions
| Permission | Allowed | Why |
|---|---|---|
net | Yes | Functions need HTTP fetch for APIs, webhooks |
env | No | Prevents access to K8s secrets/env vars |
read | No | No filesystem access |
write | No | No filesystem writes |
run | No | Cannot spawn subprocesses |
ffi | No | No foreign function interface |
Timeouts
Default timeout is 30 seconds. If a function exceeds the timeout, the invocation fails with an error but the Worker stays alive for future calls.
{
"error": "Execution timeout (30s)"
}
Deploy Lifecycle
- POST /deploy — server receives code, creates a Web Worker
- Worker compiles and sends a
readysignal (10-50ms) - Worker stays in memory, ready for invocations
- Each POST /invoke/{id} sends a message to the Worker, Worker returns result (1-5ms)
- DELETE /delete/{id} — Worker is terminated, memory freed
Provisioning Hooks
When used with the K8s Provisioning service, functions can act as lifecycle hooks:
| Hook | When | Context available |
|---|---|---|
pre-provision | Before pipeline | projectId, tier, databaseType |
post-provision | After ACTIVE | Above + credentials (host, port, password) |
pre-backup | Before backup | projectId |
post-backup | After backup | projectId, backupId |
pre-deprovision | Before delete | projectId |
post-deprovision | After cleanup | projectId |
Example post-provision hook:
async function handler(ctx) {
// Notify Slack
await fetch("https://hooks.slack.com/services/...", {
method: "POST",
body: JSON.stringify({ text: `DB ${ctx.projectId} is ready!` })
});
// Install pgvector
await fetch(`http://provisioning-svc/api/provision/${ctx.projectId}/migrations`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ sql: "CREATE EXTENSION IF NOT EXISTS vector", version: "001", name: "pgvector" })
});
return { notified: true, extensionInstalled: true };
}
Hook failures are non-blocking — the provisioning pipeline always completes regardless.