@clawnify/db

中文文档
0.4.3发布于 2026/10/01TypeScriptESM + CJS近 30 天下载 2451

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.

原始使用文档
文档目录13 节
  1. Install
  2. Two APIs
  3. Typed Drizzle (preferred for new apps)
  4. Raw SQL (escape hatch)
  5. Safety: assertBindable
  6. Backend selection
  7. Migrations
  8. API surface
  9. Typed Drizzle
  10. Raw SQL
  11. Types
  12. Versioning
  13. See also

@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:

BindingBackendDriver
env.DB (D1Database)Workers for Platforms live tierdrizzle-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? }) → DB<TSchema>, the query builder both backends share. Type helpers with DB<typeof schema>; never cast to a driver type.

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? } }
  • DB<TSchema> — the client getDB returns, named without its backend
  • DBEnv — { DB?: D1Database; STORAGE?: StorageBinding }
  • RunResult — { changes: number; lastInsertRowid: number }

Versioning

VersionWhat changed
0.4.3getDB returns DB<TSchema> (the drivers' shared base) instead of a union of the two driver types, which collapsed .select(fields) to its zero-argument overload. Partial selects, aggregates and joins now typecheck with no cast. D1-only .batch() is no longer offered.
0.4.1Add require + default conditions to exports so CJS-flavored resolvers (drizzle-kit, certain bundlers) can load the package. No API change.
0.4.0Move drizzle-orm from peerDependencies to dependencies — one install covers everything. No API change.
0.3.1Re-export common Drizzle operators + SQLite DSL so one import line covers typical apps.
0.3.0Add getDB() typed Drizzle path. Keep raw API. Bump drizzle-orm peer.
0.2.1Add assertBindable + json() helper. Safety net for raw API.
0.2.0Collapse @clawnify/storage into this package. Dual-binding via initDB.

See also

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

版本与发布记录

@clawnify/db 共发布 8 个版本,首次发布于 2026/05/15;过去 12 个月发布了 8 个版本。

版本发布日期距今
0.4.3latest2026/10/011 个月前
0.4.22026/09/151 个月前
0.4.12026/05/245 个月前
0.4.02026/05/235 个月前
0.3.12026/05/235 个月前
0.3.02026/05/235 个月前

关于 @clawnify/db 的常见问题

@clawnify/db 是什么?

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.

如何安装 @clawnify/db?

在项目目录运行 npm install @clawnify/db,或使用 pnpm add / yarn add / bun add。

@clawnify/db 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@clawnify/db 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@clawnify/db 可以商用吗?

@clawnify/db 使用 未声明 许可。package.json 没有声明许可证。没有许可证的代码默认保留全部权利,商用前请联系作者或查看仓库中的 LICENSE 文件。以上为概括,不构成法律意见,请以许可原文为准。

@clawnify/db 的下载量有多少?

根据 npm 官方下载统计,@clawnify/db 在 2026/10/10 – 2026/11/08 这 30 天内被下载 2,451 次,日均约 82 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@clawnify/db 还在维护吗?

最新版本 0.4.3 发布于 2026/10/01(1 个月前);至今共发布 8 个版本,其中过去 12 个月发布了 8 个;首次发布于 2026/05/15。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。