📦 安装

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/.../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)生产环境(~42KB / ~10KB gzip)
metona-sqlark.esm.jsES Module现代打包工具 / 浏览器 ESM
metona-sqlark.cjs.jsCommonJSNode.js require()
metona-sqlark.d.tsTypeScript 声明类型提示

🏗 创建数据库

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

const db = await MetonaSqlark.create({
  name: 'my-app',
  mode: 'hybrid',       // 'memory' | 'disk' | 'hybrid'
  diskEngine: 'indexeddb', // 'indexeddb' | 'opfs'(仅 disk/hybrid 生效)
  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'

🔍 SQL 查询

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

完整 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');

条件表达式

// 比较运算符
`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' },
})

🔒 事务

保证原子性,事务内步骤失败自动报错。

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 });
  await trx.table('accounts').update({ balance: 1 })
    .where({ userId: '3' })
    .execute();

  // 也可返回结果
  return 'success';
});

🔄 数据迁移

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

// 注册迁移
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 的迁移函数

📤 导入导出

// 导出单表 — 返回 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[]
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();

⚛️ 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''hybrid'存储模式
diskEngine'indexeddb'|'opfs''indexeddb'磁盘引擎类型
versionnumber1数据库版本号
pluginsMetonaPlugin[][]初始插件列表
onReady(db) => void-初始化完成回调
onError(err) => void-错误回调

💾 存储引擎

引擎模式持久化性能适用场景
MemoryEnginememory❌ 否⚡ 极快临时数据、缓存、测试
IndexedDBEnginedisk✅ 是🚀 快通用持久化,兼容性最好
OPFSEnginedisk✅ 是🚀 快现代浏览器,文件级存储
HybridEnginehybrid✅ 是⚡ 极快生产推荐,读写均走内存

Hybrid 引擎采用 write-through 策略:所有写操作同时写入内存和磁盘,所有读操作直接从内存返回,启动时从磁盘加载数据到内存。

⚠️ 错误处理

所有错误抛出 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唯一约束冲突
VALIDATION_ERROR数据校验失败
TYPE_ERROR字段类型错误
SCHEMA_ERROR表结构定义错误
DB_NOT_READY数据库未初始化
PARSE_ERRORSQL 语法错误
TRANSACTION_ERROR事务执行失败
COMPILE_ERROR编译 AST 到查询计划失败
CONFIG_ERROR配置错误