267 lines
8.0 KiB
Markdown
267 lines
8.0 KiB
Markdown
# MetonaSqlark
|
||
|
||
<p align="center">
|
||
<img src="https://img.shields.io/badge/version-0.1.14-blue?style=flat-square" alt="version">
|
||
<img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="license">
|
||
<img src="https://img.shields.io/badge/coverage-93.46%25-brightgreen?style=flat-square" alt="coverage">
|
||
<img src="https://img.shields.io/badge/tests-264%20passed-success?style=flat-square" alt="tests">
|
||
</p>
|
||
|
||
> 基于 TypeScript 的**前端关系型数据库**,内存与磁盘双模式运行,支持完整 SQL 查询与 Query Builder 链式 API。
|
||
|
||
---
|
||
|
||
## ✨ v0.1.14 生产加固
|
||
|
||
- 🔒 **IndexedDB 事务原子性** — flushToIDB 单事务包裹 clear+insert,崩溃安全
|
||
- 🏷 **多标签页感知** — `onversionchange` 自动检测并关闭过期连接
|
||
- 🔁 **引擎幂等 init** — 重复调用 `open()` 安全无副作用
|
||
- 🧪 **306 测试 · 93.2% 语句覆盖率** — 生产级质量保证
|
||
|
||
---
|
||
|
||
## 📦 安装
|
||
|
||
```bash
|
||
npm install @metona-team/metona-sqlark
|
||
```
|
||
|
||
> 如果提示找不到包,先配置 scope registry(一次性):
|
||
> ```bash
|
||
> npm config set @metona-team:registry https://git.metona.cn/api/packages/MetonaTeam/npm/
|
||
> ```
|
||
|
||
### CDN / 直接下载
|
||
|
||
```html
|
||
<!-- UMD 格式,暴露 window.MetonaSqlark 和 window.MeSqlark -->
|
||
<script src="https://git.metona.cn/MetonaTeam/MetonaSqlark/raw/branch/master/dist/metona-sqlark.min.js"></script>
|
||
```
|
||
|
||
或从 [`dist/`](./dist/) 目录下载:
|
||
- `metona-sqlark.js` — UMD 开发版(含 sourcemap)
|
||
- `metona-sqlark.min.js` — UMD 压缩版(~42KB)
|
||
- `metona-sqlark.esm.js` — ES Module
|
||
- `metona-sqlark.cjs.js` — CommonJS
|
||
- `metona-sqlark.d.ts` — TypeScript 类型声明
|
||
|
||
---
|
||
|
||
## 🚀 引入方式
|
||
|
||
### ESM / TypeScript
|
||
|
||
```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
|
||
|
||
```javascript
|
||
const { MetonaSqlark } = require('@metona-team/metona-sqlark');
|
||
|
||
(async () => {
|
||
const db = await MetonaSqlark.create({ name: 'my-app', mode: 'hybrid' });
|
||
})();
|
||
```
|
||
|
||
### Browser UMD
|
||
|
||
```html
|
||
<script src="metona-sqlark.min.js"></script>
|
||
<script>
|
||
(async () => {
|
||
const db = await window.MetonaSqlark.create({ name: 'my-app', mode: 'hybrid' });
|
||
// 或 window.MeSqlark(完全等价)
|
||
})();
|
||
</script>
|
||
```
|
||
|
||
---
|
||
|
||
## 🚀 快速开始
|
||
|
||
```typescript
|
||
import { MetonaSqlark } from '@metona-team/metona-sqlark';
|
||
|
||
const db = await MetonaSqlark.create({
|
||
name: 'my-app',
|
||
mode: 'hybrid', // 'memory' | 'disk' | 'hybrid'
|
||
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`);
|
||
|
||
// 事务 — 自动回滚 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'` | `'hybrid'` | 存储模式 |
|
||
| `diskEngine` | `'indexeddb' \| 'opfs'` | `'indexeddb'` | 磁盘引擎 |
|
||
| `version` | `number` | `1` | 版本号 |
|
||
|
||
### 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'` | 更新级联 🆕 |
|
||
|
||
### WHERE 操作符
|
||
|
||
| 操作符 | 含义 | 操作符 | 含义 |
|
||
|--------|------|--------|------|
|
||
| `$eq` / 直接值 | 等于 | `$gt` / `$gte` | 大于 / 大于等于 |
|
||
| `$ne` | 不等于 | `$lt` / `$lte` | 小于 / 小于等于 |
|
||
| `$in` / `$nin` | 在列表中 | `$like` | 模糊匹配 |
|
||
| `$and` / `$or` / `$not` | 逻辑组合 | | |
|
||
|
||
### 核心方法
|
||
|
||
| 方法 | 说明 |
|
||
|------|------|
|
||
| `db.query(sql)` | 执行 SQL 字符串 |
|
||
| `db.table(name)` | 获取表操作对象 |
|
||
| `db.defineTable(name, cols)` | 定义表结构 |
|
||
| `db.transaction(fn)` | 执行事务(自动回滚)🆕 |
|
||
| `db.exportTable(name)` / `db.exportAll()` | 导出数据 JSON |
|
||
| `db.importTable(name, data)` | 导入数据 |
|
||
| `db.addMigration(v, fn)` / `db.migrateTo(v)` | 数据迁移 |
|
||
| `db.subscribe(table, fn)` | 订阅表变更 |
|
||
| `db.on(hook, fn)` | 注册钩子 (14 种) |
|
||
|
||
### 连接池(v0.1.13)
|
||
|
||
| 静态方法 | 说明 |
|
||
|------|------|
|
||
| `MetonaSqlark.connect(config)` | 获取或创建数据库实例(单例复用)🆕 |
|
||
| `MetonaSqlark.disconnect(name)` | 释放连接(引用计数 -1)🆕 |
|
||
| `MetonaSqlark.disconnectAll()` | 强制关闭所有连接 🆕 |
|
||
| `MetonaSqlark.getActiveConnections()` | 获取活跃连接列表 🆕 |
|
||
|
||
### React / Vue 集成
|
||
|
||
```tsx
|
||
// 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');
|
||
```
|
||
|
||
---
|
||
|
||
## 🛠 开发
|
||
|
||
```bash
|
||
npm install # 安装依赖
|
||
npm run dev # 开发模式(localhost:3001)
|
||
npm run build # 生产构建(生成 dist/)
|
||
npm test # 运行测试
|
||
npm run lint # 代码检查
|
||
npm run typecheck # 类型检查
|
||
```
|
||
|
||
---
|
||
|
||
## 📊 项目状态
|
||
|
||
| 指标 | 数值 |
|
||
|------|------|
|
||
| 测试用例 | 264 |
|
||
| 测试套件 | 15 |
|
||
| 语句覆盖率 | 93.46% |
|
||
| SQL 关键字 | 31 |
|
||
| 存储引擎 | 4(Memory / IndexedDB / OPFS / Hybrid) |
|
||
|
||
---
|
||
|
||
## 📂 项目结构
|
||
|
||
```
|
||
src/
|
||
├── index.ts # 入口(MetonaSqlark + MeSqlark)
|
||
├── core.ts # 主类
|
||
├── constants.ts # 类型定义 + 配置 + DatabaseError
|
||
├── connection-manager.ts # 连接池管理 🆕
|
||
├── utils.ts # 工具函数
|
||
├── engine/ # 存储引擎(Memory/IndexedDB/OPFS)
|
||
├── hybrid/ # 混合引擎(write-through)
|
||
├── table/ # 表管理 + Schema 校验
|
||
├── query/ # AST + Builder + Compiler + Executor
|
||
├── sql/ # Lexer + Parser(递归下降)
|
||
├── transaction/ # 事务管理(支持回滚)
|
||
├── plugin/ # 插件系统(14 hooks)
|
||
└── integrations/ # React / Vue hooks
|
||
```
|
||
|
||
---
|
||
|
||
## 📄 License
|
||
|
||
MIT © [MetonaTeam](https://git.metona.cn/MetonaTeam/MetonaSqlark)
|