release: v0.5.1 — 存储后端生产级硬化(CRC-32/全库加密/WAL分片/页面化存储/多标签页锁/e2e)+ 深度审查修复(假实现接线/死代码清理)
CI / test (18.x) (push) Successful in 10m10s
CI / test (20.x) (push) Successful in 10m10s
CI / test (22.x) (push) Successful in 10m6s
CI / e2e (push) Successful in 9m51s
CI / test (24.x) (push) Successful in 10m28s

This commit is contained in:
thzxx
2026-08-10 12:07:00 +08:00
parent cff98b0903
commit 334067d89e
88 changed files with 15713 additions and 10626 deletions
+1262 -522
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+145 -62
View File
@@ -1,3 +1,44 @@
interface AriaEngineConfig {
/** 页面大小(默认 4096 */
pageSize?: number;
/** Buffer Pool 页面数量(默认 256 */
bufferPoolPages?: number;
/** MemTable 刷盘阈值(默认 4MB */
memtableSizeThreshold?: number;
/** LSM 层级之间的容量倍数(默认 10) */
levelSizeMultiplier?: number;
/** Bloom Filter 每 key 位数(默认 10 */
bloomFilterBitsPerKey?: number;
/** 是否启用 WAL(默认 true */
walEnabled?: boolean;
/** WAL 同步模式 */
walSyncMode?: 'full' | 'batch' | 'none';
/** Checkpoint 间隔(操作数,默认 1000 */
checkpointInterval?: number;
/** 是否启用页面压缩(默认 false) */
compression?: boolean;
/** 存储后端 */
storageBackend?: 'indexeddb' | 'opfs' | 'memory';
/** WAL 大小阈值(字节,超过则强制 checkpoint,默认 16MB */
walSizeThreshold?: number;
/** 最大内存预算(MB,默认 64) */
maxMemoryMB?: number;
/**
* v0.4.5: 全库 AES-256-GCM 加密(backend 层透明加解密,WAL/SSTable/Schema/元数据全覆盖)。
* 密钥由 PBKDF2salt 持久化于库内 __aria_keymeta)派生,重启用同一密码即可解密。
*/
encryption?: {
/** 加密密码 */
password: string;
};
/**
* v0.4.5: SSTable 页面化存储(4KB 页面 + BufferPool/FileManager 管理,LRU 缓存)。
* 默认:storageBackend === 'opfs' 时自动启用(大文件随机读/写放大优化);
* 显式 false 强制关闭(整 value 存储,兼容旧行为)。
*/
pageStorage?: boolean;
}
/**
* metona-sqlark Constants — 类型定义 / 默认配置 / 枚举
* @module constants
@@ -64,6 +105,11 @@ interface DatabaseConfig {
debug?: boolean;
/** 多标签页同步(v0.3.2):BroadcastChannel 广播表变更,其他标签页自动刷新 */
multiTabSync?: boolean;
/**
* v0.4.5: AriaEngine 专属配置(mode='aria' 时透传):
* walSyncMode / checkpointInterval / encryption / pageStorage / compression 等
*/
aria?: AriaEngineConfig;
}
/** Where 条件操作符 */
type WhereOperator = '$eq' | '$ne' | '$gt' | '$gte' | '$lt' | '$lte' | '$in' | '$nin' | '$like' | '$and' | '$or' | '$not';
@@ -118,7 +164,7 @@ interface MetonaPlugin {
/** 销毁 */
destroy(): void;
}
declare const VERSION = "0.4.4";
declare const VERSION = "0.5.1";
/**
* metona-sqlark Plugin — 插件系统
@@ -350,7 +396,28 @@ interface CommitTransactionStatement {
interface RollbackTransactionStatement {
type: 'ROLLBACK';
}
type Statement = SelectStatement | SelectUnionStatement | ExplainStatement | InsertStatement | UpdateStatement | DeleteStatement | CreateTableStatement | DropTableStatement | AlterTableStatement | TruncateTableStatement | CreateIndexStatement | DropIndexStatement | BeginTransactionStatement | CommitTransactionStatement | RollbackTransactionStatement;
/** SAVEPOINT name / ROLLBACK TO SAVEPOINT name / RELEASE SAVEPOINT name */
interface SavepointStatement {
type: 'SAVEPOINT';
name: string;
/** SAVE = 创建;ROLLBACK = 回滚到;RELEASE = 释放 */
action: 'SAVE' | 'ROLLBACK' | 'RELEASE';
}
/** ANALYZE TABLE name — 收集表统计信息 */
interface AnalyzeStatement {
type: 'ANALYZE';
table: string;
}
/** REINDEX TABLE name — 重建表二级索引 */
interface ReindexStatement {
type: 'REINDEX';
table: string;
}
/** VACUUM — 压缩 LSM + 清理碎片 */
interface VacuumStatement {
type: 'VACUUM';
}
type Statement = SelectStatement | SelectUnionStatement | ExplainStatement | InsertStatement | UpdateStatement | DeleteStatement | CreateTableStatement | DropTableStatement | AlterTableStatement | TruncateTableStatement | CreateIndexStatement | DropIndexStatement | BeginTransactionStatement | CommitTransactionStatement | RollbackTransactionStatement | SavepointStatement | AnalyzeStatement | ReindexStatement | VacuumStatement;
/**
* metona-sqlark Query Executor — AST 执行器
@@ -363,8 +430,6 @@ declare class QueryExecutor {
private engine;
private maxRowsPerQuery;
constructor(engine: IStorageEngine, maxRowsPerQuery?: number);
/** 设置查询结果行数上限 */
setMaxRowsPerQuery(max: number): void;
execute(stmt: Statement): Promise<unknown>;
/** 递归执行 UNION / UNION ALL,返回合并结果 */
private executeSelectUnion;
@@ -406,6 +471,14 @@ declare class QueryExecutor {
private executeBegin;
private executeCommit;
private executeRollback;
/** SAVEPOINT name / ROLLBACK TO SAVEPOINT name / RELEASE SAVEPOINT name */
private executeSavepoint;
/** ANALYZE TABLE name — 收集表统计信息 */
private executeAnalyze;
/** REINDEX TABLE name — 重建表二级索引 */
private executeReindex;
/** VACUUM — 压缩 LSM + 清理碎片 */
private executeVacuum;
/** 列列表是否包含 CASE WHEN 表达式 */
private hasCaseColumn;
/**
@@ -456,14 +529,6 @@ declare class QueryExecutor {
private resolveOperatorSubqueries;
}
/**
* metona-sqlark Query Builder — 链式查询构建器
* @module query/builder
*
* 链式调用 → 构建 AST → 执行引擎操作。
* 支持 JOIN(需要 Executor)。
*/
declare class SelectQueryBuilder {
private engine;
private tableName;
@@ -508,7 +573,9 @@ declare class UpdateQueryBuilder {
private _updates;
private _where;
private onWrite?;
constructor(engine: IStorageEngine, tableName: string, _updates: Record<string, unknown>, onWrite?: (table: string) => void);
/** v0.5.1: CRUD hooks 触发回调 */
private onHooks?;
constructor(engine: IStorageEngine, tableName: string, _updates: Record<string, unknown>, onWrite?: (table: string) => void, onHooks?: (hook: HookName, args: unknown[]) => Promise<void>);
where(condition: WhereCondition): this;
execute(): Promise<number>;
toAST(): UpdateStatement;
@@ -518,17 +585,14 @@ declare class DeleteQueryBuilder {
private tableName;
private _where;
private onWrite?;
constructor(engine: IStorageEngine, tableName: string, onWrite?: (table: string) => void);
/** v0.5.1: CRUD hooks 触发回调 */
private onHooks?;
constructor(engine: IStorageEngine, tableName: string, onWrite?: (table: string) => void, onHooks?: (hook: HookName, args: unknown[]) => Promise<void>);
where(condition: WhereCondition): this;
execute(): Promise<number>;
toAST(): DeleteStatement;
}
/**
* metona-sqlark Table — 表操作 API
* @module table/table
*/
declare class Table<T = Record<string, unknown>> {
readonly name: string;
private engine;
@@ -536,7 +600,9 @@ declare class Table<T = Record<string, unknown>> {
private executor;
/** 写入回调(多标签页广播,v0.3.2) */
private onWrite?;
constructor(engine: IStorageEngine, tableName: string, executor?: QueryExecutor, onWrite?: (table: string) => void);
/** v0.5.1: CRUD 生命周期钩子触发回调(beforeInsert/afterInsert/... 真实接线) */
private onHooks?;
constructor(engine: IStorageEngine, tableName: string, executor?: QueryExecutor, onWrite?: (table: string) => void, onHooks?: (hook: HookName, args: unknown[]) => Promise<void>);
getSchema(): Promise<TableSchema>;
insert(row: T & Record<string, unknown>): Promise<string>;
insertMany(rows: (T & Record<string, unknown>)[]): Promise<string[]>;
@@ -637,6 +703,11 @@ declare class MetonaSqlark {
importTable(tableName: string, data: Record<string, unknown>[]): Promise<string[]>;
/** 导出整个数据库为 JSON */
exportAll(): Promise<Record<string, Record<string, unknown>[]>>;
/**
* v0.5.1: 在线备份 — 导出全库一致性快照。
* Aria 引擎走引擎级 backup()(MVCC 一致性视图);其余引擎回退 exportAll()。
*/
backup(): Promise<Record<string, Record<string, unknown>[]>>;
private listeners;
/** 订阅表变更 */
subscribe(tableName: string, callback: (event: {
@@ -654,6 +725,12 @@ declare class MetonaSqlark {
broadcastChange(tableName: string): void;
/** 写语句对应的表名(多标签页广播用) */
private writeStatementTable;
/**
* v0.5.1: SQL 写语句触发 CRUD 生命周期钩子。
* INSERT/UPDATE/DELETE 分别触发 beforeInsert/afterInsert、beforeUpdate/afterUpdate、
* beforeDelete/afterDelete(参数与 Table API 路径一致)。
*/
private triggerStatementHooks;
private migrations;
/** 注册迁移 */
addMigration(version: number, up: (db: MetonaSqlark) => Promise<void>): void;
@@ -901,35 +978,6 @@ declare class OPFSEngine implements IStorageEngine {
* 空表不再消失、索引标记/主键/约束完整;无 schema 记录的旧库从数据推断(兼容)。
*/
private loadExistingTables;
/** 从 OPFS 加载表数据到内存缓存 */
loadTableIntoMemory(tableName: string, schema: TableSchema): Promise<void>;
}
interface AriaEngineConfig {
/** 页面大小(默认 4096 */
pageSize?: number;
/** Buffer Pool 页面数量(默认 256 */
bufferPoolPages?: number;
/** MemTable 刷盘阈值(默认 4MB */
memtableSizeThreshold?: number;
/** LSM 层级之间的容量倍数(默认 10) */
levelSizeMultiplier?: number;
/** Bloom Filter 每 key 位数(默认 10 */
bloomFilterBitsPerKey?: number;
/** 是否启用 WAL(默认 true */
walEnabled?: boolean;
/** WAL 同步模式 */
walSyncMode?: 'full' | 'batch' | 'none';
/** Checkpoint 间隔(操作数,默认 1000 */
checkpointInterval?: number;
/** 是否启用页面压缩(默认 false) */
compression?: boolean;
/** 存储后端 */
storageBackend?: 'indexeddb' | 'opfs' | 'memory';
/** WAL 大小阈值(字节,超过则强制 checkpoint,默认 16MB */
walSizeThreshold?: number;
/** 最大内存预算(MB,默认 64) */
maxMemoryMB?: number;
}
declare class AriaEngine implements IStorageEngine {
@@ -941,6 +989,9 @@ declare class AriaEngine implements IStorageEngine {
private backend;
private opened;
private dbName;
private fileManager;
private bufferPool;
private dbLock;
private schemas;
private tablePKs;
private opCounter;
@@ -949,7 +1000,6 @@ declare class AriaEngine implements IStorageEngine {
private currentTxnId;
private txnSnapshot;
private gcCounter;
private bufferPool;
constructor(config?: AriaEngineConfig);
open(dbName: string, _version: number): Promise<void>;
/** open 内部实现(错误包装在 open 外层) */
@@ -957,9 +1007,15 @@ declare class AriaEngine implements IStorageEngine {
close(): Promise<void>;
/**
* v0.4.2-fix: 崩溃恢复/自愈 — 校验并移除损坏 SSTable、截断 WAL、重建二级索引。
* v0.4.5 增强:清理 OPFS 残留临时文件、清理孤儿页面(meta 未引用的 pg_ 文件)。
* 应用层检测到异常后调用,无需删库重建。
*/
repair(): Promise<void>;
/**
* v0.4.5: 清理孤儿页面 — 扫描全部 pg_* 文件,未被任何 LSM 命名空间 meta 引用的删除。
* 孤儿页面来自:崩溃中断的 compaction/删除流程(旧 SSTable 页面残留)。
*/
private cleanupOrphanPages;
/**
* v0.4.1: 重置数据库 — 清空全部数据与表结构(演示页刷新/重新初始化用)。
* 清空存储后端、LSM、WAL、MVCC 与二级索引,后续可继续使用本实例。
@@ -1044,6 +1100,8 @@ declare class AriaEngine implements IStorageEngine {
* - id 序列独立(避免 v0.2.4 共享 id 空间导致的文件互相覆盖)
*/
private createSSTableStore;
/** v0.4.5: 是否启用页面化物理存储(默认 OPFS 后端启用,显式配置可覆盖) */
private isPageStorage;
private applyWALRecord;
/**
* v0.3.3: DROP_TABLE 恢复 — 删除 schema 并清除主 LSM 中该表的所有残留数据。
@@ -1084,21 +1142,10 @@ declare class AriaEngine implements IStorageEngine {
compactedLevels: number;
gcVersions: number;
}>;
/**
* 查询优化器:估算各索引成本,选择最优方案
*/
estimateQueryCost(tableName: string, query: QueryPlan): {
strategy: string;
estimatedRows: number;
};
private ensureOpen;
/** v0.4.2-fix: Aria 事务中 DDL 显式拒绝(结构变更无法通过行快照回滚) */
private ensureNoDDLInTransaction;
private ensureTable;
/** Get the number of WAL records stored */
private getWALCount;
/** Set the number of WAL records stored */
private setWALCount;
}
declare class HybridEngine implements IStorageEngine {
@@ -1234,6 +1281,13 @@ declare enum TokenType {
THEN = "THEN",
ELSE = "ELSE",
END = "END",
EXPLAIN = "EXPLAIN",
ANALYZE = "ANALYZE",
REINDEX = "REINDEX",
VACUUM = "VACUUM",
SAVEPOINT = "SAVEPOINT",
RELEASE = "RELEASE",
TO = "TO",
IDENTIFIER = "IDENTIFIER",
STRING = "STRING",
NUMBER = "NUMBER",
@@ -1286,6 +1340,13 @@ interface IStorageBackend {
read(key: string): Promise<ArrayBuffer | null>;
/** 写入数据块 */
write(key: string, data: ArrayBuffer): Promise<void>;
/**
* 追加写入(v0.4.5 WAL 分片用,可选):
* - OPFS 后端实现真追加(createWritable keepExistingData + seekO(chunk)
* - 未实现的后端由调用方回退 read+writeEncryptedBackend 包装时整体重写保正确性)
* 语义:在 key 现有内容末尾追加 data;key 不存在时等同 write。
*/
append?(key: string, data: ArrayBuffer): Promise<void>;
/**
* 批量原子写入(v0.4.2-fix):多个 key 在单个底层事务中提交,
* 中断时整体回滚,不留半写状态。WAL count 与记录同事务保证一致性。
@@ -1312,7 +1373,17 @@ interface IStorageBackend {
* 零外部依赖,纯浏览器文件系统 API。
* 每个 key 对应 OPFS 目录下的一个二进制文件。
*
* 浏览器要求:Chrome 102+ / Edge 102+
* 原子性与一致性保证(v0.4.5 固化):
* - 单文件 write/appendcreateWritable 为 copy-on-write —— close 前崩溃旧文件保持不变,
* close 后原子替换(单文件写入原子)
* - 多文件 writeMany/deleteManyOPFS 无跨文件事务,串行逐个落盘;调用方(WAL 分片)
* 已改为单文件语义,多键操作仅用于一次性的 schema/meta 写入
* - 所有写操作串行队列化(同源同进程顺序一致);单次任务失败不中断队列链,
* 错误如实返回给该次调用的调用方
* - close() 等待写队列排空后再释放目录句柄(杜绝 close 后挂起写丢失/读旧数据)
* - open() 自动清理崩溃残留临时文件(Chromium createWritable 的 .crswap 等)
*
* 浏览器要求:Chrome 102+ / Edge 102+Safari 15.2+ / Firefox 111+ 支持基础 OPFS
*/
declare class OPFSBackend implements IStorageBackend {
@@ -1323,8 +1394,20 @@ declare class OPFSBackend implements IStorageBackend {
open(name: string): Promise<void>;
close(): Promise<void>;
isOpen(): boolean;
/** 清理崩溃残留的临时文件(open 时自动调用,repair 也可调用) */
cleanupStaleFiles(): Promise<void>;
read(key: string): Promise<ArrayBuffer | null>;
/**
* 单文件原子写:createWritable 为 copy-on-writeclose 后原子替换;
* 写入期间崩溃 → 旧文件保持(原子性由浏览器 OPFS 实现保证)。
*/
write(key: string, data: ArrayBuffer): Promise<void>;
/**
* v0.4.5: 真追加写 — createWritable(keepExistingData) + seek 到文件末尾。
* 单文件 COW 原子(close 前崩溃旧文件保持),无需读旧内容即实现 O(chunk) 追加
* (WAL 分片高频写入用)。
*/
append(key: string, data: ArrayBuffer): Promise<void>;
/** v0.4.2-fix: 批量写入 — 串行队列内逐个落盘(OPFS 无跨文件事务,顺序保证一致) */
writeMany(entries: Record<string, ArrayBuffer>): Promise<void>;
delete(key: string): Promise<void>;
+1262 -522
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+1262 -522
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long