Files
MetonaSqlark/README.md
T
thzxx 82ad8e93b9
CI / test (18.x) (push) Successful in 10m4s
CI / test (20.x) (push) Successful in 10m0s
CI / test (22.x) (push) Successful in 10m0s
CI / test (24.x) (push) Successful in 9m54s
docs: 修正测试数/覆盖率 — 837 测试 · 44 套件 · 81.1% 覆盖率
2026-08-08 11:12:40 +08:00

420 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MetonaSqlark
<p align="center">
<img src="https://img.shields.io/badge/version-0.3.2-blue?style=flat-square" alt="version">
<img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="license">
<img src="https://img.shields.io/badge/coverage-81.1%25-brightgreen?style=flat-square" alt="coverage">
<img src="https://img.shields.io/badge/tests-837%20passed-success?style=flat-square" alt="tests">
</p>
> 基于 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/EXISTSv0.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 套件,生产级质量保证
---
## 📦 安装
```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
<!-- UMD 格式,暴露 window.MetonaSqlark 和 window.MeSqlark -->
<script src="https://git.metona.cn/MetonaTeam/MetonaSqlark/raw/branch/master/dist/metona-sqlark.min.js"></script>
```
或从 [`dist/`](./dist/) 目录下载:
- `metona-sqlark.js` — UMD 开发版(含 sourcemap
- `metona-sqlark.min.js` — UMD 压缩版(~105KBgzip ~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
<script src="metona-sqlark.min.js"></script>
<script>
(async () => {
const db = await window.MetonaSqlark.create({ name: 'my-app', mode: 'hybrid' });
// 或 window.MeSqlark(完全等价)
})();
</script>
```
---
## 🚀 快速开始
```typescript
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 集成
```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 (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.jsfake-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 的设计理念:
```typescript
// 激活 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 CompactionLevel 0 > 8 触发同步背压,compactLevel public 接口 ✅ v0.2.5 |
| **OPFS Backend** | 纯浏览器文件系统,Promise 队列串行写,零外部依赖 |
---
## 🛠 开发
```bash
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 |
| 存储引擎 | 5Memory / 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](https://git.metona.cn/MetonaTeam/MetonaSqlark)