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

@weifuwujs/db

v1.3.0

Published

weifuwu 数据面:声明集合 → REST/GraphQL/OpenAPI 三投影 + 参数化查询编译(Postgres-first,headless)

Readme

数据面(@weifuwujs/db)

状态:已发布(@weifuwujs/[email protected],工作区 lockstep 版本)。路线与边界见 plan/db.md。

声明一次集合,得到 REST + GraphQL + OpenAPI 三投影的数据面;SQL 全部参数化编译,Postgres-first。

import { col, defineDb } from '@weifuwujs/db'
import { createApp } from '@weifuwujs/weifuwu'

const db = defineDb({
  collections: {
    notes: {
      publicRead: true,
      fields: {
        id: col.uuid({ primary: true, default: 'gen_random_uuid()' }),
        title: col.text({ required: true }),
        status: col.enum('notes_status', ['draft', 'published'] as const),
        authorId: col.fk('users'),
        createdAt: col.timestamptz({ auto: 'create' }),
      },
    },
  },
})

const app = createApp().use(postgres()) // 注入 ctx.sql
app.mount(db.api(), { graphql: '/graphql', openapi: '/openapi.json' })

最小可跑示例(零依赖,node 直接运行;test/batteries/readme-examples.test.ts 会真跑它):

import assert from 'node:assert/strict'
import { col, compileInsert, compileList, defineDb } from '@weifuwujs/db'

const db = defineDb({
  collections: {
    notes: {
      fields: {
        id: col.uuid({ primary: true }),
        title: col.text({ required: true }),
        status: col.enum('notes_status', ['draft', 'published'] as const),
      },
    },
  },
})

// 编译层零依赖:不连数据库也能看到真实 SQL 与参数(值全部参数化)
const notes = db.model.collections[0]!
const insert = compileInsert(notes, { title: '第一条笔记', status: 'draft' })
assert.match(insert.text, /^insert into "notes"/)
assert.deepEqual(insert.values, ['第一条笔记', 'draft'])

const list = compileList(notes, { limit: 5 })
assert.match(list.text, /^select .+ from "notes"/)
assert.equal(list.values[0], 5)

示例标注:ts exec = 由测试真跑;ts fragment = 片段(依赖前后文,不独立运行);自足示例带包标签(如 ts tasks),由测试编译。

真 HTTP 挂载 + 默认拒绝(3.1/3.2-α):

const db = defineDb({
  sql: postgresExecutor(sql),
  collections: {
    notes: { publicRead: true, fields: { /* … */ } },
    orders: { fields: { /* … */ } },                    // 未开 publicRead:读 403
  },
  authorize: (ctx, { collection, action }) =>          // 写默认 403;钩子内接自己的鉴权
    ctx.state?.user?.role === 'admin',                  // 例:仅 admin 可写
})

createApp().mount(db.api(), { graphql: '/graphql', openapi: '/openapi.json' })
  • REST 参数:filter(JSON 串,含 eq/neq/gt/gte/lt/lte/like/ilike/in/isNull/and/or)、sort=-qty,name、 limit/offset、select=id,name;typed where/orderBy/fields 仅 GraphQL;?where={…} 直用未支持。
  • select 只收窄 SQL 列;描述符输出会把未选字段补成 null(api 对象输出语义,非本包行为)。

每个集合自动获得:

| 投影 | 形态 | | --- | --- | | REST | GET /notes(filter JSON + sort + limit/offset + select)、GET /notes/:id、POST /notes、PATCH /notes/:id、DELETE /notes/:id | | GraphQL | notes(where, orderBy, limit, offset, fields)、notesById、createNotes、updateNotes、deleteNotes,Where/OrderBy 为按列生成的 typed input | | OpenAPI | /openapi.json(3.1),可直接喂给 openapi-typescript 生成客户端类型 |

安全契约

  • 标识符白名单:表名/列名只能来自 defineDb 元数据;请求里的字段名必须命中白名单,SQL 编译输出全部双引号引用。
  • 值全参数化:任何值只以 $n 占位符进入 SQL;where 操作符限定 eq/neq/gt/gte/lt/lte/like/ilike/in/isNull/and/or。
  • 分页安全带:limit 默认 50、上限 200;offset ≥ 0。
  • 默认拒绝:未声明 publicRead 的集合连读也 403;写一律 403,除非提供 authorize 钩子放行。
  • 四包对照:docs/authorization.md——db/rag/workflow/pi-agent 的默认拒绝矩阵、决策形状与组合边界(可执行契约)。 授权判定发生在 SQL 编译/执行之前(被拒请求不触达数据库)。
  • 授权钩子:authorize(ctx, { collection, action })(action ∈ read|create|update|delete|admin)返回 boolean | { where, fields? }: true 放行、false 拒绝、{ where } 放行并强制行级过滤——作为普通 WhereInput 参数化 AND 并入 list/byId/update/delete($n 连续、零拼接),客户端 filter/or 与 GraphQL 均无法绕过; { where } 不能约束插入 → create 403;策略字段非法 → 400 且零 SQL。

字段级权限(行权 ≠ 列权)

三级授权:collection(公开与否)→ 行级 { where } → 字段级 fields,都在 SQL 编译/执行之前判定。

const users = defineDb({
  sql: postgresExecutor(sql),
  collections: {
    users: {
      fields: { id: col.uuid({ primary: true }), owner: col.text({ required: true }),
                nickname: col.text(), passwordHash: col.text() /* 可空,否则无法裁剪 */ },
      // 静态策略:未知字段名在 defineDb 时即抛 SqlError(不等到运行期)
      fieldPolicy: { read: ['nickname'], write: ['nickname'] },
    },
  },
  // 动态策略:策略可随用户变化;与静态策略取**交集**(只能收紧,不能放宽)
  // 行约束 + 列约束:create 用 fields-only 形态({ where } 无法约束插入)
  authorize: (ctx, { action }) => action === 'create'
    ? { fields: { write: ['nickname'] } }
    : { where: { owner: { eq: ctx.user.id } }, fields: { read: ['nickname'] } },
})
  • allowlist 语义:缺省 = 该动作全部字段;声明后只有列出的字段可读写(漏列不会意外开放)。
  • 策略形态:{ where }(行约束,create 无法约束 → 403)、{ fields }(仅列约束,放行全部行,可用于 create)、两者同时给; { where: null } 仍是显式拒绝(不因带 fields 而放行)。
  • 读:未显式指定字段 → SQL 投影裁剪到 allowlist;显式请求越权字段(REST select=/GraphQL 选择集)→ 403(不静默丢弃); 未请求的受限字段在响应里是 null(api 对象输出语义)。
  • 必填字段恒可读(含主键):响应类型契约要求非空字段存在,fieldPolicy 只裁剪可空字段; 要隐藏 NOT NULL 列只能改列(可空)或收行权/不暴露集合。
  • 写:create/update 输入含越权字段 → 403 且零 SQL(判定先于编译,行/列都不会动)。
  • 静态文档 = 声明面,不含列权:db.api() 的 OpenAPI/客户端类型列全部声明字段(含策略未列出的), 列权只在运行期裁剪(越权请求 403、未请求的受限字段 null)——门控流水线 OPENAPI_CODEGEN_E2E=1 对 src 与发布产物两态都断言这一点(见 test/openapi-codegen.e2e.test.ts)。
  • REST / GraphQL / 元数据 mount 三路同语义:db.mount(app, { fieldPolicy: { 集合: {...} } }) 为 introspect/_wf_* 反射集合补策略(未知集合/字段 → 抛错)。

运行时 API(本阶段)

  • defineDb(config) → { model, api(), schema, mount };api() 返回 @weifuwujs/api 描述符(静态),mount(app, opts) 动态挂载(免重启)。
  • config.sql 或 ctx.sql 任一即可提供查询执行器;postgresExecutor(sql) 把 postgres.js 客户端适配为事务化执行器。
  • BigInt 统一按十进制字符串输出(与内核约定一致)。

元数据与迁移(1.x)

const db = defineDb({ sql: postgresExecutor(sql), collections: { /* … */ } })

await db.schema.plan()    // 差异预览:{ statements, destructive, unsupported }(零副作用)
await db.schema.apply()   // 事务 + advisory lock + 锁内重读;写 _wf_* 元数据与 _wf_migrations 审计
await db.schema.apply({ actor: 'deploy@ci', allowDestructive: true })  // 破坏性变更需显式授权
  • _wf_collections / _wf_fields / _wf_migrations 三张受管表,首次 apply() 自动创建。
  • 幂等:apply() 后再次 plan() 为空;并发 apply() 由 pg_advisory_xact_lock + 锁内重读保证恰好执行一次。
  • 默认拒绝破坏性变更(删列、改列类型、改必填/默认值/枚举值/外键):plan() 会给出预览与 unsupported 原因, apply() 未授权时整体拒绝、一条 SQL 都不执行。
  • 修复过的两个真库坑(都有回归测试):to_regclass() 在事务内复用陈旧 catalog 快照 → 探针改为直查 pg_class; $n::jsonb 传 JSON 文本会双编码 → 统一 $n::text::jsonb。

接入已有表(1.3 introspection / adopt)

const { state, skipped } = await db.schema.introspect({ tables: ['orders'] })  // 只读反射
const report = await db.schema.adopt({ actor: 'ops', tables: ['orders'] })     // 只写元数据 + 审计
// report = { adopted: ['orders'], skipped: [{ table, reason }], version }
  • 零 DDL:adopt 不执行任何建表/改表语句;接入后 plan() 为空(与声明模型等价),后续按模型演进走 apply()。
  • 接入范围:uuid/text/varchar/char/int2,4/int8/bool/timestamptz/json(b);numeric、数组、自定义 enum 类型等整表跳过并给原因。
  • 单列主键才能接入;复合主键/无主键跳过。单列 UNIQUE(约束或唯一索引)→ unique; 单列 col IN (...) / = ANY (ARRAY[...]) 的 CHECK → 枚举值。
  • 默认值规范化:去类型转换后缀、CURRENT_TIMESTAMP → now();其余原样。
  • 「required=false + 有默认值」与「required=true + 有默认值」DDL 等价,元数据按 DB 真值(NOT NULL)记录—— 反射与声明因此可直接比较。

运行期建表免重启(3.4/3.5 Admin API + 动态 dispatcher)

const db = defineDb({ sql: postgresExecutor(sql), collections: {} })
const app = createApp().use(async (req, ctx, next) => {
  ctx.admin = ctx.user?.role === 'admin'   // 你自己的鉴权:注入到 ctx
  return next(req, ctx)
})
db.mount(app, { basePath: '/api', adminPath: '/_db', graphql: '/graphql', openapi: '/openapi.json' })

| Admin 端点 | 说明 | | --- | --- | | POST /_db/schema/preview | { name, publicRead?, fields } → { statements, destructive, unsupported }(零副作用)| | POST /_db/collections | 同上 → 201 { collection, schemaVersion, applied };重复 → 409 | | GET /_db/collections[/:name] | 清单 / 详情(include fields)| | PATCH /_db/collections/:name | { fields?, dropFields?, actor? };破坏性需 ?allow-destructive=true,否则 409 + 预览 SQL | | GET /_db/migrations | 审计:id / actor / statements / appliedAt | | DELETE /_db/collections/:name | 409 UNSUPPORTED_CHANGE(v1 DDL 子集不含 drop table)|

  • 默认不挂载:不传 adminPath 即 404(不暴露存在性);每个 Admin 请求先过 authorize(ctx, { collection: '_db', action: 'admin' }),未提供钩子 → 403。
  • 免重启:数据面在请求期以 _wf_* 元数据为真相源(为空退回声明模型),缓存键 = schemaVersion;Admin 建表后同进程立即 /api/<新表>、GraphQL、OpenAPI 三投影可用。
  • 字段 JSON:{ type: uuid|text|int|bigint|bool|timestamptz|json|enum|fk, required?, unique?, primary?, auto?, default?, values?, target?, enumName? }。
  • 稳定错误码:INVALID_IDENTIFIER 400 / COLLECTION_EXISTS 409 / COLLECTION_NOT_FOUND 404 / DESTRUCTIVE_REQUIRES_CONFIRM 409 / UNSUPPORTED_CHANGE 409。
  • 动态模式 GraphQL/OpenAPI 端点用绝对路径(与静态 app.mount() 语义一致)。

CLI(3.3 weifuwu-db)

npm i -D @weifuwujs/db   # bin: weifuwu-db
cat > weifuwu-db.config.ts <<'TS'
import postgres from 'postgres'
import { col, defineDb, postgresExecutor } from '@weifuwujs/db'
export default defineDb({
  sql: postgresExecutor(postgres(process.env.DATABASE_URL!)),
  collections: { notes: { fields: { id: col.uuid({ primary: true }), title: col.text({ required: true }) } } },
})
TS

weifuwu-db migrate --dry-run            # 预览 SQL(零执行)
weifuwu-db migrate --actor deploy@ci    # 执行(事务 + 锁 + 审计)
weifuwu-db collections list             # 受管集合
weifuwu-db collections create --spec new.json   # { name, publicRead?, fields: {…} }
weifuwu-db adopt --dry-run --tables orders      # 反射报告(零写入)
weifuwu-db adopt --tables orders --actor ops    # 写入 adopt 元数据
  • 配置:--config <file>(默认 cwd 下 weifuwu-db.config.ts),默认导出 defineDb(...) 的产物;连接串由配置自己提供(示例用 DATABASE_URL)。
  • 退出码:0 成功(含无变更)/ 1 错误 / 2 破坏性变更未确认(仅预览、零执行;加 --allow-destructive 执行)。
  • CLI 只编排 schema.plan/apply/introspect/adopt 与 Admin 的 spec 转换,不直连 SQL、不复制 DDL 逻辑。

尚未实现(按 plan/db.md 任务地图推进):3.1 codegen 客户端类型 e2e;4.x 守卫同步与发布。