Files
MetonaSqlark/src/connection-manager.ts
T
thzxx 799560ea05 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 已重建。
2026-09-15 08:06:23 +08:00

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;