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

@kethosbase/functions

v0.1.0

Published

Official Kethosbase SDK for serverless Functions (QuickJS-on-WASM runtime): serve(), db, fetch, secrets, and pure-JS crypto. Zero third-party runtime dependencies.

Readme

@kethosbase/functions

The official SDK you import inside a Kethosbase serverless Function.

Functions run as a WebAssembly module: you write TypeScript, the Kethosbase CLI compiles it to a QuickJS-on-WASM module (via its Javy toolchain), and the platform runs it in a wazero sandbox. This is a different runtime from @kethosbase/client. There is no fetch, no network, no Node or browser API in here. The only way a Function reaches the platform is through host-provided globals — and this SDK wraps them in an ergonomic API so you never touch them directly.

  • Zero third-party runtime dependencies.
  • Pure-JS base64, UTF-8, SHA-256 and HMAC-SHA256 (the runtime has no Web Crypto), so signature verification works with no imports.
  • Full TypeScript types.
npm install @kethosbase/functions

Hello, Function

import { serve, kethosbase } from "@kethosbase/functions";

serve(async (req) => {
  await kethosbase.log(`${req.method} ${req.path}`);

  const { rows } = await kethosbase.db.query("select $1::int as n", [1]);

  return {
    status: 200,
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ n: rows[0].n }),
  };
});

serve(handler) is the whole program: it reads the incoming request from the host, hands your handler an ergonomic request object, and writes the response back. Register exactly one handler.

The request object

interface FunctionRequest {
  method: string;
  path: string;
  query: string;                 // raw query string, no leading "?"
  query_get(name: string): string | null; // first value, percent-decoded
  headers: Record<string, string>;         // keys lower-cased
  body: Uint8Array;                         // decoded from the wire
  text(): string;                           // body as UTF-8
  json<T>(): T;                             // body parsed as JSON (throws if invalid)
}

The response

Return { status?, headers?, body? }. body may be a string (UTF-8 encoded for you) or a Uint8Array. Return nothing for an empty 200. status is clamped to the valid 100..599 range.

If your handler throws (or rejects), the SDK returns a generic 500 and logs the error detail for the operator — the error message is never put in the HTTP response body.

The kethosbase object

Every method returns a Promise so you can await it. (The host bridge is synchronous under QuickJS — there is no real async I/O in this runtime — but the async surface keeps handlers ergonomic and future-proof.)

| Call | Does | | --- | --- | | kethosbase.log(msg) | Append a line to the Function log. | | kethosbase.db.query(sql, args?) | Run parameterized SQL ($1, $2, …). Returns { rows }; throws on error. | | kethosbase.fetch(url, init?) | Outbound HTTP through the host (the only egress path). Fetch-like. | | kethosbase.secret(name) | Resolve a Function secret; throws if unset. |

Database

const { rows } = await kethosbase.db.query<{ id: string; total: number }>(
  "select id, total from invoices where org_id = $1 order by created_at desc limit 10",
  [orgId],
);

Queries run with the Function's least-privilege, RLS-bound role — they are never elevated to service_role, even if the Function was invoked with a secret key (ADR-0084 / ADR-0091). Always use $1-style placeholders; never build SQL by string concatenation.

Outbound HTTP

const res = await kethosbase.fetch("https://api.stripe.com/v1/charges", {
  method: "POST",
  headers: { authorization: `Bearer ${await kethosbase.secret("STRIPE_KEY")}` },
  body: "amount=500&currency=usd",
});
if (!res.ok) throw new Error(`stripe ${res.status}`);
const charge = res.json<{ id: string }>();

Secrets

const secret = await kethosbase.secret("STRIPE_WEBHOOK_SECRET");

Secrets are returned to your code only. The SDK never logs a secret value.

Crypto helpers

The runtime has no crypto.subtle, so the SDK ships pure-JS primitives for the one thing Functions most need them for — verifying inbound webhook signatures:

import { hmacSha256, toHex, timingSafeEqual, utf8Encode } from "@kethosbase/functions";
  • sha256(bytes) => Uint8Array
  • hmacSha256(key, message) => Uint8Array (key/message: string | Uint8Array)
  • toHex(bytes) => string
  • timingSafeEqual(a, b) => boolean — constant-time byte compare

Full example: Stripe webhook

Verifies the Stripe-Signature header (scheme t=<ts>,v1=<hmac>), then records the event. Stripe signs the string "<timestamp>.<raw-body>" with your webhook signing secret using HMAC-SHA256.

import {
  serve,
  kethosbase,
  hmacSha256,
  toHex,
  timingSafeEqual,
  utf8Encode,
} from "@kethosbase/functions";

// Parse "t=123,v1=abc,v1=def" into { t, v1: [...] }.
function parseSigHeader(header: string): { t?: string; v1: string[] } {
  const out: { t?: string; v1: string[] } = { v1: [] };
  for (const part of header.split(",")) {
    const [k, v] = part.split("=", 2);
    if (k === "t") out.t = v;
    else if (k === "v1" && v) out.v1.push(v);
  }
  return out;
}

serve(async (req) => {
  const sigHeader = req.headers["stripe-signature"];
  if (!sigHeader) return { status: 400, body: "missing signature" };

  const { t, v1 } = parseSigHeader(sigHeader);
  if (!t || v1.length === 0) return { status: 400, body: "malformed signature" };

  // Reject stale timestamps to blunt replay (5-minute tolerance).
  const ageSeconds = Math.floor(Date.now() / 1000) - Number(t);
  if (!Number.isFinite(ageSeconds) || Math.abs(ageSeconds) > 300) {
    return { status: 400, body: "timestamp outside tolerance" };
  }

  const secret = await kethosbase.secret("STRIPE_WEBHOOK_SECRET");

  // Sign "<t>.<raw body>" — use the raw bytes, not a re-serialized JSON.
  const signedPayload = `${t}.${req.text()}`;
  const expected = toHex(hmacSha256(secret, signedPayload));
  const expectedBytes = utf8Encode(expected);

  // Constant-time compare against every provided v1 (Stripe may send several
  // during secret rotation).
  const ok = v1.some((candidate) =>
    timingSafeEqual(expectedBytes, utf8Encode(candidate)),
  );
  if (!ok) return { status: 400, body: "signature mismatch" };

  // Signature verified — record the event idempotently.
  const event = req.json<{ id: string; type: string }>();
  await kethosbase.db.query(
    `insert into stripe_events (id, type, received_at)
     values ($1, $2, now())
     on conflict (id) do nothing`,
    [event.id, event.type],
  );

  return { status: 200, headers: { "content-type": "application/json" }, body: "{}" };
});

Notes

  • We compare the hex digests as bytes with timingSafeEqual so a mismatch never short-circuits and leaks how much matched.
  • The timestamp check bounds replay; the on conflict do nothing makes re-delivery idempotent.
  • req.text() returns the exact received body, which is what Stripe signed — do not re-serialize the JSON before hashing, or the signature will not match.

Runtime notes

  • Sync bridge, async API. The host globals are synchronous (QuickJS is single-threaded and there is no event loop with real timers/I/O). The public methods return Promises for ergonomics; they resolve on a microtask that the Javy runtime drains before the module returns.
  • No fetch, Buffer, atob, or crypto.subtle. The SDK is written to the QuickJS baseline and feature-detects TextEncoder/TextDecoder, so it works whether or not the runtime provides them.

License

MIT © Kerythos AI LLC.