docs(G6): 宣称与实现一致性收口 —— priority 真正生效、MVCC/backup/引擎数/打包 全部对齐

独立核验(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 已重建。
This commit is contained in:
thzxx
2026-09-15 08:06:23 +08:00
parent d14663ef80
commit 799560ea05
26 changed files with 35817 additions and 106 deletions
+91
View File
@@ -0,0 +1,91 @@
/**
* v0.8.0 回归套件 —— 插件 priority 语义(G6 宣称与实现一致)
* ============================================================================
* 修复前:`PluginManager.register()` 会把插件**按 priority 插入数组的正确位置**
* 但 `install()` 是在 `register()` 内部**立即**调用的 —— 于是 install 与钩子的
* 实际执行顺序仍等于 `config.plugins` 的数组顺序。
* 实测(priority 为 low=1 / high=100 / mid=50,数组顺序 low,high,mid):
* 钩子顺序 low→high→mid,只有 `getPlugins()` 是 high,mid,low。
* 而 README / CONTRIBUTING / `constants.ts` 一直宣称"priority 越大越先执行"
* —— 文档与实现不符。本套件锁定**让实现符合文档**后的行为(priority 是用户
* 可见的配置项,静默无效比没有这个字段更糟)。
*
* 实现要点:Core 在注册前按 priority **稳定**排序(同优先级保持数组顺序),
* 因此 `install()` 与由它注册的钩子都按优先级降序执行。
*/
import { describe, it, expect } from '@jest/globals';
import { MetonaSqlark } from '../src/core';
import { PluginManager } from '../src/plugin/index';
import type { MetonaPlugin } from '../src/constants';
function makePlugin(name: string, priority: number, log: string[]): MetonaPlugin {
return {
name,
version: '1.0.0',
priority,
install: (db?: unknown) => {
log.push(`install:${name}`);
// register(plugin) 不带 db 时(纯 PluginManager 用法)没有 db 实例可挂钩子
if (db) (db as MetonaSqlark).on('afterQuery', () => { log.push(`hook:${name}`); });
},
destroy: () => log.push(`destroy:${name}`),
} as MetonaPlugin;
}
describe('[v0.8.0] 插件 priority', () => {
it('install 与钩子都按 priority 降序执行(同优先级保持数组顺序)', async () => {
const log: string[] = [];
const db = await MetonaSqlark.create({
name: 'prio-order',
mode: 'memory',
plugins: [makePlugin('low', 1, log), makePlugin('high', 100, log), makePlugin('mid', 50, log)],
});
await db.defineTable('t', { id: { type: 'string', primaryKey: true } });
await db.query("INSERT INTO t VALUES ('1')");
expect(log.filter((l) => l.startsWith('install:'))).toEqual([
'install:high', 'install:mid', 'install:low',
]);
expect(log.filter((l) => l.startsWith('hook:'))).toEqual([
'hook:high', 'hook:mid', 'hook:low',
]);
await db.close();
});
it('同优先级保持 config 数组顺序(稳定排序)', async () => {
const log: string[] = [];
const db = await MetonaSqlark.create({
name: 'prio-stable',
mode: 'memory',
plugins: [makePlugin('a', 5, log), makePlugin('b', 5, log), makePlugin('c', 5, log)],
});
await db.defineTable('t', { id: { type: 'string', primaryKey: true } });
await db.query("INSERT INTO t VALUES ('1')");
expect(log.filter((l) => l.startsWith('install:'))).toEqual(['install:a', 'install:b', 'install:c']);
await db.close();
});
it('缺省 priority 视为 0(排在显式正优先级之后)', async () => {
const log: string[] = [];
const noPriority = { ...makePlugin('none', 0, log) } as MetonaPlugin & { priority?: number };
delete noPriority.priority;
const db = await MetonaSqlark.create({
name: 'prio-default',
mode: 'memory',
plugins: [noPriority, makePlugin('explicit', 10, log)],
});
await db.defineTable('t', { id: { type: 'string', primaryKey: true } });
await db.query("INSERT INTO t VALUES ('1')");
expect(log.filter((l) => l.startsWith('install:'))).toEqual(['install:explicit', 'install:none']);
await db.close();
});
it('PluginManager.getPlugins() 按 priority 降序(回归护栏)', () => {
const log: string[] = [];
const pm = new PluginManager();
pm.register(makePlugin('low', 1, log));
pm.register(makePlugin('high', 100, log));
pm.register(makePlugin('mid', 50, log));
expect(pm.getPlugins().map((p) => p.name)).toEqual(['high', 'mid', 'low']);
});
});