thzxx 334067d89e
CI / test (18.x) (push) Successful in 10m10s
CI / test (20.x) (push) Successful in 10m10s
CI / test (22.x) (push) Successful in 10m6s
CI / e2e (push) Successful in 9m51s
CI / test (24.x) (push) Successful in 10m28s
release: v0.5.1 — 存储后端生产级硬化(CRC-32/全库加密/WAL分片/页面化存储/多标签页锁/e2e)+ 深度审查修复(假实现接线/死代码清理)
2026-08-10 12:07:00 +08:00

MetonaSqlark

version license coverage tests

基于 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_LOCKEDv0.5.0
  • 🛡 输入校验全覆盖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+ CASE WHEN/哈希连接/组提交(v0.3.1+ 多标签页同步(v0.3.2
  • 🚰 流式查询queryStream/stream() 逐行回调,Aria LSM 惰性扫描不物化结果集(v0.4.0)
  • 🧩 派生表FROM (SELECT ...) 子查询作为行源,多列 ON 哈希连接,COUNT(DISTINCT)NULLS FIRST/LASTv0.4.0
  • 🔗 Query Builder API — 链式 .select().where().orderBy().limit().execute()
  • 🔄 事务回滚 — Memory/IndexedDB/Hybrid/Aria 四引擎事务原子性,自动回滚,MVCC 版本链接入读写路径
  • 🔗 外键级联 — ON DELETE + ON UPDATECASCADE / 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 测试 · 86.7% 覆盖率 — 62 套件 + 7 个 Playwright 真实 Chromium e2e,生产级质量保证

📦 安装

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 压缩版(~105KBgzip ~27KB
  • metona-sqlark.esm.js — ES Module
  • metona-sqlark.cjs — CommonJS
  • metona-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.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 'indexeddb' | 'opfs' 'indexeddb' 磁盘引擎(aria 模式下为存储后端)
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.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 OPFSschema 持久化) 内存+磁盘 后端决定
事务回滚 快照 原子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+ / Firefox 111+ / Safari 15.2+ 全部 全部
多标签页 Web Locks 独占锁,v0.5.0 锁保护
全库加密 AES-GCMv0.5.0
适用场景 缓存/测试 标准持久化 Chromium+ 持久化 速度+持久化 大规模/分析
测试覆盖 30+ 30+ 15+e2e 15+ 400+

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+ / Firefox 111+ / Safari 15.2+Origin Private File System
  • 限制: 每次写入重写整表 JSON 文件(大表性能差,不建议 >1000 行);多标签页并发写无保护(Hybrid 模式)
  • 能力: 完整 CRUD、重启自动加载数据(空表/索引/schema 完整保留,v0.4.2)、事务回滚、并发写安全(内存快照一致)
  • 适用: 小数据集持久化(大表请用 Aria + OPFS 页面化存储)

Hybrid 模式

  • 环境: 所有浏览器
  • 限制: 磁盘引擎决定底层限制(IndexedDB ~2GB / OPFS Chrome only
  • 能力: 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 的设计理念:

// 激活 AriaEngine(全库加密 + OPFS 页面化存储,v0.5.0
const db = await MetonaSqlark.create({
  name: 'my-app',
  mode: 'aria',           // 🆕 自研引擎模式
  diskEngine: 'opfs',     // 'indexeddb' | '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.5.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 CompactionLevel 0 > 8 触发同步背压,compactLevel public 接口
OPFS Backend 零外部依赖纯浏览器文件系统:单文件 COW 原子写、append 真追加、写队列失败不中断、close 排空、崩溃残留自动清理 v0.5.0

🛠 开发

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
测试套件 62+7 Playwright e2e
行覆盖率 86.7%
SQL 关键字 36
存储引擎 5Memory / IndexedDB / OPFS / Hybrid / Aria

🌐 浏览器兼容性

浏览器 最低版本 Memory IndexedDB OPFS Web Locks Aria
Chrome 80+ (102+) (69+)
Firefox 80+ (111+) (96+)
Safari 14+ (15.2+) (15.4+)
Edge 80+ (102+) (79+)
Node.js 16+ (fake-idb) (测试用 mock)

OPFS 支持说明(v0.4.5 更新): ChromiumChrome/Edge 102+)、Firefox 111+、Safari 15.2+ 均已支持基础 OPFS APIcreateWritable 原子写)。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 # 连接池管理
├── 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

S
Description
Frontend TypeScript SQL database with dual-mode storage
https://sqlark.metona.cn/
Readme MIT
14 MiB
Languages
TypeScript 90%
HTML 9.6%
JavaScript 0.4%