feat: site全面更新 + CONTRIBUTING/CHANGELOG完善,准备v0.1.13迭代
This commit is contained in:
+60
-3
@@ -1,8 +1,65 @@
|
|||||||
# Changelog
|
# 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
|
### Added
|
||||||
- 初始项目骨架
|
- 初始项目骨架
|
||||||
@@ -12,4 +69,4 @@ All notable changes to YOUR-PROJECT will be documented in this file.
|
|||||||
- ESLint + @typescript-eslint
|
- ESLint + @typescript-eslint
|
||||||
- Gitea Actions CI 工作流
|
- Gitea Actions CI 工作流
|
||||||
- 示例站点(index / demo / docs)
|
- 示例站点(index / demo / docs)
|
||||||
- 构建脚本 build.sh / serve.sh
|
- 构建脚本 build.sh
|
||||||
|
|||||||
+109
-50
@@ -1,4 +1,4 @@
|
|||||||
# Contributing to MetonaEditor
|
# Contributing to MetonaSqlark
|
||||||
|
|
||||||
Thanks for your interest in contributing! This document outlines the development workflow and conventions.
|
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
|
## Setup
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://git.metona.cn/MetonaTeam/MetonaEditor.git
|
git clone https://git.metona.cn/MetonaTeam/MetonaSqlark.git
|
||||||
cd MetonaEditor
|
cd MetonaSqlark
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -39,34 +39,33 @@ npm run format
|
|||||||
|
|
||||||
```
|
```
|
||||||
src/
|
src/
|
||||||
├── index.ts # Entry point, global API
|
├── index.ts # Entry point, global API (MetonaSqlark + MeSqlark)
|
||||||
├── core.ts # MarkdownEditor class
|
├── core.ts # MetonaSqlark main class
|
||||||
├── parser.ts # Markdown parser (tokenizer + renderer)
|
├── constants.ts # Types, defaults, enums, errors
|
||||||
├── plugins.ts # Plugin system & 6 presets
|
├── utils.ts # Utility functions
|
||||||
├── themes.ts # Theme system
|
├── engine/ # Storage engines
|
||||||
├── i18n.ts # Internationalization
|
│ ├── interface.ts # IStorageEngine interface
|
||||||
├── styles.ts # CSS-in-JS injection
|
│ ├── memory.ts # MemoryEngine (Map-based)
|
||||||
├── constants.ts # Types, defaults, configs
|
│ ├── indexeddb.ts # IndexedDBEngine (browser persistence)
|
||||||
├── utils.ts # Utility functions
|
│ └── opfs.ts # OPFSEngine (Origin Private File System)
|
||||||
├── animations.ts # Animation metadata
|
├── hybrid/ # HybridEngine (write-through)
|
||||||
├── icons.ts # Toolbar SVG icons
|
├── table/ # Table management & Schema validation
|
||||||
└── locales.ts # Translation data
|
├── 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/
|
tests/ # Test suite (264+ test cases, 15 test suites)
|
||||||
├── parser.test.ts
|
site/ # Documentation site (index / docs / demo)
|
||||||
├── 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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Code Conventions
|
## Code Conventions
|
||||||
@@ -89,12 +88,43 @@ site/
|
|||||||
|
|
||||||
### Commits
|
### Commits
|
||||||
- Use conventional commit messages:
|
- Use conventional commit messages:
|
||||||
- `feat: add reference link resolution`
|
- `feat: add SQL GROUP BY support`
|
||||||
- `fix: autoSave plugin state conflict`
|
- `fix: JOIN with multiple ON conditions`
|
||||||
- `docs: update API reference`
|
- `docs: update API reference`
|
||||||
- `test: add index.ts global API tests`
|
- `test: add edge case coverage`
|
||||||
- `chore: optimize rollup dev build`
|
- `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
|
## Building
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -102,11 +132,11 @@ site/
|
|||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
# Output in dist/
|
# Output in dist/
|
||||||
# ├── metona-editor.js UMD
|
# ├── metona-sqlark.js UMD
|
||||||
# ├── metona-editor.min.js UMD minified
|
# ├── metona-sqlark.min.js UMD minified (~42KB / ~10KB gzip)
|
||||||
# ├── metona-editor.esm.js ES Module
|
# ├── metona-sqlark.esm.js ES Module
|
||||||
# ├── metona-editor.cjs.js CommonJS
|
# ├── metona-sqlark.cjs.js CommonJS
|
||||||
# └── metona-editor.d.ts TypeScript declarations
|
# └── metona-sqlark.d.ts TypeScript declarations
|
||||||
```
|
```
|
||||||
|
|
||||||
## Plugin Development
|
## Plugin Development
|
||||||
@@ -114,29 +144,58 @@ npm run build
|
|||||||
Plugins follow a simple convention:
|
Plugins follow a simple convention:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const myPlugin = {
|
import type { MetonaPlugin } from '@metona-team/metona-sqlark';
|
||||||
|
|
||||||
|
const myPlugin: MetonaPlugin = {
|
||||||
name: 'myPlugin',
|
name: 'myPlugin',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
description: 'Description of my plugin',
|
description: 'Description of my plugin',
|
||||||
depends: [], // optional: plugin names this depends on
|
priority: 50, // higher = executed first
|
||||||
priority: 50, // optional: for topological sort ordering
|
|
||||||
|
|
||||||
install(editor, options?) {
|
install(db) {
|
||||||
// Called when plugin is installed
|
// Use db.on() to subscribe to hooks
|
||||||
// Use editor.on() to subscribe to events
|
db.on('beforeInsert', async (rows) => {
|
||||||
// Return a Promise for async initialization
|
// Validate or transform data
|
||||||
|
});
|
||||||
|
db.on('afterQuery', async (sql, result) => {
|
||||||
|
// Log or cache query results
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
destroy(editor) {
|
destroy() {
|
||||||
// Called when plugin is uninstalled
|
// Clean up event listeners, timers, etc.
|
||||||
// Clean up event listeners, timers, DOM nodes
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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
|
## 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`.
|
2. Update `CHANGELOG.md`.
|
||||||
3. Run full test suite: `npm test`.
|
3. Run full test suite: `npm test`.
|
||||||
4. Build: `npm run build`.
|
4. Build: `npm run build`.
|
||||||
@@ -144,4 +203,4 @@ const myPlugin = {
|
|||||||
|
|
||||||
## Questions?
|
## 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).
|
||||||
|
|||||||
+145
-89
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>在线演示 — MetonaSqlark</title>
|
<title>在线演示 — MetonaSqlark v0.1.12</title>
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--bg:#0a0a0f; --surface:#0d1117; --surface2:#161b22; --border:#30363d;
|
--bg:#0a0a0f; --surface:#0d1117; --surface2:#161b22; --border:#30363d;
|
||||||
@@ -13,7 +13,6 @@
|
|||||||
}
|
}
|
||||||
* { margin:0; padding:0; box-sizing:border-box; }
|
* { margin:0; padding:0; box-sizing:border-box; }
|
||||||
body { font-family:'Inter',-apple-system,system-ui,sans-serif; background:var(--bg); color:var(--text); height:100vh; overflow:hidden; }
|
body { font-family:'Inter',-apple-system,system-ui,sans-serif; background:var(--bg); color:var(--text); height:100vh; overflow:hidden; }
|
||||||
/* Header */
|
|
||||||
header {
|
header {
|
||||||
height:56px; display:flex; align-items:center; justify-content:space-between; padding:0 24px;
|
height:56px; display:flex; align-items:center; justify-content:space-between; padding:0 24px;
|
||||||
background:var(--surface); border-bottom:1px solid var(--border); z-index:10; position:relative;
|
background:var(--surface); border-bottom:1px solid var(--border); z-index:10; position:relative;
|
||||||
@@ -27,7 +26,6 @@
|
|||||||
.status { display:flex; align-items:center; gap:8px; font-size:0.82rem; color:var(--text2); }
|
.status { display:flex; align-items:center; gap:8px; font-size:0.82rem; color:var(--text2); }
|
||||||
.status .dot { width:7px; height:7px; border-radius:50%; background:var(--green); animation:pulse 2s infinite; }
|
.status .dot { width:7px; height:7px; border-radius:50%; background:var(--green); animation:pulse 2s infinite; }
|
||||||
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:.3} }
|
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:.3} }
|
||||||
/* Main Layout */
|
|
||||||
.main { display:flex; height:calc(100vh - 56px); }
|
.main { display:flex; height:calc(100vh - 56px); }
|
||||||
.editor-panel { flex:1; display:flex; flex-direction:column; border-right:1px solid var(--border); min-width:400px; }
|
.editor-panel { flex:1; display:flex; flex-direction:column; border-right:1px solid var(--border); min-width:400px; }
|
||||||
.result-panel { flex:1; display:flex; flex-direction:column; min-width:400px; }
|
.result-panel { flex:1; display:flex; flex-direction:column; min-width:400px; }
|
||||||
@@ -40,7 +38,6 @@
|
|||||||
font-size:0.7rem; padding:3px 10px; border-radius:999px; background:var(--surface2);
|
font-size:0.7rem; padding:3px 10px; border-radius:999px; background:var(--surface2);
|
||||||
border:1px solid var(--border); text-transform:none; letter-spacing:0;
|
border:1px solid var(--border); text-transform:none; letter-spacing:0;
|
||||||
}
|
}
|
||||||
/* SQL Editor */
|
|
||||||
.editor-area { flex:1; display:flex; flex-direction:column; }
|
.editor-area { flex:1; display:flex; flex-direction:column; }
|
||||||
#sql-input {
|
#sql-input {
|
||||||
flex:1; background:var(--surface); color:var(--text); border:none; padding:20px;
|
flex:1; background:var(--surface); color:var(--text); border:none; padding:20px;
|
||||||
@@ -48,7 +45,7 @@
|
|||||||
resize:none; outline:none; caret-color:var(--primary);
|
resize:none; outline:none; caret-color:var(--primary);
|
||||||
}
|
}
|
||||||
#sql-input::placeholder { color:var(--text2); opacity:0.5; }
|
#sql-input::placeholder { color:var(--text2); opacity:0.5; }
|
||||||
.toolbar { display:flex; gap:8px; padding:12px 20px; border-top:1px solid var(--border); background:var(--surface2); }
|
.toolbar { display:flex; gap:8px; padding:12px 20px; border-top:1px solid var(--border); background:var(--surface2); flex-wrap:wrap; }
|
||||||
.btn {
|
.btn {
|
||||||
display:inline-flex; align-items:center; gap:6px; padding:10px 20px; border-radius:8px;
|
display:inline-flex; align-items:center; gap:6px; padding:10px 20px; border-radius:8px;
|
||||||
font-weight:600; font-size:0.85rem; cursor:pointer; border:none; transition:.25s; font-family:inherit;
|
font-weight:600; font-size:0.85rem; cursor:pointer; border:none; transition:.25s; font-family:inherit;
|
||||||
@@ -60,7 +57,6 @@
|
|||||||
.btn-clear:hover { color:var(--text); border-color:var(--text2); }
|
.btn-clear:hover { color:var(--text); border-color:var(--text2); }
|
||||||
.btn-preset { background:transparent; color:var(--accent); border:1px solid var(--accent); font-size:0.78rem; padding:6px 14px; }
|
.btn-preset { background:transparent; color:var(--accent); border:1px solid var(--accent); font-size:0.78rem; padding:6px 14px; }
|
||||||
.btn-preset:hover { background:rgba(6,182,212,0.12); }
|
.btn-preset:hover { background:rgba(6,182,212,0.12); }
|
||||||
/* Result */
|
|
||||||
.result-content { flex:1; overflow-y:auto; padding:20px; }
|
.result-content { flex:1; overflow-y:auto; padding:20px; }
|
||||||
.result-table { width:100%; border-collapse:collapse; font-size:0.85rem; }
|
.result-table { width:100%; border-collapse:collapse; font-size:0.85rem; }
|
||||||
.result-table th { text-align:left; padding:10px 14px; background:var(--surface2); color:var(--accent); font-weight:600; border-bottom:2px solid var(--border); position:sticky; top:0; z-index:1; }
|
.result-table th { text-align:left; padding:10px 14px; background:var(--surface2); color:var(--accent); font-weight:600; border-bottom:2px solid var(--border); position:sticky; top:0; z-index:1; }
|
||||||
@@ -68,19 +64,12 @@
|
|||||||
.result-table tr:hover td { background:rgba(99,102,241,0.04); }
|
.result-table tr:hover td { background:rgba(99,102,241,0.04); }
|
||||||
.empty-state { display:flex; flex-direction:column; align-items:center; justify-content:center; height:100%; color:var(--text2); gap:12px; }
|
.empty-state { display:flex; flex-direction:column; align-items:center; justify-content:center; height:100%; color:var(--text2); gap:12px; }
|
||||||
.empty-state .icon { font-size:3rem; opacity:0.3; }
|
.empty-state .icon { font-size:3rem; opacity:0.3; }
|
||||||
/* Error */
|
|
||||||
.error-box { background:rgba(239,68,68,0.1); border:1px solid rgba(239,68,68,0.3); border-radius:10px; padding:16px; color:#f87171; font-family:'JetBrains Mono',monospace; font-size:0.82rem; margin-bottom:16px; }
|
.error-box { background:rgba(239,68,68,0.1); border:1px solid rgba(239,68,68,0.3); border-radius:10px; padding:16px; color:#f87171; font-family:'JetBrains Mono',monospace; font-size:0.82rem; margin-bottom:16px; }
|
||||||
.success-box { background:rgba(34,197,94,0.1); border:1px solid rgba(34,197,94,0.3); border-radius:10px; padding:12px 16px; color:#4ade80; font-size:0.85rem; margin-bottom:16px; }
|
.success-box { background:rgba(34,197,94,0.1); border:1px solid rgba(34,197,94,0.3); border-radius:10px; padding:12px 16px; color:#4ade80; font-size:0.85rem; margin-bottom:16px; }
|
||||||
/* Tabs */
|
.sql-tag { display:inline-block; background:var(--surface2); border:1px solid var(--border); border-radius:6px; padding:4px 12px; font-family:'JetBrains Mono',monospace; font-size:0.75rem; color:var(--accent); margin-bottom:8px; }
|
||||||
.tabs { display:flex; gap:4px; padding:8px 20px 0; background:var(--surface); }
|
|
||||||
.tab { padding:8px 18px; border-radius:8px 8px 0 0; font-size:0.82rem; cursor:pointer; color:var(--text2); border:1px solid transparent; transition:.2s; }
|
|
||||||
.tab:hover { color:var(--text); }
|
|
||||||
.tab.active { color:var(--text); background:var(--bg); border-color:var(--border); border-bottom-color:var(--bg); }
|
|
||||||
/* Scrollbar */
|
|
||||||
::-webkit-scrollbar { width:6px; height:6px; }
|
::-webkit-scrollbar { width:6px; height:6px; }
|
||||||
::-webkit-scrollbar-track { background:transparent; }
|
::-webkit-scrollbar-track { background:transparent; }
|
||||||
::-webkit-scrollbar-thumb { background:var(--border); border-radius:3px; }
|
::-webkit-scrollbar-thumb { background:var(--border); border-radius:3px; }
|
||||||
/* Responsive */
|
|
||||||
@media(max-width:900px){ .main { flex-direction:column; } .editor-panel,.result-panel { min-width:auto; } .editor-panel { height:50%; } }
|
@media(max-width:900px){ .main { flex-direction:column; } .editor-panel,.result-panel { min-width:auto; } .editor-panel { height:50%; } }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
@@ -93,40 +82,40 @@
|
|||||||
<a href="docs.html">文档</a>
|
<a href="docs.html">文档</a>
|
||||||
<a href="demo.html" class="nav-active">演示</a>
|
<a href="demo.html" class="nav-active">演示</a>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="status"><span class="dot"></span> Memory 模式</div>
|
<div class="status"><span class="dot"></span> Memory 模式 — v0.1.12</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<!-- Editor Panel -->
|
|
||||||
<div class="editor-panel">
|
<div class="editor-panel">
|
||||||
<div class="tabs">
|
|
||||||
<div class="tab active" data-tab="sql">SQL 查询</div>
|
|
||||||
<div class="tab" data-tab="builder">Query Builder</div>
|
|
||||||
</div>
|
|
||||||
<div class="editor-area">
|
<div class="editor-area">
|
||||||
<textarea id="sql-input" placeholder="输入 SQL 语句... SELECT * FROM users WHERE age > 18 INSERT INTO users VALUES ('4', 'Diana', 'diana@test.com', 28) SELECT u.name, o.amount FROM users u INNER JOIN orders o ON u.id = o.user_id">-- 欢迎使用 MetonaSqlark 在线演示 🚀
|
<textarea id="sql-input" placeholder="输入 SQL 语句... SELECT * FROM users; INSERT INTO users VALUES ('4', 'Diana', 'diana@test.com', 28); SELECT u.name, o.amount FROM users u INNER JOIN orders o ON u.id = o.user_id;">-- 🚀 MetonaSqlark v0.1.12 在线演示
|
||||||
-- 已预置 users / orders 表数据,试试以下查询:
|
-- 已预置 users / orders / products 表数据
|
||||||
|
|
||||||
|
-- 查看所有数据
|
||||||
SELECT * FROM users;
|
SELECT * FROM users;
|
||||||
|
|
||||||
|
-- 条件查询 + 排序
|
||||||
SELECT name, age FROM users WHERE age > 25 ORDER BY age DESC;
|
SELECT name, age FROM users WHERE age > 25 ORDER BY age DESC;
|
||||||
|
|
||||||
SELECT u.name, o.amount, o.product
|
-- JOIN 多表关联
|
||||||
|
SELECT u.name, o.product, o.amount
|
||||||
FROM users u INNER JOIN orders o ON u.id = o.user_id;
|
FROM users u INNER JOIN orders o ON u.id = o.user_id;
|
||||||
|
|
||||||
|
-- 聚合统计
|
||||||
SELECT COUNT(*) as total_users, AVG(age) as avg_age FROM users;</textarea>
|
SELECT COUNT(*) as total_users, AVG(age) as avg_age FROM users;</textarea>
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<button class="btn btn-run" onclick="runQuery()">▶ 执行 (Ctrl+Enter)</button>
|
<button class="btn btn-run" onclick="runQuery()">▶ 执行 (Ctrl+Enter)</button>
|
||||||
<button class="btn btn-clear" onclick="clearResults()">🗑 清空</button>
|
<button class="btn btn-clear" onclick="clearResults()">🗑 清空</button>
|
||||||
<span style="flex:1"></span>
|
<span style="flex:1"></span>
|
||||||
<button class="btn btn-preset" onclick="loadPreset('all')">📋 全部数据</button>
|
<button class="btn btn-preset" onclick="loadPreset('all')">📋 全数据</button>
|
||||||
<button class="btn btn-preset" onclick="loadPreset('join')">🔗 JOIN</button>
|
<button class="btn btn-preset" onclick="loadPreset('join')">🔗 JOIN</button>
|
||||||
<button class="btn btn-preset" onclick="loadPreset('agg')">📊 聚合</button>
|
<button class="btn btn-preset" onclick="loadPreset('agg')">📊 聚合</button>
|
||||||
|
<button class="btn btn-preset" onclick="loadPreset('ddl')">🏗 DDL</button>
|
||||||
|
<button class="btn btn-preset" onclick="loadPreset('crud')">✏️ CRUD</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Result Panel -->
|
|
||||||
<div class="result-panel">
|
<div class="result-panel">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
<span>📊 查询结果</span>
|
<span>📊 查询结果</span>
|
||||||
@@ -136,7 +125,7 @@ SELECT COUNT(*) as total_users, AVG(age) as avg_age FROM users;</textarea>
|
|||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
<div class="icon">⚡</div>
|
<div class="icon">⚡</div>
|
||||||
<div>执行 SQL 查询以查看结果</div>
|
<div>执行 SQL 查询以查看结果</div>
|
||||||
<div style="font-size:0.8rem;opacity:0.6">Ctrl + Enter 快捷执行</div>
|
<div style="font-size:0.8rem;opacity:0.6">Ctrl + Enter 快捷执行 · 支持多语句</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -144,7 +133,10 @@ SELECT COUNT(*) as total_users, AVG(age) as avg_age FROM users;</textarea>
|
|||||||
|
|
||||||
<script src="../dist/metona-sqlark.js"></script>
|
<script src="../dist/metona-sqlark.js"></script>
|
||||||
<script>
|
<script>
|
||||||
const { create, MetonaSqlark } = window.MetonaSqlark || window.MeSqlark || {};
|
// Resolve global: try both MetonaSqlark and MeSqlark
|
||||||
|
const globalNs = window.MetonaSqlark || window.MeSqlark || {};
|
||||||
|
const { MetonaSqlark, MeSqlark, create } = globalNs;
|
||||||
|
const DBClass = MetonaSqlark || MeSqlark;
|
||||||
|
|
||||||
let db = null;
|
let db = null;
|
||||||
const resultArea = document.getElementById('result-area');
|
const resultArea = document.getElementById('result-area');
|
||||||
@@ -153,53 +145,68 @@ const sqlInput = document.getElementById('sql-input');
|
|||||||
|
|
||||||
// Init database
|
// Init database
|
||||||
async function initDB() {
|
async function initDB() {
|
||||||
db = new MetonaSqlark({ name: 'demo', mode: 'memory' });
|
try {
|
||||||
await db.init();
|
db = new DBClass({ name: 'demo', mode: 'memory' });
|
||||||
|
await db.init();
|
||||||
|
|
||||||
// Create tables
|
// Create tables
|
||||||
await db.defineTable('users', {
|
await db.defineTable('users', {
|
||||||
id: { type: 'string', primaryKey: true },
|
id: { type: 'string', primaryKey: true },
|
||||||
name: { type: 'string', required: true },
|
name: { type: 'string', required: true },
|
||||||
email: { type: 'string' },
|
email: { type: 'string' },
|
||||||
age: { type: 'number', default: 0 },
|
age: { type: 'number', default: 0 },
|
||||||
});
|
});
|
||||||
await db.defineTable('orders', {
|
await db.defineTable('orders', {
|
||||||
id: { type: 'string', primaryKey: true },
|
id: { type: 'string', primaryKey: true },
|
||||||
user_id: { type: 'string' },
|
user_id: { type: 'string' },
|
||||||
product: { type: 'string' },
|
product: { type: 'string' },
|
||||||
amount: { type: 'number' },
|
amount: { type: 'number' },
|
||||||
});
|
quantity: { type: 'number', default: 1 },
|
||||||
|
});
|
||||||
|
await db.defineTable('products', {
|
||||||
|
id: { type: 'string', primaryKey: true },
|
||||||
|
name: { type: 'string', required: true },
|
||||||
|
price: { type: 'number', default: 0 },
|
||||||
|
category: { type: 'string' },
|
||||||
|
});
|
||||||
|
|
||||||
// Seed data
|
// Seed data
|
||||||
await db.table('users').insertMany([
|
await db.table('users').insertMany([
|
||||||
{ id: '1', name: 'Alice', email: 'alice@demo.com', age: 30 },
|
{ id: '1', name: 'Alice', email: 'alice@demo.com', age: 30 },
|
||||||
{ id: '2', name: 'Bob', email: 'bob@demo.com', age: 25 },
|
{ id: '2', name: 'Bob', email: 'bob@demo.com', age: 25 },
|
||||||
{ id: '3', name: 'Charlie', email: 'charlie@demo.com', age: 35 },
|
{ id: '3', name: 'Charlie', email: 'charlie@demo.com', age: 35 },
|
||||||
]);
|
{ id: '4', name: 'Diana', email: 'diana@demo.com', age: 28 },
|
||||||
await db.table('orders').insertMany([
|
{ id: '5', name: 'Eve', email: 'eve@demo.com', age: 22 },
|
||||||
{ id: 'o1', user_id: '1', product: 'Laptop', amount: 1200 },
|
]);
|
||||||
{ id: 'o2', user_id: '1', product: 'Mouse', amount: 50 },
|
await db.table('orders').insertMany([
|
||||||
{ id: 'o3', user_id: '2', product: 'Keyboard', amount: 150 },
|
{ id: 'o1', user_id: '1', product: 'Laptop', amount: 1200, quantity: 1 },
|
||||||
]);
|
{ id: 'o2', user_id: '1', product: 'Mouse', amount: 50, quantity: 2 },
|
||||||
|
{ id: 'o3', user_id: '2', product: 'Keyboard', amount: 150, quantity: 1 },
|
||||||
|
{ id: 'o4', user_id: '3', product: 'Monitor', amount: 400, quantity: 1 },
|
||||||
|
{ id: 'o5', user_id: '3', product: 'Cable', amount: 20, quantity: 3 },
|
||||||
|
]);
|
||||||
|
await db.table('products').insertMany([
|
||||||
|
{ id: 'p1', name: 'Laptop', price: 1200, category: 'Electronics' },
|
||||||
|
{ id: 'p2', name: 'Mouse', price: 50, category: 'Accessories' },
|
||||||
|
{ id: 'p3', name: 'Keyboard', price: 150, category: 'Accessories' },
|
||||||
|
]);
|
||||||
|
} catch (e) {
|
||||||
|
renderError('数据库初始化失败: ' + e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run SQL
|
// Run SQL
|
||||||
async function runQuery() {
|
async function runQuery() {
|
||||||
const sql = sqlInput.value.trim();
|
const sql = sqlInput.value.trim();
|
||||||
if (!sql) return;
|
if (!sql || !db) return;
|
||||||
|
|
||||||
// Handle multiple statements
|
|
||||||
const statements = sql.split(';').map(s => s.trim()).filter(s => s.length > 0);
|
const statements = sql.split(';').map(s => s.trim()).filter(s => s.length > 0);
|
||||||
|
|
||||||
// For display purposes, if only one statement, use it; otherwise use first
|
|
||||||
try {
|
try {
|
||||||
let lastResult = null;
|
|
||||||
const allResults = [];
|
const allResults = [];
|
||||||
|
|
||||||
for (const stmt of statements) {
|
for (const stmt of statements) {
|
||||||
const result = await db.query(stmt);
|
const result = await db.query(stmt);
|
||||||
allResults.push({ sql: stmt, result });
|
allResults.push({ sql: stmt, result });
|
||||||
lastResult = result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allResults.length === 1) {
|
if (allResults.length === 1) {
|
||||||
@@ -208,7 +215,7 @@ async function runQuery() {
|
|||||||
renderAllResults(allResults);
|
renderAllResults(allResults);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
renderError(e.message);
|
renderError(e.message || String(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,7 +230,7 @@ function renderResult({ sql, result }) {
|
|||||||
function renderAllResults(allResults) {
|
function renderAllResults(allResults) {
|
||||||
let html = '';
|
let html = '';
|
||||||
for (const { sql, result } of allResults) {
|
for (const { sql, result } of allResults) {
|
||||||
html += `<div style="color:var(--accent);font-size:0.78rem;margin-bottom:8px;font-family:monospace;">> ${sql}</div>`;
|
html += `<div class="sql-tag">> ${escapeHTML(sql)}</div>`;
|
||||||
if (Array.isArray(result)) {
|
if (Array.isArray(result)) {
|
||||||
if (result.length === 0) {
|
if (result.length === 0) {
|
||||||
html += '<div class="success-box">✅ 查询返回 0 行</div>';
|
html += '<div class="success-box">✅ 查询返回 0 行</div>';
|
||||||
@@ -231,9 +238,9 @@ function renderAllResults(allResults) {
|
|||||||
html += buildTableHTML(result);
|
html += buildTableHTML(result);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
html += `<div class="success-box">✅ 执行成功</div>`;
|
html += `<div class="success-box">✅ 执行成功 (影响 ${result ?? 0} 行)</div>`;
|
||||||
}
|
}
|
||||||
html += '<div style="height:16px"></div>';
|
html += '<div style="height:12px"></div>';
|
||||||
}
|
}
|
||||||
resultArea.innerHTML = html;
|
resultArea.innerHTML = html;
|
||||||
updateRowCount(allResults.reduce((sum, r) => sum + (Array.isArray(r.result) ? r.result.length : 0), 0));
|
updateRowCount(allResults.reduce((sum, r) => sum + (Array.isArray(r.result) ? r.result.length : 0), 0));
|
||||||
@@ -260,7 +267,7 @@ function buildTableHTML(rows) {
|
|||||||
html += '<tr>';
|
html += '<tr>';
|
||||||
for (const col of columns) {
|
for (const col of columns) {
|
||||||
const val = row[col];
|
const val = row[col];
|
||||||
html += `<td>${escapeHTML(val === null ? 'NULL' : String(val))}</td>`;
|
html += `<td>${formatCell(val)}</td>`;
|
||||||
}
|
}
|
||||||
html += '</tr>';
|
html += '</tr>';
|
||||||
}
|
}
|
||||||
@@ -268,6 +275,14 @@ function buildTableHTML(rows) {
|
|||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatCell(val) {
|
||||||
|
if (val === null) return '<span style="color:#64748b;font-style:italic">NULL</span>';
|
||||||
|
if (val === undefined) return '<span style="color:#64748b">-</span>';
|
||||||
|
if (typeof val === 'boolean') return `<span style="color:#fbbf24">${val}</span>`;
|
||||||
|
if (typeof val === 'number') return `<span style="color:#fbbf24">${val}</span>`;
|
||||||
|
return escapeHTML(String(val));
|
||||||
|
}
|
||||||
|
|
||||||
function renderError(msg) {
|
function renderError(msg) {
|
||||||
resultArea.innerHTML = `<div class="error-box">❌ ${escapeHTML(msg)}</div>`;
|
resultArea.innerHTML = `<div class="error-box">❌ ${escapeHTML(msg)}</div>`;
|
||||||
updateRowCount(0);
|
updateRowCount(0);
|
||||||
@@ -282,32 +297,80 @@ function clearResults() {
|
|||||||
updateRowCount(0);
|
updateRowCount(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateRowCount(n) {
|
function updateRowCount(n) { rowCount.textContent = `${n} 行`; }
|
||||||
rowCount.textContent = `${n} 行`;
|
function escapeHTML(str) { return String(str).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,'''); }
|
||||||
}
|
|
||||||
|
|
||||||
function escapeHTML(str) {
|
|
||||||
return String(str).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Presets
|
// Presets
|
||||||
const presets = {
|
const presets = {
|
||||||
all: `-- 查看所有用户
|
all: `-- 查看所有数据
|
||||||
SELECT * FROM users;
|
SELECT * FROM users;
|
||||||
|
|
||||||
-- 查看所有订单
|
SELECT * FROM orders;
|
||||||
SELECT * FROM orders;`,
|
|
||||||
|
SELECT * FROM products;`,
|
||||||
join: `-- INNER JOIN:用户 + 订单
|
join: `-- INNER JOIN:用户 + 订单
|
||||||
SELECT u.name, o.product, o.amount
|
SELECT u.name, o.product, o.amount, o.quantity
|
||||||
FROM users u
|
FROM users u
|
||||||
INNER JOIN orders o ON u.id = o.user_id
|
INNER JOIN orders o ON u.id = o.user_id
|
||||||
ORDER BY o.amount DESC;`,
|
ORDER BY o.amount DESC;
|
||||||
agg: `-- 聚合:按用户统计订单总额
|
|
||||||
|
-- LEFT JOIN:包含没有订单的用户
|
||||||
|
SELECT u.name, o.product, o.amount
|
||||||
|
FROM users u
|
||||||
|
LEFT JOIN orders o ON u.id = o.user_id;`,
|
||||||
|
agg: `-- 聚合统计:每个用户的消费
|
||||||
SELECT u.name, COUNT(*) as orders, SUM(o.amount) as total_spent
|
SELECT u.name, COUNT(*) as orders, SUM(o.amount) as total_spent
|
||||||
FROM users u
|
FROM users u
|
||||||
INNER JOIN orders o ON u.id = o.user_id
|
INNER JOIN orders o ON u.id = o.user_id
|
||||||
GROUP BY u.name
|
GROUP BY u.name
|
||||||
ORDER BY total_spent DESC;`
|
ORDER BY total_spent DESC;
|
||||||
|
|
||||||
|
-- 全局聚合
|
||||||
|
SELECT COUNT(*) as total_users, AVG(age) as avg_age,
|
||||||
|
MIN(age) as min_age, MAX(age) as max_age
|
||||||
|
FROM users;
|
||||||
|
|
||||||
|
-- 分类统计
|
||||||
|
SELECT category, COUNT(*) as product_count, AVG(price) as avg_price
|
||||||
|
FROM products
|
||||||
|
GROUP BY category;`,
|
||||||
|
ddl: `-- 查看所有表
|
||||||
|
-- (使用 db.getTableNames() 替代)
|
||||||
|
|
||||||
|
-- 创建新表
|
||||||
|
CREATE TABLE tasks (
|
||||||
|
id STRING PRIMARY KEY,
|
||||||
|
title STRING NOT NULL,
|
||||||
|
done BOOLEAN DEFAULT false,
|
||||||
|
priority NUMBER DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 插入数据
|
||||||
|
INSERT INTO tasks (id, title, done, priority) VALUES ('t1', 'Learn SQLark', false, 1);
|
||||||
|
INSERT INTO tasks VALUES ('t2', 'Write docs', true, 2);
|
||||||
|
|
||||||
|
-- 查询
|
||||||
|
SELECT * FROM tasks ORDER BY priority DESC;
|
||||||
|
|
||||||
|
-- 删除表
|
||||||
|
DROP TABLE tasks;`,
|
||||||
|
crud: `-- 增
|
||||||
|
INSERT INTO users VALUES ('9', 'Frank', 'frank@demo.com', 40);
|
||||||
|
|
||||||
|
-- 查
|
||||||
|
SELECT * FROM users WHERE name = 'Frank';
|
||||||
|
|
||||||
|
-- 改
|
||||||
|
UPDATE users SET age = 41, email = 'frank2@demo.com' WHERE id = '9';
|
||||||
|
|
||||||
|
-- 查(确认)
|
||||||
|
SELECT * FROM users WHERE id = '9';
|
||||||
|
|
||||||
|
-- 删
|
||||||
|
DELETE FROM users WHERE id = '9';
|
||||||
|
|
||||||
|
-- 确认
|
||||||
|
SELECT COUNT(*) as user_count FROM users;`
|
||||||
};
|
};
|
||||||
|
|
||||||
function loadPreset(name) {
|
function loadPreset(name) {
|
||||||
@@ -325,19 +388,12 @@ document.addEventListener('keydown', e => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Tab switching
|
|
||||||
document.querySelectorAll('.tab').forEach(tab => {
|
|
||||||
tab.addEventListener('click', function() {
|
|
||||||
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
|
||||||
this.classList.add('active');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Boot
|
// Boot
|
||||||
initDB().then(() => {
|
initDB().then(() => {
|
||||||
console.log('✅ MetonaSqlark demo ready');
|
console.log('✅ MetonaSqlark v0.1.12 demo ready');
|
||||||
// Auto-run initial query
|
setTimeout(runQuery, 300);
|
||||||
setTimeout(runQuery, 200);
|
}).catch(err => {
|
||||||
|
renderError('初始化失败: ' + err.message);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
+416
-104
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>API 文档 — MetonaSqlark</title>
|
<title>API 文档 — MetonaSqlark v0.1.12</title>
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--bg:#0a0a0f; --surface:#14141f; --surface2:#1a1a2e; --border:#2a2a45;
|
--bg:#0a0a0f; --surface:#14141f; --surface2:#1a1a2e; --border:#2a2a45;
|
||||||
@@ -13,7 +13,6 @@
|
|||||||
}
|
}
|
||||||
* { margin:0; padding:0; box-sizing:border-box; }
|
* { margin:0; padding:0; box-sizing:border-box; }
|
||||||
body { font-family:'Inter',-apple-system,BlinkMacSystemFont,system-ui,sans-serif; background:var(--bg); color:var(--text); line-height:1.7; }
|
body { font-family:'Inter',-apple-system,BlinkMacSystemFont,system-ui,sans-serif; background:var(--bg); color:var(--text); line-height:1.7; }
|
||||||
/* Header */
|
|
||||||
header {
|
header {
|
||||||
position:fixed; top:0; left:0; right:0; z-index:100; backdrop-filter:blur(20px);
|
position:fixed; top:0; left:0; right:0; z-index:100; backdrop-filter:blur(20px);
|
||||||
background:rgba(10,10,15,0.85); border-bottom:1px solid var(--border); padding:16px 32px;
|
background:rgba(10,10,15,0.85); border-bottom:1px solid var(--border); padding:16px 32px;
|
||||||
@@ -25,7 +24,6 @@
|
|||||||
nav { display:flex; gap:28px; }
|
nav { display:flex; gap:28px; }
|
||||||
nav a { color:var(--text2); text-decoration:none; font-weight:500; font-size:0.92rem; transition:.2s; }
|
nav a { color:var(--text2); text-decoration:none; font-weight:500; font-size:0.92rem; transition:.2s; }
|
||||||
nav a:hover,.nav-active { color:var(--text); }
|
nav a:hover,.nav-active { color:var(--text); }
|
||||||
/* Layout */
|
|
||||||
.layout { display:flex; max-width:1400px; margin:0 auto; padding-top:80px; min-height:100vh; }
|
.layout { display:flex; max-width:1400px; margin:0 auto; padding-top:80px; min-height:100vh; }
|
||||||
.sidebar {
|
.sidebar {
|
||||||
width:260px; min-width:260px; padding:32px 24px; position:sticky; top:80px; height:calc(100vh - 80px);
|
width:260px; min-width:260px; padding:32px 24px; position:sticky; top:80px; height:calc(100vh - 80px);
|
||||||
@@ -36,7 +34,7 @@
|
|||||||
.sidebar a { display:block; padding:7px 12px; border-radius:6px; color:var(--text2); text-decoration:none; font-size:0.9rem; transition:.15s; }
|
.sidebar a { display:block; padding:7px 12px; border-radius:6px; color:var(--text2); text-decoration:none; font-size:0.9rem; transition:.15s; }
|
||||||
.sidebar a:hover,.sidebar a.active { color:var(--text); background:var(--surface2); }
|
.sidebar a:hover,.sidebar a.active { color:var(--text); background:var(--surface2); }
|
||||||
.sidebar a.active { border-left:3px solid var(--primary); }
|
.sidebar a.active { border-left:3px solid var(--primary); }
|
||||||
.content { flex:1; padding:32px 48px 80px; max-width:900px; }
|
.content { flex:1; padding:32px 48px 80px; max-width:950px; }
|
||||||
.content h2 { font-size:1.8rem; font-weight:800; margin:48px 0 16px; padding-top:24px; border-top:1px solid var(--border); }
|
.content h2 { font-size:1.8rem; font-weight:800; margin:48px 0 16px; padding-top:24px; border-top:1px solid var(--border); }
|
||||||
.content h2:first-of-type { margin-top:0; padding-top:0; border-top:none; }
|
.content h2:first-of-type { margin-top:0; padding-top:0; border-top:none; }
|
||||||
.content h3 { font-size:1.25rem; font-weight:700; margin:32px 0 12px; color:var(--primary-glow); }
|
.content h3 { font-size:1.25rem; font-weight:700; margin:32px 0 12px; color:var(--primary-glow); }
|
||||||
@@ -53,13 +51,11 @@
|
|||||||
.content pre .k { color:#c084fc; } .content pre .s { color:#34d399; }
|
.content pre .k { color:#c084fc; } .content pre .s { color:#34d399; }
|
||||||
.content pre .f { color:#60a5fa; } .content pre .c { color:#64748b; }
|
.content pre .f { color:#60a5fa; } .content pre .c { color:#64748b; }
|
||||||
.content pre .n { color:#fbbf24; } .content pre .t { color:#f472b6; }
|
.content pre .n { color:#fbbf24; } .content pre .t { color:#f472b6; }
|
||||||
/* Table */
|
|
||||||
.content table { width:100%; border-collapse:collapse; margin:16px 0; }
|
.content table { width:100%; border-collapse:collapse; margin:16px 0; }
|
||||||
.content th,.content td { text-align:left; padding:12px 16px; border-bottom:1px solid var(--border); font-size:0.9rem; }
|
.content th,.content td { text-align:left; padding:12px 16px; border-bottom:1px solid var(--border); font-size:0.9rem; }
|
||||||
.content th { color:var(--text); font-weight:600; background:var(--surface); }
|
.content th { color:var(--text); font-weight:600; background:var(--surface); }
|
||||||
.content td { color:var(--text2); }
|
.content td { color:var(--text2); }
|
||||||
.content td code { font-size:0.82rem; }
|
.content td code { font-size:0.82rem; }
|
||||||
/* Scrollbar */
|
|
||||||
::-webkit-scrollbar { width:6px; } ::-webkit-scrollbar-track { background:transparent; } ::-webkit-scrollbar-thumb { background:var(--border); border-radius:3px; }
|
::-webkit-scrollbar { width:6px; } ::-webkit-scrollbar-track { background:transparent; } ::-webkit-scrollbar-thumb { background:var(--border); border-radius:3px; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
@@ -81,200 +77,516 @@
|
|||||||
<h4>快速开始</h4>
|
<h4>快速开始</h4>
|
||||||
<a href="#install" class="active">安装</a>
|
<a href="#install" class="active">安装</a>
|
||||||
<a href="#create">创建数据库</a>
|
<a href="#create">创建数据库</a>
|
||||||
<a href="#table">定义表</a>
|
<a href="#define-table">定义表</a>
|
||||||
<h4>查询 API</h4>
|
<h4>查询 API</h4>
|
||||||
<a href="#sql-query">SQL 查询</a>
|
<a href="#sql-query">SQL 查询</a>
|
||||||
<a href="#query-builder">Query Builder</a>
|
<a href="#query-builder">Query Builder</a>
|
||||||
<a href="#join">JOIN 查询</a>
|
<a href="#join">JOIN 查询</a>
|
||||||
<a href="#groupby">GROUP BY</a>
|
<a href="#groupby">GROUP BY & 聚合</a>
|
||||||
|
<a href="#where-ops">WHERE 操作符</a>
|
||||||
<h4>高级特性</h4>
|
<h4>高级特性</h4>
|
||||||
<a href="#transaction">事务</a>
|
<a href="#transaction">事务</a>
|
||||||
<a href="#migration">迁移</a>
|
<a href="#migration">数据迁移</a>
|
||||||
<a href="#export">导入导出</a>
|
<a href="#export">导入导出</a>
|
||||||
<a href="#plugin">插件钩子</a>
|
<a href="#plugin">插件 & 钩子</a>
|
||||||
<a href="#subscribe">发布订阅</a>
|
<a href="#subscribe">发布订阅</a>
|
||||||
<h4>类型</h4>
|
<h4>框架集成</h4>
|
||||||
|
<a href="#react">React 集成</a>
|
||||||
|
<a href="#vue">Vue 集成</a>
|
||||||
|
<h4>类型参考</h4>
|
||||||
<a href="#types">TypeScript 类型</a>
|
<a href="#types">TypeScript 类型</a>
|
||||||
<a href="#config">配置项</a>
|
<a href="#config">完整配置项</a>
|
||||||
|
<a href="#engine">存储引擎</a>
|
||||||
|
<a href="#errors">错误处理</a>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<main class="content">
|
<main class="content">
|
||||||
|
|
||||||
<h2 id="install">📦 安装</h2>
|
<h2 id="install">📦 安装</h2>
|
||||||
<pre><span class="c">// npm</span>
|
<p>MetonaSqlark 支持多种引入方式,覆盖 npm、CDN、ESM、CJS 所有常见场景。</p>
|
||||||
npm install @metona-team/MetonaSqlark
|
|
||||||
|
|
||||||
<span class="c">// ESM</span>
|
<h3>npm 安装(推荐)</h3>
|
||||||
<span class="k">import</span> { MetonaSqlark, MeSqlark } <span class="k">from</span> <span class="s">'MetonaSqlark'</span>;
|
<pre><span class="c"># 配置 Gitea Registry(一次性)</span>
|
||||||
|
npm config set @metona-team:registry https://git.metona.cn/api/packages/MetonaTeam/npm/
|
||||||
|
|
||||||
<span class="c">// Browser</span>
|
<span class="c"># 安装</span>
|
||||||
<script src=<span class="s">"metona-sqlark.min.js"</span>></script>
|
npm install @metona-team/metona-sqlark</pre>
|
||||||
<span class="c">// → window.MetonaSqlark / window.MeSqlark</span></pre>
|
|
||||||
|
<h3>CDN / UMD</h3>
|
||||||
|
<pre><span class="c"><!-- UMD 格式,暴露 window.MetonaSqlark 和 window.MeSqlark --></span>
|
||||||
|
<script src=<span class="s">"https://git.metona.cn/.../metona-sqlark.min.js"</span>></script>
|
||||||
|
<script>
|
||||||
|
<span class="k">const</span> db = <span class="k">await</span> window.<span class="f">MetonaSqlark</span>.create({...});
|
||||||
|
<span class="c">// 或 window.MeSqlark.create(...) — 完全等价</span>
|
||||||
|
</script></pre>
|
||||||
|
|
||||||
|
<h3>ESM</h3>
|
||||||
|
<pre><span class="k">import</span> { MetonaSqlark, MeSqlark } <span class="k">from</span> <span class="s">'@metona-team/metona-sqlark'</span>;
|
||||||
|
<span class="c">// MeSqlark 是 MetonaSqlark 的别名,行为完全一致</span></pre>
|
||||||
|
|
||||||
|
<h3>CJS</h3>
|
||||||
|
<pre><span class="k">const</span> { MetonaSqlark } = <span class="f">require</span>(<span class="s">'@metona-team/metona-sqlark'</span>);</pre>
|
||||||
|
|
||||||
|
<p>输出文件说明:</p>
|
||||||
|
<table>
|
||||||
|
<tr><th>文件</th><th>格式</th><th>用途</th></tr>
|
||||||
|
<tr><td><code>metona-sqlark.js</code></td><td>UMD</td><td>浏览器开发版(含 sourcemap)</td></tr>
|
||||||
|
<tr><td><code>metona-sqlark.min.js</code></td><td>UMD (minified)</td><td>生产环境(~42KB / ~10KB gzip)</td></tr>
|
||||||
|
<tr><td><code>metona-sqlark.esm.js</code></td><td>ES Module</td><td>现代打包工具 / 浏览器 ESM</td></tr>
|
||||||
|
<tr><td><code>metona-sqlark.cjs.js</code></td><td>CommonJS</td><td>Node.js require()</td></tr>
|
||||||
|
<tr><td><code>metona-sqlark.d.ts</code></td><td>TypeScript 声明</td><td>类型提示</td></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
<h2 id="create">🏗 创建数据库</h2>
|
<h2 id="create">🏗 创建数据库</h2>
|
||||||
<p><code>MetonaSqlark.create(config)</code> 工厂函数,返回初始化好的实例。</p>
|
<p><code>MetonaSqlark.create(config)</code> — 工厂函数,自动创建并初始化数据库实例。</p>
|
||||||
|
|
||||||
<pre><span class="k">const</span> db = <span class="k">await</span> <span class="f">MetonaSqlark.create</span>({
|
<pre><span class="k">const</span> db = <span class="k">await</span> <span class="f">MetonaSqlark.create</span>({
|
||||||
<span class="s">name</span>: <span class="s">'my-app'</span>,
|
<span class="s">name</span>: <span class="s">'my-app'</span>,
|
||||||
<span class="s">mode</span>: <span class="s">'hybrid'</span>, <span class="c">// 'memory' | 'disk' | 'hybrid'</span>
|
<span class="s">mode</span>: <span class="s">'hybrid'</span>, <span class="c">// 'memory' | 'disk' | 'hybrid'</span>
|
||||||
<span class="s">diskEngine</span>: <span class="s">'indexeddb'</span>, <span class="c">// 'indexeddb' | 'opfs'</span>
|
<span class="s">diskEngine</span>: <span class="s">'indexeddb'</span>, <span class="c">// 'indexeddb' | 'opfs'(仅 disk/hybrid 生效)</span>
|
||||||
<span class="s">version</span>: <span class="n">1</span>,
|
<span class="s">version</span>: <span class="n">1</span>,
|
||||||
});</pre>
|
<span class="s">plugins</span>: [], <span class="c">// MetonaPlugin[]</span>
|
||||||
|
<span class="s">onReady</span>: (db) => {}, <span class="c">// 就绪回调</span>
|
||||||
|
<span class="s">onError</span>: (err) => {}, <span class="c">// 错误回调</span>
|
||||||
|
});
|
||||||
|
|
||||||
<table>
|
<span class="c">// 也可手动实例化</span>
|
||||||
<tr><th>属性</th><th>类型</th><th>默认</th><th>说明</th></tr>
|
<span class="k">const</span> db2 = <span class="k">new</span> <span class="f">MetonaSqlark</span>(config);
|
||||||
<tr><td><code>name</code></td><td><code>string</code></td><td>-</td><td>数据库名称</td></tr>
|
<span class="k">await</span> db2.<span class="f">init</span>();
|
||||||
<tr><td><code>mode</code></td><td><code>'memory'|'disk'|'hybrid'</code></td><td><code>'hybrid'</code></td><td>存储模式</td></tr>
|
|
||||||
<tr><td><code>diskEngine</code></td><td><code>'indexeddb'|'opfs'</code></td><td><code>'indexeddb'</code></td><td>磁盘引擎</td></tr>
|
<span class="c">// 检查状态</span>
|
||||||
<tr><td><code>version</code></td><td><code>number</code></td><td><code>1</code></td><td>版本号</td></tr>
|
db.<span class="f">isReady</span>(); <span class="c">// true</span>
|
||||||
</table>
|
|
||||||
|
<span class="c">// 关闭</span>
|
||||||
|
<span class="k">await</span> db.<span class="f">close</span>();</pre>
|
||||||
|
|
||||||
|
<h2 id="define-table">📋 定义表</h2>
|
||||||
|
<p>使用 <code>db.defineTable(name, columns)</code> 定义表结构。</p>
|
||||||
|
|
||||||
<h2 id="table">📋 定义表</h2>
|
|
||||||
<pre><span class="k">await</span> db.<span class="f">defineTable</span>(<span class="s">'users'</span>, {
|
<pre><span class="k">await</span> db.<span class="f">defineTable</span>(<span class="s">'users'</span>, {
|
||||||
<span class="s">id</span>: { <span class="s">type</span>: <span class="s">'string'</span>, <span class="s">primaryKey</span>: <span class="k">true</span> },
|
<span class="s">id</span>: { <span class="s">type</span>: <span class="s">'string'</span>, <span class="s">primaryKey</span>: <span class="k">true</span> },
|
||||||
<span class="s">name</span>: { <span class="s">type</span>: <span class="s">'string'</span>, <span class="s">required</span>: <span class="k">true</span> },
|
<span class="s">name</span>: { <span class="s">type</span>: <span class="s">'string'</span>, <span class="s">required</span>: <span class="k">true</span> },
|
||||||
<span class="s">email</span>: { <span class="s">type</span>: <span class="s">'string'</span>, <span class="s">unique</span>: <span class="k">true</span>, <span class="s">index</span>: <span class="k">true</span> },
|
<span class="s">email</span>: { <span class="s">type</span>: <span class="s">'string'</span>, <span class="s">unique</span>: <span class="k">true</span>, <span class="s">index</span>: <span class="k">true</span> },
|
||||||
<span class="s">age</span>: { <span class="s">type</span>: <span class="s">'number'</span>, <span class="s">default</span>: <span class="n">0</span> },
|
<span class="s">age</span>: { <span class="s">type</span>: <span class="s">'number'</span>, <span class="s">default</span>: <span class="n">0</span>, <span class="s">min</span>: <span class="n">0</span>, <span class="s">max</span>: <span class="n">150</span> },
|
||||||
<span class="s">dept_id</span>: { <span class="s">type</span>: <span class="s">'number'</span>, <span class="s">references</span>: <span class="s">'departments.id'</span> },
|
<span class="s">active</span>: { <span class="s">type</span>: <span class="s">'boolean'</span>, <span class="s">default</span>: <span class="k">true</span> },
|
||||||
});</pre>
|
<span class="s">birthday</span>: { <span class="s">type</span>: <span class="s">'date'</span> },
|
||||||
|
<span class="s">meta</span>: { <span class="s">type</span>: <span class="s">'json'</span> },
|
||||||
|
<span class="s">dept_id</span>: { <span class="s">type</span>: <span class="s">'number'</span>, <span class="s">references</span>: <span class="s">'departments.id'</span> },
|
||||||
|
});
|
||||||
|
|
||||||
|
<span class="c">// 表操作</span>
|
||||||
|
<span class="k">await</span> db.<span class="f">getTableNames</span>(); <span class="c">// ['users']</span>
|
||||||
|
<span class="k">await</span> db.<span class="f">dropTable</span>(<span class="s">'users'</span>); <span class="c">// 删除表</span></pre>
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<tr><th>字段</th><th>类型</th><th>说明</th></tr>
|
<tr><th>ColumnDef 属性</th><th>类型</th><th>说明</th></tr>
|
||||||
<tr><td><code>type</code></td><td><code>string|number|boolean|date|json</code></td><td>数据类型</td></tr>
|
<tr><td><code>type</code></td><td><code>'string'|'number'|'boolean'|'date'|'json'</code></td><td>数据类型</td></tr>
|
||||||
<tr><td><code>primaryKey</code></td><td><code>boolean</code></td><td>主键</td></tr>
|
<tr><td><code>primaryKey</code></td><td><code>boolean</code></td><td>主键(每表至少一个)</td></tr>
|
||||||
<tr><td><code>required</code></td><td><code>boolean</code></td><td>必填</td></tr>
|
<tr><td><code>required</code></td><td><code>boolean</code></td><td>是否必填</td></tr>
|
||||||
<tr><td><code>unique</code></td><td><code>boolean</code></td><td>唯一约束</td></tr>
|
<tr><td><code>unique</code></td><td><code>boolean</code></td><td>唯一约束(自动建索引)</td></tr>
|
||||||
<tr><td><code>index</code></td><td><code>boolean</code></td><td>创建索引</td></tr>
|
<tr><td><code>index</code></td><td><code>boolean</code></td><td>创建哈希索引,O(1) 加速查询</td></tr>
|
||||||
<tr><td><code>default</code></td><td><code>unknown</code></td><td>默认值</td></tr>
|
<tr><td><code>default</code></td><td><code>unknown</code></td><td>默认值</td></tr>
|
||||||
<tr><td><code>references</code></td><td><code>string</code></td><td>外键引用</td></tr>
|
<tr><td><code>maxLength</code></td><td><code>number</code></td><td>字符串最大长度</td></tr>
|
||||||
|
<tr><td><code>min</code>/<code>max</code></td><td><code>number</code></td><td>数值范围</td></tr>
|
||||||
|
<tr><td><code>references</code></td><td><code>string</code></td><td>外键引用 <code>'table.column'</code></td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<h2 id="sql-query">🔍 SQL 查询</h2>
|
<h2 id="sql-query">🔍 SQL 查询</h2>
|
||||||
<pre><span class="c">// INSERT</span>
|
<p><code>db.query(sql)</code> — 执行标准 SQL 字符串,返回查询结果。</p>
|
||||||
|
|
||||||
|
<h3>完整 SQL 语法支持</h3>
|
||||||
|
<pre><span class="c">// SELECT — 核心查询</span>
|
||||||
|
<span class="k">const</span> rows = <span class="k">await</span> db.<span class="f">query</span>(<span class="s">`SELECT * FROM users
|
||||||
|
WHERE age > 18
|
||||||
|
ORDER BY name ASC
|
||||||
|
LIMIT 10 OFFSET 0`</span>);
|
||||||
|
|
||||||
|
<span class="c">// INSERT — 插入数据</span>
|
||||||
<span class="k">await</span> db.<span class="f">query</span>(<span class="s">"INSERT INTO users (id, name, age) VALUES ('1', 'Alice', 30)"</span>);
|
<span class="k">await</span> db.<span class="f">query</span>(<span class="s">"INSERT INTO users (id, name, age) VALUES ('1', 'Alice', 30)"</span>);
|
||||||
|
<span class="k">await</span> db.<span class="f">query</span>(<span class="s">"INSERT INTO users VALUES ('2', 'Bob', 25)"</span>);
|
||||||
|
<span class="c">// 支持多行插入</span>
|
||||||
|
<span class="k">await</span> db.<span class="f">query</span>(<span class="s">"INSERT INTO users VALUES ('3', 'C'), ('4', 'D')"</span>);
|
||||||
|
|
||||||
<span class="c">// SELECT</span>
|
<span class="c">// UPDATE — 更新数据</span>
|
||||||
<span class="k">const</span> rows = <span class="k">await</span> db.<span class="f">query</span>(<span class="s">'SELECT * FROM users WHERE age > 18 ORDER BY name LIMIT 10'</span>);
|
<span class="k">await</span> db.<span class="f">query</span>(<span class="s">"UPDATE users SET age = 31, active = true WHERE id = '1'"</span>);
|
||||||
|
|
||||||
<span class="c">// UPDATE</span>
|
<span class="c">// DELETE — 删除数据</span>
|
||||||
<span class="k">await</span> db.<span class="f">query</span>(<span class="s">"UPDATE users SET age = 31 WHERE id = '1'"</span>);
|
|
||||||
|
|
||||||
<span class="c">// DELETE</span>
|
|
||||||
<span class="k">await</span> db.<span class="f">query</span>(<span class="s">"DELETE FROM users WHERE id = '1'"</span>);
|
<span class="k">await</span> db.<span class="f">query</span>(<span class="s">"DELETE FROM users WHERE id = '1'"</span>);
|
||||||
|
|
||||||
<span class="c">// DDL</span>
|
<span class="c">// DDL — 表结构操作</span>
|
||||||
<span class="k">await</span> db.<span class="f">query</span>(<span class="s">'DROP TABLE users'</span>);</pre>
|
<span class="k">await</span> db.<span class="f">query</span>(<span class="s">`CREATE TABLE products (
|
||||||
|
id STRING PRIMARY KEY,
|
||||||
|
name STRING NOT NULL,
|
||||||
|
price NUMBER DEFAULT 0
|
||||||
|
)`</span>);
|
||||||
|
<span class="k">await</span> db.<span class="f">query</span>(<span class="s">'DROP TABLE products'</span>);</pre>
|
||||||
|
|
||||||
|
<h3>条件表达式</h3>
|
||||||
|
<pre><span class="c">// 比较运算符</span>
|
||||||
|
<span class="s">`WHERE age > 18 AND name LIKE 'A%'`</span>
|
||||||
|
<span class="s">`WHERE salary >= 5000 OR dept = 'Engineering'`</span>
|
||||||
|
|
||||||
|
<span class="c">// IN / NOT IN</span>
|
||||||
|
<span class="s">`WHERE dept IN ('Engineering', 'Sales')`</span>
|
||||||
|
|
||||||
|
<span class="c">// NULL 检查</span>
|
||||||
|
<span class="s">`WHERE email IS NULL`</span>
|
||||||
|
<span class="s">`WHERE email IS NOT NULL`</span>
|
||||||
|
|
||||||
|
<span class="c">// NOT 取反</span>
|
||||||
|
<span class="s">`WHERE NOT (age < 18 OR age > 65)`</span>
|
||||||
|
|
||||||
|
<span class="c">// 嵌套条件</span>
|
||||||
|
<span class="s">`WHERE (age > 18 AND active = true) OR role = 'admin'`</span></pre>
|
||||||
|
|
||||||
<h2 id="query-builder">⛓ Query Builder</h2>
|
<h2 id="query-builder">⛓ Query Builder</h2>
|
||||||
|
<p>链式 API,TypeScript 友好,享受 IDE 自动补全。</p>
|
||||||
|
|
||||||
|
<h3>SELECT</h3>
|
||||||
<pre><span class="k">const</span> users = db.<span class="f">table</span>(<span class="s">'users'</span>);
|
<pre><span class="k">const</span> users = db.<span class="f">table</span>(<span class="s">'users'</span>);
|
||||||
|
|
||||||
<span class="c">// 插入</span>
|
<span class="c">// 全量查询</span>
|
||||||
<span class="k">await</span> users.<span class="f">insert</span>({ <span class="s">id</span>: <span class="s">'1'</span>, <span class="s">name</span>: <span class="s">'Alice'</span> });
|
<span class="k">await</span> users.<span class="f">select</span>().<span class="f">execute</span>();
|
||||||
<span class="k">await</span> users.<span class="f">insertMany</span>([...]);
|
|
||||||
|
|
||||||
<span class="c">// 查询</span>
|
<span class="c">// 指定列 + 条件 + 排序 + 分页</span>
|
||||||
<span class="k">const</span> result = <span class="k">await</span> users
|
<span class="k">const</span> result = <span class="k">await</span> users
|
||||||
.<span class="f">select</span>([<span class="s">'name'</span>, <span class="s">'age'</span>])
|
.<span class="f">select</span>([<span class="s">'name'</span>, <span class="s">'age'</span>, <span class="s">'email'</span>])
|
||||||
.<span class="f">where</span>({ <span class="s">age</span>: { <span class="s">$gt</span>: <span class="n">18</span> }, <span class="s">name</span>: { <span class="s">$like</span>: <span class="s">'A%'</span> } })
|
.<span class="f">where</span>({
|
||||||
|
<span class="s">age</span>: { <span class="s">$gt</span>: <span class="n">18</span> },
|
||||||
|
<span class="s">name</span>: { <span class="s">$like</span>: <span class="s">'A%'</span> },
|
||||||
|
})
|
||||||
.<span class="f">orderBy</span>(<span class="s">'age'</span>, <span class="s">'desc'</span>)
|
.<span class="f">orderBy</span>(<span class="s">'age'</span>, <span class="s">'desc'</span>)
|
||||||
.<span class="f">limit</span>(<span class="n">10</span>).<span class="f">offset</span>(<span class="n">0</span>)
|
.<span class="f">limit</span>(<span class="n">10</span>)
|
||||||
.<span class="f">execute</span>();
|
.<span class="f">offset</span>(<span class="n">0</span>)
|
||||||
|
.<span class="f">execute</span>();</pre>
|
||||||
|
|
||||||
<span class="c">// 更新/删除</span>
|
<h3>INSERT</h3>
|
||||||
<span class="k">await</span> users.<span class="f">update</span>({ <span class="s">age</span>: <span class="n">31</span> }).<span class="f">where</span>({ <span class="s">id</span>: <span class="s">'1'</span> }).<span class="f">execute</span>();
|
<pre><span class="c">// 单行插入 — 返回主键值</span>
|
||||||
<span class="k">await</span> users.<span class="f">delete</span>().<span class="f">where</span>({ <span class="s">id</span>: <span class="s">'1'</span> }).<span class="f">execute</span>();</pre>
|
<span class="k">const</span> pk = <span class="k">await</span> users.<span class="f">insert</span>({ <span class="s">id</span>: <span class="s">'1'</span>, <span class="s">name</span>: <span class="s">'Alice'</span>, <span class="s">age</span>: <span class="n">30</span> });
|
||||||
|
|
||||||
|
<span class="c">// 批量插入 — 返回主键值数组</span>
|
||||||
|
<span class="k">const</span> pks = <span class="k">await</span> users.<span class="f">insertMany</span>([
|
||||||
|
{ <span class="s">id</span>: <span class="s">'2'</span>, <span class="s">name</span>: <span class="s">'Bob'</span>, <span class="s">age</span>: <span class="n">25</span> },
|
||||||
|
{ <span class="s">id</span>: <span class="s">'3'</span>, <span class="s">name</span>: <span class="s">'Charlie'</span>, <span class="s">age</span>: <span class="n">35</span> },
|
||||||
|
]);</pre>
|
||||||
|
|
||||||
|
<h3>UPDATE / DELETE</h3>
|
||||||
|
<pre><span class="c">// 更新 — 返回影响行数</span>
|
||||||
|
<span class="k">const</span> updated = <span class="k">await</span> users
|
||||||
|
.<span class="f">update</span>({ <span class="s">age</span>: <span class="n">31</span>, <span class="s">active</span>: <span class="k">false</span> })
|
||||||
|
.<span class="f">where</span>({ <span class="s">id</span>: <span class="s">'1'</span> })
|
||||||
|
.<span class="f">execute</span>(); <span class="c">// 1</span>
|
||||||
|
|
||||||
|
<span class="c">// 删除 — 返回影响行数</span>
|
||||||
|
<span class="k">const</span> deleted = <span class="k">await</span> users
|
||||||
|
.<span class="f">delete</span>()
|
||||||
|
.<span class="f">where</span>({ <span class="s">id</span>: <span class="s">'1'</span> })
|
||||||
|
.<span class="f">execute</span>(); <span class="c">// 1</span>
|
||||||
|
|
||||||
|
<span class="c">// 全表删除</span>
|
||||||
|
<span class="k">await</span> users.<span class="f">delete</span>().<span class="f">execute</span>();</pre>
|
||||||
|
|
||||||
<h2 id="join">🔗 JOIN 查询</h2>
|
<h2 id="join">🔗 JOIN 查询</h2>
|
||||||
<pre><span class="c">// SQL</span>
|
<p>支持 INNER / LEFT / RIGHT / CROSS JOIN,SQL 和 Query Builder 两种方式。</p>
|
||||||
<span class="k">await</span> db.<span class="f">query</span>(<span class="s">`SELECT u.name, d.name
|
|
||||||
|
<pre><span class="c">// SQL 方式 — 支持表别名</span>
|
||||||
|
<span class="k">await</span> db.<span class="f">query</span>(<span class="s">`SELECT u.name, d.name AS dept_name
|
||||||
FROM users u
|
FROM users u
|
||||||
INNER JOIN departments d ON u.dept_id = d.id
|
INNER JOIN departments d ON u.dept_id = d.id
|
||||||
WHERE d.name = 'Engineering'`</span>);
|
LEFT JOIN orders o ON u.id = o.user_id
|
||||||
|
WHERE d.name = 'Engineering'
|
||||||
|
ORDER BY u.name`</span>);
|
||||||
|
|
||||||
<span class="c">// QueryBuilder</span>
|
<span class="c">// Query Builder 方式</span>
|
||||||
<span class="k">await</span> db.<span class="f">table</span>(<span class="s">'users'</span>).<span class="f">select</span>()
|
<span class="k">await</span> db.<span class="f">table</span>(<span class="s">'users'</span>).<span class="f">select</span>()
|
||||||
.<span class="f">innerJoin</span>(<span class="s">'departments'</span>, { <span class="s">'users.dept_id'</span>: { <span class="s">$col</span>: <span class="s">'departments.id'</span> } })
|
.<span class="f">as</span>(<span class="s">'u'</span>)
|
||||||
|
.<span class="f">innerJoin</span>(<span class="s">'departments'</span>, { <span class="s">'u.dept_id'</span>: { <span class="s">$col</span>: <span class="s">'d.id'</span> } }, <span class="s">'d'</span>)
|
||||||
|
.<span class="f">leftJoin</span>(<span class="s">'orders'</span>, { <span class="s">'u.id'</span>: { <span class="s">$col</span>: <span class="s">'o.user_id'</span> } }, <span class="s">'o'</span>)
|
||||||
.<span class="f">execute</span>();
|
.<span class="f">execute</span>();
|
||||||
|
|
||||||
<span class="c">// LEFT JOIN / RIGHT JOIN / CROSS JOIN</span>
|
<span class="c">// CROSS JOIN — 笛卡尔积</span>
|
||||||
.<span class="f">leftJoin</span>(<span class="s">'table'</span>, on)
|
.<span class="f">crossJoin</span>(<span class="s">'metadata'</span>, <span class="s">'m'</span>)</pre>
|
||||||
.<span class="f">rightJoin</span>(<span class="s">'table'</span>, on)
|
|
||||||
.<span class="f">crossJoin</span>(<span class="s">'table'</span>)</pre>
|
|
||||||
|
|
||||||
<h2 id="groupby">📊 GROUP BY & 聚合</h2>
|
<h2 id="groupby">📊 GROUP BY & 聚合</h2>
|
||||||
<pre><span class="k">await</span> db.<span class="f">query</span>(<span class="s">`SELECT dept, COUNT(*) as cnt, SUM(salary) as total
|
<p>五大聚合函数 + GROUP BY + HAVING + DISTINCT,完整的数据分析能力。</p>
|
||||||
|
|
||||||
|
<pre><span class="c">// GROUP BY + 聚合</span>
|
||||||
|
<span class="k">await</span> db.<span class="f">query</span>(<span class="s">`SELECT dept, COUNT(*) as cnt, SUM(salary) as total,
|
||||||
|
AVG(salary) as avg_sal, MIN(age), MAX(age)
|
||||||
FROM employees
|
FROM employees
|
||||||
GROUP BY dept
|
GROUP BY dept
|
||||||
HAVING COUNT(*) > 1
|
HAVING COUNT(*) > 1
|
||||||
ORDER BY total DESC`</span>);
|
ORDER BY total DESC
|
||||||
|
LIMIT 5`</span>);
|
||||||
|
|
||||||
<span class="c">// 支持的聚合函数:COUNT, SUM, AVG, MIN, MAX</span>
|
<span class="c">// DISTINCT 去重</span>
|
||||||
<span class="c">// 支持别名:COUNT(*) AS cnt</span></pre>
|
<span class="k">await</span> db.<span class="f">query</span>(<span class="s">'SELECT DISTINCT dept FROM employees'</span>);
|
||||||
|
|
||||||
|
<span class="c">// 聚合函数别名</span>
|
||||||
|
<span class="k">await</span> db.<span class="f">query</span>(<span class="s">'SELECT COUNT(*) AS total_users, AVG(age) AS avg_age FROM users'</span>);</pre>
|
||||||
|
|
||||||
|
<h2 id="where-ops">🎯 WHERE 操作符</h2>
|
||||||
|
<p>Query Builder 使用 <code>$</code> 前缀操作符,支持逻辑组合。</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr><th>操作符</th><th>含义</th><th>示例</th></tr>
|
||||||
|
<tr><td>直接值 / <code>$eq</code></td><td>等于</td><td><code>{ age: 30 }</code> / <code>{ age: { $eq: 30 } }</code></td></tr>
|
||||||
|
<tr><td><code>$ne</code></td><td>不等于</td><td><code>{ age: { $ne: 30 } }</code></td></tr>
|
||||||
|
<tr><td><code>$gt</code> / <code>$gte</code></td><td>大于 / 大于等于</td><td><code>{ age: { $gt: 18 } }</code></td></tr>
|
||||||
|
<tr><td><code>$lt</code> / <code>$lte</code></td><td>小于 / 小于等于</td><td><code>{ age: { $lt: 65 } }</code></td></tr>
|
||||||
|
<tr><td><code>$in</code> / <code>$nin</code></td><td>在列表中 / 不在</td><td><code>{ dept: { $in: ['IT','HR'] } }</code></td></tr>
|
||||||
|
<tr><td><code>$like</code></td><td>模糊匹配</td><td><code>{ name: { $like: 'A%' } }</code></td></tr>
|
||||||
|
<tr><td><code>$and</code></td><td>逻辑与</td><td><code>{ $and: [{...}, {...}] }</code></td></tr>
|
||||||
|
<tr><td><code>$or</code></td><td>逻辑或</td><td><code>{ $or: [{...}, {...}] }</code></td></tr>
|
||||||
|
<tr><td><code>$not</code></td><td>逻辑非</td><td><code>{ age: { $not: { $gt: 18 } } }</code></td></tr>
|
||||||
|
<tr><td><code>$col</code></td><td>列引用(JOIN ON)</td><td><code>{ 'a.id': { $col: 'b.a_id' } }</code></td></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<pre><span class="c">// 复合条件</span>
|
||||||
|
.<span class="f">where</span>({
|
||||||
|
<span class="s">$or</span>: [
|
||||||
|
{ <span class="s">age</span>: { <span class="s">$lt</span>: <span class="n">18</span> } },
|
||||||
|
{ <span class="s">age</span>: { <span class="s">$gt</span>: <span class="n">65</span> } },
|
||||||
|
],
|
||||||
|
<span class="s">active</span>: <span class="k">true</span>,
|
||||||
|
<span class="s">name</span>: { <span class="s">$like</span>: <span class="s">'A%'</span>, <span class="s">$ne</span>: <span class="s">'Admin'</span> },
|
||||||
|
})</pre>
|
||||||
|
|
||||||
<h2 id="transaction">🔒 事务</h2>
|
<h2 id="transaction">🔒 事务</h2>
|
||||||
|
<p>保证原子性,事务内步骤失败自动报错。</p>
|
||||||
|
|
||||||
<pre><span class="k">await</span> db.<span class="f">transaction</span>(<span class="k">async</span> (trx) => {
|
<pre><span class="k">await</span> db.<span class="f">transaction</span>(<span class="k">async</span> (trx) => {
|
||||||
|
<span class="c">// trx.table() 获取事务内表操作对象</span>
|
||||||
<span class="k">await</span> trx.<span class="f">table</span>(<span class="s">'users'</span>).<span class="f">insert</span>({ <span class="s">id</span>: <span class="s">'3'</span>, <span class="s">name</span>: <span class="s">'Charlie'</span> });
|
<span class="k">await</span> trx.<span class="f">table</span>(<span class="s">'users'</span>).<span class="f">insert</span>({ <span class="s">id</span>: <span class="s">'3'</span>, <span class="s">name</span>: <span class="s">'Charlie'</span> });
|
||||||
<span class="k">await</span> trx.<span class="f">table</span>(<span class="s">'orders'</span>).<span class="f">insert</span>({ <span class="s">id</span>: <span class="s">'o1'</span>, <span class="s">userId</span>: <span class="s">'3'</span>, <span class="s">amount</span>: <span class="n">99</span> });
|
<span class="k">await</span> trx.<span class="f">table</span>(<span class="s">'orders'</span>).<span class="f">insert</span>({ <span class="s">id</span>: <span class="s">'o1'</span>, <span class="s">userId</span>: <span class="s">'3'</span>, <span class="s">amount</span>: <span class="n">99</span> });
|
||||||
<span class="c">// 任何一步失败 → 全部回滚</span>
|
<span class="k">await</span> trx.<span class="f">table</span>(<span class="s">'accounts'</span>).<span class="f">update</span>({ <span class="s">balance</span>: <span class="n">1</span> })
|
||||||
|
.<span class="f">where</span>({ <span class="s">userId</span>: <span class="s">'3'</span> })
|
||||||
|
.<span class="f">execute</span>();
|
||||||
|
|
||||||
|
<span class="c">// 也可返回结果</span>
|
||||||
|
<span class="k">return</span> <span class="s">'success'</span>;
|
||||||
});</pre>
|
});</pre>
|
||||||
|
|
||||||
<h2 id="migration">🔄 数据迁移</h2>
|
<h2 id="migration">🔄 数据迁移</h2>
|
||||||
<pre>db.<span class="f">addMigration</span>(<span class="n">2</span>, <span class="k">async</span> (db) => {
|
<p>按版本号管理表结构变更。</p>
|
||||||
<span class="k">await</span> db.<span class="f">defineTable</span>(<span class="s">'products'</span>, { ... });
|
|
||||||
|
<pre><span class="c">// 注册迁移</span>
|
||||||
|
db.<span class="f">addMigration</span>(<span class="n">2</span>, <span class="k">async</span> (db) => {
|
||||||
|
<span class="k">await</span> db.<span class="f">defineTable</span>(<span class="s">'products'</span>, {
|
||||||
|
<span class="s">id</span>: { <span class="s">type</span>: <span class="s">'string'</span>, <span class="s">primaryKey</span>: <span class="k">true</span> },
|
||||||
|
<span class="s">name</span>: { <span class="s">type</span>: <span class="s">'string'</span>, <span class="s">required</span>: <span class="k">true</span> },
|
||||||
|
});
|
||||||
});
|
});
|
||||||
<span class="k">await</span> db.<span class="f">migrateTo</span>(<span class="n">2</span>); <span class="c">// 执行所有未执行的迁移</span></pre>
|
|
||||||
|
db.<span class="f">addMigration</span>(<span class="n">3</span>, <span class="k">async</span> (db) => {
|
||||||
|
<span class="c">// 添加新列、数据迁移等</span>
|
||||||
|
<span class="k">await</span> db.<span class="f">query</span>(<span class="s">"UPDATE users SET role = 'user' WHERE role IS NULL"</span>);
|
||||||
|
});
|
||||||
|
|
||||||
|
<span class="c">// 执行迁移到目标版本</span>
|
||||||
|
<span class="k">await</span> db.<span class="f">migrateTo</span>(<span class="n">3</span>); <span class="c">// 依次执行 v2, v3 的迁移函数</span></pre>
|
||||||
|
|
||||||
<h2 id="export">📤 导入导出</h2>
|
<h2 id="export">📤 导入导出</h2>
|
||||||
<pre><span class="c">// 导出单表</span>
|
<pre><span class="c">// 导出单表 — 返回 JSON 数组</span>
|
||||||
<span class="k">const</span> data = <span class="k">await</span> db.<span class="f">exportTable</span>(<span class="s">'users'</span>);
|
<span class="k">const</span> userData = <span class="k">await</span> db.<span class="f">exportTable</span>(<span class="s">'users'</span>);
|
||||||
|
<span class="c">// [{ id: '1', name: 'Alice', ... }, ...]</span>
|
||||||
|
|
||||||
<span class="c">// 导出全库</span>
|
<span class="c">// 导出全库 — 返回 { tableName: rows[] }</span>
|
||||||
<span class="k">const</span> all = <span class="k">await</span> db.<span class="f">exportAll</span>();
|
<span class="k">const</span> allData = <span class="k">await</span> db.<span class="f">exportAll</span>();
|
||||||
|
<span class="c">// { users: [...], orders: [...], products: [...] }</span>
|
||||||
|
|
||||||
<span class="c">// 导入</span>
|
<span class="c">// 导入数据 — 返回主键列表</span>
|
||||||
<span class="k">await</span> db.<span class="f">importTable</span>(<span class="s">'users'</span>, data);</pre>
|
<span class="k">const</span> pks = <span class="k">await</span> db.<span class="f">importTable</span>(<span class="s">'users'</span>, userData);</pre>
|
||||||
|
|
||||||
<h2 id="plugin">🧩 插件钩子</h2>
|
<h2 id="plugin">🧩 插件 & 钩子</h2>
|
||||||
<pre><span class="c">// 14 个生命周期钩子</span>
|
<p>14 种生命周期钩子,支持插件机制。</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr><th>钩子名称</th><th>触发时机</th><th>参数</th></tr>
|
||||||
|
<tr><td><code>beforeCreateTable</code></td><td>创建表前</td><td>schema</td></tr>
|
||||||
|
<tr><td><code>afterCreateTable</code></td><td>创建表后</td><td>schema</td></tr>
|
||||||
|
<tr><td><code>beforeDropTable</code></td><td>删除表前</td><td>tableName</td></tr>
|
||||||
|
<tr><td><code>afterDropTable</code></td><td>删除表后</td><td>tableName</td></tr>
|
||||||
|
<tr><td><code>beforeInsert</code></td><td>插入前</td><td>rows[]</td></tr>
|
||||||
|
<tr><td><code>afterInsert</code></td><td>插入后</td><td>rows[]</td></tr>
|
||||||
|
<tr><td><code>beforeUpdate</code></td><td>更新前</td><td>query, updates</td></tr>
|
||||||
|
<tr><td><code>afterUpdate</code></td><td>更新后</td><td>query, updates, count</td></tr>
|
||||||
|
<tr><td><code>beforeDelete</code></td><td>删除前</td><td>query</td></tr>
|
||||||
|
<tr><td><code>afterDelete</code></td><td>删除后</td><td>query, count</td></tr>
|
||||||
|
<tr><td><code>beforeQuery</code></td><td>SQL 查询前</td><td>sql</td></tr>
|
||||||
|
<tr><td><code>afterQuery</code></td><td>SQL 查询后</td><td>sql, result</td></tr>
|
||||||
|
<tr><td><code>beforeTransaction</code></td><td>事务开始前</td><td>-</td></tr>
|
||||||
|
<tr><td><code>afterTransaction</code></td><td>事务完成后</td><td>-</td></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<pre><span class="c">// 注册钩子</span>
|
||||||
db.<span class="f">on</span>(<span class="s">'beforeInsert'</span>, <span class="k">async</span> (row) => {
|
db.<span class="f">on</span>(<span class="s">'beforeInsert'</span>, <span class="k">async</span> (row) => {
|
||||||
<span class="f">console.log</span>(<span class="s">'即将插入:'</span>, row);
|
<span class="f">console.log</span>(<span class="s">'即将插入:'</span>, row);
|
||||||
|
<span class="c">// 可在此校验、转换数据</span>
|
||||||
});
|
});
|
||||||
|
|
||||||
db.<span class="f">on</span>(<span class="s">'afterQuery'</span>, <span class="k">async</span> (sql, result) => {
|
db.<span class="f">on</span>(<span class="s">'afterQuery'</span>, <span class="k">async</span> (sql, result) => {
|
||||||
<span class="f">console.log</span>(<span class="s">'查询完成:'</span>, sql);
|
<span class="f">console.log</span>(<span class="s">`查询完成 [</span><span class="k">${sql}</span><span class="s">] → </span><span class="k">${(result as any[]).length}</span><span class="s"> 行`</span>);
|
||||||
|
});
|
||||||
|
|
||||||
|
<span class="c">// 注册自定义插件</span>
|
||||||
|
<span class="k">const</span> loggerPlugin = {
|
||||||
|
<span class="s">name</span>: <span class="s">'logger'</span>,
|
||||||
|
<span class="s">version</span>: <span class="s">'1.0.0'</span>,
|
||||||
|
<span class="s">description</span>: <span class="s">'记录所有数据库操作'</span>,
|
||||||
|
<span class="s">priority</span>: <span class="n">100</span>,
|
||||||
|
<span class="f">install</span>(db) {
|
||||||
|
db.<span class="f">on</span>(<span class="s">'beforeQuery'</span>, (sql) => <span class="f">console.log</span>(<span class="s">'SQL:'</span>, sql));
|
||||||
|
},
|
||||||
|
<span class="f">destroy</span>() { <span class="c">/* 清理 */</span> },
|
||||||
|
};
|
||||||
|
|
||||||
|
<span class="c">// 在 create 配置中注册</span>
|
||||||
|
<span class="k">const</span> db = <span class="k">await</span> <span class="f">MetonaSqlark.create</span>({
|
||||||
|
<span class="s">name</span>: <span class="s">'my-app'</span>,
|
||||||
|
<span class="s">plugins</span>: [loggerPlugin],
|
||||||
});</pre>
|
});</pre>
|
||||||
<table>
|
|
||||||
<tr><th>钩子</th><th>触发时机</th></tr>
|
|
||||||
<tr><td><code>beforeCreateTable / afterCreateTable</code></td><td>创建表前后</td></tr>
|
|
||||||
<tr><td><code>beforeDropTable / afterDropTable</code></td><td>删除表前后</td></tr>
|
|
||||||
<tr><td><code>beforeInsert / afterInsert</code></td><td>插入前后</td></tr>
|
|
||||||
<tr><td><code>beforeUpdate / afterUpdate</code></td><td>更新前后</td></tr>
|
|
||||||
<tr><td><code>beforeDelete / afterDelete</code></td><td>删除前后</td></tr>
|
|
||||||
<tr><td><code>beforeQuery / afterQuery</code></td><td>查询前后</td></tr>
|
|
||||||
<tr><td><code>beforeTransaction / afterTransaction</code></td><td>事务前后</td></tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<h2 id="subscribe">📡 发布订阅</h2>
|
<h2 id="subscribe">📡 发布订阅</h2>
|
||||||
<pre><span class="c">// 订阅表变更</span>
|
<pre><span class="c">// 订阅表变更</span>
|
||||||
<span class="k">const</span> unsubscribe = db.<span class="f">subscribe</span>(<span class="s">'users'</span>, (event) => {
|
<span class="k">const</span> unsubscribe = db.<span class="f">subscribe</span>(<span class="s">'users'</span>, (event) => {
|
||||||
<span class="c">// event: { type: 'insert'|'update'|'delete', row: {...} }</span>
|
<span class="c">// event.type: 'insert' | 'update' | 'delete'</span>
|
||||||
|
<span class="c">// event.row: 被操作的行数据</span>
|
||||||
|
<span class="f">console.log</span>(<span class="s">`users 表 </span><span class="k">${event.type}</span><span class="s">`</span>, event.row);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
<span class="c">// 手动触发变更</span>
|
||||||
|
db.<span class="f">emit</span>(<span class="s">'users'</span>, { <span class="s">type</span>: <span class="s">'insert'</span>, <span class="s">row</span>: { <span class="s">id</span>: <span class="s">'1'</span>, <span class="s">name</span>: <span class="s">'Alice'</span> } });
|
||||||
|
|
||||||
<span class="c">// 取消订阅</span>
|
<span class="c">// 取消订阅</span>
|
||||||
<span class="f">unsubscribe</span>();</pre>
|
<span class="f">unsubscribe</span>();</pre>
|
||||||
|
|
||||||
|
<h2 id="react">⚛️ React 集成</h2>
|
||||||
|
<pre><span class="k">import</span> { useQuery, useTable, useDatabase } <span class="k">from</span> <span class="s">'@metona-team/metona-sqlark/react'</span>;
|
||||||
|
<span class="k">import</span> { db } <span class="k">from</span> <span class="s">'./db'</span>;
|
||||||
|
|
||||||
|
<span class="k">function</span> <span class="f">UserList</span>() {
|
||||||
|
<span class="c">// 执行 SQL 查询,自动响应 db 变化</span>
|
||||||
|
<span class="k">const</span> { data, loading, error, refresh } = <span class="f">useQuery</span>(
|
||||||
|
db,
|
||||||
|
<span class="s">'SELECT * FROM users WHERE age > 18'</span>,
|
||||||
|
[<span class="c">/* deps */</span>]
|
||||||
|
);
|
||||||
|
|
||||||
|
<span class="k">if</span> (loading) <span class="k">return</span> <div>Loading...</div>;
|
||||||
|
<span class="k">if</span> (error) <span class="k">return</span> <div>Error: {error.message}</div>;
|
||||||
|
|
||||||
|
<span class="k">return</span> (
|
||||||
|
<div>
|
||||||
|
{data.<span class="f">map</span>(u => <div key={u.id}>{u.name} ({u.age})</div>)}
|
||||||
|
<button onClick={refresh}>刷新</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
<span class="c">// 便捷 hook — 查询整张表</span>
|
||||||
|
<span class="k">const</span> { data, loading, refresh } = <span class="f">useTable</span>(db, <span class="s">'users'</span>);
|
||||||
|
|
||||||
|
<span class="c">// 管理数据库生命周期</span>
|
||||||
|
<span class="k">function</span> <span class="f">App</span>() {
|
||||||
|
<span class="k">const</span> { db, ready, error } = <span class="f">useDatabase</span>({
|
||||||
|
<span class="s">name</span>: <span class="s">'my-app'</span>,
|
||||||
|
<span class="s">mode</span>: <span class="s">'hybrid'</span>,
|
||||||
|
});
|
||||||
|
<span class="k">if</span> (!ready) <span class="k">return</span> <div>Initializing...</div>;
|
||||||
|
<span class="k">return</span> <UserList />;
|
||||||
|
}</pre>
|
||||||
|
|
||||||
|
<h2 id="vue">🟢 Vue 集成</h2>
|
||||||
|
<pre><span class="k">import</span> { useSqlarkQuery, useSqlarkTable, useSqlarkDatabase }
|
||||||
|
<span class="k">from</span> <span class="s">'@metona-team/metona-sqlark/vue'</span>;
|
||||||
|
<span class="k">import</span> { db } <span class="k">from</span> <span class="s">'./db'</span>;
|
||||||
|
|
||||||
|
<span class="c">// useSqlarkQuery — 执行 SQL 查询</span>
|
||||||
|
<span class="k">const</span> { data, loading, error, refresh } = <span class="f">useSqlarkQuery</span>(
|
||||||
|
db,
|
||||||
|
<span class="s">'SELECT * FROM users WHERE age > 18'</span>
|
||||||
|
);
|
||||||
|
|
||||||
|
<span class="c">// useSqlarkTable — 获取全表数据</span>
|
||||||
|
<span class="k">const</span> { data, loading, refresh } = <span class="f">useSqlarkTable</span>(db, <span class="s">'users'</span>);
|
||||||
|
|
||||||
|
<span class="c">// useSqlarkDatabase — 管理数据库生命周期</span>
|
||||||
|
<span class="k">const</span> { db, ready, error } = <span class="f">useSqlarkDatabase</span>({
|
||||||
|
<span class="s">name</span>: <span class="s">'my-app'</span>,
|
||||||
|
<span class="s">mode</span>: <span class="s">'hybrid'</span>,
|
||||||
|
});</pre>
|
||||||
|
|
||||||
<h2 id="types">🔷 TypeScript 泛型</h2>
|
<h2 id="types">🔷 TypeScript 泛型</h2>
|
||||||
<pre><span class="k">interface</span> <span class="t">User</span> {
|
<pre><span class="k">interface</span> <span class="t">User</span> {
|
||||||
<span class="s">id</span>: <span class="t">string</span>;
|
<span class="s">id</span>: <span class="t">string</span>;
|
||||||
<span class="s">name</span>: <span class="t">string</span>;
|
<span class="s">name</span>: <span class="t">string</span>;
|
||||||
<span class="s">age</span>: <span class="t">number</span>;
|
<span class="s">age</span>: <span class="t">number</span>;
|
||||||
|
<span class="s">email</span>?: <span class="t">string</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<span class="c">// 泛型表操作 — 类型安全的 insert/select</span>
|
||||||
<span class="k">const</span> users = db.<span class="f">table</span><<span class="t">User</span>>(<span class="s">'users'</span>);
|
<span class="k">const</span> users = db.<span class="f">table</span><<span class="t">User</span>>(<span class="s">'users'</span>);
|
||||||
<span class="k">await</span> users.<span class="f">insert</span>({ <span class="s">id</span>: <span class="s">'1'</span>, <span class="s">name</span>: <span class="s">'Alice'</span>, <span class="s">age</span>: <span class="n">30</span> }); <span class="c">// ✅ 类型安全</span></pre>
|
|
||||||
|
|
||||||
<h2 id="config">⚙️ 配置项</h2>
|
<span class="c">// ✅ 类型检查通过</span>
|
||||||
<pre><span class="k">import</span> { MetonaSqlark, MeSqlark } <span class="k">from</span> <span class="s">'MetonaSqlark'</span>;
|
<span class="k">await</span> users.<span class="f">insert</span>({ <span class="s">id</span>: <span class="s">'1'</span>, <span class="s">name</span>: <span class="s">'Alice'</span>, <span class="s">age</span>: <span class="n">30</span> });
|
||||||
<span class="c">// MeSqlark 是 MetonaSqlark 的别名,完全等价</span></pre>
|
|
||||||
|
<span class="c">// ❌ TypeScript 报错:缺少 name</span>
|
||||||
|
<span class="c">// await users.insert({ id: '2', age: 25 });</span></pre>
|
||||||
|
|
||||||
|
<h2 id="config">⚙️ 完整配置项</h2>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr><th>属性</th><th>类型</th><th>默认值</th><th>说明</th></tr>
|
||||||
|
<tr><td><code>name</code></td><td><code>string</code></td><td><code>'metona-sqlark'</code></td><td>数据库名称(必填)</td></tr>
|
||||||
|
<tr><td><code>mode</code></td><td><code>'memory'|'disk'|'hybrid'</code></td><td><code>'hybrid'</code></td><td>存储模式</td></tr>
|
||||||
|
<tr><td><code>diskEngine</code></td><td><code>'indexeddb'|'opfs'</code></td><td><code>'indexeddb'</code></td><td>磁盘引擎类型</td></tr>
|
||||||
|
<tr><td><code>version</code></td><td><code>number</code></td><td><code>1</code></td><td>数据库版本号</td></tr>
|
||||||
|
<tr><td><code>plugins</code></td><td><code>MetonaPlugin[]</code></td><td><code>[]</code></td><td>初始插件列表</td></tr>
|
||||||
|
<tr><td><code>onReady</code></td><td><code>(db) => void</code></td><td>-</td><td>初始化完成回调</td></tr>
|
||||||
|
<tr><td><code>onError</code></td><td><code>(err) => void</code></td><td>-</td><td>错误回调</td></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2 id="engine">💾 存储引擎</h2>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr><th>引擎</th><th>模式</th><th>持久化</th><th>性能</th><th>适用场景</th></tr>
|
||||||
|
<tr><td><code>MemoryEngine</code></td><td>memory</td><td>❌ 否</td><td>⚡ 极快</td><td>临时数据、缓存、测试</td></tr>
|
||||||
|
<tr><td><code>IndexedDBEngine</code></td><td>disk</td><td>✅ 是</td><td>🚀 快</td><td>通用持久化,兼容性最好</td></tr>
|
||||||
|
<tr><td><code>OPFSEngine</code></td><td>disk</td><td>✅ 是</td><td>🚀 快</td><td>现代浏览器,文件级存储</td></tr>
|
||||||
|
<tr><td><code>HybridEngine</code></td><td>hybrid</td><td>✅ 是</td><td>⚡ 极快</td><td>生产推荐,读写均走内存</td></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p>Hybrid 引擎采用 <strong>write-through</strong> 策略:所有写操作同时写入内存和磁盘,所有读操作直接从内存返回,启动时从磁盘加载数据到内存。</p>
|
||||||
|
|
||||||
|
<h2 id="errors">⚠️ 错误处理</h2>
|
||||||
|
<p>所有错误抛出 <code>DatabaseError</code> 实例。</p>
|
||||||
|
|
||||||
|
<pre><span class="k">try</span> {
|
||||||
|
<span class="k">await</span> db.<span class="f">query</span>(<span class="s">'SELECT * FROM nonexistent'</span>);
|
||||||
|
} <span class="k">catch</span> (err) {
|
||||||
|
<span class="k">if</span> (err <span class="k">instanceof</span> <span class="t">DatabaseError</span>) {
|
||||||
|
<span class="f">console.log</span>(err.message); <span class="c">// 'Table "nonexistent" does not exist'</span>
|
||||||
|
<span class="f">console.log</span>(err.code); <span class="c">// 'TABLE_NOT_FOUND'</span>
|
||||||
|
<span class="f">console.log</span>(err.details); <span class="c">// 附加信息</span>
|
||||||
|
}
|
||||||
|
}</pre>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr><th>错误码</th><th>触发场景</th></tr>
|
||||||
|
<tr><td><code>TABLE_NOT_FOUND</code></td><td>表不存在</td></tr>
|
||||||
|
<tr><td><code>TABLE_EXISTS</code></td><td>表已存在</td></tr>
|
||||||
|
<tr><td><code>DUPLICATE_KEY</code></td><td>主键重复</td></tr>
|
||||||
|
<tr><td><code>UNIQUE_VIOLATION</code></td><td>唯一约束冲突</td></tr>
|
||||||
|
<tr><td><code>VALIDATION_ERROR</code></td><td>数据校验失败</td></tr>
|
||||||
|
<tr><td><code>TYPE_ERROR</code></td><td>字段类型错误</td></tr>
|
||||||
|
<tr><td><code>SCHEMA_ERROR</code></td><td>表结构定义错误</td></tr>
|
||||||
|
<tr><td><code>DB_NOT_READY</code></td><td>数据库未初始化</td></tr>
|
||||||
|
<tr><td><code>PARSE_ERROR</code></td><td>SQL 语法错误</td></tr>
|
||||||
|
<tr><td><code>TRANSACTION_ERROR</code></td><td>事务执行失败</td></tr>
|
||||||
|
<tr><td><code>COMPILE_ERROR</code></td><td>编译 AST 到查询计划失败</td></tr>
|
||||||
|
<tr><td><code>CONFIG_ERROR</code></td><td>配置错误</td></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+81
-19
@@ -151,7 +151,7 @@
|
|||||||
<!-- Hero -->
|
<!-- Hero -->
|
||||||
<section class="hero">
|
<section class="hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="badge" style="margin-bottom:24px;"><span class="dot"></span> v0.1.11 已发布</div>
|
<div class="badge" style="margin-bottom:24px;"><span class="dot"></span> v0.1.12 已发布</div>
|
||||||
<h1>前端的 <span class="gradient-text">SQL 数据库</span></h1>
|
<h1>前端的 <span class="gradient-text">SQL 数据库</span></h1>
|
||||||
<p>TypeScript 原生构建,内存与磁盘双模式,支持完整 SQL 查询。<br>零运行时依赖,开箱即用。</p>
|
<p>TypeScript 原生构建,内存与磁盘双模式,支持完整 SQL 查询。<br>零运行时依赖,开箱即用。</p>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
@@ -232,63 +232,123 @@ npm install @metona-team/metona-sqlark
|
|||||||
<div class="feature-card">
|
<div class="feature-card">
|
||||||
<div class="icon">🧠</div>
|
<div class="icon">🧠</div>
|
||||||
<h3>双模式存储</h3>
|
<h3>双模式存储</h3>
|
||||||
<p>Memory(内存)保证极致速度,Disk(IndexedDB / OPFS)提供持久化。Hybrid 模式一键兼顾。</p>
|
<p>Memory(内存)保证极致速度,Disk(IndexedDB / OPFS)提供持久化。Hybrid 模式 write-through 策略,读写均在微秒级完成。</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-card">
|
<div class="feature-card">
|
||||||
<div class="icon">⚡</div>
|
<div class="icon">⚡</div>
|
||||||
<h3>完整 SQL 支持</h3>
|
<h3>完整 SQL 解析器</h3>
|
||||||
<p>SELECT / INSERT / UPDATE / DELETE / JOIN / GROUP BY / HAVING / DISTINCT — 手写递归下降解析器。</p>
|
<p>手写递归下降 SQL 解析器,无第三方依赖。支持 SELECT / INSERT / UPDATE / DELETE / CREATE TABLE / DROP TABLE / JOIN / GROUP BY / HAVING / DISTINCT。</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-card">
|
<div class="feature-card">
|
||||||
<div class="icon">🔗</div>
|
<div class="icon">🔗</div>
|
||||||
<h3>Query Builder</h3>
|
<h3>Query Builder</h3>
|
||||||
<p>链式 API,TypeScript 友好。.select().where().innerJoin().orderBy().limit() — 代码即查询。</p>
|
<p>链式 API + TypeScript 泛型支持。.select().where().innerJoin().orderBy().limit() — IDE 自动补全,类型安全。</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-card">
|
<div class="feature-card">
|
||||||
<div class="icon">🔒</div>
|
<div class="icon">🔒</div>
|
||||||
<h3>事务支持</h3>
|
<h3>事务支持</h3>
|
||||||
<p>原子性操作,事务内任意步骤失败自动回滚。IndexedDB 引擎利用浏览器原生事务。</p>
|
<p>原子性操作,事务内任意步骤失败自动回滚。IndexedDB 引擎原生事务 + Memory 引擎模拟事务。</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-card">
|
<div class="feature-card">
|
||||||
<div class="icon">🧩</div>
|
<div class="icon">🧩</div>
|
||||||
<h3>插件系统</h3>
|
<h3>插件系统</h3>
|
||||||
<p>14 种生命周期钩子,注册自定义插件。beforeQuery / afterInsert / beforeTransaction……</p>
|
<p>14 种生命周期钩子:beforeCreateTable / afterInsert / beforeQuery / afterTransaction……按优先级注册,完整生命周期管理。</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-card">
|
<div class="feature-card">
|
||||||
<div class="icon">📦</div>
|
<div class="icon">📦</div>
|
||||||
<h3>零运行时依赖</h3>
|
<h3>零运行时依赖</h3>
|
||||||
<p>纯 TypeScript 实现,不依赖任何第三方库。Tree-shakable,最小体积不到 10KB gzip。</p>
|
<p>纯 TypeScript 实现,不依赖任何第三方库。Tree-shakable,UMD/ESM/CJS 多格式输出,最小体积 ~10KB gzip。</p>
|
||||||
|
</div>
|
||||||
|
<div class="feature-card">
|
||||||
|
<div class="icon">📊</div>
|
||||||
|
<h3>聚合 & 分组</h3>
|
||||||
|
<p>COUNT / SUM / AVG / MIN / MAX 五大聚合函数,GROUP BY + HAVING 子句,DISTINCT 去重,完整的数据分析能力。</p>
|
||||||
|
</div>
|
||||||
|
<div class="feature-card">
|
||||||
|
<div class="icon">🔄</div>
|
||||||
|
<h3>数据迁移</h3>
|
||||||
|
<p>内置版本迁移系统,addMigration + migrateTo API。导入导出支持单表/全库 JSON 序列化。</p>
|
||||||
|
</div>
|
||||||
|
<div class="feature-card">
|
||||||
|
<div class="icon">📡</div>
|
||||||
|
<h3>发布订阅</h3>
|
||||||
|
<p>subscribe() 订阅表变更事件,emit() 触发通知。React useQuery / Vue useSqlarkQuery 开箱即用。</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Code Example -->
|
<!-- Architecture -->
|
||||||
|
<section>
|
||||||
|
<div class="container">
|
||||||
|
<div class="section-title">
|
||||||
|
<h2>🏗 <span>架构</span>设计</h2>
|
||||||
|
<p>清晰的分层结构,各模块职责单一</p>
|
||||||
|
</div>
|
||||||
|
<div class="code-block" style="text-align:center;font-size:0.82rem;line-height:2;background:transparent;border:none;">
|
||||||
|
<pre style="color:var(--text2);">
|
||||||
|
┌─────────────────────────────────────────────────────┐
|
||||||
|
│ <span style="color:#f472b6;">MetonaSqlark / MeSqlark</span> │
|
||||||
|
│ create() · query() · table() · tx() │
|
||||||
|
├──────────┬──────────┬──────────┬──────────┬─────────┤
|
||||||
|
│ <span style="color:#c084fc;">SQL Parser</span> │ <span style="color:#c084fc;">QueryBuilder</span> │ <span style="color:#c084fc;">Transaction</span>│ <span style="color:#c084fc;">Plugin</span> │ <span style="color:#c084fc;">Migration</span> │
|
||||||
|
│ Lexer → │ .select() │ Manager │ Manager │ System │
|
||||||
|
│ Parser │ .where() │ │ 14 hooks │ │
|
||||||
|
├──────────┴──────────┴──────────┴──────────┴─────────┤
|
||||||
|
│ <span style="color:#60a5fa;">QueryExecutor</span> (AST → Results) │
|
||||||
|
│ JOIN · GROUP BY · HAVING · DISTINCT │
|
||||||
|
├─────────────────────────────────────────────────────┤
|
||||||
|
│ <span style="color:#34d399;">IStorageEngine</span> Interface │
|
||||||
|
├──────────┬──────────┬──────────┬───────────────────┤
|
||||||
|
│ <span style="color:#fbbf24;">Memory </span>│ <span style="color:#fbbf24;">IndexedDB </span>│ <span style="color:#fbbf24;">OPFS </span>│ <span style="color:#fbbf24;">Hybrid </span>│
|
||||||
|
│ Engine │ Engine │ Engine │ Engine │
|
||||||
|
│ (Map) │ (IDB) │ (File) │ (Write-through) │
|
||||||
|
└──────────┴──────────┴──────────┴───────────────────┘
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
<section style="background:var(--gradient2);">
|
<section style="background:var(--gradient2);">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-title">
|
<div class="section-title">
|
||||||
<h2>5 行<span>代码</span>开始</h2>
|
<h2>5 行<span>代码</span>开始</h2>
|
||||||
<p>像写 SQL 一样操作前端数据</p>
|
<p>像写 SQL 一样操作前端数据 — 或使用 Query Builder 链式 API</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="code-block">
|
<div class="code-block">
|
||||||
<pre><span class="comment">// 创建数据库</span>
|
<pre><span class="comment">// 创建数据库 — MeSqlark 是别名,完全等价</span>
|
||||||
<span class="keyword">const</span> db = <span class="keyword">await</span> <span class="func">MetonaSqlark.create</span>({ <span class="string">name</span>: <span class="string">'my-app'</span>, <span class="string">mode</span>: <span class="string">'hybrid'</span> });
|
<span class="keyword">const</span> db = <span class="keyword">await</span> <span class="func">MetonaSqlark.create</span>({ <span class="string">name</span>: <span class="string">'my-app'</span>, <span class="string">mode</span>: <span class="string">'hybrid'</span> });
|
||||||
|
|
||||||
<span class="comment">// 定义表结构</span>
|
<span class="comment">// 定义表结构</span>
|
||||||
<span class="keyword">await</span> db.<span class="func">defineTable</span>(<span class="string">'users'</span>, {
|
<span class="keyword">await</span> db.<span class="func">defineTable</span>(<span class="string">'users'</span>, {
|
||||||
<span class="string">id</span>: { <span class="string">type</span>: <span class="string">'string'</span>, <span class="string">primaryKey</span>: <span class="keyword">true</span> },
|
<span class="string">id</span>: { <span class="string">type</span>: <span class="string">'string'</span>, <span class="string">primaryKey</span>: <span class="keyword">true</span> },
|
||||||
<span class="string">name</span>: { <span class="string">type</span>: <span class="string">'string'</span>, <span class="string">required</span>: <span class="keyword">true</span> },
|
<span class="string">name</span>: { <span class="string">type</span>: <span class="string">'string'</span>, <span class="string">required</span>: <span class="keyword">true</span> },
|
||||||
<span class="string">age</span>: { <span class="string">type</span>: <span class="string">'number'</span> },
|
<span class="string">age</span>: { <span class="string">type</span>: <span class="string">'number'</span>, <span class="string">default</span>: <span class="number">0</span> },
|
||||||
|
<span class="string">email</span>: { <span class="string">type</span>: <span class="string">'string'</span>, <span class="string">unique</span>: <span class="keyword">true</span>, <span class="string">index</span>: <span class="keyword">true</span> },
|
||||||
});
|
});
|
||||||
|
|
||||||
<span class="comment">// SQL 查询</span>
|
<span class="comment">// SQL 查询</span>
|
||||||
<span class="keyword">await</span> db.<span class="func">query</span>(<span class="string">"INSERT INTO users VALUES ('1', 'Alice', 30)"</span>);
|
<span class="keyword">await</span> db.<span class="func">query</span>(<span class="string">"INSERT INTO users VALUES ('1', 'Alice', 30, 'alice@demo.com')"</span>);
|
||||||
<span class="keyword">const</span> rows = <span class="keyword">await</span> db.<span class="func">query</span>(<span class="string">'SELECT * FROM users WHERE age > 18'</span>);
|
<span class="keyword">const</span> rows = <span class="keyword">await</span> db.<span class="func">query</span>(<span class="string">'SELECT * FROM users WHERE age > 18 ORDER BY name'</span>);
|
||||||
|
|
||||||
<span class="comment">// 或者 Query Builder</span>
|
<span class="comment">// Query Builder — 类型安全,链式调用</span>
|
||||||
<span class="keyword">const</span> rows2 = <span class="keyword">await</span> db.<span class="func">table</span>(<span class="string">'users'</span>)
|
<span class="keyword">const</span> rows2 = <span class="keyword">await</span> db.<span class="func">table</span>(<span class="string">'users'</span>)
|
||||||
.<span class="func">select</span>().<span class="func">where</span>({ <span class="string">age</span>: { <span class="string">$gt</span>: <span class="number">18</span> } })
|
.<span class="func">select</span>([<span class="string">'name'</span>, <span class="string">'age'</span>]).<span class="func">where</span>({ <span class="string">age</span>: { <span class="string">$gt</span>: <span class="number">18</span> } })
|
||||||
.<span class="func">orderBy</span>(<span class="string">'name'</span>).<span class="func">limit</span>(<span class="number">10</span>).<span class="func">execute</span>();</pre>
|
.<span class="func">orderBy</span>(<span class="string">'name'</span>).<span class="func">limit</span>(<span class="number">10</span>).<span class="func">execute</span>();
|
||||||
|
|
||||||
|
<span class="comment">// JOIN 多表关联</span>
|
||||||
|
<span class="keyword">await</span> db.<span class="func">query</span>(<span class="string">`SELECT u.name, o.product, o.amount
|
||||||
|
FROM users u INNER JOIN orders o ON u.id = o.user_id
|
||||||
|
WHERE o.amount > 100`</span>);
|
||||||
|
|
||||||
|
<span class="comment">// GROUP BY 聚合分析</span>
|
||||||
|
<span class="keyword">await</span> db.<span class="func">query</span>(<span class="string">`SELECT dept, COUNT(*) as cnt, AVG(salary) as avg
|
||||||
|
FROM employees GROUP BY dept HAVING COUNT(*) > 1`</span>);
|
||||||
|
|
||||||
|
<span class="comment">// 事务 — 原子操作</span>
|
||||||
|
<span class="keyword">await</span> db.<span class="func">transaction</span>(<span class="keyword">async</span> (trx) => {
|
||||||
|
<span class="keyword">await</span> trx.<span class="func">table</span>(<span class="string">'users'</span>).<span class="func">insert</span>({ id: <span class="string">'3'</span>, name: <span class="string">'Charlie'</span> });
|
||||||
|
<span class="keyword">await</span> trx.<span class="func">table</span>(<span class="string">'orders'</span>).<span class="func">insert</span>({ id: <span class="string">'o1'</span>, userId: <span class="string">'3'</span>, amount: <span class="number">99</span> });
|
||||||
|
});</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -301,10 +361,12 @@ npm install @metona-team/metona-sqlark
|
|||||||
<p>MetonaSqlark 的核心指标</p>
|
<p>MetonaSqlark 的核心指标</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="stats">
|
<div class="stats">
|
||||||
<div class="stat-card"><div class="num">235+</div><div class="label">测试用例</div></div>
|
<div class="stat-card"><div class="num">264+</div><div class="label">测试用例</div></div>
|
||||||
<div class="stat-card"><div class="num">78.9%</div><div class="label">代码覆盖率</div></div>
|
<div class="stat-card"><div class="num">93.5%</div><div class="label">代码覆盖率</div></div>
|
||||||
<div class="stat-card"><div class="num">~10KB</div><div class="label">gzip 体积</div></div>
|
<div class="stat-card"><div class="num">~10KB</div><div class="label">gzip 体积</div></div>
|
||||||
<div class="stat-card"><div class="num">4</div><div class="label">存储引擎</div></div>
|
<div class="stat-card"><div class="num">4</div><div class="label">存储引擎</div></div>
|
||||||
|
<div class="stat-card"><div class="num">29</div><div class="label">SQL 关键字</div></div>
|
||||||
|
<div class="stat-card"><div class="num">15</div><div class="label">测试套件</div></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Reference in New Issue
Block a user