📦 安装

MetonaSqlark 支持多种引入方式,覆盖 npm、CDN、ESM、CJS 所有常见场景。

npm 安装(推荐)

# 配置 Gitea Registry(一次性)
npm config set @metona-team:registry https://git.metona.cn/api/packages/MetonaTeam/npm/

# 安装
npm install @metona-team/metona-sqlark

CDN / UMD

<!-- UMD 格式,暴露 window.MetonaSqlark 和 window.MeSqlark -->
<script src="https://git.metona.cn/MetonaTeam/MetonaSqlark/raw/branch/master/dist/metona-sqlark.min.js"></script>
<script>
  const db = await window.MetonaSqlark.create({...});
  // 或 window.MeSqlark.create(...)  — 完全等价
</script>

ESM

import { MetonaSqlark, MeSqlark } from '@metona-team/metona-sqlark';
// MeSqlark 是 MetonaSqlark 的别名,行为完全一致

CJS

const { MetonaSqlark } = require('@metona-team/metona-sqlark');

输出文件说明:

文件格式用途
metona-sqlark.jsUMD浏览器开发版(含 sourcemap)
metona-sqlark.min.jsUMD (minified)生产环境(实测 251,731 字节 / gzip 63,431 字节,含全部引擎与 SQL 层)
metona-sqlark.esm.jsES Module现代打包工具 / 浏览器 ESM
metona-sqlark.cjsCommonJSNode.js require()
metona-sqlark.d.tsTypeScript 声明类型提示

🏗 创建数据库

MetonaSqlark.create(config) — 工厂函数,自动创建并初始化数据库实例。

const db = await MetonaSqlark.create({
  name: 'my-app',
  mode: 'hybrid',       // 'memory' | 'disk' | 'hybrid' | 'aria' 🆕
  diskEngine: 'opfs', // 'opfs' | 'kv' | 'memory'(v0.6.0: IndexedDB 已移除)
  version: 1,
  plugins: [],            // MetonaPlugin[]
  onReady: (db) => {},    // 就绪回调
  onError: (err) => {},   // 错误回调
});

// 也可手动实例化
const db2 = new MetonaSqlark(config);
await db2.init();

// 检查状态
db.isReady();  // true

// 关闭
await db.close();

📋 定义表

使用 db.defineTable(name, columns) 定义表结构。

await db.defineTable('users', {
  id:       { type: 'string',  primaryKey: true },
  name:     { type: 'string',  required: true },
  email:    { type: 'string',  unique: true, index: true },
  age:      { type: 'number',  default: 0, min: 0, max: 150 },
  active:   { type: 'boolean', default: true },
  birthday: { type: 'date' },
  meta:     { type: 'json' },
  dept_id:  { type: 'number',  references: 'departments.id' },
});

// 表操作
await db.getTableNames();         // ['users']
await db.dropTable('users');     // 删除表
ColumnDef 属性类型说明
type'string'|'number'|'boolean'|'date'|'json'数据类型
primaryKeyboolean主键(每表至少一个)
requiredboolean是否必填
uniqueboolean唯一约束(自动建索引)
indexboolean创建哈希索引,O(1) 加速查询
defaultunknown默认值
maxLengthnumber字符串最大长度
min/maxnumber数值范围
referencesstring外键引用 'table.column'
onDelete'CASCADE'\|'SET NULL'\|'RESTRICT'删除级联 ✅ v0.4.1
onUpdate'CASCADE'\|'SET NULL'\|'RESTRICT'更新级联(更新主键时触发)✅ v0.4.2

🔍 SQL 查询

db.query(sql) — 执行标准 SQL 字符串,返回查询结果。

db.queryStream(sql, onRow) — 流式查询(v0.4.0):逐行回调不物化结果集,大表友好。支持简单 SELECT(WHERE/LIMIT/OFFSET/列投影);JOIN/GROUP BY/UNION/聚合/ORDER BY 自动回退物化。

// 流式查询 — 大表逐行处理
let count = 0;
await db.queryStream("SELECT * FROM logs WHERE level = 'error'", (row) => {
  count++;
  processRow(row);
});

// 派生表 / COUNT(DISTINCT) / NULLS 排序(v0.4.0)
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");

完整 SQL 语法支持

// SELECT — 核心查询
const rows = await db.query(`SELECT * FROM users
  WHERE age > 18
  ORDER BY name ASC
  LIMIT 10 OFFSET 0`);

// INSERT — 插入数据
await db.query("INSERT INTO users (id, name, age) VALUES ('1', 'Alice', 30)");
await db.query("INSERT INTO users VALUES ('2', 'Bob', 25)");
// 支持多行插入
await db.query("INSERT INTO users VALUES ('3', 'C'), ('4', 'D')");

// UPDATE — 更新数据
await db.query("UPDATE users SET age = 31, active = true WHERE id = '1'");

// DELETE — 删除数据
await db.query("DELETE FROM users WHERE id = '1'");

// DDL — 表结构操作
await db.query(`CREATE TABLE products (
  id STRING PRIMARY KEY,
  name STRING NOT NULL,
  price NUMBER DEFAULT 0
)`);
await db.query('DROP TABLE products');

-- 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.7.0)

位置参数 ? 在词法层安全绑定(仅替换字符串字面量与注释之外的占位符),值按 SQL 字面量编码 —— 杜绝 SQL 注入,无需手动转义。

// 位置参数绑定 — 字符串 '' 转义自动完成
await db.query("INSERT INTO users VALUES (?, ?, ?, ?)", ['2', "O'Brien", 25, 'ob@demo.com']);
const row = await db.query('SELECT * FROM users WHERE name = ?', ["O'Brien"]);

// 参数数量不匹配 → PARAM_ERROR;对象/数组参数显式拒绝;NaN/Infinity → NULL

SQL 扩展 (v0.3.0+)

// 多语句 — 分号分隔一次执行(返回最后一条结果)
await db.query(`CREATE TABLE t (id STRING PRIMARY KEY);
  INSERT INTO t VALUES ('1'); INSERT INTO t VALUES ('2')`);

// 事务语句 — BEGIN / COMMIT / ROLLBACK
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 merged = await db.query(
  `SELECT name FROM users WHERE city = 'Beijing'
   UNION SELECT name FROM users WHERE age < 30`);

// EXISTS / NOT EXISTS — 关联子查询
const hasOrders = await db.query(`SELECT * FROM users u
  WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id)`);

// CASE WHEN — SELECT 列 / WHERE / 聚合 (v0.3.1 / v0.3.2)
const labeled = await db.query(`SELECT name,
  CASE WHEN age >= 18 THEN 'adult' ELSE 'minor' END AS status FROM users`);
const adults = await db.query(`SELECT SUM(CASE WHEN age >= 18 THEN 1 ELSE 0 END) FROM users`);

// CREATE / DROP INDEX — 动态二级索引
await db.query('CREATE INDEX idx_users_city ON users (city)');
await db.query('DROP INDEX idx_users_city ON users (city)');

// v0.7.4: 建表 UNIQUE 约束不可经 DROP INDEX 解除(NOT_SUPPORTED,重建表解除);
//   仅 CREATE UNIQUE INDEX 添加的唯一约束可随 DROP INDEX 一并删除(对齐 SQLite)
await db.query('CREATE UNIQUE INDEX idx_u ON users (email)');
await db.query('DROP INDEX idx_u ON users (email)'); // unique 随之解除

维护语句 (v0.5.1 SQL 入口)

// EXPLAIN — 查询计划(真实索引命中信息 v0.7.0)
await db.query("EXPLAIN SELECT * FROM users WHERE email = 'a@x.com'");
// { type, table, usingIndex: 'index:email', estimatedRows, actualTimeMs, ... }

// ANALYZE / REINDEX / VACUUM(Aria 引擎;v0.7.3 统计含二级索引)
await db.query('ANALYZE users');
await db.query('REINDEX users');
await db.query('VACUUM');

// SAVEPOINT — 嵌套事务保存点
await db.query('BEGIN');
await db.query('SAVEPOINT sp1');
await db.query('ROLLBACK TO SAVEPOINT sp1');
await db.query('RELEASE SAVEPOINT sp1');
// 不支持的引擎执行维护语句抛 NOT_SUPPORTED

条件表达式

// 比较运算符
`WHERE age > 18 AND name LIKE 'A%'`
`WHERE salary >= 5000 OR dept = 'Engineering'`

// IN / NOT IN
`WHERE dept IN ('Engineering', 'Sales')`

// NULL 检查
`WHERE email IS NULL`
`WHERE email IS NOT NULL`

// NOT 取反
`WHERE NOT (age < 18 OR age > 65)`

// 嵌套条件
`WHERE (age > 18 AND active = true) OR role = 'admin'`

⛓ Query Builder

链式 API,TypeScript 友好,享受 IDE 自动补全。

SELECT

const users = db.table('users');

// 全量查询
await users.select().execute();

// 指定列 + 条件 + 排序 + 分页
const result = await users
  .select(['name', 'age', 'email'])
  .where({
    age: { $gt: 18 },
    name: { $like: 'A%' },
  })
  .orderBy('age', 'desc')
  .limit(10)
  .offset(0)
  .execute();

INSERT

// 单行插入 — 返回主键值
const pk = await users.insert({ id: '1', name: 'Alice', age: 30 });

// 批量插入 — 返回主键值数组
const pks = await users.insertMany([
  { id: '2', name: 'Bob', age: 25 },
  { id: '3', name: 'Charlie', age: 35 },
]);

UPDATE / DELETE

// 更新 — 返回影响行数
const updated = await users
  .update({ age: 31, active: false })
  .where({ id: '1' })
  .execute();  // 1

// 删除 — 返回影响行数
const deleted = await users
  .delete()
  .where({ id: '1' })
  .execute();  // 1

// 全表删除
await users.delete().execute();

🔗 JOIN 查询

支持 INNER / LEFT / RIGHT / CROSS JOIN,SQL 和 Query Builder 两种方式。

// SQL 方式 — 支持表别名
await db.query(`SELECT u.name, d.name AS dept_name
  FROM users u
  INNER JOIN departments d ON u.dept_id = d.id
  LEFT JOIN orders o ON u.id = o.user_id
  WHERE d.name = 'Engineering'
  ORDER BY u.name`);

// Query Builder 方式
await db.table('users').select()
  .as('u')
  .innerJoin('departments', { 'u.dept_id': { $col: 'd.id' } }, 'd')
  .leftJoin('orders', { 'u.id': { $col: 'o.user_id' } }, 'o')
  .execute();

// CROSS JOIN — 笛卡尔积
.crossJoin('metadata', 'm')

📊 GROUP BY & 聚合

五大聚合函数 + GROUP BY + HAVING + DISTINCT,完整的数据分析能力。

// GROUP BY + 聚合
await db.query(`SELECT dept, COUNT(*) as cnt, SUM(salary) as total,
       AVG(salary) as avg_sal, MIN(age), MAX(age)
  FROM employees
  GROUP BY dept
  HAVING COUNT(*) > 1
  ORDER BY total DESC
  LIMIT 5`);

// DISTINCT 去重
await db.query('SELECT DISTINCT dept FROM employees');

// 聚合函数别名
await db.query('SELECT COUNT(*) AS total_users, AVG(age) AS avg_age FROM users');

🎯 WHERE 操作符

Query Builder 使用 $ 前缀操作符,支持逻辑组合。

操作符含义示例
直接值 / $eq等于{ age: 30 } / { age: { $eq: 30 } }
$ne不等于{ age: { $ne: 30 } }
$gt / $gte大于 / 大于等于{ age: { $gt: 18 } }
$lt / $lte小于 / 小于等于{ age: { $lt: 65 } }
$in / $nin在列表中 / 不在{ dept: { $in: ['IT','HR'] } }
$like模糊匹配{ name: { $like: 'A%' } }
$and逻辑与{ $and: [{...}, {...}] }
$or逻辑或{ $or: [{...}, {...}] }
$not逻辑非{ age: { $not: { $gt: 18 } } }
$col列引用(JOIN ON){ 'a.id': { $col: 'b.a_id' } }
// 复合条件
.where({
  $or: [
    { age: { $lt: 18 } },
    { age: { $gt: 65 } },
  ],
  active: true,
  name: { $like: 'A%', $ne: 'Admin' },
})

🔒 事务 & 回滚

v0.1.13 起支持真正的自动回滚:事务内任何一步失败,所有变更自动撤销。

await db.transaction(async (trx) => {
  // trx.table() 获取事务内表操作对象
  await trx.table('users').insert({ id: '3', name: 'Charlie' });
  await trx.table('orders').insert({ id: 'o1', userId: '3', amount: 99 });
  // ✅ 全部成功 → 自动 commit
  // ❌ 任何一步失败 → 自动 rollback,数据恢复原状
  // Memory 引擎:快照回滚 | KVStore 引擎:原子日志 flush
});

🔍 子查询

v0.1.13 新增子查询支持,可在 WHERE 条件中嵌套 SELECT。

// IN 子查询 — 查询有高额订单的用户
await db.query(`SELECT * FROM users
  WHERE id IN (SELECT user_id FROM orders WHERE amount > 100)`);

// 标量子查询 — 查询年龄等于平均年龄的用户
await db.query(`SELECT * FROM users
  WHERE age = (SELECT AVG(age) FROM users)`);

// NOT IN 子查询
await db.query(`SELECT * FROM users
  WHERE id NOT IN (SELECT user_id FROM orders)`);

// v0.7.4: UPDATE / DELETE 的 WHERE 同样支持子查询(四引擎)
//   此前未解析的 $subquery 在引擎层恒 false → 静默影响 0 行
await db.query(`UPDATE users SET role = 'vip'
  WHERE id IN (SELECT user_id FROM orders WHERE amount > 100)`);
await db.query(`DELETE FROM logs
  WHERE ts < (SELECT MIN(ts) FROM keep_logs)`);

// 写语句中关联引用($col / EXISTS 引用外层行)显式 NOT_SUPPORTED(不静默)

🏗 ALTER TABLE (🆕 v0.2.5)

v0.2.5 新增 ALTER TABLE 语法,支持动态添加和删除列。

ADD COLUMN

-- 添加新列
await db.query('ALTER TABLE users ADD COLUMN phone STRING');

-- 带约束的添加
await db.query('ALTER TABLE users ADD COLUMN email STRING UNIQUE');

-- 带可选 COLUMN 关键字
await db.query('ALTER TABLE users ADD COLUMN age NUMBER DEFAULT 0');

DROP COLUMN

-- 删除列
await db.query('ALTER TABLE users DROP COLUMN phone');

-- 带可选 COLUMN 关键字
await db.query('ALTER TABLE users DROP COLUMN email');
语法说明
ALTER TABLE name ADD COLUMN col type [constraints]添加列(COLUMN 可选)
ALTER TABLE name DROP COLUMN col删除列(COLUMN 可选)

🗑 TRUNCATE TABLE (🆕 v0.2.5)

v0.2.5 新增 TRUNCATE TABLE 语法,快速清空表数据(保留表结构)。

-- 快速清空表数据
await db.query('TRUNCATE TABLE old_logs');

-- 等价于 DELETE FROM old_logs,但语义更清晰
语法说明
TRUNCATE TABLE name清空表数据,保留表结构

🔗 外键级联

v0.1.13 支持外键级联操作,定义表时可指定 ON DELETE / ON UPDATE 行为。

// 定义时指定外键 + 级联策略
await db.defineTable('orders', {
  id: { type: 'string', primaryKey: true },
  user_id: {
    type: 'string',
    references: 'users.id',
    onDelete: 'CASCADE',  // 删除用户时级联删除订单
    onUpdate: 'RESTRICT', // 禁止更新被引用的用户 ID
  },
  amount: { type: 'number' },
});

// SQL DDL 同样支持
await db.query(`CREATE TABLE orders (
  id STRING PRIMARY KEY,
  user_id STRING REFERENCES users(id) ON DELETE CASCADE ON UPDATE RESTRICT,
  amount NUMBER
)`);

// 删除用户 → 其所有订单自动删除
await db.query("DELETE FROM users WHERE id = '1'");
级联选项行为
CASCADE级联删除/更新子表中的匹配行
SET NULL将子表中的外键列设为 NULL
RESTRICT禁止操作(默认行为)

🏊 连接池

v0.1.13 新增连接池管理器,避免重复创建同名数据库实例。

// connect() — 获取或创建实例(单例复用)
const db1 = await MetonaSqlark.connect({ name: 'my-app', mode: 'hybrid' });
const db2 = await MetonaSqlark.connect({ name: 'my-app' });
// db1 === db2 — 复用已有实例,避免重复打开底层存储

// disconnect() — 释放连接(引用计数 -1)
await db2.disconnect(); // 引用计数: 2 → 1
await db1.disconnect(); // 引用计数: 1 → 0,自动 close()

// disconnectAll() — 强制关闭所有连接
await MetonaSqlark.disconnectAll();

// getActiveConnections() — 查看活跃连接
MetonaSqlark.getActiveConnections(); // ['my-app']

🔄 数据迁移

按版本号管理表结构变更。

// 注册迁移
db.addMigration(2, async (db) => {
  await db.defineTable('products', {
    id: { type: 'string', primaryKey: true },
    name: { type: 'string', required: true },
  });
});

db.addMigration(3, async (db) => {
  // 添加新列、数据迁移等
  await db.query("UPDATE users SET role = 'user' WHERE role IS NULL");
});

// 执行迁移到目标版本
await db.migrateTo(3);  // 依次执行 v2, v3 的迁移函数

v0.4.2: 迁移版本持久化到库内 — 重启后从持久化版本继续执行,已执行迁移不重跑(此前 version 每次从 config 重置,可能重跑不幂等的迁移)。

🛡 崩溃恢复自愈(v0.4.2)

异常退出(强杀/断电)后无需删库重建:打开数据库时自动跳过残缺 SSTable,应用层可调用自愈 API 恢复一致性。

// 自愈 — 校验并清理损坏 SSTable / 重建二级索引 / 截断 WAL
await db.repair();

// 清空全部数据与表结构(保留库本身,实例可继续使用)
await db.clearAll();

// 引擎级元数据(迁移版本等)
// 引擎接口 IStorageEngine 可选扩展:repair() / clearAll() / getMeta() / setMeta()

📤 导入导出

// 导出单表 — 返回 JSON 数组
const userData = await db.exportTable('users');
// [{ id: '1', name: 'Alice', ... }, ...]

// 导出全库 — 返回 { tableName: rows[] }
const allData = await db.exportAll();
// { users: [...], orders: [...], products: [...] }

// 导入数据 — 返回主键列表
const pks = await db.importTable('users', userData);

🧩 插件 & 钩子

14 种生命周期钩子,支持插件机制。

钩子名称触发时机参数
beforeCreateTable创建表前schema
afterCreateTable创建表后schema
beforeDropTable删除表前tableName
afterDropTable删除表后tableName
beforeInsert插入前rows[]
afterInsert插入后rows[], pks
beforeUpdate更新前query, updates
afterUpdate更新后query, updates, count
beforeDelete删除前query
afterDelete删除后query, count
beforeQuerySQL 查询前sql
afterQuerySQL 查询后sql, result
beforeTransaction事务开始前-
afterTransaction事务完成后-
// 注册钩子
db.on('beforeInsert', async (row) => {
  console.log('即将插入:', row);
  // 可在此校验、转换数据
});

db.on('afterQuery', async (sql, result) => {
  console.log(`查询完成 [${sql}] → ${(result as any[]).length} 行`);
});

// 注册自定义插件
const loggerPlugin = {
  name: 'logger',
  version: '1.0.0',
  description: '记录所有数据库操作',
  priority: 100,
  install(db) {
    db.on('beforeQuery', (sql) => console.log('SQL:', sql));
  },
  destroy() { /* 清理 */ },
};

// 在 create 配置中注册
const db = await MetonaSqlark.create({
  name: 'my-app',
  plugins: [loggerPlugin],
});

📡 发布订阅

// 订阅表变更
const unsubscribe = db.subscribe('users', (event) => {
  // event.type: 'insert' | 'update' | 'delete'
  // event.row:  被操作的行数据
  console.log(`users 表 ${event.type}`, event.row);
});

// 手动触发变更
db.emit('users', { type: 'insert', row: { id: '1', name: 'Alice' } });

// 取消订阅
unsubscribe();

多标签页同步 (v0.3.2)

// 启用 multiTabSync 后,其他标签页的写操作会广播到此标签页
const db = await MetonaSqlark.create({
  name: 'my-app',
  mode: 'hybrid',
  multiTabSync: true,
});

// 订阅其他标签页的变更(event.type === 'external')
db.subscribe('users', (event) => {
  if (event.type === 'external') {
    // Hybrid 模式已自动从磁盘重载,此处可刷新 UI
    refreshList();
  }
});

// 手动广播(Table API 已自动广播;自定义写入可调用)
db.broadcastChange('users');

⚛️ React 集成

import { useQuery, useTable, useDatabase } from '@metona-team/metona-sqlark/react';
import { db } from './db';

function UserList() {
  // 执行 SQL 查询,自动响应 db 变化
  const { data, loading, error, refresh } = useQuery(
    db,
    'SELECT * FROM users WHERE age > 18',
    [/* deps */]
  );

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      {data.map(u => <div key={u.id}>{u.name} ({u.age})</div>)}
      <button onClick={refresh}>刷新</button>
    </div>
  );
}

// 便捷 hook — 查询整张表
const { data, loading, refresh } = useTable(db, 'users');

// 管理数据库生命周期
function App() {
  const { db, ready, error } = useDatabase({
    name: 'my-app',
    mode: 'hybrid',
  });
  if (!ready) return <div>Initializing...</div>;
  return <UserList />;
}

🟢 Vue 集成

import { useSqlarkQuery, useSqlarkTable, useSqlarkDatabase }
  from '@metona-team/metona-sqlark/vue';
import { db } from './db';

// useSqlarkQuery — 执行 SQL 查询
const { data, loading, error, refresh } = useSqlarkQuery(
  db,
  'SELECT * FROM users WHERE age > 18'
);

// useSqlarkTable — 获取全表数据
const { data, loading, refresh } = useSqlarkTable(db, 'users');

// useSqlarkDatabase — 管理数据库生命周期
const { db, ready, error } = useSqlarkDatabase({
  name: 'my-app',
  mode: 'hybrid',
});

🔷 TypeScript 泛型

interface User {
  id: string;
  name: string;
  age: number;
  email?: string;
}

// 泛型表操作 — 类型安全的 insert/select
const users = db.table<User>('users');

// ✅ 类型检查通过
await users.insert({ id: '1', name: 'Alice', age: 30 });

// ❌ TypeScript 报错:缺少 name
// await users.insert({ id: '2', age: 25 });

⚙️ 完整配置项

属性类型默认值说明
namestring'metona-sqlark'数据库名称(必填)
mode'memory'|'disk'|'hybrid'|'aria''hybrid'存储模式 🆕 aria
diskEngine'opfs'|'memory'|'kv''opfs'磁盘引擎类型('kv' = 自研 KVStore 后端;v0.6.0: IndexedDB 已移除)
versionnumber1数据库版本号
pluginsMetonaPlugin[][]初始插件列表
onReady(db) => void-初始化完成回调
onError(err) => void-错误回调(v0.2.5 接入执行路径)
maxRowsPerQuerynumber0查询结果行数上限(0=不限制)✅ v0.2.5 生效
multiTabSyncbooleanfalse多标签页同步:BroadcastChannel 广播表变更,其他标签页自动刷新 🆕 v0.3.2
ariaAriaEngineConfig-AriaEngine 专属配置透传:walSyncMode / checkpointInterval / encryption / pageStorage / compression 等 🆕 v0.5.0

💾 存储引擎

引擎模式持久化索引事务适用场景
MemoryEnginememory哈希快照回滚临时数据、缓存、测试
KVStoreEngine 🆕disk✅ KVStore(OPFS)哈希原子日志 flush标准持久化(v0.6.0 替代 IndexedDB)
HybridEnginehybrid✅ Write-Through哈希双引擎代理生产推荐,读写均走内存
AriaEnginearia✅ WAL + SSTableLSM-Tree版本链 undo + 事务串行自研引擎:大表、高并发、需崩溃恢复

v0.6.0: IndexedDB 已完全移除 — disk 模式改用自研 KVStore 引擎(多 key 原子写 + 快照/日志崩溃恢复),旧库可经 migrateFromIndexedDB() 一键迁移。

🌲 AriaEngine 自研存储引擎

v0.2.0 新增 — AriaEngine 是专为 MetonaSqlark 设计的页面式存储引擎,对标 SQLite 设计理念。
v0.2.4 生产级 — 二级索引 · MVCC · BloomFilter · WAL CRC全同步 · AES-GCM加密 · Savepoint · EXPLAIN · ANALYZE · REINDEX · VACUUM · BufferPool · 零死代码。
v0.3.2 表达式与并发 — WAL full模式真正同步 · MVCC接入读写路径 · SSTableReader二分查找统一 · crypto实例化 · IndexedDB索引利用 · compactLevel public接口 · WAL大小阈值自动checkpoint · SQL注入防护 · ALTER TABLE · TRUNCATE TABLE · 多标签页同步 · IDB schema持久化。
v0.4.2 生产就绪与崩溃自愈 — 残缺 SSTable 打开自动跳过(不删库)· WAL 记录与计数原子写入 + 按 key 扫描恢复 · 事务进行中 checkpoint 不截断 WAL · 二级索引跨重启自动恢复 · ALTER TABLE / 事务内 DDL 全引擎持久化 · ON UPDATE 外键级联(含更新主键)· OPFS schema 持久化(空表/索引完整保留)· `repair()` / `clearAll()` 统一自愈接口 · 迁移版本持久化到库内。
v0.4.3 关闭时序与后台任务加固 — 后台 flush/compaction 不再使用 setTimeout 延迟(close 排空全部任务后才关闭存储,杜绝"backend 关闭后写存储/重开污染")· 后台失败在 `flush()`/`close()` 显式报告(`ARIA_BACKGROUND_ERROR`,不静默吞错)· 预加载等待链稳定(修复 compaction 竞态跳块丢数据)· 事务提交先落 WAL 再合并快照(崩溃一致)· OPFS 写操作串行队列 + close 等待。
v0.4.4 SSTable 编码修复 — 大段中文内容(如 300KB 笔记)写入 AriaEngine 不再崩溃:块大小估算改 UTF-8 字节精确计算(修复中文 3 字节 vs 1 码元导致的缓冲区低估越界)· 长度字段 u16 → u32(修复 >64KB value 截断)· 大 value 独立成块 · 格式 v2("SSTC")与 v1("SSTB")双格式兼容(旧库数据不丢)· 中文主键 / 大内容索引列同步支持。
v0.5.0 存储后端生产级硬化 — 真实 CRC-32 完整性校验(SSTable 整文件 + WAL 记录,旧文件兼容)· 全库 AES-256-GCM 透明加密(EncryptedBackend + PBKDF2 密钥派生 + 密码验证)· WAL 分片文件重构(真追加 + 空洞检测 + 旧格式迁移)· SSTable 4KB 页面化物理存储(BufferPool/FileManager 真实接入,meta 存 pageIds 兼容旧数据)· OPFS 后端 v2(append 真追加 / 写队列健壮性 / 残留清理)· Web Locks 多标签页独占锁(ARIA_LOCKED)· LZ4 v2 原始大小头 · Playwright 真实 Chromium e2e(7 用例)· DatabaseConfig.aria 配置透传。
v0.5.1 深度审查修复 — 14 个生命周期钩子全部真实接线(此前 6 个 CRUD 钩子从未触发)· EXPLAIN / ANALYZE / REINDEX / VACUUM / SAVEPOINT SQL 入口补齐(此前仅有引擎方法无法触发)· db.backup() 公共方法 · 删除全部死代码(utils.ts 整文件 / MVCC 读侧 / estimateQueryCost 未接线优化器 / 40+ 统计辅助方法)。
v0.6.0 完全移除 IndexedDB — 自研 KVStore 事务存储引擎(多 key 原子写 = 单日志记录原子追加 · 快照 checkpoint + 两阶段崩溃恢复 · CRC-32 自愈)· disk 模式切换 KVStoreEngine(替代 IndexedDBEngine + OPFSEngine)· 事务内 DDL / 外键级联 / 二级索引完整持久化 · migrateFromIndexedDB() 旧库一键迁移 · 10 万 key 压力验证 · e2e 崩溃注入 + KVStoreEngine 真实环境(12 用例)。
v0.6.1 生产可用性深度审查 — MemoryEngine 级联环(A→B→A)无限递归修复(visited 保护,与 Aria 对齐)· KVStore 快照损坏水位 bug 修复(metaSeq 误跳日志)· 多表事务 / 级联写入合并单条日志记录真原子(崩溃无部分提交)· 未 open 防护统一 · AriaEngine 可选自研 KVStore 后端(storageBackend: 'kv':KVStore APPEND 日志支持 WAL 追加、writeMany 真原子、不再依赖浏览器 OPFS)· 二级索引范围扫描尾块漏读 P0 修复 · 批量插入性能悬崖修复(10 万行 kv 后端 353s → 12.5s)· 页面 id 崩溃回退 / flush 并发写 meta 两个崩溃恢复 P0 修复 · 10 万级 kv/opfs 双后端回归。
v0.6.2 深度审计修复(数据正确性专项) — KVStoreEngine 数值主键 update 丢行 P0 · update 主键变更撞已有主键静默覆盖 P0 · Aria 二级索引范围查询边界算法错误(小数/字符串静默丢数据)P1 · 索引列 IS NULL 恒空(Aria)P1 · Aria unique 约束未强制(批内互查 + 索引前缀扫描)· 非主键 update 索引旧值残留 · EXPLAIN 写语句产生真实副作用修复。
v0.6.3 原子性 / 一致性 / 资源治理 — KVStore 混合写单记录真原子(writeBatch,主键变更/级联全有或全无)· WAL full 模式写入失败抛错(不再吞错)· Memory SET NULL 级联索引残留 · delete 级联两阶段 RESTRICT 预检(无事务部分级联修复)· BufferPool 驱逐清理 pages Map(内存预算真实生效)· MVCC 已提交版本清理 · LSM.flush 重复入链 · rollbackToSavepoint 重建二级索引。
v0.7.0 参数化查询 / 事务性能 / 语义硬化参数化查询db.query(sql, params) 位置参数 `?`,词法层绑定 + SQL 字面量安全编码,注入防护从根上成立)· EXPLAIN 真实索引命中信息(pk / index:col)· KVStoreEngine 事务行级增量 flush(大表事务改 1 行 commit 仅 1 条记录)· 移除每次 commit 强制全量 checkpoint · 复合主键显式拒绝(SCHEMA_ERROR,v0.8 路线图)。
v0.7.1 API 修复与防御统一MetonaSqlark.create 静态工厂(README 示例在 ESM/Node 下可用)· close 未初始化崩溃防御 · AriaEngine 未 open 防护统一 · 事务回滚失败不掩盖原始错误 · __proto__ 列名原型污染防护 · lint 清零。
v0.7.2 语句级原子性 / 事务 DDL / 约束硬化 — UPDATE 语句级两阶段原子(多行匹配第 N 行失败整句不执行 + 批内唯一互查)· 事务内 DDL 显式拒绝(四引擎对齐)· SET NULL 级联绕过 required 约束整体拒绝 · 参数绑定注释感知(注释中 `?`/引号不参与绑定)· 未闭合字符串显式 PARSE_ERROR · 未知 where 操作符抛 QUERY_ERROR(此前静默全匹配)· update undefined 语义化(保留旧值)· Hybrid write-through 失败补偿(磁盘失败自动重载内存对齐)。
v0.7.3 深度审计第六阶段:INSERT 原子 / 索引一致性 / 边界窗口 — INSERT 语句级两阶段原子(三引擎 + Aria PK 批内重复,此前部分提交)· 索引列 IS NULL 恒空修复(Memory/KVStore/Hybrid,对齐 Aria)· delete RESTRICT 预检不再破坏索引 · queryStream 子查询静默空结果修复($subquery/$exists/$col 回退物化)· ALTER DROP 索引列残留清理 · CREATE UNIQUE INDEX 存量重复数据校验(失败原子回滚)· SELECT *, col AS alias 解析与投影 · WAL BEGIN/ROLLBACK 写失败窗口修复(事务不泄漏/数据不复活)· aria $in 批级预加载(消除逐值 drainChain 性能悬崖)· 多条件 AND 等值下推(索引真正生效,EXPLAIN 同步)· ANALYZE 统计含二级索引 · React/Vue hooks 生命周期修复(config 变更重建 / 卸载关闭)· 迁移无主键旧库兜底 · 1256 测试 75 套件 · 90.0% 行覆盖率。
v0.7.4 深度审计第七阶段:写语句子查询 / 约束硬化 / 真惰性流式 — UPDATE/DELETE WHERE 子查询正确执行(四引擎,此前静默 0 行;关联引用显式 NOT_SUPPORTED;EXPLAIN 估算同步修复)· 主键 NULL/undefined 强制拒绝(SQL 语义 PK 隐含 NOT NULL,此前静默生成 "null"/"undefined" 主键)· DROP INDEX 保留建表 UNIQUE 约束(对齐 SQLite:需重建表解除;仅 CREATE UNIQUE INDEX 添加的可随索引删除)· GROUP BY / DISTINCT / UNION 键类型安全编码(null 与 'null' 字符串不再合并)· UPDATE 未知列显式 COLUMN_NOT_FOUND(此前脏列写入存储行)· queryStream 多语句显式 PARSE_ERROR(此前静默忽略后续语句)· KVStore 后台错误跨 reopen 清理 + Hybrid beginTransaction 失败补偿回滚 · Aria findStream 真惰性(MergeIterator 迭代器化 + SSTable/MemTable 生成器扫描,limit 提前终止,大表流式内存 O(1)——此前内部 drain 全量物化)· REINDEX 单次全表扫描重建全部索引列(此前每列一次全扫描)· RB-Tree 删除双黑修复边界 + LSM 死代码清理 · 1304 测试 76 套件 · 90.1% 行覆盖率。

v0.8.0 根治性迭代:统一语义 / 消灭复发结构 / 验证基础设施 — 三份行校验实现收敛为唯一 choke point(未知列/NaN 显式拒绝)· 唯一值比较与编码原语 · SQL 与 TABLE API 单管线(QueryBuilder 只产 AST)· CASE 表达式改 token 流递归下降 · 输出列序号与分隔标识符 · `__aria_manifest_<generation>` 单一提交点(页面水位 + SSTable 元数据 + 表结构 + WAL 水位 + 冻结表意图一次原子提交;头部/载荷双 CRC、先写后验、保留两代;顺序固定为数据落盘 → manifest 提交 → 才允许截断 WAL / 删除旧文件)· 元数据损坏不再静默空库(`ARIA_MANIFEST_CORRUPT` / `ARIA_LEGACY_META_CORRUPT`)· WAL LSN 全库单调 + 按水位删除分片 + 空洞与记录级损坏如实上报(`ARIA_WAL_GAP` / `droppedWALRecords`)· LSM 冻结表一等状态、compaction 不再摘整层、底部层原地合并回收墓碑、按层 `compacting`、退休表 + 读者 epoch · 读路径"快照 + 结构版本乐观重试"(删除全部 prefetch 依赖)· checkpoint 不等 compaction(根治 "8~11s 悬崖")· 介质读故障与"文件不存在"分离(`ARIA_SSTABLE_READ_FAILED`,不误删元数据)· 恢复报告 `getRecoveryReport()` · 覆盖率门禁 + 变异验证(40 项)成为标准做法 ✅ v0.8.0

存储模式对比

特性MemoryDisk (KVStore)HybridAria
持久化✅ KVStore(OPFS / 内存介质)✅ 双写✅ 后端决定
事务✅ 快照回滚✅ 单日志记录原子写✅ 双引擎(磁盘优先)✅ 快照回滚(事务串行,非 MVCC 隔离)
索引HashHash(重启恢复)HashLSM 二级(重启恢复)
上限内存磁盘可用磁盘可用内存
多标签页锁✅ Web Locks
全库加密✅ AES-GCM
浏览器全部Chrome/Edge 102+ · Firefox 111+ · Safari 15.2+同上同上(后端决定)

核心特性

特性说明
LSM-Tree 索引MemTable (红黑树) + 多级 SSTable,写优化,支持范围扫描
4KB 页面化物理存储SSTable 切分为 4KB 页面(FileManager 分配 pageId + BufferPool LRU 缓存 256 页 ≈ 1MB),save 即落盘,`SSTableMeta.pageIds` 持久化(旧整 value 数据兼容)✅ v0.5.0
Buffer PoolLRU 页面缓存,可控内存占用(默认 256 页 ≈ 1MB),脏页写回 ✅ v0.5.0 真实接入
WAL 日志分片文件(`__wal_%06d.bin`,4MB 阈值切换 + 真追加)· 标准 CRC32 记录校验(记录级 CRC 失败 → droppedWALRecords + 数据丢失标记,不再只打日志)· full/batch/none 三种同步模式(full 真正同步)· 空洞如实上报(内部空洞 → ARIA_WAL_GAP 且拒绝静默继续) · 16MB 阈值自动 checkpoint ✅ v0.8.0 补齐
MVCC 版本链版本链仅作事务内 undo(提交即清理,自动 GC),不提供快照隔离;引擎事务读取走 txnSnapshot,同一实例同时只允许一个事务(并发 begin 抛 TX_ACTIVE)✅ v0.8.0 如实描述
全库 AES-GCM 加密EncryptedBackend 透明加解密(WAL/SSTable/Schema/元数据全密文)· PBKDF2 密钥派生 + salt 持久化 + 密码验证 · 明文/加密库开关一致性检测 ✅ v0.5.0
多标签页独占锁Web Locks API:第二个标签页打开同一库抛 `ARIA_LOCKED`,open 失败自动释放 ✅ v0.5.0
Bloom Filter快速判定 key 不存在,减少无效磁盘 I/O,SSTableReader 二分查找统一 ✅ v0.2.5
LZ4 压缩(v2)SSTable 级压缩,压缩流自带原始大小头(高压缩率不截断)✅ v0.5.0

使用方式

// 激活 AriaEngine(OPFS 后端默认页面化存储)
	const db = await MetonaSqlark.create({
	  name: 'my-app',
	  mode: 'aria',             // 🆕 AriaEngine 模式
	  diskEngine: 'opfs',        // 底层存储后端(opfs | kv | memory)
	  aria: {                     // AriaEngine 专属配置透传 🆕 v0.5.0
	    walSyncMode: 'full',    // WAL 同步模式
	    encryption: { password: 'my-password' }, // 全库 AES-GCM 加密 🆕 v0.5.0
	  },
	});

	// 或直接实例化 — 支持细粒度配置
	import { AriaEngine } from '@metona-team/metona-sqlark';
	const engine = new AriaEngine({
	  bufferPoolPages: 256,       // Buffer Pool 页面数量
	  memtableSizeThreshold: 4194304, // MemTable 刷盘阈值 4MB
	  walSyncMode: 'full',        // 'full' | 'batch' | 'none'(默认 full)
	  storageBackend: 'opfs', // 存储后端(opfs | kv | memory)
	  encryption: { password: 'my-password' }, // 全库加密
	});

AriaEngine 配置项

属性类型默认值说明
pageSizenumber4096页面大小(字节)
bufferPoolPagesnumber256Buffer Pool 页面数量
memtableSizeThresholdnumber4194304MemTable 刷盘阈值(字节)
levelSizeMultipliernumber10LSM 层级容量倍数
bloomFilterBitsPerKeynumber10Bloom Filter 每 key 位数
walEnabledbooleantrue是否启用 WAL
walSyncMode'full'|'batch'|'none''full'WAL 同步策略(full 真正同步 ✅ v0.2.4 起默认 full)
checkpointIntervalnumber1000Checkpoint 触发间隔(操作数)
walSizeThresholdnumber16777216WAL 大小阈值(字节),超阈值触发 checkpoint ✅ v0.2.5
compressionbooleanfalse是否启用页面压缩
storageBackend'opfs'|'memory'|'kv''opfs'存储后端类型('kv' = 自研 KVStore 后端 🆕 v0.6.1)
maxMemoryMBnumber64内存预算(MB):主 LSM 估算内存超限时触发 flush + MVCC GC
encryption{ password: string }-全库 AES-256-GCM 透明加密(PBKDF2 派生 + salt 持久化 + 密码验证)🆕 v0.5.0
pageStoragebooleanopfs/kv 自动启用SSTable 4KB 页面化物理存储(BufferPool 缓存)🆕 v0.5.0

⚠️ 错误处理

所有错误抛出 DatabaseError 实例。

try {
  await db.query('SELECT * FROM nonexistent');
} catch (err) {
  if (err instanceof DatabaseError) {
    console.log(err.message);  // 'Table "nonexistent" does not exist'
    console.log(err.code);    // 'TABLE_NOT_FOUND'
    console.log(err.details); // 附加信息
  }
}
错误码触发场景
TABLE_NOT_FOUND表不存在
TABLE_EXISTS表已存在
DUPLICATE_KEY主键重复
UNIQUE_VIOLATION唯一约束冲突
FOREIGN_KEY_VIOLATION外键约束冲突:删除/更新被引用行时存在依赖行(含 RESTRICT / CASCADE 语义)
VALIDATION_ERROR数据校验失败
TYPE_ERROR字段类型错误
SCHEMA_ERROR表结构定义错误
DB_NOT_READY数据库未初始化
PARSE_ERRORSQL 语法错误
TRANSACTION_ERROR事务执行失败
COMPILE_ERROR编译 AST 到查询计划失败
CONFIG_ERROR配置错误
ARIA_LOCKED数据库已被其他标签页打开(Web Locks 独占锁)🆕 v0.5.0
ARIA_DECRYPT_ERROR解密失败:密码错误 / 密钥元数据损坏 / 数据被篡改 🆕 v0.5.0
ARIA_ENCRYPT_REQUIRED库已加密但未提供密码 🆕 v0.5.0
ARIA_ENCRYPT_CONFIG_ERROR明文库无法以加密模式打开 / 空密码 🆕 v0.5.0
ARIA_BACKGROUND_ERROR后台 flush/compaction 失败(flush/close 时显式报告)
SAVEPOINT_EXISTS / SAVEPOINT_NOT_FOUND保存点已存在 / 回滚到不存在的保存点 🆕 v0.5.1 SQL 入口
NOT_SUPPORTED引擎不支持的操作(如 Memory 引擎执行 ANALYZE / VACUUM / SAVEPOINT)🆕 v0.5.1
PARAM_ERROR参数绑定错误:数量不匹配 / 对象数组参数 🆕 v0.7.0
QUERY_ERROR未知 where 操作符(如拼错的 $betwen)🆕 v0.7.2
COLUMN_NOT_FOUND列不存在:UPDATE 未知列 / ALTER DROP 不存在列(v0.7.4 UPDATE 未知列显式报错)
INDEX_NOT_FOUNDDROP 不存在的索引
ARIA_MANIFEST_CORRUPT存储元数据(manifest)全部世代校验失败:拒绝打开,而不是当成空库 🆕 v0.8.0
ARIA_MANIFEST_WRITE_FAILEDmanifest 提交后回读校验失败(提交未生效,内存态不前进)🆕 v0.8.0
ARIA_LEGACY_META_CORRUPT旧格式(v0.8.0 之前)元数据损坏,无法安全迁移 🆕 v0.8.0
ARIA_SSTABLE_READ_FAILED介质读故障(区别于"文件不存在":不删元数据、不回退成静默空结果)🆕 v0.8.0
ARIA_WAL_GAPWAL 分片空洞(含前缀缺失):拒绝在"少了一段日志"的情况下静默继续 🆕 v0.8.0
ARIA_WRITE_LOSTmanifest 声称有未落盘数据,但 WAL 中没有任何可重放的记录(已确认写入确实丢失)🆕 v0.8.0
STALE_INSTANCE陈旧实例拒绝提交(另一个实例已提交更新的世代),不会静默覆盖 🆕 v0.8.0
ARIA_MANIFEST_NOT_LOADEDload() 就提交 manifest:拒绝写坏介质 🆕 v0.8.0
ARIA_SSTABLE_SAVE_CONTRACT存储实现违反 save() 契约(既不抛错也不返回结果):不把"写调用返回了"当成落盘成功 🆕 v0.8.0
ARIA_DB_NOT_OPENopen() 之前使用 Aria 存储层(如加密后端) 🆕 v0.8.0
ARIA_OPEN_ERRORAriaEngine 打开失败(底层错误保留在 err.details 与标准 err.cause 上,便于定位根因) 🆕 v0.8.0
ARIA_OPFS_UNAVAILABLEOPFS 在当前环境不可用(如 file:// 页面被 Chromium 禁止访问 OPFS):错误信息给出可操作建议(改用 http(s) 或 mode:'memory') 🆕 v0.8.0
DB_NOT_OPEN数据库未打开(KVStoreEngine 操作前未 open()
KV_SCHEMA_ERRORKVStore 中的表结构记录损坏
KV_LOG_ERROR / KV_BACKGROUND_ERRORKVStore 日志写入失败 / 后台刷盘失败
TX_NONE没有活跃事务时执行 COMMIT / ROLLBACK
TX_ACTIVE已有事务进行中时再次 BEGIN
TX_COMMIT_ERRORhybrid 模式提交失败:磁盘侧已提交、内存侧失败(错误信息说明磁盘数据已落盘)
UNKNOWN_STATEMENT执行器遇到未知语句类型
COLUMN_EXISTSADD COLUMN 的列已存在