Metona CN OpenSource Team
中国-四川-成都

@metona-team/metona-sqlark (0.8.0)

Published 2026-09-15 17:31:07 +08:00 by thzxx in MetonaTeam/MetonaSqlark

Installation

@metona-team:registry=https://git.metona.cn/api/packages/MetonaTeam/npm/
npm install @metona-team/metona-sqlark@0.8.0
"@metona-team/metona-sqlark": "0.8.0"

About this package

Frontend SQL database with in-memory and disk dual-mode storage

MetonaSqlark

version license coverage tests

基于 TypeScript 的前端关系型数据库:完整 SQL + Query Builder 双 API, 4 种存储模式(memory / disk / hybrid / aria)+ 3 种后端(OPFS / KVStore / Memory)可选, aria 模式内置自研 LSM-Tree 存储引擎(AriaEngine:WAL + 页面化 + 可选压缩/加密, 事务用未提交快照回滚)。 零运行时依赖,浏览器 / Node.js 开箱即用。


目录


核心特性

数据库能力

  • 完整 SQL — SELECT(JOIN / 子查询 / 派生表 / UNION / GROUP BY / HAVING / DISTINCT / CASE WHEN / EXISTS / BETWEEN / NULLS 排序)、INSERT...SELECT、UPDATE/DELETE 子查询、ALTER TABLE、TRUNCATE TABLE、CREATE INDEX、事务语句(BEGIN / COMMIT / ROLLBACK / SAVEPOINT)、维护语句(EXPLAIN / ANALYZE / REINDEX / VACUUM)
  • Query Builder — 链式 .select().where().innerJoin().orderBy().limit().execute(),类型安全
  • 流式查询queryStream / table().stream() 逐行回调,Aria 引擎真惰性扫描(limit 提前终止,不物化结果集)
  • 事务 — 四引擎事务原子性 + 自动回滚;Aria 用引擎级事务 + 未提交快照(txnSnapshot)回滚。同一实例同时只允许一个事务(并发 beginTransactionTX_ACTIVE)—— 不是 MVCC 快照隔离
  • 外键级联ON DELETE / ON UPDATE 支持 CASCADE / SET NULL / RESTRICT(含主键变更级联、级联环路保护)
  • 数据迁移 — 版本化迁移(持久化到库内,重启不重跑)、单表/全库导入导出、backup() 全库导出(逐表读取,非跨表快照 —— 见「已知限制」)
  • 连接池MetonaSqlark.connect() 单例复用,引用计数自动关闭

存储模式(4 种)+ 存储后端(3 种)

  • memory — 纯内存,测试 / 缓存
  • disk — 自研 KVStore 事务引擎(v0.6.0 起替代 IndexedDB):多 key 原子写、快照 + 日志崩溃恢复
  • hybrid — write-through 双写,读走内存
  • aria — 自研 AriaEngine(LSM-Tree + WAL + 页面化 + 可选压缩/加密), 后端可选 diskEngine: 'opfs' | 'kv' | 'memory'

diskhybrid 的磁盘侧恒用自研 KVStorediskEngine 项只对 mode: 'aria' 生效,其余模式忽略该配置 —— 已在类型注释中写明)。

生产级可靠性

  • 崩溃恢复 — WAL 原子写入 + CRC-32 完整性校验(SSTable 整文件 + WAL 记录)、分片空洞与记录损坏如实上报ARIA_WAL_GAP / droppedWALRecords,拒绝静默截断)、打开时损坏自愈、repair() 清理重建
  • 全库加密 — AES-256-GCM 透明加密(PBKDF2 密钥派生 + 密码验证 + 篡改检测),encryption.password 一键启用
  • 多标签页独占锁 — Web Locks API,第二个标签页打开同一库抛 ARIA_LOCKED
  • 数据安全 — 输入校验(required / maxLength / min / max)、SQL 注入防护、Bloom Filter 快速否定
  • 大表性能 — 10 万行级验证;批量插入组提交;SSTable 4KB 页面化 + BufferPool LRU 缓存

生态

  • React / Vue 集成useQuery / useSqlarkQuery 等 hooks(构建为 dist/react.js / dist/vue.js,配套手写精确类型声明;react / vue可选 peer dependency, 不装也不影响核心库使用)
  • 插件系统 — 14 种生命周期钩子(beforeInsert / afterQuery / ...), priority 越大越先执行(同优先级保持注册顺序)
  • 旧库迁移migrateFromIndexedDB() 一键把旧 IndexedDB 库导入新引擎
  • 浏览器兼容 — Chrome 102+ / Firefox 111+ / Safari 15.2+ / Edge 102+ / Node.js 16+

安装

npm install @metona-team/metona-sqlark

私有 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 开发版)/ metona-sqlark.min.js(压缩版:实测 251,731 字节 / gzip 63,431 字节)/ metona-sqlark.esm.js / metona-sqlark.cjs / metona-sqlark.d.ts


快速开始

import { MetonaSqlark } from '@metona-team/metona-sqlark';
// 别名:import { MeSqlark } from '@metona-team/metona-sqlark';

const db = await MetonaSqlark.create({
  name: 'my-app',
  mode: 'hybrid',            // 'memory' | 'disk' | 'hybrid' | 'aria'
  // 仅 mode:'aria' 时生效(Aria 的存储后端);disk/hybrid 恒用自研 KVStore
  diskEngine: 'opfs',        // 'opfs' | 'kv' | 'memory'
});

// 定义表 — 支持约束与外键级联
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');

// 参数化查询(v0.7.0)— 位置参数 `?`,安全编码杜绝 SQL 注入
await db.query("INSERT INTO users VALUES (?, ?, ?, ?)", ['2', "O'Brien", 25, 'ob@demo.com']);
const row2 = await db.query('SELECT * FROM users WHERE name = ?', ["O'Brien"]);

// 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.amount FROM users u
  INNER JOIN orders o ON u.id = o.user_id WHERE o.amount > 100`);

// 子查询 / UNION / EXISTS
await db.query(`SELECT * FROM users WHERE id IN (SELECT user_id FROM orders WHERE amount > 100)`);
await db.query(`SELECT name FROM users WHERE city = 'Beijing'
  UNION SELECT name FROM users WHERE age < 30`);
await db.query(`SELECT * FROM users u WHERE EXISTS
  (SELECT 1 FROM orders o WHERE o.user_id = u.id)`);

// GROUP BY / CASE WHEN
await db.query(`SELECT dept, COUNT(*) FROM employees GROUP BY dept HAVING COUNT(*) > 1`);
await db.query(`SELECT name, CASE WHEN age >= 18 THEN 'adult' ELSE 'minor' END AS status FROM users`);

// ALTER TABLE / TRUNCATE TABLE
await db.query('ALTER TABLE users ADD COLUMN phone STRING');
await db.query('TRUNCATE TABLE old_logs');

// 事务 — 失败自动回滚
await db.transaction(async (trx) => {
  await trx.table('users').insert({ id: '3', name: 'Charlie' });
  await trx.table('orders').insert({ id: 'o1', user_id: '3', amount: 99 });
});

// 流式查询 — 大表逐行回调
await db.queryStream('SELECT * FROM logs WHERE level = \'error\'', (row) => {
  processRow(row);
});

// 崩溃恢复自愈
await db.repair();
await db.clearAll();

// 连接池 — 同库复用
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' 存储模式
diskEngine 'opfs' | 'memory' | 'kv' 'opfs' mode: 'aria' 生效(Aria 的存储后端;'kv' = 自研 KVStore)。disk/hybrid 恒用 KVStore,忽略此项
version number 1 版本号
maxRowsPerQuery number 0 查询结果行数上限(0 = 不限)
debug boolean false 调试模式
multiTabSync boolean false 多标签页同步(BroadcastChannel)
plugins MetonaPlugin[] 插件列表
onReady (db) => void 就绪回调
onError (error) => void 全局错误回调
aria AriaEngineConfig AriaEngine 配置透传(walSyncMode / encryption / pageStorage / compression 等)

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' 更新级联(更新主键时触发)
maxLength number 字符串最大长度
min / max number 数字最小值 / 最大值

WHERE 操作符

操作符 含义 操作符 含义
$eq / 直接值 等于 $gt / $gte 大于 / 大于等于
$ne 不等于 $lt / $lte 小于 / 小于等于
$in / $nin 在列表中 $like 模糊匹配
$and / $or / $not 逻辑组合

核心方法

方法 说明
db.query(sql) / db.query(sql, params) 执行 SQL(支持分号多语句;v0.7.0 位置参数 ? 绑定)
db.queryStream(sql, onRow) 流式查询(逐行回调,不物化)
db.table(name) 获取表操作对象(insert / select / update / delete / count / stream / clear / drop
db.defineTable(name, cols) 定义表结构
db.dropTable(name) / db.getTableNames() 删除表 / 列出表
db.transaction(fn) 执行事务(自动回滚)
db.repair() 崩溃恢复自愈:校验清理损坏数据、重建索引
db.clearAll() 清空全部数据与表结构(保留库本身)
db.exportTable(name) / db.exportAll() 导出数据 JSON
db.importTable(name, data) 导入数据
db.backup() 导出全库数据(逐表读取,见下方"已知限制")
db.addMigration(v, fn) / db.migrateTo(v) 版本化数据迁移(版本持久化,重启不重跑)
db.subscribe(table, fn) 订阅表变更(返回退订函数)
db.on(hook, fn) 注册生命周期钩子(14 种)
db.close() 关闭数据库

维护语句(SQL 入口)

语句 说明 支持引擎
EXPLAIN SELECT ... 输出查询计划(type/table/where/usingIndex/estimatedRows/actualTimeMs) 全部
ANALYZE [TABLE] name 收集表统计信息(行数/行大小/索引深度/列基数) Aria
REINDEX [TABLE] name 重建表二级索引 Aria
VACUUM 压缩 LSM + 清理版本碎片 Aria
SAVEPOINT name / ROLLBACK TO name / RELEASE name 嵌套事务保存点 Aria

不支持的引擎执行维护语句抛 NOT_SUPPORTED

旧库迁移(v0.6.0)

IndexedDB 已从引擎中完全移除。旧版本(v0.5.x 及更早)的 disk 模式用户可通过一次性迁移工具导入:

// 两种等价写法(v0.8.0 起主入口也导出,避免深路径依赖)
import { migrateFromIndexedDB } from '@metona-team/metona-sqlark/migration';
// import { migrateFromIndexedDB } from '@metona-team/metona-sqlark';

const target = await MetonaSqlark.create({ name: 'my-app-new', mode: '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 模式库为引擎私有格式(SSTable/WAL),无法按行迁移——请从应用层 exportAll() 后重新导入。

连接池

静态方法 说明
MetonaSqlark.connect(config) 获取或创建数据库实例(单例复用)
MetonaSqlark.disconnect(name) 释放连接(引用计数 -1)
MetonaSqlark.disconnectAll() 强制关闭所有连接
MetonaSqlark.getActiveConnections() 获取活跃连接列表

实现说明:这四个静态方法由 connection-manager 模块在模块加载时注入 (主入口 src/index.ts 以 side-effect 方式 import './connection-manager'), 因此从包入口引入即可用,无需额外操作。类型上(v0.8.0 起)声明为可选静态成员; 若在未加载该模块的自定义构建里调用,其值为 undefined 并抛 TypeError —— 不会静默无效。


存储引擎

特性 Memory Disk (KVStore) Hybrid Aria
持久化 重启丢失 KVStore(OPFS / 内存介质) 内存 + 磁盘 后端决定
事务 快照回滚 单日志记录原子写 双引擎(磁盘优先) 快照回滚(事务串行,非 MVCC 隔离)
二级索引 Hash Hash(重启恢复) Hash LSM(重启恢复)
查询性能 O(1) PK O(1) PK(内存热路径) O(1) PK O(log n)
数据上限 内存 磁盘可用 磁盘可用 内存
全库加密 AES-GCM
多标签页锁 Web Locks
适用场景 缓存 / 测试 标准持久化(替代 IndexedDB) 速度 + 持久化 大规模 / 分析
测试覆盖 30+ 50+(含 10 万级压测) 15+ 400+

选型建议

  • 临时数据 / 单元测试memory
  • 标准前端持久化(替代 IndexedDB)→ disk(KVStore,多 key 原子事务,10 万级验证)
  • 内存速度 + 磁盘持久化hybrid(write-through,读走内存)
  • 大规模 / 需要自研引擎可控性aria(LSM-Tree + WAL + 加密 + 页面化存储)

AriaEngine 自研存储引擎

AriaEngine 是内置的页面式存储引擎,对标 SQLite 的设计理念: LSM-Tree 索引 + WAL 崩溃恢复 + 快照回滚事务 + 页面化物理存储 + 全库加密

const db = await MetonaSqlark.create({
  name: 'my-app',
  mode: 'aria',
  diskEngine: 'opfs',         // 存储后端:'opfs' | 'kv' | 'memory'
  aria: {
    walSyncMode: 'full',      // 'full' | 'batch' | 'none'
    compression: true,        // LZ4 页面压缩
    encryption: { password: 'my-password' },  // 全库 AES-256-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 v0.8.0               │
├──────────────────────────────────────────────┤
│  LSM-Tree      │  Buffer Pool   │    WAL     │
│  MemTable      │  LRU (256页)   │  分片文件   │
│  +SSTable      │  +FileManager  │  +CRC-32   │
│ (4KB 页面)     │               │  +空洞检测  │
├────────────────┼───────────────┼────────────┤
│  事务/版本管理 │  Bloom Filter │  LZ4 压缩  │
│  快照回滚      │  二级索引 LSM │  +大小头    │
│  +Savepoint    │  (每列独立)   │            │
├────────────────┴───────────────┴────────────┤
│  __aria_manifest 单一提交点(数据→提交→截断)│
├──────────────────────────────────────────────┤
│  EncryptedBackend (AES-256-GCM 全库透明加密) │
├──────────────────────────────────────────────┤
│  存储后端: OPFS Backend / KVStore Backend    │
│  Web Locks 多标签页独占锁                    │
└──────────────────────────────────────────────┘

核心机制

机制 说明
LSM-Tree MemTable(红黑树)→ 多级 SSTable,异步 Compaction(从存储兜底加载),写背压
页面化存储 SSTable 存为 4KB 页面(FileManager 分配 pageId + BufferPool LRU 缓存 256 页 ≈ 1MB),pageStorage 在 opfs/kv 后端默认启用
WAL 分片文件 __wal_%06d.bin + 真追加;标准 CRC32 记录校验(记录级 CRC 失败会计数并上报 droppedWALRecords);full/batch/none 三模式;LSN 全库单调(manifest 记高水位);分片号绝不回退、也绝不低于 manifest 水位(整体清空后允许复用最后用过的号);内部空洞与记录损坏显式上报(gaps / corruptRecords),水位从未推进时的前缀缺失同样按空洞上报,水位已推进时前缀缺失视为"已清理的前缀";16MB 阈值自动 checkpoint(活跃事务期间不截断)
单一提交点 __aria_manifest_<gen>:页面水位 + 各命名空间 SSTable 元数据 + 表结构 + WAL 起始位置 + 待落盘冻结表意图,一次原子提交(头部/载荷双 CRC,先写后验,保留两代)。顺序固定为数据落盘 → manifest 提交 → 才允许截断 WAL / 删除旧文件;恢复只认最后一份 CRC 通过的世代,元数据损坏抛 ARIA_MANIFEST_CORRUPT(不再静默当空库)
崩溃恢复 打开时完整性校验(整文件 CRC-32;介质读故障不再被当成"文件不存在",抛 ARIA_SSTABLE_READ_FAILED 且不误删元数据)、按 LSN 水位重放 WAL、恢复后自动重建二级索引;getRecoveryReport() 返回 {droppedSSTables, dataLossSuspected, walGaps, droppedWALRecords, legacyImported, manifestFallback}repair() 只在 manifest 健康时回收孤儿页面
Compaction 整层合并不再"先摘层再合并"(合并期间该层对读者始终可见);底部层原地合并回收墓碑(删除密集场景空间不再无界增长);按层 compacting 集合(跨层触发不丢失);被取代的 SSTable 进入退休表,等更早的读者退出后才物理删除
全库加密 encryption.password → EncryptedBackend 透明加解密(WAL/SSTable/Schema/元数据全密文);PBKDF2 派生 + salt 持久化;密码错误/篡改 → ARIA_DECRYPT_ERROR
MVCC 版本链仅作事务内 undo(提交即清理,无快照隔离;事务串行);自动 GC
二级索引 每列独立 LSM Tree,支持等值/范围扫描,跨重启恢复,WAL 恢复后自动重建
Bloom Filter FNV-1a + Murmur 双哈希,SSTable footer 序列化,查询时快速否定
多标签页锁 Web Locks 库级独占锁,第二个标签页抛 ARIA_LOCKED;不支持的环境降级无锁并告警
维护语句 ANALYZE(表统计)/ REINDEX(重建索引)/ VACUUM(压缩 + 版本 GC)/ EXPLAIN(查询计划)

AriaEngine 配置项

属性 类型 默认 说明
pageSize number 4096 页面大小(字节)
bufferPoolPages number 256 Buffer Pool 页面数(≈1MB)
memtableSizeThreshold number 4MB MemTable 刷盘阈值
levelSizeMultiplier number 10 LSM 层级容量倍数
bloomFilterBitsPerKey number 10 Bloom Filter 每 key 位数
maxMemoryMB number 64 内存预算(MB):主 LSM 估算内存超限时触发 flush + MVCC GC
walEnabled boolean true 是否启用 WAL
walSyncMode 'full' | 'batch' | 'none' 'full' WAL 同步模式
checkpointInterval number 1000 Checkpoint 间隔(操作数)
walSizeThreshold number 16MB WAL 大小阈值(超则强制 checkpoint)
compression boolean false 是否启用 LZ4 压缩
storageBackend 'opfs' | 'kv' | 'memory' 'opfs' 存储后端
encryption { password: string } 全库 AES-256-GCM 加密
pageStorage boolean 自动(opfs/kv 后端默认启用) SSTable 页面化存储

框架集成

// React
import { useQuery, useTable, useDatabase } from '@metona-team/metona-sqlark/react';
const { data, loading, error, refresh } = useQuery(db, 'SELECT * FROM users');

// Vue
import { useSqlarkQuery, useSqlarkTable, useSqlarkDatabase } from '@metona-team/metona-sqlark/vue';
const { data, loading, error, refresh } = useSqlarkQuery(db, 'SELECT * FROM users');

开发

npm install       # 安装依赖
npm run dev       # 开发模式(localhost:3001)
npm run build     # 生产构建(生成 dist/)
npm test          # 运行测试(1985 用例 · 93 套件;+4 个重型套件)
python3 scripts/mutation-b6.py   # 变异验证:把 B-6 的修复逐项回退,对应用例必须失败
npm run test:e2e  # Playwright e2e(真实 Chromium + OPFS + 崩溃注入,需先 build)
npm run lint      # 代码检查
npm run typecheck # 类型检查

项目状态

指标 数值
测试用例 1985(93 套件)+ 14 Playwright e2e,另 4 个重型套件在独立 CI job 串行运行
语句覆盖率 90.59%(8538/9424)
分支覆盖率 82.61%(4459/5397)
函数覆盖率 94.14%(1223/1299)
行覆盖率 93.50%(7733/8270)
SQL 关键字 72
存储模式 4(memory / disk / hybrid / aria
存储后端 3(OPFS / KVStore / Memory),Aria 引擎另有 LSM-Tree + WAL + 页面化
运行时依赖 0

覆盖率口径collectCoverageFrom = src/**/*.ts,仅排除两个纯类型声明文件 (engine/interface.tsquery/ast.ts —— 它们只有 interface/type,可执行语句为 0, 纳入统计只会稀释分母)。v0.8.0 审查曾发现 interface.ts 里混着三个运行时函数 (cloneRow/cloneRowFallback/cloneRows)—— 已搬到 src/engine/row_clone.ts 并纳入统计(搬完门禁立刻因 functions 93.84% < 94% 失败,补测退化路径后通过)。 CI 常规 job 带 --coverage 运行, jest.config.cjscoverageThreshold 为 statements 90 / branches 82 / functions 94 / lines 93,任一项不达标即失败 —— 门槛不达标不允许发版

上述四个数字由与 CI 常规 job 完全相同的命令产出(可复现):

npx jest --coverage --testPathIgnorePatterns='/node_modules/|/tests/e2e/|aria-prod-load|kvstore-stress|aria-matrix-audit|aria-idx-flush-race'

修改覆盖范围、阈值或测试选择时必须同步更新本表(G5 门禁要求 README 数字与 CI 产出一致)。

已知限制(v0.8.0)

  • 存储布局在 v0.8.0 变更 — 元数据从"每个命名空间一份裸 JSON"(__aria_lsm_meta* / __aria_schemas)收敛为 __aria_manifest_<generation>(带世代号与双 CRC)。 旧库首次用 v0.8.0 打开时自动迁移(旧键保留不删),迁移遇到损坏的旧元数据会 明确报 ARIA_LEGACY_META_CORRUPT 而不是当成空库。直接读取这些内部 key 的外部 脚本需要跟着改(引擎侧无公开 API 依赖它们)。
    • 回退是单向的:迁移后所有新写入只进 manifest,旧的 __aria_lsm_meta* / __aria_schemas 停留在迁移那一刻。用旧版本打开同一个库会看到迁移时刻的旧 视图(不是"数据都在"),继续写入还会让两套布局分叉 —— 需要回退旧版本时, 先用 v0.8.0 导出数据,不要指望旧键是新数据的镜像。
  • 单列主键 — 复合主键暂不支持(建表时显式 SCHEMA_ERROR),列入 v0.8 路线图
  • 写语句关联引用 — UPDATE/DELETE 的 WHERE 支持非关联子查询(IN (SELECT) / 标量子查询),关联引用($col / 关联 EXISTS)显式抛 NOT_SUPPORTED(不静默)
  • backup() 不是跨表一致性快照 — 实现为逐表读取(Aria 走引擎级 backup(), 其余引擎回退 exportAll())。若在备份过程中有并发写入,不同表之间可能来自 不同时间点(单表内部是一致的)。需要强一致备份时请先 close() 或用 db.transaction() 包住调用(事务期间并发写会被 TX_ACTIVE 拒绝)。
    • 说明:此前文档宣称"在线一致性快照",但引擎层没有实现跨表快照原语 —— 与其保留一个不成立的宣称,这里按实际行为描述(真快照列入后续版本)。
  • 建表 UNIQUE 约束 — 不可经 DROP INDEX 解除(对齐 SQLite,需重建表);仅 CREATE UNIQUE INDEX 添加的约束可随索引删除
  • 唯一值交换更新 — 同一语句内两行互换唯一列值(A:x→y, B:y→x)保守拒绝(最终状态合法但报 UNIQUE_VIOLATION
  • 主键非空 — 主键列强制非空(SQL 语义 PK 隐含 NOT NULL),INSERT/UPDATE 置 null/undefined 抛 VALIDATION_ERROR
  • 多实例写入保护依赖 Web Locks — AriaEngine 用 Web Locks 做库级独占:第二个实例 打开同一个库时抛 ARIA_LOCKED,这是唯一受支持的多标签页写入方式。运行环境 没有 Web Locks 时该保护会自动降级为"提交点冲突检测"(manifest 世代号单调 + 提交前 检查是否存在别的实例提交的更新世代 → 抛 STALE_INSTANCE),但降级只保证 不静默覆盖别人的提交,不保证多实例写入的数据完整性:被拒绝的那一方此前 已写入自己 WAL 分片的记录,可能被胜出实例的 checkpoint 当作可回收前缀清掉。 结论:不要在没有 Web Locks 的环境里让两个实例同时写同一个库;需要并发访问时 由应用层串行化(如 SharedWorker / 主标签页代理)。
  • manifest 体积随 SSTable 数量增长 — 单一提交点把全部命名空间的 SSTable 元数据 (键范围、页面 id 列表、大小)+ 表结构 + WAL 水位写进同一个文件,每次提交 整体重写(保留两代)。因此元数据量与已落盘 SSTable 数成正比:长期高频写入、 层级很多且迟迟不合并的库,其 manifest 会明显大于数据本身之外的一般预期。当前 没有"元数据分层/增量"机制,vacuum() 合并层级是唯一的收敛手段(列入后续版本)。
  • 尾部 WAL 分片丢失无法从介质自身识别 — 分片内部空洞(中间缺号)与记录级 CRC 损坏都会被上报;但如果最后一个分片整个消失,介质上没有任何"它本该存在"的证据 (manifest 只记 startSegment/nextLsn,不记最后分片号),此时只能靠 ARIA_WRITE_LOST(有未落盘冻结表却重放不到任何记录)兜住"确定丢数据"的情况。

浏览器兼容性

浏览器 最低版本 Memory Disk (KVStore) Aria (OPFS)
Chrome / Edge 102+
Firefox 111+
Safari 15.2+
Node.js 16+ (内存介质) (内存介质)

OPFS:基础 API(createWritable 原子写)在 Chromium 102+ / Firefox 111+ / Safari 15.2+ 均支持。 无跨文件事务,AriaEngine 以 WAL 分片单文件原子写 + manifest 单一提交点保证崩溃一致性; 分片空洞与记录级损坏都会被显式上报(恢复报告 + 告警),不做静默截断。

多标签页保护:AriaEngine 通过 Web Locks 获取库级独占锁,第二个标签页打开同一库抛 ARIA_LOCKED

OPFS 需要 http(s) 页面aria + diskEngine: 'opfs'file:// 直接打开的页面里不可用 —— Chromium 会以 SecurityError 拒绝 navigator.storage.getDirectory()(注意此时 isSecureContext 仍是 true、API 也存在,只有真正调用才会发现)。引擎会抛 ARIA_OPFS_UNAVAILABLE,消息里给出两条出路:用本地服务器打开 (node tests/e2e/server.cjs 3344http://127.0.0.1:3344/...),或改用 mode: 'memory' / KVStore 后端。


项目结构

src/
├── index.ts              # 入口(MetonaSqlark + MeSqlark)
├── core.ts               # 主类(create/query/table/transaction/migration...)
├── constants.ts          # 配置类型 + DatabaseError + VERSION
├── connection-manager.ts # 连接池管理
├── engine/               # 存储引擎
│   ├── memory.ts         #   MemoryEngine(内存 + 快照事务)
│   ├── kvstore_engine.ts #   KVStoreEngine(disk 模式,v0.6.0 替代 IndexedDB)
│   ├── kvstore/          #   自研 KVStore(日志 + 快照 + 原子写)
│   └── aria/             #   AriaEngine(LSM-Tree + WAL + MVCC + 页面化 + 加密)
│       ├── index/        #     MemTable / SSTable / LSM / Bloom / MergeIterator
│       ├── wal/          #     WAL 分片 + checkpoint
│       ├── store/        #     OPFS / KVStore / 加密 backend + FileManager
│       ├── buffer/       #     BufferPool + LRU 驱逐
│       └── transaction/  #     MVCC
├── hybrid/               # 混合引擎(write-through)
├── migration/            # 旧 IndexedDB 数据迁移工具(一次性)
├── query/                # AST + Builder + Compiler + Executor
├── sql/                  # Lexer + Parser(递归下降,72 关键字)
├── table/                # 表管理 + Schema 校验
├── transaction/          # 事务管理(自动回滚)
├── plugin/               # 插件系统(14 hooks)
└── integrations/         # React / Vue hooks

License

MIT © MetonaTeam

Dependencies

Development Dependencies

ID Version
@babel/core ^7.22.0
@babel/plugin-transform-modules-commonjs ^7.22.0
@babel/preset-env ^7.22.0
@babel/preset-typescript ^7.22.0
@playwright/test ^1.62.1
@rollup/plugin-commonjs ^25.0.0
@rollup/plugin-node-resolve ^15.0.0
@rollup/plugin-terser ^0.4.0
@rollup/plugin-typescript ^11.1.0
@types/jest ^29.5.0
@typescript-eslint/eslint-plugin ^8.0.0
@typescript-eslint/parser ^8.0.0
babel-jest ^29.5.0
eslint ^8.40.0
fake-indexeddb ^6.2.5
jest ^29.5.0
jest-environment-jsdom ^29.5.0
playwright ^1.62.1
prettier ^2.8.0
rollup ^3.20.0
rollup-plugin-dts ^5.3.0
rollup-plugin-livereload ^2.0.5
rollup-plugin-serve ^2.0.1
tslib ^2.6.0
typescript ^5.0.0

Peer Dependencies

ID Version
react >=17
vue >=3

Keywords

database sql frontend indexeddb opfs memory browser typescript
Details
npm
2026-09-15 17:31:07 +08:00
1
thzxx
MIT
latest
1.9 MiB
Assets (1)
Versions (23) View all
0.8.0 2026-09-15
0.7.4 2026-08-15
0.7.3 2026-08-14
0.7.2 2026-08-13
0.7.1 2026-08-13