Package Exports
- @run402/functions
Readme
@run402/functions
In-function helper library for Run402 serverless functions. Imported inside a deployed function — gives you typed access to the caller's database (RLS-respecting) and the project's admin database, the caller's auth, the project's mailbox, and AI helpers.
import { db, adminDb, getUser, email, ai } from "@run402/functions";
export default async (req: Request) => {
const user = await getUser(req);
if (!user) return new Response("unauthorized", { status: 401 });
const mine = await db(req).from("items").select("*").eq("user_id", user.id);
return Response.json(mine);
};This package is auto-bundled into every deployed function zip at deploy time — you don't need to declare it in --deps. Install it locally only when you want TypeScript autocomplete in your editor while authoring function code.
Install (local autocomplete)
npm install @run402/functionsThe two DB clients
The most important distinction in this library: db(req) runs as the caller, adminDb() bypasses RLS.
db(req).from(table) — caller-context
Forwards the request's Authorization header to PostgREST. Row-Level Security policies evaluate against the caller's role — anon, authenticated, project_admin, or whatever the JWT carries. This is the default choice. Routes to /rest/v1/*.
// Reads everything the caller is authorized to see — could be 0 rows for unauthenticated callers.
const mine = await db(req).from("items").select("title, done").eq("user_id", user.id);
// Writes go through RLS too. If the policy says the caller can't insert, it errors.
const [created] = await db(req).from("items").insert({ title: "New", done: false });adminDb().from(table) — bypass RLS
Uses the project's service_key. Returns all rows regardless of RLS. Routes to /admin/v1/rest/* (the gateway rejects role=service_role on /rest/v1/*, so bypass traffic lives on its own surface).
Use only when the function acts on behalf of the platform, not the caller — audit logs, cron cleanup, webhook handlers, fan-out writes after a Stripe event.
// Audit log — capture every event regardless of who triggered the function.
await adminDb().from("audit_log").insert({ event: "payment.succeeded", user_id: userId });
// Cron cleanup — there's no caller to evaluate RLS against.
await adminDb()
.from("sessions")
.delete()
.lt("expires_at", new Date().toISOString());Fluent surface (same on both clients)
.select(cols?)
.eq(col, val) / .neq() / .gt() / .lt() / .gte() / .lte()
.like(col, pattern) / .ilike(col, pattern)
.in(col, [vals])
.order(col, { ascending? })
.limit(n) / .offset(n)
// Writes return arrays of affected rows.
.insert(obj | obj[])
.update(obj) // chain with .eq() to scope
.delete() // chain with .eq() to scope
// Column narrowing on writes:
.insert({ title: "x" }).select("id, title")adminDb().sql(query, params?) — raw SQL, always BYPASSRLS
const { rows, rowCount } = await adminDb().sql(
"SELECT count(*)::int AS n FROM items WHERE user_id = $1",
[userId],
);
// { status: "ok", schema: "p0001", rows: [{ n: 42 }], rowCount: 1 }For SELECT, rows is the result set and rowCount is the row count. For INSERT/UPDATE/DELETE, rows is [] and rowCount is the affected count.
getUser(req) — caller identity
Verifies the caller's JWT and returns the user, or null for unauthenticated requests.
const user = await getUser(req);
if (!user) return new Response("unauthorized", { status: 401 });
// user: { id: string, email: string, role: "authenticated" | "project_admin" | ... }The function's own RUN402_PROJECT_ID is used to scope the verification.
email.send(...) — send mail from the project's mailbox
Auto-discovers the project's mailbox on first call (the project must already have one — create it once with run402 email create <slug> or the create_mailbox MCP tool). After that the mailbox id is cached for the function's lifetime.
// Template mode
await email.send({
to: "user@example.com",
template: "notification",
variables: { project_name: "My App", message: "Hello!" },
});
// Raw HTML mode
await email.send({
to: "user@example.com",
subject: "Welcome!",
html: "<h1>Hi</h1>",
from_name: "My App",
});Templates: project_invite (project_name, invite_url), magic_link (project_name, link_url, expires_in), notification (project_name, message ≤ 500 chars). Throws on rate limit, suppression, or no-mailbox.
ai.translate / ai.moderate
const { text, from } = await ai.translate("Hello world", {
to: "es",
context: "marketing tagline",
});
const { flagged, categories } = await ai.moderate("Some user-generated text");Translation requires the AI Translation add-on on the project; moderation is free for all projects.
Static-site generation (build-time use)
The same library works at build time for static-site generation if you set RUN402_SERVICE_KEY and RUN402_PROJECT_ID in your .env:
// build-time render — feed the page with current data
const items = await adminDb().from("items").select("title, slug").order("created_at", { ascending: false });Use adminDb() (not db(req)) here — there's no incoming request to forward.
Imports auto-resolved
Inside a deployed function you can import { ... } from "@run402/functions" directly — the gateway bundles this library plus any --deps you declared at deploy time. Do not list @run402/functions in your --deps — it's rejected. Native binary modules (sharp, canvas, native bcrypt, etc.) are also rejected.
The bundled version lands in the deploy response's runtime_version field; resolved --deps versions land in deps_resolved.
Errors
All helpers throw on non-2xx responses. The error message includes the HTTP status and the response body so you can branch on code / category / retryable (the v1.34+ agent-operable error envelope).
Engines
Node 22 in deployed functions. >=18 for local use (autocomplete and SSG).
Other interfaces
@run402/functions is one of five surfaces in the run402 monorepo:
@run402/functions(this) — in-function helper, auto-bundled@run402/sdk— typed TypeScript client for the platform APIrun402— the CLIrun402-mcp— MCP server for Claude Desktop / Cursor / Cline / Claude Code- OpenClaw skill — script-based skill for OpenClaw agents
All five release in lockstep at the same version.
Links
- Run402: https://run402.com
- HTTP API reference: https://run402.com/llms.txt
- CLI reference: https://run402.com/llms-cli.txt
License
MIT