diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ea4369..83af352 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,65 @@ # Changelog -All notable changes to YOUR-PROJECT will be documented in this file. +All notable changes to MetonaSqlark will be documented in this file. -## [0.0.1] - 2026-07-25 +## [0.1.12] - 2026-07-26 + +### Added +- `MeSqlark` 别名导出,与 `MetonaSqlark` 完全等价 +- `metona-sqlark.cjs.js` CommonJS 构建产物 +- 完善的 `where-matcher.ts` 消除 220+ 行重复代码 +- `$col` 列引用支持 JOIN ON 条件 +- `$and/$or` 嵌套支持(字段级 + 顶层) +- `$not` 逻辑非操作符 +- `$like` 正则缓存加速 +- `projectColumns` 列投影支持 `table.column` 格式 +- `DISTINCT` 去重支持(O(n) 时间,列值拼接优化) +- 测试覆盖率提升至 93.46%(264 测试/15 套件) + +### Changed +- `where-matcher` 统一 MemoryEngine / IndexedDBEngine / Executor 的 WHERE 逻辑 +- JOIN ON 匹配支持 `$col` 语法 +- LIKE 正则改为缓存式编译 +- Executor DISTINCT 用列值拼接代替 JSON.stringify +- JOIN 嵌套循环连接优化:避免 ON 时对象扩散 + +### Fixed +- IndexedDBEngine `update/delete` 方法正确同步内存缓存 +- OPFSEngine `getTableNames` 兼容 `entries()` 返回值 +- SQL Parser 正确处理字符串转义字符 +- SQL Parser 正确处理表别名(无 AS 关键字) +- SQL Parser 解析 `IS NULL` / `IS NOT NULL` / `NOT LIKE` +- `peekTokenIs` 方法正确暴露给语法分析 +- 多处边界条件空值/假值处理 + +--- + +## [0.1.11] - 2026-07-25 + +### Added +- React 集成 hooks: `useQuery`, `useTable`, `useDatabase` +- Vue 集成 composables: `useSqlarkQuery`, `useSqlarkTable`, `useSqlarkDatabase` +- 发布订阅系统: `subscribe()`, `emit()` +- 数据迁移系统: `addMigration()`, `migrateTo()` +- 导入导出: `exportTable()`, `exportAll()`, `importTable()` +- 完整 SQL 解析器: `Lexer` + `Parser`(递归下降) +- SQL 支持: SELECT, INSERT, UPDATE, DELETE, CREATE TABLE, DROP TABLE +- JOIN 支持: INNER, LEFT, RIGHT, CROSS +- GROUP BY + HAVING + 聚合函数 (COUNT, SUM, AVG, MIN, MAX) +- DISTINCT, ORDER BY, LIMIT, OFFSET +- WHERE 条件: AND, OR, NOT, IN, LIKE, IS NULL +- Query Builder 链式 API: `select().where().orderBy().limit().execute()` +- 插件系统: 14 种生命周期钩子 + PluginManager +- Metadata 系统: ColumnDef 完整约束 (type/primaryKey/required/unique/index/default/references/maxLength/min/max) + +### Changed +- 架构重构为分层设计:Engine → QueryExecutor → Table/QueryBuilder → SQL Parser → Public API +- AST 作为统一中间表示,SQL 和 QueryBuilder 行为完全一致 +- 测试框架完善:core / edge / groupby / join / query-system / sql / table / engine / transaction / plugin / hybrid 全覆盖 + +--- + +## [0.0.1] - 2026-07-24 ### Added - 初始项目骨架 @@ -12,4 +69,4 @@ All notable changes to YOUR-PROJECT will be documented in this file. - ESLint + @typescript-eslint - Gitea Actions CI 工作流 - 示例站点(index / demo / docs) -- 构建脚本 build.sh / serve.sh +- 构建脚本 build.sh diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7f5f087..8122b58 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -# Contributing to MetonaEditor +# Contributing to MetonaSqlark Thanks for your interest in contributing! This document outlines the development workflow and conventions. @@ -10,8 +10,8 @@ Thanks for your interest in contributing! This document outlines the development ## Setup ```bash -git clone https://git.metona.cn/MetonaTeam/MetonaEditor.git -cd MetonaEditor +git clone https://git.metona.cn/MetonaTeam/MetonaSqlark.git +cd MetonaSqlark npm install ``` @@ -39,34 +39,33 @@ npm run format ``` src/ -├── index.ts # Entry point, global API -├── core.ts # MarkdownEditor class -├── parser.ts # Markdown parser (tokenizer + renderer) -├── plugins.ts # Plugin system & 6 presets -├── themes.ts # Theme system -├── i18n.ts # Internationalization -├── styles.ts # CSS-in-JS injection -├── constants.ts # Types, defaults, configs -├── utils.ts # Utility functions -├── animations.ts # Animation metadata -├── icons.ts # Toolbar SVG icons -└── locales.ts # Translation data +├── index.ts # Entry point, global API (MetonaSqlark + MeSqlark) +├── core.ts # MetonaSqlark main class +├── constants.ts # Types, defaults, enums, errors +├── utils.ts # Utility functions +├── engine/ # Storage engines +│ ├── interface.ts # IStorageEngine interface +│ ├── memory.ts # MemoryEngine (Map-based) +│ ├── indexeddb.ts # IndexedDBEngine (browser persistence) +│ └── opfs.ts # OPFSEngine (Origin Private File System) +├── hybrid/ # HybridEngine (write-through) +├── table/ # Table management & Schema validation +├── query/ # Query system +│ ├── ast.ts # SQL AST type definitions +│ ├── builder.ts # Chainable QueryBuilder API +│ ├── compiler.ts # AST → QueryPlan compiler +│ ├── executor.ts # QueryExecutor with JOIN/GROUP BY support +│ └── where-matcher.ts # Unified WHERE matching logic +├── sql/ # SQL parser +│ ├── tokens.ts # Token types & keywords +│ ├── lexer.ts # Tokenizer +│ └── parser.ts # Recursive descent parser +├── transaction/ # Transaction manager +├── plugin/ # Plugin system (14 lifecycle hooks) +└── integrations/ # React & Vue hooks -tests/ -├── parser.test.ts -├── core.test.ts -├── plugins.test.ts -├── themes.test.ts -├── i18n.test.ts -├── utils.test.ts -├── animations.test.ts -├── index.test.ts -└── styles.test.ts - -site/ -├── index.html # Landing page -├── demo.html # Full-featured demo -└── docs.html # API documentation +tests/ # Test suite (264+ test cases, 15 test suites) +site/ # Documentation site (index / docs / demo) ``` ## Code Conventions @@ -89,12 +88,43 @@ site/ ### Commits - Use conventional commit messages: - - `feat: add reference link resolution` - - `fix: autoSave plugin state conflict` + - `feat: add SQL GROUP BY support` + - `fix: JOIN with multiple ON conditions` - `docs: update API reference` - - `test: add index.ts global API tests` + - `test: add edge case coverage` - `chore: optimize rollup dev build` +## Architecture + +MetonaSqlark follows a layered architecture: + +``` +┌──────────────────────────────────────────────────┐ +│ MetonaSqlark / MeSqlark (Public API) │ +├──────────────────────────────────────────────────┤ +│ SQL Parser │ QueryBuilder │ Transaction │ Plugin │ +│ (Lexer→AST) │ (.select…) │ Manager │ System │ +├──────────────────────────────────────────────────┤ +│ QueryExecutor (AST → Results) │ +│ JOIN · GROUP BY · HAVING · DISTINCT · Agg │ +├──────────────────────────────────────────────────┤ +│ IStorageEngine (Interface) │ +├─────────┬──────────┬──────────┬──────────────────┤ +│ Memory │ IndexedDB │ OPFS │ Hybrid │ +│ Engine │ Engine │ Engine │ Engine │ +└─────────┴──────────┴──────────┴──────────────────┘ +``` + +### Key Design Decisions + +1. **AST as Universal Intermediate Representation**: Both SQL strings and Query Builder produce the same AST, ensuring consistent behavior regardless of API used. + +2. **Write-Through Hybrid Strategy**: When using `mode: 'hybrid'`, all writes go to both memory and disk simultaneously. Reads always hit memory for maximum speed. + +3. **Plugin Hook Pipeline**: 14 lifecycle hooks allow intercepting database operations without modifying core code. + +4. **Hand-Written SQL Parser**: No dependencies on parser generators — a recursive-descent parser keeps the bundle size minimal. + ## Building ```bash @@ -102,11 +132,11 @@ site/ npm run build # Output in dist/ -# ├── metona-editor.js UMD -# ├── metona-editor.min.js UMD minified -# ├── metona-editor.esm.js ES Module -# ├── metona-editor.cjs.js CommonJS -# └── metona-editor.d.ts TypeScript declarations +# ├── metona-sqlark.js UMD +# ├── metona-sqlark.min.js UMD minified (~42KB / ~10KB gzip) +# ├── metona-sqlark.esm.js ES Module +# ├── metona-sqlark.cjs.js CommonJS +# └── metona-sqlark.d.ts TypeScript declarations ``` ## Plugin Development @@ -114,29 +144,58 @@ npm run build Plugins follow a simple convention: ```typescript -const myPlugin = { +import type { MetonaPlugin } from '@metona-team/metona-sqlark'; + +const myPlugin: MetonaPlugin = { name: 'myPlugin', version: '1.0.0', description: 'Description of my plugin', - depends: [], // optional: plugin names this depends on - priority: 50, // optional: for topological sort ordering + priority: 50, // higher = executed first - install(editor, options?) { - // Called when plugin is installed - // Use editor.on() to subscribe to events - // Return a Promise for async initialization + install(db) { + // Use db.on() to subscribe to hooks + db.on('beforeInsert', async (rows) => { + // Validate or transform data + }); + db.on('afterQuery', async (sql, result) => { + // Log or cache query results + }); }, - destroy(editor) { - // Called when plugin is uninstalled - // Clean up event listeners, timers, DOM nodes + destroy() { + // Clean up event listeners, timers, etc. }, }; + +// Register in config +const db = await MetonaSqlark.create({ + name: 'my-app', + plugins: [myPlugin], +}); ``` +### Available Lifecycle Hooks + +| Hook | Trigger | Parameters | +|------|---------|------------| +| `beforeCreateTable` | Before table creation | schema | +| `afterCreateTable` | After table creation | schema | +| `beforeDropTable` | Before table drop | tableName | +| `afterDropTable` | After table drop | tableName | +| `beforeInsert` | Before row insert | rows[] | +| `afterInsert` | After row insert | rows[] | +| `beforeUpdate` | Before row update | query, updates | +| `afterUpdate` | After row update | query, updates, count | +| `beforeDelete` | Before row delete | query | +| `afterDelete` | After row delete | query, count | +| `beforeQuery` | Before SQL query | sql | +| `afterQuery` | After SQL query | sql, result | +| `beforeTransaction` | Before transaction | - | +| `afterTransaction` | After transaction | - | + ## Releasing -1. Update version in `package.json` and `src/index.ts` (`VERSION` constant). +1. Update version in `package.json` and `src/constants.ts` (`VERSION` constant). 2. Update `CHANGELOG.md`. 3. Run full test suite: `npm test`. 4. Build: `npm run build`. @@ -144,4 +203,4 @@ const myPlugin = { ## Questions? -Open an issue at [git.metona.cn/MetonaTeam/MetonaEditor/issues](https://git.metona.cn/MetonaTeam/MetonaEditor/issues). +Open an issue at [git.metona.cn/MetonaTeam/MetonaSqlark/issues](https://git.metona.cn/MetonaTeam/MetonaSqlark/issues). diff --git a/site/demo.html b/site/demo.html index 6e3f3df..73de90c 100644 --- a/site/demo.html +++ b/site/demo.html @@ -3,7 +3,7 @@ -在线演示 — MetonaSqlark +在线演示 — MetonaSqlark v0.1.12 @@ -93,40 +82,40 @@ 文档 演示 -
Memory 模式
+
Memory 模式 — v0.1.12
-
-
-
SQL 查询
-
Query Builder
-
-
- + + +
-
📊 查询结果 @@ -136,7 +125,7 @@ SELECT COUNT(*) as total_users, AVG(age) as avg_age FROM users;
执行 SQL 查询以查看结果
-
Ctrl + Enter 快捷执行
+
Ctrl + Enter 快捷执行 · 支持多语句
@@ -144,7 +133,10 @@ SELECT COUNT(*) as total_users, AVG(age) as avg_age FROM users; diff --git a/site/docs.html b/site/docs.html index fa88eab..e8598bb 100644 --- a/site/docs.html +++ b/site/docs.html @@ -3,7 +3,7 @@ -API 文档 — MetonaSqlark +API 文档 — MetonaSqlark v0.1.12 @@ -81,200 +77,516 @@

快速开始

安装 创建数据库 - 定义表 + 定义表

查询 API

SQL 查询 Query Builder JOIN 查询 - GROUP BY + GROUP BY & 聚合 + WHERE 操作符

高级特性

事务 - 迁移 + 数据迁移 导入导出 - 插件钩子 + 插件 & 钩子 发布订阅 -

类型

+

框架集成

+ React 集成 + Vue 集成 +

类型参考

TypeScript 类型 - 配置项 + 完整配置项 + 存储引擎 + 错误处理

📦 安装

-
// npm
-npm install @metona-team/MetonaSqlark
+

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

-// ESM -import { MetonaSqlark, MeSqlark } from 'MetonaSqlark'; +

npm 安装(推荐)

+
# 配置 Gitea Registry(一次性)
+npm config set @metona-team:registry https://git.metona.cn/api/packages/MetonaTeam/npm/
 
-// Browser
-<script src="metona-sqlark.min.js"></script>
-// → window.MetonaSqlark / window.MeSqlark
+# 安装 +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) 工厂函数,返回初始化好的实例。

+

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

+
const db = await MetonaSqlark.create({
   name: 'my-app',
   mode: 'hybrid',       // 'memory' | 'disk' | 'hybrid'
-  diskEngine: 'indexeddb', // 'indexeddb' | 'opfs'
+  diskEngine: 'indexeddb', // 'indexeddb' | 'opfs'(仅 disk/hybrid 生效)
   version: 1,
-});
+ plugins: [], // MetonaPlugin[] + onReady: (db) => {}, // 就绪回调 + onError: (err) => {}, // 错误回调 +}); - - - - - - -
属性类型默认说明
namestring-数据库名称
mode'memory'|'disk'|'hybrid''hybrid'存储模式
diskEngine'indexeddb'|'opfs''indexeddb'磁盘引擎
versionnumber1版本号
+// 也可手动实例化 +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 },
-  dept_id: { type: 'number', references: 'departments.id' },
-});
+ 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'); // 删除表 - - - - - - + + + + + + - + + +
字段类型说明
typestring|number|boolean|date|json数据类型
primaryKeyboolean主键
requiredboolean必填
uniqueboolean唯一约束
indexboolean创建索引
ColumnDef 属性类型说明
type'string'|'number'|'boolean'|'date'|'json'数据类型
primaryKeyboolean主键(每表至少一个)
requiredboolean是否必填
uniqueboolean唯一约束(自动建索引)
indexboolean创建哈希索引,O(1) 加速查询
defaultunknown默认值
referencesstring外键引用
maxLengthnumber字符串最大长度
min/maxnumber数值范围
referencesstring外键引用 'table.column'

🔍 SQL 查询

-
// INSERT
+

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')");
 
-// SELECT
-const rows = await db.query('SELECT * FROM users WHERE age > 18 ORDER BY name LIMIT 10');
+// UPDATE — 更新数据
+await db.query("UPDATE users SET age = 31, active = true WHERE id = '1'");
 
-// UPDATE
-await db.query("UPDATE users SET age = 31 WHERE id = '1'");
-
-// DELETE
+// DELETE — 删除数据
 await db.query("DELETE FROM users WHERE id = '1'");
 
-// DDL
-await db.query('DROP TABLE users');
+// 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.insert({ id: '1', name: 'Alice' });
-await users.insertMany([...]);
+// 全量查询
+await users.select().execute();
 
-// 查询
+// 指定列 + 条件 + 排序 + 分页
 const result = await users
-  .select(['name', 'age'])
-  .where({ age: { $gt: 18 }, name: { $like: 'A%' } })
+  .select(['name', 'age', 'email'])
+  .where({
+    age: { $gt: 18 },
+    name: { $like: 'A%' },
+  })
   .orderBy('age', 'desc')
-  .limit(10).offset(0)
-  .execute();
+  .limit(10)
+  .offset(0)
+  .execute();
-// 更新/删除 -await users.update({ age: 31 }).where({ id: '1' }).execute(); -await users.delete().where({ id: '1' }).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 查询

-
// SQL
-await db.query(`SELECT u.name, d.name
+

支持 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
-  WHERE d.name = 'Engineering'`);
+  LEFT JOIN orders o ON u.id = o.user_id
+  WHERE d.name = 'Engineering'
+  ORDER BY u.name`);
 
-// QueryBuilder
+// Query Builder 方式
 await db.table('users').select()
-  .innerJoin('departments', { 'users.dept_id': { $col: 'departments.id' } })
+  .as('u')
+  .innerJoin('departments', { 'u.dept_id': { $col: 'd.id' } }, 'd')
+  .leftJoin('orders', { 'u.id': { $col: 'o.user_id' } }, 'o')
   .execute();
 
-// LEFT JOIN / RIGHT JOIN / CROSS JOIN
-.leftJoin('table', on)
-.rightJoin('table', on)
-.crossJoin('table')
+// CROSS JOIN — 笛卡尔积 +.crossJoin('metadata', 'm')

📊 GROUP BY & 聚合

-
await db.query(`SELECT dept, COUNT(*) as cnt, SUM(salary) as total
+

五大聚合函数 + 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`);
+  ORDER BY total DESC
+  LIMIT 5`);
 
-// 支持的聚合函数:COUNT, SUM, AVG, MIN, MAX
-// 支持别名:COUNT(*) AS cnt
+// 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', { ... });
+

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

+ +
// 注册迁移
+db.addMigration(2, async (db) => {
+  await db.defineTable('products', {
+    id: { type: 'string', primaryKey: true },
+    name: { type: 'string', required: true },
+  });
 });
-await db.migrateTo(2); // 执行所有未执行的迁移
+ +db.addMigration(3, async (db) => { + // 添加新列、数据迁移等 + await db.query("UPDATE users SET role = 'user' WHERE role IS NULL"); +}); + +// 执行迁移到目标版本 +await db.migrateTo(3); // 依次执行 v2, v3 的迁移函数

📤 导入导出

-
// 导出单表
-const data = await db.exportTable('users');
+
// 导出单表 — 返回 JSON 数组
+const userData = await db.exportTable('users');
+// [{ id: '1', name: 'Alice', ... }, ...]
 
-// 导出全库
-const all = await db.exportAll();
+// 导出全库 — 返回 { tableName: rows[] }
+const allData = await db.exportAll();
+// { users: [...], orders: [...], products: [...] }
 
-// 导入
-await db.importTable('users', data);
+// 导入数据 — 返回主键列表 +const pks = await db.importTable('users', userData);
-

🧩 插件钩子

-
// 14 个生命周期钩子
+

🧩 插件 & 钩子

+

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);
+  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],
 });
- - - - - - - - - -
钩子触发时机
beforeCreateTable / afterCreateTable创建表前后
beforeDropTable / afterDropTable删除表前后
beforeInsert / afterInsert插入前后
beforeUpdate / afterUpdate更新前后
beforeDelete / afterDelete删除前后
beforeQuery / afterQuery查询前后
beforeTransaction / afterTransaction事务前后

📡 发布订阅

// 订阅表变更
 const unsubscribe = db.subscribe('users', (event) => {
-  // event: { type: 'insert'|'update'|'delete', row: {...} }
+  // 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 }); // ✅ 类型安全
-

⚙️ 配置项

-
import { MetonaSqlark, MeSqlark } from 'MetonaSqlark';
-// MeSqlark 是 MetonaSqlark 的别名,完全等价
+// ✅ 类型检查通过 +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配置错误
diff --git a/site/index.html b/site/index.html index 73fdc25..87d5bab 100644 --- a/site/index.html +++ b/site/index.html @@ -151,7 +151,7 @@
-
v0.1.11 已发布
+
v0.1.12 已发布

前端的 SQL 数据库

TypeScript 原生构建,内存与磁盘双模式,支持完整 SQL 查询。
零运行时依赖,开箱即用。

@@ -232,63 +232,123 @@ npm install @metona-team/metona-sqlark
🧠

双模式存储

-

Memory(内存)保证极致速度,Disk(IndexedDB / OPFS)提供持久化。Hybrid 模式一键兼顾。

+

Memory(内存)保证极致速度,Disk(IndexedDB / OPFS)提供持久化。Hybrid 模式 write-through 策略,读写均在微秒级完成。

-

完整 SQL 支持

-

SELECT / INSERT / UPDATE / DELETE / JOIN / GROUP BY / HAVING / DISTINCT — 手写递归下降解析器。

+

完整 SQL 解析器

+

手写递归下降 SQL 解析器,无第三方依赖。支持 SELECT / INSERT / UPDATE / DELETE / CREATE TABLE / DROP TABLE / JOIN / GROUP BY / HAVING / DISTINCT。

🔗

Query Builder

-

链式 API,TypeScript 友好。.select().where().innerJoin().orderBy().limit() — 代码即查询。

+

链式 API + TypeScript 泛型支持。.select().where().innerJoin().orderBy().limit() — IDE 自动补全,类型安全。

🔒

事务支持

-

原子性操作,事务内任意步骤失败自动回滚。IndexedDB 引擎利用浏览器原生事务。

+

原子性操作,事务内任意步骤失败自动回滚。IndexedDB 引擎原生事务 + Memory 引擎模拟事务。

🧩

插件系统

-

14 种生命周期钩子,注册自定义插件。beforeQuery / afterInsert / beforeTransaction……

+

14 种生命周期钩子:beforeCreateTable / afterInsert / beforeQuery / afterTransaction……按优先级注册,完整生命周期管理。

📦

零运行时依赖

-

纯 TypeScript 实现,不依赖任何第三方库。Tree-shakable,最小体积不到 10KB gzip。

+

纯 TypeScript 实现,不依赖任何第三方库。Tree-shakable,UMD/ESM/CJS 多格式输出,最小体积 ~10KB gzip。

+
+
+
📊
+

聚合 & 分组

+

COUNT / SUM / AVG / MIN / MAX 五大聚合函数,GROUP BY + HAVING 子句,DISTINCT 去重,完整的数据分析能力。

+
+
+
🔄
+

数据迁移

+

内置版本迁移系统,addMigration + migrateTo API。导入导出支持单表/全库 JSON 序列化。

+
+
+
📡
+

发布订阅

+

subscribe() 订阅表变更事件,emit() 触发通知。React useQuery / Vue useSqlarkQuery 开箱即用。

- + +
+
+
+

🏗 架构设计

+

清晰的分层结构,各模块职责单一

+
+
+
+  ┌─────────────────────────────────────────────────────┐
+  │                  MetonaSqlark / MeSqlark                  │
+  │              create() · query() · table() · tx()     │
+  ├──────────┬──────────┬──────────┬──────────┬─────────┤
+  │ SQL ParserQueryBuilderTransactionPluginMigration │
+  │ Lexer →   │ .select() │ Manager  │ Manager  │ System  │
+  │ Parser    │ .where()  │          │ 14 hooks │         │
+  ├──────────┴──────────┴──────────┴──────────┴─────────┤
+  │              QueryExecutor (AST → Results)               │
+  │         JOIN · GROUP BY · HAVING · DISTINCT          │
+  ├─────────────────────────────────────────────────────┤
+  │              IStorageEngine Interface                   │
+  ├──────────┬──────────┬──────────┬───────────────────┤
+  │ Memory    IndexedDB OPFS     Hybrid            │
+  │ Engine   │ Engine   │ Engine   │ Engine            │
+  │ (Map)    │ (IDB)    │ (File)   │ (Write-through)   │
+  └──────────┴──────────┴──────────┴───────────────────┘
+
+
+
+

5 行代码开始

-

像写 SQL 一样操作前端数据

+

像写 SQL 一样操作前端数据 — 或使用 Query Builder 链式 API

-
// 创建数据库
+
// 创建数据库 — MeSqlark 是别名,完全等价
 const db = await MetonaSqlark.create({ name: 'my-app', mode: 'hybrid' });
 
 // 定义表结构
 await db.defineTable('users', {
   id: { type: 'string', primaryKey: true },
   name: { type: 'string', required: true },
-  age: { type: 'number' },
+  age: { type: 'number', default: 0 },
+  email: { type: 'string', unique: true, index: true },
 });
 
 // SQL 查询
-await db.query("INSERT INTO users VALUES ('1', 'Alice', 30)");
-const rows = await db.query('SELECT * FROM users WHERE age > 18');
+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
+// Query Builder — 类型安全,链式调用
 const rows2 = await db.table('users')
-  .select().where({ age: { $gt: 18 } })
-  .orderBy('name').limit(10).execute();
+ .select(['name', 'age']).where({ age: { $gt: 18 } }) + .orderBy('name').limit(10).execute(); + +// JOIN 多表关联 +await db.query(`SELECT u.name, o.product, o.amount + FROM users u INNER JOIN orders o ON u.id = o.user_id + WHERE o.amount > 100`); + +// GROUP BY 聚合分析 +await db.query(`SELECT dept, COUNT(*) as cnt, AVG(salary) as avg + FROM employees GROUP BY dept HAVING COUNT(*) > 1`); + +// 事务 — 原子操作 +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 }); +});
@@ -301,10 +361,12 @@ npm install @metona-team/metona-sqlark

MetonaSqlark 的核心指标

-
235+
测试用例
-
78.9%
代码覆盖率
+
264+
测试用例
+
93.5%
代码覆盖率
~10KB
gzip 体积
4
存储引擎
+
29
SQL 关键字
+
15
测试套件