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

@h-ai/reldb

v0.1.0-alpha.10

Published

Hai Framework relational database module for SQLite, PostgreSQL, and MySQL.

Downloads

558

Readme

@h-ai/reldb

关系型数据库访问模块,通过统一的 reldb 对象访问 SQLite、PostgreSQL、MySQL。

支持的数据库

  • SQLite
  • PostgreSQL
  • MySQL

快速开始

import { HaiReldbError, reldb } from '@h-ai/reldb'

// 初始化
await reldb.init({ type: 'sqlite', database: ':memory:' })

// DDL
await reldb.ddl.createTable('users', {
  id: { type: 'INTEGER', primaryKey: true, autoIncrement: true },
  name: { type: 'TEXT', notNull: true },
  email: { type: 'TEXT', unique: true },
})

// SQL
await reldb.sql.execute('INSERT INTO users (name, email) VALUES (?, ?)', ['张三', '[email protected]'])
const users = await reldb.sql.query<{ id: number, name: string }>('SELECT * FROM users')

// 分页查询
const page = await reldb.sql.queryPage<{ id: number, name: string }>({
  sql: 'SELECT id, name FROM users ORDER BY id',
  pagination: { page: 1, pageSize: 20 },
})

// CRUD
const userCrud = reldb.crud.table({
  table: 'users',
  idColumn: 'id',
  select: ['id', 'name', 'email'],
  createColumns: ['name', 'email'],
  updateColumns: ['name', 'email'],
  dbType: 'sqlite',
})
await userCrud.create({ name: '李四', email: '[email protected]' })
const user = await userCrud.findById(1)

// 关闭
await reldb.close()

配置

支持连接字符串(url)或分字段(host/port/database/user/password)。

// SQLite
await reldb.init({ type: 'sqlite', database: './data.db' })

// PostgreSQL
await reldb.init({ type: 'postgresql', url: 'postgres://user:pass@localhost:5432/mydb' })

// MySQL
await reldb.init({
  type: 'mysql',
  host: 'localhost',
  port: 3306,
  database: 'mydb',
  user: 'admin',
  password: 'secret',
  mysql: { charset: 'utf8mb4' },
})

事务

三种使用方式:

wrap(自动提交/回滚)

const result = await reldb.tx.wrap(async (tx) => {
  await tx.execute('INSERT INTO users (name) VALUES (?)', ['用户1'])
  await tx.execute('INSERT INTO users (name) VALUES (?)', ['用户2'])
  return 'ok'
})
// 回调正常返回 → 自动提交;抛异常 → 自动回滚

begin + commit(分步提交)

const txResult = await reldb.tx.begin()
if (!txResult.success) { /* 处理错误 */ }
const tx = txResult.data

await tx.execute('INSERT INTO users (name) VALUES (?)', ['用户1'])
await tx.execute('INSERT INTO users (name) VALUES (?)', ['用户2'])
await tx.commit()

begin + rollback(分步回滚)

const txResult = await reldb.tx.begin()
if (!txResult.success) { /* 处理错误 */ }
const tx = txResult.data

await tx.execute('INSERT INTO users (name) VALUES (?)', ['用户1'])
// 业务检查失败,手动回滚
await tx.rollback()

事务内使用 batch / queryPage

await reldb.tx.wrap(async (tx) => {
  await tx.batch([
    { sql: 'INSERT INTO users (name) VALUES (?)', params: ['用户1'] },
    { sql: 'INSERT INTO users (name) VALUES (?)', params: ['用户2'] },
  ])

  const page = await tx.queryPage<{ id: number, name: string }>({
    sql: 'SELECT id, name FROM users ORDER BY id',
    pagination: { page: 1, pageSize: 10 },
  })
})

Crud

通过 reldb.crud.table() 创建单表 CRUD 仓库,免写 SQL:

const userCrud = reldb.crud.table<{ id: number, name: string, email: string }>({
  table: 'users',
  idColumn: 'id', // 主键列,默认 'id'
  select: ['id', 'name', 'email'], // 查询列,默认 '*'
  createColumns: ['name', 'email'], // 允许插入的列
  updateColumns: ['name', 'email'], // 允许更新的列
  dbType: 'sqlite', // 数据库类型
})

create / createMany

// 单条插入
const result = await userCrud.create({ name: '张三', email: '[email protected]' })
// result.data → { changes: 1, lastInsertRowid: 1 }

// 批量插入
await userCrud.createMany([
  { name: '用户A', email: '[email protected]' },
  { name: '用户B', email: '[email protected]' },
])

createOrUpdate

主键存在时更新,否则创建(upsert):

const userCrud = reldb.crud.table<{ id: number, name: string, email: string }>({
  table: 'users',
  idColumn: 'id',
  createColumns: ['id', 'name', 'email'],
  updateColumns: ['name', 'email'],
  dbType: 'sqlite',
})

await userCrud.createOrUpdate({ id: 1, name: '张三', email: '[email protected]' })
// 不存在 → 插入;已存在 → 更新 name、email

BaseReldbCrudRepository 自动获取 dbType,无需手动传入。

findById / getById / findAll / findPage

// 主键查找(不存在时返回 null)
const user = await userCrud.findById(1)
// user.data → { id: 1, name: '张三', email: '[email protected]' } | null

// 主键获取(不存在时返回 RECORD_NOT_FOUND 错误)
const user2 = await userCrud.getById(1)
// user2.data → { id: 1, name: '张三', email: '[email protected]' }

// 条件查询(where + params 占位符)
const actives = await userCrud.findAll({
  where: 'name LIKE ?',
  params: ['%张%'],
  orderBy: 'id DESC',
  limit: 10,
  offset: 0,
})

// 分页查询
const page = await userCrud.findPage({
  where: 'name LIKE ?',
  params: ['%张%'],
  orderBy: 'id DESC',
  pagination: { page: 1, pageSize: 20 },
})
// page.data → { items: [...], total: 100, page: 1, pageSize: 20 }

updateById / deleteById

await userCrud.updateById(1, { name: '李四' })
await userCrud.deleteById(1)

count / exists / existsById

const total = await userCrud.count({ where: 'name LIKE ?', params: ['%张%'] })
// total.data → 5

const has = await userCrud.exists({ where: 'email = ?', params: ['[email protected]'] })
// has.data → true

const found = await userCrud.existsById(1)
// found.data → true

事务中使用 Crud

所有方法均支持传入 tx 参数,自动路由到事务上下文:

await reldb.tx.wrap(async (tx) => {
  await userCrud.create({ name: '用户A', email: '[email protected]' }, tx)
  await userCrud.updateById(1, { name: '新名字' }, tx)
  const user = await userCrud.findById(1, tx)
})

BaseReldbCrudRepository

业务仓库继承 BaseReldbCrudRepository,通过 fields 定义字段映射,自动建表、类型转换:

import type { DmlWithTxOperations, ReldbCrudFieldDefinition } from '@h-ai/reldb'
import { BaseReldbCrudRepository, reldb } from '@h-ai/reldb'

interface UserRow {
  id: number
  name: string
  email: string
  createdAt: Date
  updatedAt: Date
}

const USER_FIELDS: ReldbCrudFieldDefinition[] = [
  { fieldName: 'id', columnName: 'id', def: { type: 'INTEGER', primaryKey: true, autoIncrement: true }, select: true, create: false, update: false },
  { fieldName: 'name', columnName: 'name', def: { type: 'TEXT', notNull: true }, select: true, create: true, update: true },
  { fieldName: 'email', columnName: 'email', def: { type: 'TEXT', notNull: true }, select: true, create: true, update: true },
  { fieldName: 'createdAt', columnName: 'created_at', def: { type: 'TIMESTAMP', notNull: true }, select: true, create: true, update: false },
  { fieldName: 'updatedAt', columnName: 'updated_at', def: { type: 'TIMESTAMP', notNull: true }, select: true, create: true, update: false },
]

class UserRepository extends BaseReldbCrudRepository<UserRow> {
  constructor() {
    super(reldb, { table: 'users', idColumn: 'id', fields: USER_FIELDS })
  }

  /** 自定义查询方法 */
  async findByEmail(email: string) {
    return this.findAll({ where: 'email = ?', params: [email], limit: 1 })
  }

  /** 自定义方法中使用 this.sql(tx) 自动路由到事务 */
  async insertRaw(data: { name: string, email: string }, tx?: DmlWithTxOperations) {
    const now = Date.now()
    return this.sql(tx).execute(
      'INSERT INTO users (name, email, created_at, updated_at) VALUES (?, ?, ?, ?)',
      [data.name, data.email, now, now],
    )
  }
}

// 使用
const repo = new UserRepository()
await repo.create({ name: '张三', email: '[email protected]' })
const user = await repo.findById(1)

// 创建或更新(upsert)
await repo.createOrUpdate({ id: 1, name: '李四', email: '[email protected]' })
// id=1 已存在 → 更新 name;不存在 → 插入新记录

// 跨仓库事务
const txResult = await reldb.tx.begin()
if (txResult.success) {
  const tx = txResult.data
  await repo.create({ name: '用户A', email: '[email protected]' }, tx)
  await repo.create({ name: '用户B', email: '[email protected]' }, tx)
  await tx.commit()
}

JSON 路径操作

通过 reldb.json 构建跨数据库统一的 JSON 路径操作 SQL 表达式。

路径格式遵循 SQL/JSON Path 标准($.key / $.key.subkey / $[0]),各数据库后端自动映射到对应函数或操作符。

| 操作 | SQLite | PostgreSQL | MySQL | | --------- | -------------- | -------------- | ------------------ | | extract | json_extract | #> (text[]) | JSON_EXTRACT | | set | json_set | jsonb_set | JSON_SET | | insert | json_insert | jsonb_insert | JSON_INSERT | | remove | json_remove | #- (text[]) | JSON_REMOVE | | merge | json_patch | \|\| ::jsonb | JSON_MERGE_PATCH |

所有方法返回 { sql, params } 片段,可直接嵌入 reldb.sql.query / reldb.sql.execute

extract — 提取 JSON 字段值

// 用于 WHERE 条件
const { sql, params } = reldb.json.extract('settings', '$.theme')
const rows = await reldb.sql.query(
  `SELECT * FROM users WHERE ${sql} = ?`,
  [...params, '"dark"'],
)

// 用于 SELECT 列
const { sql, params } = reldb.json.extract('profile', '$.name')
const result = await reldb.sql.query(
  `SELECT id, ${sql} as display_name FROM users`,
  params,
)

set — 设置 JSON 字段路径(创建或替换)

const { sql, params } = reldb.json.set('settings', '$.theme', 'dark')
await reldb.sql.execute(
  `UPDATE users SET settings = ${sql} WHERE id = ?`,
  [...params, userId],
)

insert — 插入 JSON 字段路径(仅当路径不存在时)

const { sql, params } = reldb.json.insert('data', '$.firstSeen', new Date().toISOString())
await reldb.sql.execute(
  `UPDATE events SET data = ${sql} WHERE id = ?`,
  [...params, eventId],
)

remove — 删除 JSON 字段路径

const { sql, params } = reldb.json.remove('settings', '$.deprecated')
await reldb.sql.execute(
  `UPDATE users SET settings = ${sql} WHERE id = ?`,
  [...params, userId],
)

merge — 合并 JSON 对象(RFC 7396 Merge Patch)

// 覆盖已有键、保留未提及键、将 null 键删除
const { sql, params } = reldb.json.merge('profile', {
  bio: '新简介',
  avatar: null, // 传入 null 表示删除该键
})
await reldb.sql.execute(
  `UPDATE users SET profile = ${sql} WHERE id = ?`,
  [...params, userId],
)

完整示例

// 建表(含 JSON 列)
await reldb.ddl.createTable('products', {
  id: { type: 'INTEGER', primaryKey: true, autoIncrement: true },
  meta: { type: 'JSON' },
})

// 插入初始 JSON
await reldb.sql.execute(
  `INSERT INTO products (meta) VALUES (?)`,
  [JSON.stringify({ status: 'draft', tags: ['new'], version: 1 })],
)

// 设置字段
const { sql: setSql, params: setParams } = reldb.json.set('meta', '$.status', 'published')
await reldb.sql.execute(`UPDATE products SET meta = ${setSql} WHERE id = ?`, [...setParams, 1])

// 提取字段(用于过滤)
const { sql: exSql, params: exParams } = reldb.json.extract('meta', '$.status')
const published = await reldb.sql.query(
  `SELECT * FROM products WHERE ${exSql} = ?`,
  [...exParams, '"published"'],
)

// 合并更新
const { sql: mSql, params: mParams } = reldb.json.merge('meta', { version: 2, reviewer: 'admin' })
await reldb.sql.execute(`UPDATE products SET meta = ${mSql} WHERE id = ?`, [...mParams, 1])

// 删除字段
const { sql: rmSql, params: rmParams } = reldb.json.remove('meta', '$.reviewer')
await reldb.sql.execute(`UPDATE products SET meta = ${rmSql} WHERE id = ?`, [...rmParams, 1])

错误处理

所有操作返回 HaiResult<T>,通过 result.success 判断成功或失败。

import { HaiReldbError, reldb } from '@h-ai/reldb'

const result = await reldb.sql.query('SELECT * FROM users')
if (!result.success) {
  switch (result.error.code) {
    case HaiReldbError.NOT_INITIALIZED.code:
      // 请先调用 reldb.init()
      break
    case HaiReldbError.QUERY_FAILED.code:
      // SQL 执行错误
      break
    case HaiReldbError.CONSTRAINT_VIOLATION.code:
      // 约束违反(如唯一约束、外键等)
      break
  }
}

常用错误码:

| 错误码 | code | 说明 | | ------------------------------------ | --------------- | ------------ | | HaiReldbError.NOT_INITIALIZED | hai:reldb:010 | 未初始化 | | HaiReldbError.CONNECTION_FAILED | hai:reldb:001 | 连接失败 | | HaiReldbError.QUERY_FAILED | hai:reldb:002 | 查询失败 | | HaiReldbError.CONSTRAINT_VIOLATION | hai:reldb:003 | 约束违反 | | HaiReldbError.TRANSACTION_FAILED | hai:reldb:004 | 事务失败 | | HaiReldbError.DDL_FAILED | hai:reldb:020 | DDL 操作失败 | | HaiReldbError.CONFIG_ERROR | hai:reldb:013 | 配置错误 |

测试

pnpm --filter @h-ai/reldb test

MySQL/PostgreSQL 测试需要 Docker。

License

Apache-2.0