# MetonaSqlark
> 基于 TypeScript 的**前端关系型数据库**,支持完整 SQL 查询、Query Builder 链式 API、与 **AriaEngine 自研存储引擎**。
---
## ✨ 特性
- 🚀 **AriaEngine 自研存储引擎** — LSM-Tree + **SSTable 4KB 页面化物理存储**(BufferPool LRU 缓存 + FileManager 页面管理,v0.5.0 真实接入)、WAL 分片文件(真追加 + 空洞检测)、LZ4 压缩
- 💾 **OPFS 自研存储后端** — 纯浏览器文件系统,零 IndexedDB 依赖,二进制页面文件,schema 持久化,单文件 COW 原子写,崩溃残留自动清理
- 🔒 **生产级数据安全** — WAL 原子写入 + 标准 CRC32 完整性校验(SSTable 整文件校验 + WAL 记录校验)、**全库 AES-256-GCM 透明加密**(PBKDF2 密钥派生 + salt 持久化 + 密码验证,v0.5.0 真实接线)、`RESTRICT` 外键约束、崩溃恢复自愈
- 🔐 **多标签页独占锁** — Web Locks API,第二个标签页打开同一库抛 `ARIA_LOCKED`(v0.5.0)
- 🛠 **维护语句 SQL 入口** — `EXPLAIN`/`ANALYZE`/`REINDEX`/`VACUUM`/`SAVEPOINT` 原生 SQL 支持(v0.5.1 补齐)
- 🛡 **输入校验全覆盖** — `maxLength`/`min`/`max` 约束、类型检查、必填验证
- 💾 **多引擎架构** — Memory / **KVStore**(自研 KV 引擎)/ OPFS / Hybrid(write-through) / Aria 五种模式(v0.6.0: IndexedDB 完全移除)
- 💾 **KVStore 自研 KV 引擎** — 日志结构化事务存储:多 key 原子写(putMany/deleteMany 单记录原子追加)、快照 checkpoint、崩溃两阶段恢复、CRC-32 自愈;替代 IndexedDB(v0.6.0)
- 🔄 **旧库一键迁移** — `migrateFromIndexedDB()` 把旧 IndexedDB 数据(schema/索引/行)导入新引擎(v0.6.0)
- 📝 **完整 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)+ CASE WHEN/哈希连接/组提交(v0.3.1)+ 多标签页同步(v0.3.2)
- 🚰 **流式查询** — `queryStream`/`stream()` 逐行回调,Aria LSM 惰性扫描不物化结果集(v0.4.0)
- 🧩 **派生表** — `FROM (SELECT ...)` 子查询作为行源,多列 ON 哈希连接,COUNT(DISTINCT),NULLS FIRST/LAST(v0.4.0)
- 🔗 **Query Builder API** — 链式 `.select().where().orderBy().limit().execute()`
- 🔄 **事务回滚** — Memory/IndexedDB/Hybrid/Aria 四引擎事务原子性,自动回滚,MVCC 版本链接入读写路径
- 🔗 **外键级联** — ON DELETE + ON UPDATE(CASCADE / SET NULL / RESTRICT)全引擎支持,支持更新主键(v0.4.2)
- 🛡 **崩溃恢复自愈** — 残缺 SSTable 打开自动跳过、`db.repair()` 自愈(含孤儿页面清理与残留清理)、`db.clearAll()` 重置、迁移版本持久化(v0.4.2/v0.5.0)
- 🧵 **关闭时序与后台任务加固** — 后台 flush/compaction 串行入队(close 排空后才关闭存储,失败显式报告 `ARIA_BACKGROUND_ERROR`)
- 📏 **SSTable 编码修复** — 块大小按 UTF-8 字节精确计算、长度字段 u32、v1/v2 双格式兼容(v0.4.4)
- 🌲 **RB-Tree 完整实现** — 标准红黑树插入+删除修复,O(log n) 保证
- ⚡ **性能优化** — SSTableReader 二分查找统一、IndexedDB 索引利用、crypto 实例化避免全局状态
- 🌐 **浏览器兼容** — Chrome 80+ / Firefox 80+ / Safari 14+ / Edge 80+ / Node.js 16+
- 🧪 **1022 测试 · 89.15% 覆盖率** — 64 套件 + 12 个 Playwright 真实 Chromium e2e(含崩溃注入),生产级质量保证
---
## 📦 安装
```bash
npm install @metona-team/metona-sqlark
```
> 如果提示找不到包,先配置 scope registry(一次性):
> ```bash
> npm config set @metona-team:registry https://git.metona.cn/api/packages/MetonaTeam/npm/
> ```
### CDN / 直接下载
```html
```
或从 [`dist/`](./dist/) 目录下载:
- `metona-sqlark.js` — UMD 开发版(含 sourcemap)
- `metona-sqlark.min.js` — UMD 压缩版(~105KB,gzip ~27KB)
- `metona-sqlark.esm.js` — ES Module
- `metona-sqlark.cjs` — CommonJS
- `metona-sqlark.d.ts` — TypeScript 类型声明
---
## 🚀 引入方式
### ESM / TypeScript
```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
```javascript
const { MetonaSqlark } = require('@metona-team/metona-sqlark');
(async () => {
const db = await MetonaSqlark.create({ name: 'my-app', mode: 'hybrid' });
})();
```
### Browser UMD
```html
```
---
## 🚀 快速开始
```typescript
import { MetonaSqlark } from '@metona-team/metona-sqlark';
const db = await MetonaSqlark.create({
name: 'my-app',
mode: 'hybrid', // 'memory' | 'disk' | 'hybrid' | 'aria'
diskEngine: 'opfs', // 'opfs' | 'memory'(v0.6.0: IndexedDB 已移除)
});
// 定义表 — 支持外键级联
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.4.0 — 流式查询(大表逐行回调,不物化全部结果)
let count = 0;
await db.queryStream('SELECT * FROM logs WHERE level = \'error\'', (row) => {
count++;
processRow(row);
});
// v0.4.0 — 派生表 / 多列哈希连接 / COUNT(DISTINCT) / NULLS 排序
const top = await db.query('SELECT dept, total FROM (SELECT dept, SUM(salary) AS total FROM emp GROUP BY dept) AS t WHERE total > 100 ORDER BY total DESC');
await db.query('SELECT COUNT(DISTINCT city) AS n FROM users');
await db.query('SELECT name FROM users ORDER BY age ASC NULLS FIRST');
// v0.4.2 — 崩溃恢复自愈(无需删库重建)
await db.repair(); // 校验清理损坏数据,恢复一致性
await db.clearAll(); // 清空全部数据与表结构(保留库本身)
// v0.4.2 — 迁移版本持久化(重启后从持久化版本继续,不重跑不跳跑)
db.addMigration(1, async (d) => { /* ... */ });
await db.migrateTo(1);
// 事务 — 自动回滚 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` | `'opfs' \| 'memory'` | `'opfs'` | 磁盘引擎(aria 模式下为存储后端;v0.6.0: IndexedDB 已移除) |
| `version` | `number` | `1` | 版本号 |
| `maxRowsPerQuery` | `number` | `0` | 查询结果行数上限(0=不限制)✅ v0.2.5 生效 |
| `debug` | `boolean` | `false` | 调试模式,输出详细日志 🆕 |
| `onError` | `(error) => void` | — | 全局错误回调 🆕 |
| `aria` | `AriaEngineConfig` | — | AriaEngine 专属配置透传:`walSyncMode`/`checkpointInterval`/`encryption`/`pageStorage`/`compression` 等 🆕 v0.5.0 |
### 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'` | 删除级联 ✅ v0.4.1 |
| `onUpdate` | `'CASCADE'\|'SET NULL'\|'RESTRICT'` | 更新级联(更新主键时触发)✅ v0.4.2 |
### WHERE 操作符
| 操作符 | 含义 | 操作符 | 含义 |
|--------|------|--------|------|
| `$eq` / 直接值 | 等于 | `$gt` / `$gte` | 大于 / 大于等于 |
| `$ne` | 不等于 | `$lt` / `$lte` | 小于 / 小于等于 |
| `$in` / `$nin` | 在列表中 | `$like` | 模糊匹配 |
| `$and` / `$or` / `$not` | 逻辑组合 | | |
### 核心方法
| 方法 | 说明 |
|------|------|
| `db.query(sql)` | 执行 SQL 字符串 |
| `db.queryStream(sql, onRow)` | 流式查询(逐行回调,不物化)🆕 |
| `db.table(name)` | 获取表操作对象 |
| `db.defineTable(name, cols)` | 定义表结构 |
| `db.transaction(fn)` | 执行事务(自动回滚)🆕 |
| `db.repair()` | 崩溃恢复自愈:校验清理损坏数据、恢复索引一致性(无需删库重建)🆕 v0.4.2 |
| `db.clearAll()` | 清空全部数据与表结构(保留库本身,实例可继续使用)🆕 v0.4.2 |
| `db.exportTable(name)` / `db.exportAll()` | 导出数据 JSON |
| `db.importTable(name, data)` | 导入数据 |
| `db.backup()` | 在线备份:全库一致性快照(Aria 引擎级备份 / 其余引擎回退 exportAll)🆕 v0.5.1 |
| `db.addMigration(v, fn)` / `db.migrateTo(v)` | 数据迁移(版本持久化到库内,重启不重跑)🆕 v0.4.2 |
| `db.subscribe(table, fn)` | 订阅表变更 |
| `db.on(hook, fn)` | 注册钩子 (14 种,全部真实触发)🆕 v0.5.1 |
### 维护语句(v0.5.1 SQL 入口)
| 语句 | 说明 | 引擎支持 |
|------|------|----------|
| `EXPLAIN SELECT ...` | 输出查询计划(type/table/where/estimatedRows/actualTimeMs) | 全部 |
| `ANALYZE [TABLE] name` | 收集表统计信息(行数/平均行大小/索引深度/列基数) | Aria |
| `REINDEX [TABLE] name` | 重建表二级索引 | Aria |
| `VACUUM` | 压缩 LSM + 清理 MVCC 碎片 | Aria |
| `SAVEPOINT name` | 创建嵌套事务保存点 | Aria |
| `ROLLBACK TO [SAVEPOINT] name` | 回滚到保存点 | Aria |
| `RELEASE [SAVEPOINT] name` | 释放保存点 | Aria |
> 不支持的引擎执行维护语句抛 `NOT_SUPPORTED`。
### 旧库迁移(v0.6.0)
> IndexedDB 已从引擎中完全移除。使用旧版本(v0.5.x 及更早)的用户,可通过
> 一次性迁移工具把磁盘模式(IndexedDBEngine)旧库导入新引擎(KVStore)。
```typescript
import { migrateFromIndexedDB } from '@metona-team/metona-sqlark/migration';
// 目标库(新引擎,disk 模式)
const target = await MetonaSqlark.create({ name: 'my-app-new', mode: 'disk' });
// 从旧 IndexedDB 库导入(旧库名 'my-app',旧引擎 disk 模式)
const result = await migrateFromIndexedDB({
dbName: 'my-app',
engine: 'disk', // 仅支持 disk 模式(IndexedDBEngine)
target,
onProgress: (done, total, table) => console.log(`迁移 ${done}/${total}: ${table}`),
});
// result: { migratedTables, rowCount, skippedTables }
```
> 注:aria 模式旧库(IndexedDBBackend)数据为引擎私有格式(SSTable/WAL),
> 无法按行迁移——此类用户请从应用层导出(exportAll)后重新导入。
### 连接池(v0.1.13)
| 静态方法 | 说明 |
|------|------|
| `MetonaSqlark.connect(config)` | 获取或创建数据库实例(单例复用)🆕 |
| `MetonaSqlark.disconnect(name)` | 释放连接(引用计数 -1)🆕 |
| `MetonaSqlark.disconnectAll()` | 强制关闭所有连接 🆕 |
| `MetonaSqlark.getActiveConnections()` | 获取活跃连接列表 🆕 |
### React / Vue 集成
```tsx
// 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 (KVStore) | Disk (OPFS) | Hybrid | Aria |
|------|--------|----------------|-------------|--------|------|
| **持久化** | ❌ 重启丢失 | ✅ KVStore(OPFS) | ✅ OPFS(schema 持久化) | ✅ 内存+磁盘 | ✅ 后端决定 |
| **事务回滚** | ✅ 快照 | ✅ 原子日志 flush | ✅ 快照 | ✅ 双引擎 | ✅ MVCC |
| **多 key 原子写** | — | ✅ 单日志记录原子(v0.6.0) | — | ✅ 委托磁盘 | ✅ WAL 单文件原子 |
| **二级索引** | ✅ Hash | ✅ Hash(重启恢复) | ✅ Hash(重启恢复) | ✅ Hash | ✅ LSM(重启恢复) |
| **查询性能** | ⚡ O(1) PK | ⚡ O(1) PK(内存热路径) | 🟡 O(1) PK | ⚡ O(1) PK | ⚡ O(log n) |
| **数据上限** | 内存限制 | 磁盘可用(行级存储,大表可行) | ~磁盘可用(整表 JSON,≤1000 行) | 磁盘可用 | 内存限制 |
| **浏览器** | 全部 | Chrome/Edge 102+ / Firefox 111+ / Safari 15.2+ | 同上 | 同上 | 同上 |
| **多标签页** | — | —(无事务锁,Hybrid 场景) | ❌ 无保护 | ❌ 无保护 | ✅ Web Locks 独占锁(v0.5.0) |
| **全库加密** | — | — | — | — | ✅ AES-GCM(v0.5.0) |
| **适用场景** | 缓存/测试 | 标准持久化(替代 IndexedDB) | 小数据集 | 速度+持久化 | 大规模/分析 |
| **测试覆盖** | 30+ | 50+(含 10 万级压力) | 15 | 15+ | 400+ |
### Memory 模式
- **环境**: 所有浏览器、Node.js
- **限制**: 数据不持久化,页面刷新/进程重启后数据丢失
- **能力**: 完整 CRUD、事务回滚、外键级联、二级索引、SQL 全支持
- **适用**: 临时数据、单元测试、缓存层
### Disk (KVStore) 模式
- **环境**: Chrome/Edge 102+ / Firefox 111+ / Safari 15.2+(OPFS)、Node.js(内存介质)
- **限制**: 依赖 OPFS;多标签页并发写无锁保护(单标签页内可靠)
- **能力**: 完整 CRUD、**多 key 原子事务**(单日志记录原子追加)、外键级联、事务内 DDL、
二级索引(重启恢复)、schema/数据/索引完整持久化、10 万级数据量压力验证
- **适用**: 标准前端数据库持久化(v0.6.0 替代 IndexedDB)
### Disk (OPFS) 模式
- **环境**: Chrome 102+ / Edge 102+ / Firefox 111+ / Safari 15.2+(Origin Private File System)
- **限制**: 每次写入重写整表 JSON 文件(大表性能差,不建议 >1000 行);多标签页并发写无保护(Hybrid 模式)
- **能力**: 完整 CRUD、重启自动加载数据(空表/索引/schema 完整保留,v0.4.2)、事务回滚、并发写安全(内存快照一致)
- **适用**: 小数据集持久化(大表请用 Aria + OPFS 页面化存储)
### Hybrid 模式
- **环境**: 所有浏览器
- **限制**: 磁盘引擎决定底层限制(IndexedDB ~2GB / OPFS 需 OPFS 支持浏览器)
- **能力**: write-through 双写(内存+磁盘)、提交顺序保证(磁盘优先)、读从内存
- **适用**: 需要内存速度 + 磁盘持久化的混合场景
### Aria 模式
- **环境**: 所有浏览器(后端可选 IndexedDB / OPFS / Memory)
- **限制**: Memory 后端重启丢失;IndexedDB 后端受配额限制;OPFS 后端需 OPFS 支持(Chrome 102+ / Firefox 111+ / Safari 15.2+);同一库仅允许一个标签页打开(Web Locks 独占锁,第二个标签页抛 `ARIA_LOCKED`)
- **能力**: LSM-Tree 存储引擎、SSTable 4KB 页面化物理存储(OPFS 后端默认启用,BufferPool LRU 缓存)、二级索引(跨重启恢复)、MVCC 事务、WAL 分片文件原子写入崩溃恢复(真追加 + 空洞检测)、ON UPDATE/DELETE 外键级联、Bloom Filter、**全库 AES-GCM 透明加密**(v0.5.0)、LZ4 压缩、Savepoint、EXPLAIN、ANALYZE、REINDEX、VACUUM、`repair()` 自愈(含孤儿页面清理)
- **适用**: 大规模数据分析、需要自研引擎可控性的高级场景
---
## 🌲 AriaEngine — 自研存储引擎
AriaEngine 是内置的页面式存储引擎,对标 SQLite 的设计理念:
```typescript
// 激活 AriaEngine(全库加密 + OPFS 页面化存储,v0.5.0)
const db = await MetonaSqlark.create({
name: 'my-app',
mode: 'aria', // 🆕 自研引擎模式
diskEngine: 'opfs', // 'opfs' | 'memory'
aria: { // 🆕 AriaEngine 专属配置透传(v0.5.0)
walSyncMode: 'full', // WAL 同步模式
encryption: { password: 'my-password' }, // 全库 AES-GCM 加密
},
});
// 与现有 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.6.0 │
│ (implements IStorageEngine) │
├──────────────────────────────────────────┤
│ LSM-Tree │ Buffer Pool │ WAL │
│ MemTable │ LRU (256pp) │ Segmented │
│ +SSTable │ +FileManager │ +CRC-32 │
│ (4KB Page) │ │ +空洞检测 │
├──────────────────────────────────────────┤
│ MVCC │ Bloom Filter │ LZ4 v2 │
│ Snapshot │ FNV-1a+Murmur│ +大小头 │
│ +Savepoint│ +Serialize │ │
├──────────────────────────────────────────┤
│ EncryptedBackend │ 二级索引 │ ANALYZE │
│ AES-GCM 全库 │ Per-Column│ EXPLAIN │
├──────────────────────────────────────────┤
│ Web Locks 独占锁 │ OPFS Backend (v2) │
│ (多标签页) │ append/COW原子/清理 │
└──────────────────────────────────────────┘
```
| 特性 | 说明 |
|------|------|
| **LSM-Tree** | MemTable (红黑树) → SSTable 多级索引,异步 Compaction(从存储兜底加载,不依赖缓存),写背压 |
| **页面化物理存储** | OPFS 后端下 SSTable 存为 4KB 页面(`pageStorage` 自动启用):FileManager 分配 pageId、BufferPool LRU 缓存(256 页 ≈ 1MB 可控内存)、save 即落盘(WAL checkpoint 截断安全)、`SSTableMeta.pageIds` 持久化(旧整 value 数据兼容)v0.5.0 |
| **WAL** | 分片文件 `__wal_%06d.bin`(4MB 阈值切换)+ 真追加(createWritable keepExistingData);标准 CRC32 记录校验;full/batch/none 三种模式(full 真正同步);空洞检测截断;16MB 阈值自动 checkpoint(活跃事务期间不截断)v0.5.0 |
| **崩溃恢复** | 打开时完整性校验(残缺 SSTable 自动跳过并清理 + 整文件 CRC-32 校验)、WAL 按 key 扫描恢复(不丢记录)、恢复后自动重建二级索引 |
| **全库加密** | `encryption.password` 配置 → EncryptedBackend 透明加解密(WAL/SSTable/Schema/元数据全密文);PBKDF2-SHA256 密钥派生 + salt/verifier 持久化;密码错误/数据篡改 → `ARIA_DECRYPT_ERROR`;明文库/加密库开关一致性检测 v0.5.0 |
| **MVCC** | 版本链 + 快照隔离,事务读写不互斥,提交/回滚按事务写入 key 精准清理,自动 GC |
| **Bloom Filter** | FNV-1a + Murmur 双哈希,SSTable footer 序列化,查询时 probe |
| **二级索引** | 每列独立 LSM Tree,支持 $eq/$in/$gt/$lt 范围扫描,跨重启自动恢复,WAL 恢复后自动重建 |
| **多标签页锁** | Web Locks API 库级独占锁(`ifAvailable` 不排队):第二个标签页抛 `ARIA_LOCKED`;不支持的环境降级无锁并告警 v0.5.0 |
| **Compaction** | 异步 Leveled Compaction,Level 0 > 8 触发同步背压,compactLevel public 接口 |
| **OPFS Backend** | 零外部依赖纯浏览器文件系统:单文件 COW 原子写、`append` 真追加、写队列失败不中断、close 排空、崩溃残留自动清理 v0.5.0 |
---
## 🛠 开发
```bash
npm install # 安装依赖
npm run dev # 开发模式(localhost:3001)
npm run build # 生产构建(生成 dist/)
npm test # 运行测试
npm run test:e2e # Playwright e2e(真实 Chromium + OPFS + 崩溃注入,需先 build)
npm run lint # 代码检查
npm run typecheck # 类型检查
```
---
## 📊 项目状态
| 指标 | 数值 |
|------|------|
| 测试用例 | 1022 |
| 测试套件 | 64(+12 Playwright e2e) |
| 行覆盖率 | 89.15% |
| SQL 关键字 | 72 |
| 存储引擎 | 5(Memory / **KVStore** / OPFS / Hybrid / **Aria**) |
### 🌐 浏览器兼容性
| 浏览器 | 最低版本 | Memory | KVStore(OPFS) | OPFS | Web Locks | Aria |
|--------|----------|--------|---------------|------|-----------|------|
| Chrome | 102+ | ✅ | ✅ | ✅ | ✅ (69+) | ✅ |
| Firefox | 111+ | ✅ | ✅ | ✅ | ✅ (96+) | ✅ |
| Safari | 15.2+ | ✅ | ✅ | ✅ | ✅ (15.4+) | ✅ |
| Edge | 102+ | ✅ | ✅ | ✅ | ✅ (79+) | ✅ |
| Node.js | 16+ | ✅ | ✅ (内存介质) | ❌ (测试用 mock) | ❌ | ✅ |
> **OPFS 支持说明(v0.4.5 更新)**: Chromium(Chrome/Edge 102+)、Firefox 111+、Safari 15.2+
> 均已支持基础 OPFS API(`createWritable` 原子写)。OPFS 无跨文件事务,
> AriaEngine 以 WAL 分片单文件原子写 + 空洞检测截断保证崩溃一致性。
>
> **多标签页保护(v0.4.5)**: AriaEngine 打开库时通过 Web Locks API 获取库级独占锁,
> 第二个标签页打开同一库会抛 `ARIA_LOCKED`。Web Locks 不可用的环境降级为无锁并告警
> (仅理论上,现代浏览器均支持)。
---
## 📂 项目结构
```
src/
├── index.ts # 入口(MetonaSqlark + MeSqlark)
├── core.ts # 主类
├── constants.ts # 类型定义 + 配置 + DatabaseError
├── connection-manager.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](https://git.metona.cn/MetonaTeam/MetonaSqlark)