独立核验(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 已重建。
125 lines
3.5 KiB
TypeScript
125 lines
3.5 KiB
TypeScript
/**
|
|
* metona-sqlark Connection Manager — 数据库实例连接池
|
|
* @module connection-manager
|
|
*
|
|
* v0.1.13: 避免重复创建同名数据库实例,通过 connect() 复用已有连接。
|
|
* 管理实例生命周期,防止重复 open IndexedDB。
|
|
*/
|
|
|
|
import { MetonaSqlark } from './core';
|
|
import type { DatabaseConfig } from './constants';
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// ConnectionManager
|
|
// ---------------------------------------------------------------------------
|
|
|
|
class ConnectionManager {
|
|
/** 活跃连接:dbName → MetonaSqlark */
|
|
private connections = new Map<string, MetonaSqlark>();
|
|
|
|
/** 连接引用计数:dbName → count */
|
|
private refCount = new Map<string, number>();
|
|
|
|
/**
|
|
* 获取或创建数据库实例
|
|
*
|
|
* 如果同名数据库已打开,复用已有实例并增加引用计数。
|
|
* 否则创建新实例。
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* const db = await MetonaSqlark.connect({ name: 'my-app', mode: 'hybrid' });
|
|
* // ... use db
|
|
* await db.disconnect(); // 引用计数 -1,归零时自动关闭
|
|
* ```
|
|
*/
|
|
async connect(config: DatabaseConfig): Promise<MetonaSqlark> {
|
|
const name = config.name;
|
|
|
|
// 已有连接 → 复用
|
|
const existing = this.connections.get(name);
|
|
if (existing && existing.isReady()) {
|
|
const count = (this.refCount.get(name) ?? 0) + 1;
|
|
this.refCount.set(name, count);
|
|
return existing;
|
|
}
|
|
|
|
// 创建新连接
|
|
const db = new MetonaSqlark(config);
|
|
await db.init();
|
|
|
|
this.connections.set(name, db);
|
|
this.refCount.set(name, 1);
|
|
|
|
// 注入 disconnect 方法(类型已在 MetonaSqlark 上声明,无需 any 断言)
|
|
db.disconnect = async () => {
|
|
await this.release(name);
|
|
};
|
|
|
|
return db;
|
|
}
|
|
|
|
/**
|
|
* 释放连接引用。引用计数归零时自动关闭数据库。
|
|
*/
|
|
async release(dbName: string): Promise<void> {
|
|
const count = (this.refCount.get(dbName) ?? 1) - 1;
|
|
if (count <= 0) {
|
|
const db = this.connections.get(dbName);
|
|
if (db) {
|
|
await db.close();
|
|
this.connections.delete(dbName);
|
|
}
|
|
this.refCount.delete(dbName);
|
|
} else {
|
|
this.refCount.set(dbName, count);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 强制关闭指定数据库(忽略引用计数)
|
|
*/
|
|
async forceClose(dbName: string): Promise<void> {
|
|
const db = this.connections.get(dbName);
|
|
if (db) {
|
|
await db.close();
|
|
this.connections.delete(dbName);
|
|
}
|
|
this.refCount.delete(dbName);
|
|
}
|
|
|
|
/**
|
|
* 强制关闭所有连接
|
|
*/
|
|
async closeAll(): Promise<void> {
|
|
for (const [, db] of this.connections) {
|
|
try { await db.close(); } catch { /* ignore */ }
|
|
}
|
|
this.connections.clear();
|
|
this.refCount.clear();
|
|
}
|
|
|
|
/**
|
|
* 获取所有活跃连接名
|
|
*/
|
|
getActiveConnections(): string[] {
|
|
return Array.from(this.connections.keys());
|
|
}
|
|
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 全局单例
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const manager = new ConnectionManager();
|
|
|
|
// 挂载到 MetonaSqlark 静态方法(v0.8.0:类型已在类上声明,无需 any 断言)
|
|
MetonaSqlark.connect = (config: DatabaseConfig) => manager.connect(config);
|
|
MetonaSqlark.disconnect = (dbName: string) => manager.release(dbName);
|
|
MetonaSqlark.disconnectAll = () => manager.closeAll();
|
|
MetonaSqlark.getActiveConnections = () => manager.getActiveConnections();
|
|
|
|
export { manager as connectionManager };
|
|
export default manager;
|