Files
MetonaSqlark/CONTRIBUTING.md
T
thzxx c1c3036abd fix(site/v0.8.0): 站点版本同步 + 现场失败修复(file:// 打不开 OPFS 的可操作错误)
用户报告"站点演示失败了",实测复现并定位根因:

- 现象:直接双击 site/demo.html(file://)→ 点「🌲 Aria」→
  " 数据库初始化失败: Failed to open AriaEngine database "demo""(Memory 正常)。
- 根因:file:// 属不透明来源,Chromium 拒绝 navigator.storage.getDirectory()
  并抛 SecurityError;此时 isSecureContext 仍为 true、API 也存在,无法提前探测。
  引擎把它包成 ARIA_OPEN_ERROR 时丢掉了底层错误 → 消息对用户不可操作。
- 修复:OPFSBackend.open() 显式检查并抛 ARIA_OPFS_UNAVAILABLE,消息给出两条出路
  (用 http(s) 打开 / 改用 mode:'memory'),原始 SecurityError 挂 cause;
  site/demo.html 额外用中文说明"为什么失败 + 怎么修"。

顺带修掉一个更普遍的问题:DatabaseError 的第三个参数只进 details,err.cause
恒为 undefined,而文档/注释多处写"底层错误作为 cause 保留"。现在两者都成立
(details 语义不变;cause 声明为公开字段并接入标准错误链)。

站点版本同步:demo.html(title / 状态栏 / SQL 预置脚本 / console 日志)与
benchmark.html(title)此前仍是 v0.7.4(日志甚至是 v0.4.2)→ 统一 v0.8.0;
docs.html 的 AriaEngine 版本演进列表补上 v0.8.0 条目、错误码表补
ARIA_OPFS_UNAVAILABLE;README 补"OPFS 需要 http(s) 页面"的浏览器兼容说明。

回归与门禁:tests/engine/aria-opfs-unavailable.test.ts(5 项,含正常环境正控);
变异 R19 / R20 均被拦住(总计 42/42);93 套件 / 1985 用例;覆盖率
90.59 / 82.61 / 94.14 / 93.50(阈值 90/82/94/93);e2e 14/14;lint + 两份 tsc 干净;
dist 重建(251,731 B / gzip 63,431 B)并已同步全部体积宣称。
2026-09-15 17:09:29 +08:00

11 KiB
Raw Blame History

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 (1985 test cases, 92 suites + 14 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 any where possible.
  • Use interface for object shapes, type for unions/primitives.

Style

  • Run npm run format before 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.tstests/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 support
    • fix: JOIN with multiple ON conditions
    • docs: update API reference
    • test: add edge case coverage
    • chore: optimize rollup dev build

Architecture

MetonaSqlark follows a layered architecture:

┌──────────────────────────────────────────────────┐
│         MetonaSqlark / MeSqlark (Public API)      │
├──────────────────────────────────────────────────┤
│  SQL Parser  │ QueryBuilder │ Transaction │ Plugin │
│  (Lexer→AST) │ (.select…)   │  Manager    │ System │
├──────────────────────────────────────────────────┤
│         QueryExecutor (AST → Results)             │
│    JOIN · GROUP BY · HAVING · DISTINCT · Agg     │
├──────────────────────────────────────────────────┤
│         IStorageEngine (Interface)                │
├─────────┬──────────┬──────────┬──────────────────┤
│ Memory  │ IndexedDB │ OPFS     │ Hybrid           │
│ Engine  │ Engine    │ Engine   │ Engine           │
└─────────┴──────────┴──────────┴──────────────────┘

Key Design Decisions

  1. AST as Universal Intermediate Representation: Both SQL strings and Query Builder produce the same AST, ensuring consistent behavior regardless of API used.

  2. Write-Through Hybrid Strategy: When using mode: 'hybrid', all writes go to both memory and disk simultaneously. Reads always hit memory for maximum speed.

  3. Plugin Hook Pipeline: 14 lifecycle hooks 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.
  4. 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 (251,731 B / gzip 63,431 B)
# ├── 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 firstv0.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

  1. Update version in package.json and src/constants.ts (VERSION constant).
  2. Update CHANGELOG.md.
  3. Run full test suite: npm test.
  4. Build: npm run build.
  5. Publish: npm publish.

Questions?

Open an issue at git.metona.cn/MetonaTeam/MetonaSqlark/issues.


变异验证(回归套件的"是否只是陪跑"检查)

修复类提交必须能回答一个问题:把修复回退到修复前的行为,对应用例会不会失败? 不会失败的用例等于没有保护。

# 全部 42 项变异(存储单一提交点 / LSM 结构根治 / v0.8.0 全量审查修复)
python3 scripts/mutation-b6.py

# 只跑其中一条(按名字子串匹配)
python3 scripts/mutation-b6.py "WAL 分片号复用"

脚本会临时改写 src/、运行对应用例、再恢复源码(收到 SIGINT/SIGTERM 也会恢复), 最后打印每一条是"被拦住"还是"仍然通过"。出现任何一条"仍然通过",本次提交不算完成。

脚本自身的保证(v0.8.0 审查后加固)

机制 为什么需要
正控 先跑一遍未变异的干净基线(B-6 套件 + 分片 WAL 套件必须全绿)。没有正控就无法区分"变异被测试拦住"与"这套件本来就是红的"
编译失败 ≠ 被拦住 Test suite failed to run / error TS#### 单独归类为 BAD(变异破坏编译):一个把源码改成语法错误的"变异"什么都没验证
用例未匹配 ≠ 被拦住 Tests: 0 total / No tests found 归类为 BAD(用例未匹配)(模式写错时最容易被误读成"测试通过了")
锚点唯一性 锚点在文件里出现多次时拒绝变异(否则可能改到另一处、验错位置)
超时 单条变异 300s 上限,超时归类为 BAD(超时) 而不是放过
逐字节恢复校验 每条变异跑完用 sha256 比对恢复结果;不一致立即 FATAL 退出
进程锁 .mutation-b6.lockO_EXCL):并发运行会互相踩掉对方的源码,直接拒绝启动

判定顺序固定为:编译失败 → 用例未匹配 → /Tests: N failed(= 被拦住)→ 其余为"仍然通过"。 只有 OK(变异被拦住) 计入成功,其余任何状态都会让脚本以非零退出码结束。