release: v0.4.2 — 生产就绪与崩溃自愈 + 问题清单修复 + 版本迭代
CI / test (18.x) (push) Successful in 10m7s
CI / test (20.x) (push) Successful in 10m6s
CI / test (22.x) (push) Successful in 10m2s
CI / test (24.x) (push) Successful in 10m0s

This commit is contained in:
thzxx
2026-08-09 19:15:37 +08:00
parent a1e4f5071c
commit 22b0b1fad4
34 changed files with 6626 additions and 565 deletions
+146 -32
View File
@@ -118,7 +118,7 @@ interface MetonaPlugin {
/** 销毁 */
destroy(): void;
}
declare const VERSION = "0.4.1";
declare const VERSION = "0.4.2";
/**
* metona-sqlark Plugin — 插件系统
@@ -202,6 +202,14 @@ interface IStorageEngine {
releaseSavepoint?(name: string): Promise<void>;
/** 在线备份:导出全库一致性快照 */
backup?(): Promise<Record<string, Record<string, unknown>[]>>;
/** 崩溃恢复自愈:校验并清理损坏数据、恢复一致性(检测到异常后调用,无需删库重建) */
repair?(): Promise<void>;
/** 清空全部数据与表结构(保留库本身,供演示页刷新/重建用) */
clearAll?(): Promise<void>;
/** 读取库内元数据(迁移版本持久化用) */
getMeta?(key: string): Promise<string | null>;
/** 写入库内元数据(迁移版本持久化用) */
setMeta?(key: string, value: string): Promise<void>;
}
/**
@@ -651,6 +659,16 @@ declare class MetonaSqlark {
addMigration(version: number, up: (db: MetonaSqlark) => Promise<void>): void;
/** 执行迁移到指定版本 */
migrateTo(targetVersion: number): Promise<void>;
/**
* 崩溃恢复自愈 — 校验并清理损坏数据、恢复一致性。
* 检测到异常后调用,无需删库重建。
*/
repair(): Promise<void>;
/**
* 清空全部数据与表结构(保留库本身)。
* 支持后续继续使用本实例重建表。
*/
clearAll(): Promise<void>;
/** 获取插件管理器 */
getPluginManager(): PluginManager;
/** 注册钩子 */
@@ -667,31 +685,47 @@ declare class MetonaSqlark {
private _debug;
}
/**
* metona-sqlark Memory Engine — 基于 Map 的内存存储引擎
* @module engine/memory
*/
declare class MemoryEngine implements IStorageEngine {
readonly name = "memory";
private tables;
private schemas;
private indexes;
private opened;
/** v0.4.2-fix: 库内元数据(迁移版本持久化用) */
private metaStore;
private snapshot;
open(_dbName: string, _version: number): Promise<void>;
close(): Promise<void>;
isOpen(): boolean;
/** 内存引擎无需修复(无持久化损坏概念) */
repair(): Promise<void>;
/** 清空全部数据与表结构 */
clearAll(): Promise<void>;
getMeta(key: string): Promise<string | null>;
setMeta(key: string, value: string): Promise<void>;
createTable(schema: TableSchema): Promise<void>;
dropTable(tableName: string): Promise<void>;
hasTable(tableName: string): Promise<boolean>;
getTableNames(): Promise<string[]>;
getTableSchema(tableName: string): Promise<TableSchema | null>;
/**
* v0.4.2-fix: 引擎级 ALTER TABLE — 直接修改内存 schema 引用并清理行数据。
* (此前走 executor 通用路径,行为相同;统一到引擎层保证 Hybrid/IndexedDB 委托一致性)
*/
alterTable(tableName: string, action: 'ADD' | 'DROP', column: ColumnDef & {
name: string;
}): Promise<void>;
insert(tableName: string, rows: Record<string, unknown>[]): Promise<string[]>;
find(tableName: string, query: QueryPlan): Promise<Record<string, unknown>[]>;
/** v0.4.0: 流式查询 — 逐行回调(单次迭代,不物化结果数组) */
findStream(tableName: string, query: QueryPlan, onRow: (row: Record<string, unknown>) => void): Promise<number>;
update(tableName: string, query: QueryPlan, updates: Record<string, unknown>): Promise<number>;
/**
* v0.4.2-fix: ON UPDATE 外键级联 — 被引用表主键变更时处理引用表:
* RESTRICT 抛错 / CASCADE 更新 FK 值 / SET NULL 置空。
* 分两阶段:先全量 RESTRICT 检查(任何修改前),再执行级联(防部分修改)。
*/
private applyUpdateCascade;
delete(tableName: string, query: QueryPlan): Promise<number>;
count(tableName: string, query?: QueryPlan): Promise<number>;
clear(tableName: string): Promise<void>;
@@ -721,13 +755,6 @@ declare class MemoryEngine implements IStorageEngine {
private cascadeDelete;
}
/**
* metona-sqlark IndexedDB Engine — 基于 IndexedDB 的持久化存储引擎
* @module engine/indexeddb
*
* v0.1.13: 支持事务 — beginTransaction 延迟 IDB 写入,commit 批量刷盘,rollback 恢复快照。
*/
declare class IndexedDBEngine implements IStorageEngine {
readonly name = "indexeddb";
private db;
@@ -736,6 +763,29 @@ declare class IndexedDBEngine implements IStorageEngine {
private memoryCache;
private txActive;
open(dbName: string, version: number): Promise<void>;
/** v0.4.2-fix: 多标签页冲突处理 — 其他标签页升级版本时自动关闭当前连接 */
private setupVersionChangeHandler;
/**
* v0.4.2-fix: 打开 IndexedDB 连接。
* - P0-2: 请求版本低于库实际版本(VersionError)时,先无版本参数探测库当前版本,
* 再以实际版本重开(建表每张表版本号 +1,config.version 会过期)
* - P2-8: onblocked 为瞬时状态(另一连接短暂持有),等待后重试多次,超时才抛 IDB_BLOCKED
*/
private openDatabaseWithRetry;
/**
* 发起一次 indexedDB.open 请求(success/error/blocked 三态收敛)。
* onblocked 不立即失败:阻塞解除后 success 仍会触发,仅超时兜底判失败,
* 避免"拒绝后连接迟到成功"泄漏未关闭的数据库连接。
*/
private openRequest;
/** 无版本参数打开库,解析其当前实际版本号(随后立即关闭) */
private resolveCurrentVersion;
/**
* v0.4.2-fix (P2-7): 确保 __metona_schema store 存在。
* 新库(或版本升级前创建的旧库)没有该 store 时,通过一次版本升级创建,
* 使 getMeta/setMeta(迁移版本持久化)始终可用。
*/
private ensureSchemaStore;
/**
* 从 IDB 恢复内存 schema
* 1. 优先读取持久化的 schema 记录('__metona_schema' storev0.3.2
@@ -743,12 +793,31 @@ declare class IndexedDBEngine implements IStorageEngine {
*/
private rebuildSchemaFromIDB;
close(): Promise<void>;
/**
* v0.4.2-fix: 自愈 — 从磁盘重建内存 schema 与数据(schema 丢失/内存不一致时调用)。
* 无删库需求即可恢复可用的库。
*/
repair(): Promise<void>;
/**
* v0.4.2-fix: 清空全部数据与表结构(含持久化 schema 记录),保留库本身。
* 单个版本升级事务内原子完成。
*/
clearAll(): Promise<void>;
getMeta(key: string): Promise<string | null>;
setMeta(key: string, value: string): Promise<void>;
isOpen(): boolean;
createTable(schema: TableSchema): Promise<void>;
dropTable(tableName: string): Promise<void>;
hasTable(tableName: string): Promise<boolean>;
getTableNames(): Promise<string[]>;
getTableSchema(tableName: string): Promise<TableSchema | null>;
/**
* v0.4.2-fix: 引擎级 ALTER TABLE — schema 持久化到 __metona_schema store
* 重启后 ALTER 不丢失(此前通用路径只改内存引用,重启回退;DROP 的行数据也没真正删)。
*/
alterTable(tableName: string, action: 'ADD' | 'DROP', column: ColumnDef & {
name: string;
}): Promise<void>;
insert(tableName: string, rows: Record<string, unknown>[]): Promise<string[]>;
find(tableName: string, query: QueryPlan): Promise<Record<string, unknown>[]>;
/** v0.4.0: 流式查询 — IDB 批量读入后逐行回调(保持接口一致性) */
@@ -771,19 +840,13 @@ declare class IndexedDBEngine implements IStorageEngine {
private idbUpdate;
private idbDelete;
private idbClear;
/** 持久化单个表 schema 到 __metona_schema storev0.4.2-fix: ALTER TABLE 用) */
private persistSchema;
/** 将内存缓存中的所有表数据原子性刷新到 IndexedDB */
private flushToIDB;
private ensureDB;
}
/**
* metona-sqlark OPFS Engine — 基于 Origin Private File System 的持久化存储引擎
* @module engine/opfs
*
* 使用 JSON-per-table 文件存储方案。
* 目录结构:{dbName}/tables/{tableName}.json
*/
declare class OPFSEngine implements IStorageEngine {
readonly name = "opfs";
private root;
@@ -793,11 +856,21 @@ declare class OPFSEngine implements IStorageEngine {
open(dbName: string, version: number): Promise<void>;
close(): Promise<void>;
isOpen(): boolean;
/** 自愈:重置内存缓存后从 OPFS 重新加载(单文件损坏不影响其他表) */
repair(): Promise<void>;
/** 清空全部数据与表结构(删除目录内全部文件) */
clearAll(): Promise<void>;
getMeta(key: string): Promise<string | null>;
setMeta(key: string, value: string): Promise<void>;
createTable(schema: TableSchema): Promise<void>;
dropTable(tableName: string): Promise<void>;
hasTable(tableName: string): Promise<boolean>;
getTableNames(): Promise<string[]>;
getTableSchema(tableName: string): Promise<TableSchema | null>;
/** v0.4.2-fix: 引擎级 ALTER TABLE — 内存 + schema 持久化 + 整表文件重写 */
alterTable(tableName: string, action: 'ADD' | 'DROP', column: ColumnDef & {
name: string;
}): Promise<void>;
insert(tableName: string, rows: Record<string, unknown>[]): Promise<string[]>;
find(tableName: string, query: QueryPlan): Promise<Record<string, unknown>[]>;
/** v0.4.0: 流式查询(委托内存缓存) */
@@ -814,7 +887,11 @@ declare class OPFSEngine implements IStorageEngine {
private ensureDir;
private writeTableData;
private readTableData;
/** 从 OPFS 加载已有表数据到内存缓存 */
/**
* 从 OPFS 加载已有表到内存缓存。
* v0.4.2-fix: 优先从持久化 schema__metona_schema_*.meta)恢复 —
* 空表不再消失、索引标记/主键/约束完整;无 schema 记录的旧库从数据推断(兼容)。
*/
private loadExistingTables;
/** 从 OPFS 加载表数据到内存缓存 */
loadTableIntoMemory(tableName: string, schema: TableSchema): Promise<void>;
@@ -867,21 +944,41 @@ declare class AriaEngine implements IStorageEngine {
private bufferPool;
constructor(config?: AriaEngineConfig);
open(dbName: string, _version: number): Promise<void>;
/** open 内部实现(错误包装在 open 外层) */
private openInternal;
close(): Promise<void>;
/**
* v0.4.2-fix: 崩溃恢复/自愈 — 校验并移除损坏 SSTable、截断 WAL、重建二级索引。
* 应用层检测到异常后调用,无需删库重建。
*/
repair(): Promise<void>;
/**
* v0.4.1: 重置数据库 — 清空全部数据与表结构(演示页刷新/重新初始化用)。
* 清空存储后端、LSM、WAL、MVCC 与二级索引,后续可继续使用本实例。
*/
clearAll(): Promise<void>;
isOpen(): boolean;
getMeta(key: string): Promise<string | null>;
setMeta(key: string, value: string): Promise<void>;
createTable(schema: TableSchema): Promise<void>;
dropTable(tableName: string): Promise<void>;
/**
* v0.4.2-fix: 清理指定表的全部二级索引 LSM(内存 + 存储文件 + meta)。
* dropTable / DROP_TABLE 恢复 / alterTable DROP 索引列 共用。
*/
private cleanupTableIndexes;
hasTable(tableName: string): Promise<boolean>;
getTableNames(): Promise<string[]>;
getTableSchema(tableName: string): Promise<TableSchema | null>;
insert(tableName: string, rows: Record<string, unknown>[]): Promise<string[]>;
find(tableName: string, query: QueryPlan): Promise<Record<string, unknown>[]>;
update(tableName: string, query: QueryPlan, updates: Record<string, unknown>): Promise<number>;
/**
* v0.4.2-fix: ON UPDATE 外键级联 — 主键 oldPk → newPk 时处理引用表。
* RESTRICT 抛错 / CASCADE 更新 FK / SET NULL 置空(含索引与 WAL 记录)。
* 两阶段:先全量 RESTRICT 检查,再执行级联。
*/
private applyForeignKeyUpdateRules;
delete(tableName: string, query: QueryPlan): Promise<number>;
/**
* v0.4.1: 外键级联规则 — 对齐 MemoryEngine.cascadeDelete 行为。
@@ -970,6 +1067,8 @@ declare class AriaEngine implements IStorageEngine {
* REINDEX: 重建指定表的所有二级索引
*/
reindexTable(tableName: string): Promise<number>;
/** v0.4.2-fix: 重建索引内部实现(不校验 opened,供 open 恢复流程调用) */
private reindexTableInternal;
/**
* VACUUM: 压缩 LSM + 清理碎片
*/
@@ -985,6 +1084,8 @@ declare class AriaEngine implements IStorageEngine {
estimatedRows: number;
};
private ensureOpen;
/** v0.4.2-fix: Aria 事务中 DDL 显式拒绝(结构变更无法通过行快照回滚) */
private ensureNoDDLInTransaction;
private ensureTable;
/** Get the number of WAL records stored */
private getWALCount;
@@ -992,16 +1093,6 @@ declare class AriaEngine implements IStorageEngine {
private setWALCount;
}
/**
* metona-sqlark Hybrid Engine — 内存 + 磁盘混合存储引擎
* @module hybrid/index
*
* 采用 write-through 策略:
* - 所有写操作同时写入内存和磁盘
* - 所有读操作直接从内存返回
* - 数据库打开时从磁盘加载数据到内存
*/
declare class HybridEngine implements IStorageEngine {
readonly name = "hybrid";
private memoryEngine;
@@ -1018,11 +1109,21 @@ declare class HybridEngine implements IStorageEngine {
reloadMemoryFromDisk(): Promise<void>;
close(): Promise<void>;
isOpen(): boolean;
/** 自愈:修复磁盘引擎后重载内存缓存 */
repair(): Promise<void>;
/** 清空全部数据与表结构 */
clearAll(): Promise<void>;
getMeta(key: string): Promise<string | null>;
setMeta(key: string, value: string): Promise<void>;
createTable(schema: TableSchema): Promise<void>;
dropTable(tableName: string): Promise<void>;
hasTable(tableName: string): Promise<boolean>;
getTableNames(): Promise<string[]>;
getTableSchema(tableName: string): Promise<TableSchema | null>;
/** v0.4.2-fix: 引擎级 ALTER TABLE — 双引擎同步(磁盘持久化 + 内存引用) */
alterTable(tableName: string, action: 'ADD' | 'DROP', column: ColumnDef & {
name: string;
}): Promise<void>;
insert(tableName: string, rows: Record<string, unknown>[]): Promise<string[]>;
find(tableName: string, query: QueryPlan): Promise<Record<string, unknown>[]>;
/** v0.4.0: 流式查询(内存引擎逐行回调) */
@@ -1177,8 +1278,17 @@ interface IStorageBackend {
read(key: string): Promise<ArrayBuffer | null>;
/** 写入数据块 */
write(key: string, data: ArrayBuffer): Promise<void>;
/**
* 批量原子写入(v0.4.2-fix):多个 key 在单个底层事务中提交,
* 中断时整体回滚,不留半写状态。WAL count 与记录同事务保证一致性。
*/
writeMany(entries: Record<string, ArrayBuffer>): Promise<void>;
/** 删除数据块 */
delete(key: string): Promise<void>;
/**
* 批量原子删除(v0.4.2-fix):多个 key 在单个底层事务中提交。
*/
deleteMany(keys: string[]): Promise<void>;
/** 列出所有 key */
listKeys(): Promise<string[]>;
/** 检查 key 是否存在 */
@@ -1207,7 +1317,11 @@ declare class OPFSBackend implements IStorageBackend {
isOpen(): boolean;
read(key: string): Promise<ArrayBuffer | null>;
write(key: string, data: ArrayBuffer): Promise<void>;
/** v0.4.2-fix: 批量写入 — 串行队列内逐个落盘(OPFS 无跨文件事务,顺序保证一致) */
writeMany(entries: Record<string, ArrayBuffer>): Promise<void>;
delete(key: string): Promise<void>;
/** v0.4.2-fix: 批量删除 — 串行队列内逐个删除 */
deleteMany(keys: string[]): Promise<void>;
listKeys(): Promise<string[]>;
exists(key: string): Promise<boolean>;
clear(): Promise<void>;