indexeddb-toolkit-package
v1.0.1
Published
A PostgreSQL-style IndexedDB toolkit with connection pooling, schema builder, transactions, and SQL-like query API
Maintainers
Readme
indexeddb-toolkit
PostgreSQL 风格的 IndexedDB 工具库 — 连接池、Schema DDL、事务、SQL-like 查询。
特性
- 连接池 (Pool/Client) —
createPool()管理连接,自动复用,可配置最大连接数 - Schema DDL 定义 — Knex 风格的表结构声明,支持索引、唯一约束、multiEntry
- SQL-like 链式查询 —
.select().where().orderBy().limit().offset()完整链式 API - 事务支持 —
pool.transaction(async tx => {...})自动 commit/rollback - 索引优化 — 查询自动利用
IDBKeyRange走索引,避免全表扫描 - 冲突处理 —
onConflict('field', 'skip' | 'update')支持 upsert - 文件存储插件 — 自动建表、Blob URL 生命周期追踪、cursor 真分页
- JSON 导入导出 — 完整数据库快照与恢复,支持冲突策略
- 强类型 — 泛型
IDBResult<T>、IDBError错误码体系、完整类型推导 - 双格式输出 — ESM + CJS dual package + UMD bundle
安装
npm install indexeddb-toolkit-package浏览器直接使用:
<script src="dist/indexeddb-toolkit.umd.js"></script>
<script>
const { createPool } = IDBToolkit
</script>快速开始
import { createPool } from 'indexeddb-toolkit-package'
// 1. 创建连接池 + 定义 Schema
const pool = createPool({
database: 'myApp',
version: 1,
schema: (s) => {
s.createTable('users', (t) => {
t.autoIncrement('id')
t.string('name').index()
t.string('email').unique()
t.number('age').index()
t.boolean('active')
})
},
})
// 2. 初始化连接(首次使用前需要)
await pool.connect().then(c => c.release())
// 3. 插入
await pool.query('users').insert({ name: 'Alice', email: '[email protected]', age: 25, active: true }).execute()
// 4. 查询
const result = await pool.query('users')
.select('name', 'age')
.where('age', '>=', 18)
.orderBy('name', 'asc')
.limit(10)
.execute()
console.log(result.data) // [{ name: 'Alice', age: 25 }]
// 5. 关闭
await pool.end()asyncQuery — 自动初始化
如果不想手动 connect(),可以使用 asyncQuery(),它会自动确保数据库已初始化:
const pool = createPool({ database: 'myApp', version: 1, schema: ... })
// 无需先 connect(),asyncQuery 自动初始化
const qb = await pool.asyncQuery('users')
const result = await qb.select().execute()注意:
pool.query()是同步方法,要求数据库已经初始化(即至少调用过一次connect()或asyncQuery())。如果在未初始化时调用会抛出CONNECTION_CLOSED错误。
API 参考
createPool(config)
const pool = createPool({
database: 'myApp', // 必填,数据库名
version: 1, // 默认 1
maxConnections: 10, // 默认 10
schema: (builder) => {}, // 可选,表结构定义
})Schema 定义
schema: (s) => {
s.createTable('tableName', (t) => {
t.autoIncrement('id') // 自增主键
t.primaryKey('key') // 手动主键(无自增)
t.string('name').index() // 字符串列 + 普通索引
t.string('email').unique() // 唯一索引
t.number('age') // 数字列
t.boolean('active') // 布尔列
t.date('createdAt') // 日期列
t.any('data') // 任意类型列
t.string('tags').multiEntry() // 多值索引(数组字段)
})
s.dropTable('oldTable') // 删除表
}查询构建器
// SELECT
const all = await pool.query('users').select().execute()
const filtered = await pool.query('users').select('name', 'age').where('age', '>', 18).execute()
const first = await pool.query('users').select().where('id', '=', 1).first()
const count = await pool.query('users').where('active', '=', true).count()
// INSERT
await pool.query('users').insert({ name: 'Alice', age: 25 }).execute()
await pool.query('users').insert([item1, item2, item3]).execute()
// INSERT 冲突处理
await pool.query('users').insert(data).onConflict('email', 'skip').execute() // 跳过
await pool.query('users').insert(data).onConflict('email', 'update').execute() // upsert
// UPDATE
await pool.query('users').where('id', '=', 1).update({ age: 26 }).execute()
// DELETE
await pool.query('users').where('id', '=', 1).delete()
await pool.query('users').delete() // 清空
// WHERE 运算符: =, !=, >, >=, <, <=, between, in, notIn, like
await pool.query('users').where('age', 'between', [20, 30]).execute()
await pool.query('users').where('name', 'in', ['Alice', 'Bob']).execute()
await pool.query('users').where('name', 'like', 'A%').execute()
// 排序 + 分页
await pool.query('users').select().orderBy('age', 'desc').limit(10).offset(20).execute()Client(手动连接管理)
const client = await pool.connect()
try {
await client.query('users').insert({ name: 'Alice' }).execute()
const result = await client.query('users').select().execute()
} finally {
client.release() // 必须归还连接
}事务
// 通过 pool
await pool.transaction(async (tx) => {
await tx.add('accounts', { name: 'Alice', balance: 100 })
const all = await tx.getAll('accounts')
await tx.put('accounts', { ...all[0], balance: 150 })
// 正常结束自动 commit,抛异常自动 rollback
})
// 通过 client
const client = await pool.connect()
try {
await client.transaction('accounts', async (tx) => {
await tx.add('accounts', { name: 'Bob', balance: 200 })
})
} finally {
client.release()
}文件存储
import { FileStorage } from 'indexeddb-toolkit-package'
// FileStorage 会自动创建所需的 object store(无需在 schema 中声明)
const files = new FileStorage(pool, '_files')
await files.save(fileObj, { metadata: { category: 'avatar' } })
const file = await files.get(fileId, { withData: true })
await files.download(fileId)
await files.downloadAll({ format: 'zip', zipFilename: 'backup.zip' })
// Blob URL 生命周期管理
const result = await files.get(fileId, { asUrl: true })
files.revokeUrl(result.data.url) // 释放单个 URL
files.revokeAllUrls() // 释放所有已追踪的 URLJSON 导入导出
import { exportDatabase, importDatabase } from 'indexeddb-toolkit-package'
// 导出
const snapshot = await exportDatabase(pool, { tables: ['users', 'posts'] })
const json = JSON.stringify(snapshot.data)
// 导入
await importDatabase(pool, json, { onConflict: 'skip' }) // 跳过冲突
await importDatabase(pool, json, { onConflict: 'update' }) // 覆盖冲突错误处理
所有错误都是 IDBError 实例,带有类型化的 code 字段:
import { IDBError, IDBErrorCode } from 'indexeddb-toolkit-package'
try {
await pool.query('nonexistent').select().execute()
} catch (err) {
if (err instanceof IDBError) {
console.log(err.code) // IDBErrorCode.TABLE_NOT_FOUND
console.log(err.message) // 'Table "nonexistent" does not exist'
console.log(err.detail) // 'nonexistent'
}
}错误码列表:CONNECTION_FAILED, CONNECTION_BLOCKED, CONNECTION_CLOSED, TABLE_NOT_FOUND, TABLE_ALREADY_EXISTS, CONSTRAINT_VIOLATION, TRANSACTION_ABORTED, INVALID_QUERY, INVALID_ARGUMENT, POOL_EXHAUSTED, POOL_CLOSED, SCHEMA_ERROR, VERSION_CONFLICT, NOT_FOUND, TIMEOUT 等。
返回类型
所有操作返回 IDBResult<T>:
interface IDBResult<T> {
success: boolean
data: T
meta?: {
count?: number
duration?: number
affectedRows?: number
}
}注意:错误通过
throw抛出而非通过success: false返回。成功操作始终返回success: true。
日志级别
import { setLogLevel } from 'indexeddb-toolkit-package'
setLogLevel('debug') // 'debug' | 'info' | 'warn' | 'error'交互测试
项目根目录包含 index.html 可交互测试页,在浏览器中直接打开即可测试所有 API。
npm run build:umd # 构建 UMD bundle
# 然后用浏览器打开 index.html构建
npm run build # ESM + CJS + .d.ts 类型声明
npm run build:umd # UMD bundle(供 <script> 标签使用)
npm run test # 运行测试
npm run lint # 代码检查License
MIT
