npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@run402/functions

v2.4.1

Published

In-function helper library for Run402 serverless functions — db, adminDb, getUser, email, ai, assets. Auto-bundled into deployed functions; also installable for local TypeScript autocomplete.

Downloads

10,362

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, AI helpers, and runtime asset uploads.

import { db, adminDb, getUser, email, ai, assets } 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/functions

The 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: "[email protected]",
  template: "notification",
  variables: { project_name: "My App", message: "Hello!" },
});

// Raw HTML mode
await email.send({
  to: "[email protected]",
  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 / ai.generateImage

const { text, from } = await ai.translate("Hello world", {
  to: "es",
  context: "marketing tagline",
});

const { flagged, categories } = await ai.moderate("Some user-generated text");

const image = await ai.generateImage({
  prompt: "a moonlit dream journal illustration",
  aspect: "landscape",
});
// { image: "<base64 PNG>", content_type: "image/png", aspect: "landscape" }

ai.generateImage supports aspect: "square" | "landscape" | "portrait" and returns base64 image bytes plus content_type and aspect. It uses the function's RUN402_SERVICE_KEY against the project runtime image endpoint; it does not need allowance wallets, x402 wrapping, or local signing inside the function. Runtime image generation is billed and rate-limited against the project's billing account. Quota, rate-limit, and spend-cap failures are ordinary thrown errors such as Image generation failed (403): QUOTA_EXCEEDED: ...; handle them in your app response instead of forwarding raw details to the browser.

Translation requires the AI Translation add-on on the project; moderation is free for all projects.

assets.put(...) — upload runtime assets

Upload bytes from inside a deployed function using the project's service key. This routes through the same CAS-backed apply substrate as deploy-time assets, so public/private visibility, immutable URLs, retention, quota checks, and storage billing match r.project(id).apply({ assets: { put: [...] } }).

import { assets } from "@run402/functions";

const asset = await assets.put("generated/avatar.png", pngBytes, {
  contentType: "image/png",
  visibility: "public",
  immutable: true,
});

return Response.json({ url: asset.immutableUrl ?? asset.url });

source can be a string, Uint8Array, { content: string }, or { bytes: Uint8Array }. The returned AssetRef includes both snake_case wire fields (immutable_url, size_bytes, content_type) and SDK-style camelCase aliases (immutableUrl, size, contentType).

Routed image generation example

Use a routed function when the browser should request an image at app runtime. Keep app-level auth/rate limits in your handler before calling ai.generateImage, especially for public routes.

import { ai, getUser } from "@run402/functions";

export default async function handler(req: Request): Promise<Response> {
  if (req.method !== "POST") {
    return new Response("method not allowed", { status: 405 });
  }

  const user = await getUser(req);
  if (!user) return new Response("unauthorized", { status: 401 });

  const { prompt } = await req.json() as { prompt?: string };
  if (!prompt || prompt.length > 500) {
    return Response.json({ error: "prompt_required" }, { status: 400 });
  }

  try {
    const result = await ai.generateImage({ prompt, aspect: "landscape" });
    return Response.json(result, {
      headers: { "cache-control": "private, no-store" },
    });
  } catch (err) {
    return Response.json(
      { error: "image_generation_unavailable", detail: (err as Error).message },
      { status: 503 },
    );
  }
}

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.

Routed HTTP functions

Deploy-v2 web routes can map public same-origin browser paths to functions, for example routes.replace / "routes": { "replace": [{ "pattern": "/api/*", "target": { "type": "function", "name": "api" } }] }. Use exact /admin plus final-wildcard /admin/* when a dynamic section root and its children should route to the same function. A browser request to a routed path does not need a Run402 API key at the public edge. Direct /functions/v1/:name invocation is unchanged: it remains API-key protected and API-shaped.

Routed browser traffic invokes the same Node 22 Fetch Request -> Response handler used by direct functions:

export default async function handler(req: Request): Promise<Response> {
  const url = new URL(req.url);

  if (req.method === "OPTIONS") {
    return new Response(null, {
      status: 204,
      headers: {
        "access-control-allow-origin": "https://app.example.com",
        "access-control-allow-methods": "GET, POST, OPTIONS",
        "access-control-allow-headers": "content-type, authorization",
      },
    });
  }

  if (req.method === "POST" && !req.headers.has("x-csrf-token")) {
    return Response.json({ error: "csrf_required" }, { status: 403 });
  }

  const headers = new Headers({ "cache-control": "private, no-store" });
  headers.append("Set-Cookie", "sid=abc; HttpOnly; Secure; SameSite=Lax; Path=/");
  headers.append("Set-Cookie", "theme=dark; Secure; SameSite=Lax; Path=/");
  return Response.json({ ok: true, path: url.pathname, query: url.search }, { headers });
}

Request fields:

  • req.method is the original browser method. GET routes also match HEAD; HEAD reaches the handler as HEAD.
  • req.url is the full public URL, including scheme, host, path, and query, on managed subdomains, deployment hosts, and verified custom domains. Derive OAuth callback URLs from new URL(req.url).origin.
  • req.headers is a Fetch Headers object. Cookie data is available through the cookie header.
  • await req.text(), await req.json(), and await req.arrayBuffer() read the buffered request body, capped at 6 MiB.

Response behavior:

  • Return a Web Response with status 200 through 599 except 101 Switching Protocols.
  • Append each cookie with headers.append("Set-Cookie", value); Run402 preserves multiple Set-Cookie values as separate browser headers.
  • Redirects are ordinary 3xx responses with a Location header. HEAD responses send headers without body bytes.
  • Request and response bodies are capped at 6 MiB. WebSockets, 101 Switching Protocols, streaming, and SSE are not supported in Phase 1.

Limits and defaults: Run402 does not add wildcard CORS. Run402 does not store routed dynamic responses in a shared cache; if your function sets no Cache-Control, the gateway adds Cache-Control: private, no-store and x-run402-cache: dynamic-bypass.

Security notes: application auth, authorization, sessions, OAuth callbacks, CORS, and CSRF belong in your function code. For cookie-authenticated POST, PUT, PATCH, or DELETE, validate a CSRF token or an equivalent same-site defense. Do not trust spoofable forwarding headers for authorization.

The raw run402.routed_http.v1 envelope is an internal gateway transport. Low-level routedHttp helpers and RoutedHttpRequestV1 / RoutedHttpResponseV1 types remain exported for tests and gateway-adjacent utilities, but browser route handlers should use Fetch Request and Response.

Runtime route failure codes to branch on: ROUTE_MANIFEST_LOAD_FAILED (manifest/propagation), ROUTED_INVOKE_WORKER_SECRET_MISSING (custom-domain Worker secret), ROUTED_INVOKE_AUTH_FAILED (internal invoke signature), ROUTED_ROUTE_STALE (selected route failed release revalidation), ROUTE_METHOD_NOT_ALLOWED (method mismatch), and ROUTED_RESPONSE_TOO_LARGE (body over 6 MiB).

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 API
  • run402 — the CLI
  • run402-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

License

MIT