独立核验(12 条宣称逐条对源码验证)发现 5 处**硬伤**与 2 处**数字过期**, 本提交按"能改代码就让宣称成立、改不动就如实描述"的原则全部收口。 让实现符合文档(2 处): 1. **插件 priority 此前不生效** — `register()` 虽按 priority 插入数组,但 `install()` 在 register 内**立即**执行,因此 install 与钩子顺序 = config 数组 顺序(实测 priority low=1/high=100/mid=50 时钩子按 low→high→mid 触发, 只有 `getPlugins()` 是 high,mid,low)。而 README/CONTRIBUTING/constants 一直宣称"越大越先执行"。 现在 Core 注册前按 priority **稳定降序**排序(同优先级保持数组顺序), install 与钩子都按优先级执行 → 宣称成立。新增 `tests/v080-plugin-priority.test.ts` 锁定 install 顺序、钩子顺序、稳定性、缺省值。 2. **连接池静态方法不在类型系统里** — `MetonaSqlark.connect/disconnect/ disconnectAll/getActiveConnections` 由 connection-manager 用 `as unknown as Record<string, unknown>` 注入,README 的连接池表格在 TypeScript 下全部 TS2339。现在在类上声明为可选静态成员,注入处去掉断言。 如实描述(3 处): 3. **MVCC 快照隔离**(README 三处 + 实现对照)— `snapshotLsn` / `prevVersion` 只写不读,事务读走 `txnSnapshot`+LSM,commit 即清理版本链,并发 `beginTransaction` 抛 `TX_ACTIVE`。改为"快照回滚(事务串行,非 MVCC 隔离)", 并在 README 架构图与维护语句表里同步措辞。 4. **"存储引擎(5 种)"** — 实际是 4 种模式 + 3 种后端,引擎类只有 4 个 (Memory / KVStore / Hybrid / Aria),OPFS 是后端而非引擎。标题与条目已改写, 并写明"`disk`/`hybrid` 恒用 KVStore"。 5. **`diskEngine` 生效范围** — 仅 `mode:'aria'` 生效;`constants.ts` 的注释 此前写成"仅 mode='disk'|'hybrid' 时生效"(正好写反),已改正;README 配置表、 快速开始示例与 Aria 示例同步标注。 数字口径统一(可复现): - 测试 1872(90 套件)+ 14 e2e,另 4 个重型套件在独立 CI job 串行运行; - 覆盖率 语句 90.43% / 分支 82.21% / 函数 94.27% / 行 93.44%; - README 明确写出**产出这些数字的完整命令**(与 CI 常规 job 一致), 并要求改动覆盖范围/阈值时同步更新表格(G5)。 - CHANGELOG 0.8.0 条目与 site 首页/文档页同步。 另修 **CONTRIBUTING 的钩子契约**:明确写出"返回值被忽略(不能取消/改写)、 就地改参数在 Table API 生效、抛异常可取消、SQL 路径的 beforeInsert 收到副本" —— 此前只写 "allow intercepting",容易被理解为返回值可改变行为。 验证:全量 90 套件 / 1872 测试通过(+4 重型套件);覆盖率四项均高于阈值; typecheck(src+tests)、lint、build 零错误零告警;e2e 14 项通过;dist 已重建。
8.9 KiB
Contributing to MetonaSqlark
Thanks for your interest in contributing! This document outlines the development workflow and conventions.
Prerequisites
- Node.js >= 16.0.0
- npm >= 8.0.0
Setup
git clone https://git.metona.cn/MetonaTeam/MetonaSqlark.git
cd MetonaSqlark
npm install
Development
# Start dev server with hot reload (port 3001)
npm run dev
# Run tests in watch mode
npm run test:watch
# Type check
npm run typecheck
# Lint
npm run lint
npm run lint:fix
# Format
npm run format
Project Structure
src/
├── index.ts # Entry point, global API (MetonaSqlark + MeSqlark)
├── core.ts # MetonaSqlark main class
├── constants.ts # Types, defaults, enums, errors
├── connection-manager.ts # Connection pool (connect/disconnect)
├── engine/ # Storage engines
│ ├── interface.ts # IStorageEngine interface
│ ├── memory.ts # MemoryEngine (Map-based)
│ ├── kvstore_engine.ts # KVStoreEngine (disk mode, self-built KV store)
│ ├── kvstore/ # KVStore (log + snapshot + atomic multi-key write)
│ └── aria/ # AriaEngine (LSM-Tree page storage engine)
│ ├── index/ # LSM / MemTable / SSTable / Bloom / MergeIterator
│ ├── page/ # 4KB slotted page format
│ ├── buffer/ # Buffer Pool (LRU eviction)
│ ├── wal/ # Write-Ahead Log + Checkpoint
│ ├── transaction/ # MVCC manager
│ ├── store/ # Backends (OPFS / KVStore / Memory / Encrypted)
│ ├── locks.ts # Web Locks multi-tab exclusive lock
│ └── compression/ # LZ4
├── hybrid/ # HybridEngine (write-through)
├── migration/ # Legacy IndexedDB migration tool (one-shot)
├── table/ # Table management & Schema validation
├── query/ # Query system
│ ├── ast.ts # SQL AST type definitions
│ ├── builder.ts # Chainable QueryBuilder API
│ ├── compiler.ts # AST → QueryPlan compiler
│ ├── executor.ts # QueryExecutor with JOIN/GROUP BY support
│ └── where-matcher.ts # Unified WHERE matching logic
├── sql/ # SQL parser
│ ├── tokens.ts # Token types & keywords
│ ├── lexer.ts # Tokenizer
│ ├── parser.ts # Recursive descent parser
│ └── params.ts # Parameter binding (? placeholders)
├── transaction/ # Transaction manager
├── plugin/ # Plugin system (14 lifecycle hooks)
└── integrations/ # React & Vue hooks
tests/ # Test suite (1304 test cases, 76 suites + 12 e2e)
tests/helpers/ # 共享测试工具(OPFS mock 等)
tests/e2e/ # Playwright e2e(真实 Chromium + OPFS)
site/ # Documentation site (index / docs / demo)
Code Conventions
TypeScript
- Strict mode is enabled — all code must pass
tsc --noEmit. - Export types explicitly. Avoid
anywhere possible. - Use
interfacefor object shapes,typefor unions/primitives.
Style
- Run
npm run formatbefore committing (uses Prettier). - Follow existing comment patterns: JSDoc
/** */for public APIs,//for inline notes. - Keep functions focused and under ~60 lines where practical.
Testing
- Every new feature must include tests.
- Test files mirror source structure:
src/foo.ts→tests/foo.test.ts. - Use descriptive test names:
('does X when Y'). - Run the full suite before submitting:
npm test.
Commits
- Use conventional commit messages:
feat: add SQL GROUP BY supportfix: JOIN with multiple ON conditionsdocs: update API referencetest: add edge case coveragechore: 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
-
AST as Universal Intermediate Representation: Both SQL strings and Query Builder produce the same AST, ensuring consistent behavior regardless of API used.
-
Write-Through Hybrid Strategy: When using
mode: 'hybrid', all writes go to both memory and disk simultaneously. Reads always hit memory for maximum speed. -
Plugin Hook Pipeline: 14 lifecycle hooks let plugins observe and adjust database operations without modifying core code. Hook contract (v0.8.0):
- return values are ignored (you cannot cancel an operation by returning
false, nor rewrite the SQL by returning a string); - mutate the argument object in place to change it — this works on the Table API
path (
db.table(...).insert(rows)); - throw to abort the operation (the error propagates to the caller);
- the SQL path passes a copy for
beforeInsert, so mutating it there has no effect. Use the Table API when you need to transform rows. These rules are covered by tests; changing them requires updating this section.
- return values are ignored (you cannot cancel an operation by returning
-
Hand-Written SQL Parser: No dependencies on parser generators — a recursive-descent parser keeps the bundle size minimal.
Building
# Production build (all formats)
npm run build
# Output in dist/
# ├── metona-sqlark.js UMD
# ├── metona-sqlark.min.js UMD minified (~105KB / ~27KB gzip)
# ├── metona-sqlark.esm.js ES Module
# ├── metona-sqlark.cjs CommonJS
# └── metona-sqlark.d.ts TypeScript declarations
Plugin Development
Plugins follow a simple convention:
import type { MetonaPlugin } from '@metona-team/metona-sqlark';
const myPlugin: MetonaPlugin = {
name: 'myPlugin',
version: '1.0.0',
description: 'Description of my plugin',
// higher = executed first(v0.8.0 起真正生效:install 与钩子都按优先级降序;
// 同优先级保持 plugins 数组顺序)
priority: 50,
install(db) {
// Use db.on() to subscribe to hooks
db.on('beforeInsert', async (rows) => {
// Validate or transform data
});
db.on('afterQuery', async (sql, result) => {
// Log or cache query results
});
},
destroy() {
// Clean up event listeners, timers, etc.
},
};
// Register in config
const db = await MetonaSqlark.create({
name: 'my-app',
plugins: [myPlugin],
});
Available Lifecycle Hooks
| Hook | Trigger | Parameters |
|---|---|---|
beforeCreateTable |
Before table creation | schema |
afterCreateTable |
After table creation | schema |
beforeDropTable |
Before table drop | tableName |
afterDropTable |
After table drop | tableName |
beforeInsert |
Before row insert | rows[] |
afterInsert |
After row insert | rows[] |
beforeUpdate |
Before row update | query, updates |
afterUpdate |
After row update | query, updates, count |
beforeDelete |
Before row delete | query |
afterDelete |
After row delete | query, count |
beforeQuery |
Before SQL query | sql |
afterQuery |
After SQL query | sql, result |
beforeTransaction |
Before transaction | - |
afterTransaction |
After transaction | - |
Releasing
- Update version in
package.jsonandsrc/constants.ts(VERSIONconstant). - Update
CHANGELOG.md. - Run full test suite:
npm test. - Build:
npm run build. - Publish:
npm publish.
Questions?
Open an issue at git.metona.cn/MetonaTeam/MetonaSqlark/issues.