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

@clawnify/db

v0.4.1

Published

Dual-binding SQLite adapter for Clawnify apps. Auto-detects D1 (production WfP) or the Clawnify Storage capability binding (Dynamic Workers preview) and exposes typed Drizzle queries to user code. Also owns the StorageBinding RPC contract.

Readme

@clawnify/db

Dual-binding SQLite adapter for Clawnify apps. Auto-detects D1 (production WfP) or the Clawnify Storage capability binding (Dynamic Workers preview tier) and exposes a single API to user code.

Install

pnpm add @clawnify/db

That's it. drizzle-orm is a regular dependency of this package, so it's pulled in transitively — no separate install to remember. Apps that need to import from drizzle-orm directly can still pnpm add drizzle-orm to pin a specific version; pnpm dedupes.

Two APIs

Typed Drizzle (preferred for new apps)

Define your schema once in TypeScript. The query builder is typed against it, JSON columns auto-serialize, and D1_TYPE_ERROR: Type 'object' becomes structurally impossible.

// src/server/schema.ts
import { sqliteTable, text, integer } from "@clawnify/db";

export const users = sqliteTable("users", {
  id: integer("id").primaryKey({ autoIncrement: true }),
  email: text("email").notNull().unique(),
  prefs: text("prefs", { mode: "json" }).$type<{ theme: string }>(),
  createdAt: text("created_at").notNull().default("CURRENT_TIMESTAMP"),
});
// src/server/index.ts
import { getDB, eq } from "@clawnify/db";
import * as schema from "./schema";

app.get("/api/users/:id", async (c) => {
  const db = getDB(c.env, { schema });
  const user = await db
    .select()
    .from(schema.users)
    .where(eq(schema.users.id, Number(c.req.param("id"))))
    .get();
  return c.json(user);
});

app.post("/api/users", async (c) => {
  const db = getDB(c.env, { schema });
  await db.insert(schema.users).values({
    email: "[email protected]",
    prefs: { theme: "dark" },   // ← JSON column auto-serialized
  });
  return c.json({ ok: true });
});

The common Drizzle surface — operators (eq, and, sql, asc, desc, ...) and the SQLite schema DSL (sqliteTable, text, integer, ...) — is re-exported from @clawnify/db, so one import line covers a typical app. drizzle-orm is still a peer dep and you can import from it directly for anything not re-exported (custom types, relational query builder helpers, etc.).

Raw SQL (escape hatch)

For schema-as-data patterns (open-cms-style apps that let end users define collections at runtime), Drizzle can't model the dynamic tables. Use the raw API:

import { initDB, query, get, run, json } from "@clawnify/db";

initDB(c.env);

// Reads: identical signatures
const rows = await query<User>("SELECT * FROM users WHERE org_id = ?", [orgId]);
const row = await get<User>("SELECT * FROM users WHERE id = ?", [id]);

// Writes: wrap JSON columns with json()
await run(
  "INSERT INTO content_types (uid, info) VALUES (?, ?)",
  [uid, json(info)],
);

json(value) is JSON.stringify(value) but reads as intent at the call site. It returns null for null/undefined so the column stores NULL instead of the literal string "null".

Safety: assertBindable

Every raw query / get / run call walks the params array and throws a TypeError if any param isn't a scalar (string, number, boolean, bigint, null, undefined, Uint8Array, ArrayBuffer):

TypeError: @clawnify/db: param [3] is object — D1/SQLite only binds
scalars. If this is a JSON column, wrap with json(value).
Got: {"singularName":"post","pluralName":"posts",...}

This catches the class of bug where an app forgets to JSON.stringify a column. The native errors are unhelpful here:

  • D1: D1_TYPE_ERROR: Type 'object' not supported for value 'X' (where 'X' is often the wrong param — D1 reports the first one it stringified, not the offending object)
  • SQLite via Facet: Wrong number of parameter bindings for SQL query when the binding is silently dropped

The same assertBindable runs on the supervisor side as defense in depth, so direct StorageBinding.query consumers get the same actionable error.

Backend selection

getDB(env) and initDB(env) auto-pick the binding:

| Binding | Backend | Driver | |---|---|---| | env.DB (D1Database) | Workers for Platforms live tier | drizzle-orm/d1 (native) | | env.STORAGE (Storage RPC) | Dynamic Workers preview tier (Facet SQLite) | drizzle-orm/sqlite-proxy (custom callback) |

The Facet path uses sqlite-proxy and converts our object-row format to the positional-row shape Drizzle expects via Object.values(row). Works because Drizzle generates the SELECT column order itself; the object's insertion order matches what Drizzle reads back.

Migrations

Use drizzle-kit (installed by the consumer) to generate migrations:

pnpm drizzle-kit generate

This emits numbered SQL files in drizzle/ and a schema snapshot in drizzle/meta/. The build pipeline applies them transactionally and records state in __clawnify_migrations. Agents author migrations implicitly by editing schema.ts — no hand-written SQL.

A drizzle.config.ts belongs in the app root pointing at schema.ts. The Clawnify build harness picks it up automatically.

API surface

Typed Drizzle

  • getDB(env, { schema?, logger? })DrizzleD1Database<TSchema> | SqliteRemoteDatabase<TSchema>

Raw SQL

  • initDB(env | D1Database) — call once per request before query/get/run
  • query<T>(sql, params?)T[]
  • get<T>(sql, params?)T | undefined
  • run(sql, params?){ changes, lastInsertRowid }
  • json(value)string | null
  • assertBindable(params) — throws TypeError on non-scalar; called implicitly by query/get/run

Types

  • StorageBinding — the RPC contract the supervisor implements
  • StorageResult{ rows: Record<string, unknown>[]; meta?: { changes?, last_row_id? } }
  • DBEnv{ DB?: D1Database; STORAGE?: StorageBinding }
  • RunResult{ changes: number; lastInsertRowid: number }

Versioning

| Version | What changed | |---|---| | 0.4.1 | Add require + default conditions to exports so CJS-flavored resolvers (drizzle-kit, certain bundlers) can load the package. No API change. | | 0.4.0 | Move drizzle-orm from peerDependencies to dependencies — one install covers everything. No API change. | | 0.3.1 | Re-export common Drizzle operators + SQLite DSL so one import line covers typical apps. | | 0.3.0 | Add getDB() typed Drizzle path. Keep raw API. Bump drizzle-orm peer. | | 0.2.1 | Add assertBindable + json() helper. Safety net for raw API. | | 0.2.0 | Collapse @clawnify/storage into this package. Dual-binding via initDB. |

See also

  • docs/packages.md — overall Clawnify SDK strategy
  • docs/app-template.md — gold-standard app layout
  • docs/preview-tier.md — how the supervisor uses StorageBinding