17 KiB
17 KiB
MetonaSqlark
基于 TypeScript 的前端关系型数据库,支持完整 SQL 查询、Query Builder 链式 API、与 AriaEngine 自研页面式存储引擎。
✨ 特性
- 🚀 AriaEngine 自研存储引擎 — LSM-Tree 页面式存储,4KB Slotted Page、WAL 崩溃恢复(full 模式真正同步)、LZ4 压缩
- 💾 OPFS 自研存储后端 — 纯浏览器文件系统,零 IndexedDB 依赖,二进制页面文件
- 🔒 生产级数据安全 — WAL CRC 完整性校验、
RESTRICT外键约束、Hybrid 提交原子性、SQL 注入防护 - 🛡 输入校验全覆盖 —
maxLength/min/max约束、类型检查、必填验证 - 💾 多引擎架构 — Memory / IndexedDB / OPFS / Hybrid(write-through) / Aria 五种模式
- 📝 完整 SQL 支持 — SELECT/JOIN/子查询/GROUP BY/HAVING/ORDER BY/LIMIT/BETWEEN/IF NOT EXISTS/ALTER TABLE/TRUNCATE TABLE/UNION/INSERT...SELECT/事务语句/CREATE INDEX/EXISTS(v0.3.0)
- 🔗 Query Builder API — 链式
.select().where().orderBy().limit().execute() - 🔄 事务回滚 — Memory/IndexedDB/Hybrid/Aria 四引擎事务原子性,自动回滚,MVCC 版本链接入读写路径
- 🌲 RB-Tree 完整实现 — 标准红黑树插入+删除修复,O(log n) 保证
- ⚡ 性能优化 — SSTableReader 二分查找统一、IndexedDB 索引利用、crypto 实例化避免全局状态
- 🌐 浏览器兼容 — Chrome 80+ / Firefox 80+ / Safari 14+ / Edge 80+ / Node.js 16+
- 🧪 837 测试 · 81.1% 覆盖率 — 44 套件,生产级质量保证
📦 安装
npm install @metona-team/metona-sqlark
如果提示找不到包,先配置 scope registry(一次性):
npm config set @metona-team:registry https://git.metona.cn/api/packages/MetonaTeam/npm/
CDN / 直接下载
<!-- UMD 格式,暴露 window.MetonaSqlark 和 window.MeSqlark -->
<script src="https://git.metona.cn/MetonaTeam/MetonaSqlark/raw/branch/master/dist/metona-sqlark.min.js"></script>
或从 dist/ 目录下载:
metona-sqlark.js— UMD 开发版(含 sourcemap)metona-sqlark.min.js— UMD 压缩版(~105KB,gzip ~27KB)metona-sqlark.esm.js— ES Modulemetona-sqlark.cjs— CommonJSmetona-sqlark.d.ts— TypeScript 类型声明
🚀 引入方式
ESM / TypeScript
import { MetonaSqlark } from '@metona-team/metona-sqlark';
// 别名
import { MeSqlark } from '@metona-team/metona-sqlark';
const db = await MetonaSqlark.create({ name: 'my-app', mode: 'hybrid' });
CommonJS
const { MetonaSqlark } = require('@metona-team/metona-sqlark');
(async () => {
const db = await MetonaSqlark.create({ name: 'my-app', mode: 'hybrid' });
})();
Browser UMD
<script src="metona-sqlark.min.js"></script>
<script>
(async () => {
const db = await window.MetonaSqlark.create({ name: 'my-app', mode: 'hybrid' });
// 或 window.MeSqlark(完全等价)
})();
</script>
🚀 快速开始
import { MetonaSqlark } from '@metona-team/metona-sqlark';
const db = await MetonaSqlark.create({
name: 'my-app',
mode: 'hybrid', // 'memory' | 'disk' | 'hybrid' | 'aria'
diskEngine: 'indexeddb', // 'indexeddb' | 'opfs'
});
// 定义表 — 支持外键级联
await db.defineTable('users', {
id: { type: 'string', primaryKey: true },
name: { type: 'string', required: true },
age: { type: 'number', default: 0 },
email: { type: 'string', unique: true, index: true },
});
await db.defineTable('orders', {
id: { type: 'string', primaryKey: true },
user_id: { type: 'string', references: 'users.id', onDelete: 'CASCADE' },
amount: { type: 'number' },
});
// SQL 查询
await db.query("INSERT INTO users VALUES ('1', 'Alice', 30, 'alice@demo.com')");
const rows = await db.query('SELECT * FROM users WHERE age > 18 ORDER BY name');
// Query Builder
const result = await db.table('users')
.select(['name', 'age'])
.where({ age: { $gt: 18 } })
.orderBy('age', 'desc')
.limit(10)
.execute();
// JOIN
await db.query(`SELECT u.name, o.product FROM users u INNER JOIN orders o ON u.id = o.user_id`);
// 子查询 v0.1.13
await db.query(`SELECT * FROM users WHERE id IN (SELECT user_id FROM orders WHERE amount > 100)`);
// GROUP BY
await db.query(`SELECT dept, COUNT(*) FROM employees GROUP BY dept HAVING COUNT(*) > 1`);
// ALTER TABLE — 动态修改表结构 v0.2.5
await db.query('ALTER TABLE users ADD COLUMN phone STRING');
await db.query('ALTER TABLE users DROP COLUMN phone');
// TRUNCATE TABLE — 快速清空表 v0.2.5
await db.query('TRUNCATE TABLE old_logs');
// v0.3.0 — SQL 功能扩展
// 多语句(分号分隔)
await db.query("CREATE TABLE t (id STRING PRIMARY KEY); INSERT INTO t VALUES ('1'); INSERT INTO t VALUES ('2')");
// 事务语句
await db.query('BEGIN');
await db.query("INSERT INTO t VALUES ('3')");
await db.query('ROLLBACK'); // 回滚
// INSERT INTO ... SELECT
await db.query('INSERT INTO t SELECT id FROM t2 WHERE x > 1');
// UNION / UNION ALL
const rows = await db.query('SELECT name FROM users WHERE city = \'Beijing\' UNION SELECT name FROM users WHERE age < 30');
// 动态索引
await db.query('CREATE INDEX idx_users_city ON users (city)');
await db.query('DROP INDEX idx_users_city ON users (city)');
// EXISTS 关联子查询
const hasOrders = await db.query('SELECT * FROM users u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id)');
// v0.3.1 — CASE WHEN / JOIN 关联子查询 / 组提交
const labeled = await db.query("SELECT name, CASE WHEN age >= 18 THEN 'adult' ELSE 'minor' END AS status FROM users");
const joinExists = await db.query('SELECT u.name FROM users u JOIN orders o ON u.id = o.user_id WHERE EXISTS (SELECT 1 FROM orders o2 WHERE o2.user_id = u.id AND o2.amount > 150)');
// 事务 — 自动回滚 v0.1.13
await db.transaction(async (trx) => {
await trx.table('users').insert({ id: '3', name: 'Charlie' });
await trx.table('orders').insert({ id: 'o1', userId: '3', amount: 99 });
// 任何一步失败 → 全部回滚
});
// 连接池 v0.1.13
const db2 = await MetonaSqlark.connect({ name: 'my-app', mode: 'hybrid' });
// db2 === db(复用已有实例)
await db2.disconnect(); // 引用计数 -1
📖 API 速览
数据库配置
| 属性 | 类型 | 默认 | 说明 |
|---|---|---|---|
name |
string |
'metona-sqlark' |
数据库名称 |
mode |
'memory' | 'disk' | 'hybrid' | 'aria' |
'hybrid' |
存储模式 🆕 aria |
diskEngine |
'indexeddb' | 'opfs' |
'indexeddb' |
磁盘引擎(aria 模式下为存储后端) |
version |
number |
1 |
版本号 |
maxRowsPerQuery |
number |
0 |
查询结果行数上限(0=不限制)✅ v0.2.5 生效 |
debug |
boolean |
false |
调试模式,输出详细日志 🆕 |
onError |
(error) => void |
— | 全局错误回调 🆕 |
ColumnDef 列定义
| 属性 | 类型 | 说明 |
|---|---|---|
type |
'string'|'number'|'boolean'|'date'|'json' |
数据类型 |
primaryKey |
boolean |
主键 |
required |
boolean |
必填 |
unique |
boolean |
唯一约束 |
index |
boolean |
创建哈希索引 |
default |
unknown |
默认值 |
references |
string |
外键引用 'table.column' |
onDelete |
'CASCADE'|'SET NULL'|'RESTRICT' |
删除级联 🆕 |
onUpdate |
'CASCADE'|'SET NULL'|'RESTRICT' |
更新级联 🆕 |
WHERE 操作符
| 操作符 | 含义 | 操作符 | 含义 |
|---|---|---|---|
$eq / 直接值 |
等于 | $gt / $gte |
大于 / 大于等于 |
$ne |
不等于 | $lt / $lte |
小于 / 小于等于 |
$in / $nin |
在列表中 | $like |
模糊匹配 |
$and / $or / $not |
逻辑组合 |
核心方法
| 方法 | 说明 |
|---|---|
db.query(sql) |
执行 SQL 字符串 |
db.table(name) |
获取表操作对象 |
db.defineTable(name, cols) |
定义表结构 |
db.transaction(fn) |
执行事务(自动回滚)🆕 |
db.exportTable(name) / db.exportAll() |
导出数据 JSON |
db.importTable(name, data) |
导入数据 |
db.addMigration(v, fn) / db.migrateTo(v) |
数据迁移 |
db.subscribe(table, fn) |
订阅表变更 |
db.on(hook, fn) |
注册钩子 (14 种) |
连接池(v0.1.13)
| 静态方法 | 说明 |
|---|---|
MetonaSqlark.connect(config) |
获取或创建数据库实例(单例复用)🆕 |
MetonaSqlark.disconnect(name) |
释放连接(引用计数 -1)🆕 |
MetonaSqlark.disconnectAll() |
强制关闭所有连接 🆕 |
MetonaSqlark.getActiveConnections() |
获取活跃连接列表 🆕 |
React / Vue 集成
// React
import { useQuery } from '@metona-team/metona-sqlark/react';
const { data, loading, refresh } = useQuery(db, 'SELECT * FROM users');
// Vue
import { useSqlarkQuery } from '@metona-team/metona-sqlark/vue';
const { data, loading, refresh } = useSqlarkQuery(db, 'SELECT * FROM users');
📊 存储模式对比
| 特性 | Memory | Disk (IndexedDB) | Disk (OPFS) | Hybrid | Aria |
|---|---|---|---|---|---|
| 持久化 | ❌ 重启丢失 | ✅ IndexedDB | ✅ OPFS文件系统 | ✅ 内存+磁盘 | ✅ 后端决定 |
| 事务回滚 | ✅ 快照 | ✅ 原子flush | ✅ 快照 | ✅ 双引擎 | ✅ MVCC |
| 二级索引 | ✅ Hash | ✅ Hash | ✅ Hash | ✅ Hash | ✅ LSM |
| 查询性能 | ⚡ O(1) PK | 🟡 O(1) PK | 🟡 O(1) PK | ⚡ O(1) PK | ⚡ O(log n) |
| 数据上限 | 内存限制 | ~2GB(IDB限制) | ~磁盘可用 | ~2GB(IDB) | 内存限制 |
| 浏览器 | 全部 | 全部 | Chrome/Edge 102+ | 全部 | 全部 |
| 适用场景 | 缓存/测试 | 标准持久化 | Chromium专有 | 速度+持久化 | 大规模/分析 |
| 测试覆盖 | 30+ | 30+ | 12 | 15+ | 200+ |
Memory 模式
- 环境: 所有浏览器、Node.js
- 限制: 数据不持久化,页面刷新/进程重启后数据丢失
- 能力: 完整 CRUD、事务回滚、外键级联、二级索引、SQL 全支持
- 适用: 临时数据、单元测试、缓存层
Disk (IndexedDB) 模式
- 环境: 所有现代浏览器(Chrome/Firefox/Safari/Edge)、Node.js(fake-indexeddb)
- 限制: 受浏览器 IndexedDB 配额限制(通常 ~2GB),多标签页需处理版本冲突
- 能力: 完整 CRUD、事务原子性(单 IDB 事务包裹)、外键级联、onversionchange 感知
- 适用: 标准前端数据库持久化场景
Disk (OPFS) 模式
- 环境: 仅限 Chrome 102+ / Edge 102+(Origin Private File System)
- 限制: Firefox/Safari 不支持 OPFS API;每次写入重写整表 JSON 文件(大表性能差,不建议 >1000 行)
- 能力: 完整 CRUD、重启自动加载数据、事务回滚
- 适用: Chromium 独占场景、小数据集持久化
Hybrid 模式
- 环境: 所有浏览器
- 限制: 磁盘引擎决定底层限制(IndexedDB ~2GB / OPFS Chrome only)
- 能力: write-through 双写(内存+磁盘)、提交顺序保证(磁盘优先)、读从内存
- 适用: 需要内存速度 + 磁盘持久化的混合场景
Aria 模式
- 环境: 所有浏览器(后端可选 IndexedDB / OPFS / Memory)
- 限制: Memory 后端重启丢失;IndexedDB 后端受配额限制;OPFS 后端仅 Chromium
- 能力: LSM-Tree 存储引擎、二级索引、MVCC 事务、WAL 崩溃恢复、Bloom Filter、AES-GCM 加密、Savepoint、EXPLAIN、ANALYZE、REINDEX、VACUUM
- 适用: 大规模数据分析、需要自研引擎可控性的高级场景
🌲 AriaEngine — 自研存储引擎
AriaEngine 是内置的页面式存储引擎,对标 SQLite 的设计理念:
// 激活 AriaEngine
const db = await MetonaSqlark.create({
name: 'my-app',
mode: 'aria', // 🆕 自研引擎模式
diskEngine: 'indexeddb', // 'indexeddb' | 'opfs' | 'memory'
});
// 与现有 API 完全兼容
await db.defineTable('users', {
id: { type: 'string', primaryKey: true },
name: { type: 'string', required: true },
});
await db.query("INSERT INTO users VALUES ('1', 'Alice')");
const rows = await db.query('SELECT * FROM users');
AriaEngine 架构
┌──────────────────────────────────────────┐
│ AriaEngine v0.2.5 │
│ (implements IStorageEngine) │
├──────────────────────────────────────────┤
│ LSM-Tree │ Buffer Pool │ WAL │
│ MemTable │ LRU (256pp) │ Recovery │
│ +SSTable │ +FileManager │ +CRC │
├──────────────────────────────────────────┤
│ MVCC │ Bloom Filter │ LZ4 │
│ Snapshot │ FNV-1a+Murmur│ Compress │
│ +Savepoint│ +Serialize │ │
├──────────────────────────────────────────┤
│ AES-GCM │ 二级索引 │ ANALYZE │
│ Encrypt │ Per-Column │ EXPLAIN │
├──────────────────────────────────────────┤
│ Storage Backend (IDB / OPFS / Memory) │
└──────────────────────────────────────────┘
| 特性 | 说明 |
|---|---|
| LSM-Tree | MemTable (红黑树) → SSTable 多级索引,异步 Compaction,写背压 |
| WAL | Write-Ahead Log 二进制格式,CRC 校验,full/batch/none 三种模式(full 模式真正同步 ✅ v0.2.5),16MB 阈值自动 checkpoint |
| MVCC | 版本链 + 快照隔离,事务读写不互斥,自动 GC(每10次检查点),读写路径接入版本链 ✅ v0.2.5 |
| Buffer Pool | SSTable 缓存 LRU 上限(bufferPoolPages × pageSize,默认 256 页 ≈ 1MB 可控内存)✅ v0.2.6 生效,查询前异步预加载兜底,缓存驱逐不丢数据 |
| Bloom Filter | FNV-1a + Murmur 双哈希,SSTable footer 序列化,查询时 probe |
| 二级索引 | 每列独立 LSM Tree,支持 $eq/$in/$gt/$lt 范围扫描,SSTableReader 二分查找统一 ✅ v0.2.5 |
| AES-GCM | PBKDF2 密钥派生 + AES-256-GCM 页面级加密,CryptoManager 实例化 ✅ v0.2.5 |
| Compaction | 异步 Leveled Compaction,Level 0 > 8 触发同步背压,compactLevel public 接口 ✅ v0.2.5 |
| OPFS Backend | 纯浏览器文件系统,Promise 队列串行写,零外部依赖 |
🛠 开发
npm install # 安装依赖
npm run dev # 开发模式(localhost:3001)
npm run build # 生产构建(生成 dist/)
npm test # 运行测试
npm run lint # 代码检查
npm run typecheck # 类型检查
📊 项目状态
| 指标 | 数值 |
|---|---|
| 测试用例 | 837 |
| 测试套件 | 44 |
| 行覆盖率 | 81.1% |
| SQL 关键字 | 36 |
| 存储引擎 | 5(Memory / IndexedDB / OPFS / Hybrid / Aria) |
🌐 浏览器兼容性
| 浏览器 | 最低版本 | Memory | IndexedDB | OPFS | Aria |
|---|---|---|---|---|---|
| Chrome | 80+ | ✅ | ✅ | ✅ (102+) | ✅ |
| Firefox | 80+ | ✅ | ✅ | ❌ | ✅ |
| Safari | 14+ | ✅ | ✅ | ❌ | ✅ |
| Edge | 80+ | ✅ | ✅ | ✅ (102+) | ✅ |
| Node.js | 16+ | ✅ | ✅ (fake-idb) | ❌ | ✅ |
注意: OPFS 模式仅限 Chromium 内核浏览器 (Chrome/Edge 102+),Firefox/Safari 请使用
diskEngine: 'indexeddb'。
📂 项目结构
src/
├── index.ts # 入口(MetonaSqlark + MeSqlark)
├── core.ts # 主类
├── constants.ts # 类型定义 + 配置 + DatabaseError
├── connection-manager.ts # 连接池管理
├── utils.ts # 工具函数
├── engine/ # 存储引擎(Memory/IndexedDB/OPFS/Aria)
├── hybrid/ # 混合引擎(write-through)
├── table/ # 表管理 + Schema 校验
├── query/ # AST + Builder + Compiler + Executor
├── sql/ # Lexer + Parser(递归下降)
├── transaction/ # 事务管理(支持回滚)
├── plugin/ # 插件系统(14 hooks)
└── integrations/ # React / Vue hooks
📄 License
MIT © MetonaTeam