release: v0.6.0 — 完全移除 IndexedDB,自研 KVStore 事务存储引擎(多key原子写/快照日志恢复/CRC自愈)+ KVStoreEngine + 旧库迁移工具 + 10万级压力验证 + 崩溃注入e2e
CI / test (20.x) (push) Successful in 10m53s
CI / test (22.x) (push) Successful in 10m47s
CI / test (24.x) (push) Successful in 10m42s
CI / e2e (push) Successful in 10m27s
CI / test (18.x) (push) Successful in 11m1s

This commit is contained in:
thzxx
2026-08-10 13:56:04 +08:00
parent 2668bd47cf
commit 24b3ca1079
74 changed files with 8016 additions and 7688 deletions
+24 -21
View File
@@ -13,7 +13,10 @@ import { createSchema } from '../src/table/schema';
import { MemoryEngine } from '../src/engine/memory';
import { HybridEngine } from '../src/hybrid/index';
import { MetonaSqlark } from '../src/core';
import 'fake-indexeddb/auto';
import { installOPFSMock } from './helpers/opfs-mock';
beforeEach(() => { installOPFSMock(new Map()); });
let idbCounter = 0;
function uniqueDB(): string {
@@ -53,7 +56,7 @@ describe('P0-A — Compaction 缓存独立性', () => {
it('多层级 compaction 后数据仍完整且可重开', async () => {
const dbName = uniqueDB();
const engine = new AriaEngine({
storageBackend: 'indexeddb',
storageBackend: 'opfs',
bufferPoolPages: 8, // 32KB 缓存
memtableSizeThreshold: 16 * 1024,
checkpointInterval: 100000,
@@ -74,7 +77,7 @@ describe('P0-A — Compaction 缓存独立性', () => {
// 重开:数据完整(meta/文件未被 compaction 破坏)
const engine2 = new AriaEngine({
storageBackend: 'indexeddb',
storageBackend: 'opfs',
bufferPoolPages: 8,
memtableSizeThreshold: 16 * 1024,
checkpointInterval: 100000,
@@ -92,7 +95,7 @@ describe('P0-A — Compaction 缓存独立性', () => {
describe('P1-A — 二级索引恢复', () => {
it('Aria 重开后二级索引可用(索引 LSM 持久化恢复)', async () => {
const dbName = uniqueDB();
const engine = new AriaEngine({ storageBackend: 'indexeddb', checkpointInterval: 100000 });
const engine = new AriaEngine({ storageBackend: 'opfs', checkpointInterval: 100000 });
await engine.open(dbName, 1);
await engine.createTable(createSchema('users', {
id: { type: 'string', primaryKey: true },
@@ -107,7 +110,7 @@ describe('P1-A — 二级索引恢复', () => {
await engine.close();
// 重开:二级索引 LSM 应自动恢复(修复前为空 → 索引查询回退全表,createIndex 也静默跳过)
const engine2 = new AriaEngine({ storageBackend: 'indexeddb', checkpointInterval: 100000 });
const engine2 = new AriaEngine({ storageBackend: 'opfs', checkpointInterval: 100000 });
await engine2.open(dbName, 1);
// 强断言:索引 LSM 真实存在且数据完整(防止"索引缺失静默回退全表扫描"的假通过)
const idxLsm = (engine2 as any).secondaryIndexes.get('users:idx:email');
@@ -124,7 +127,7 @@ describe('P1-A — 二级索引恢复', () => {
it('WAL 恢复后二级索引与主数据一致(崩溃前索引未更新)', async () => {
const dbName = uniqueDB();
const engine = new AriaEngine({ storageBackend: 'indexeddb', checkpointInterval: 100000 });
const engine = new AriaEngine({ storageBackend: 'opfs', checkpointInterval: 100000 });
await engine.open(dbName, 1);
await engine.createTable(createSchema('users', {
id: { type: 'string', primaryKey: true },
@@ -138,7 +141,7 @@ describe('P1-A — 二级索引恢复', () => {
await (engine as any).backend.close();
(engine as any).opened = false;
const engine2 = new AriaEngine({ storageBackend: 'indexeddb', checkpointInterval: 100000 });
const engine2 = new AriaEngine({ storageBackend: 'opfs', checkpointInterval: 100000 });
await engine2.open(dbName, 1);
// 强断言:索引 LSM 已恢复且包含 WAL 回放的行(崩溃前索引未更新,恢复后必须重建)
const idxLsm = (engine2 as any).secondaryIndexes.get('users:idx:city');
@@ -154,7 +157,7 @@ describe('P1-A — 二级索引恢复', () => {
it('createIndex 重开后仍可新建(schema 标记恢复后不阻塞)', async () => {
const dbName = uniqueDB();
const engine = new AriaEngine({ storageBackend: 'indexeddb', checkpointInterval: 100000 });
const engine = new AriaEngine({ storageBackend: 'opfs', checkpointInterval: 100000 });
await engine.open(dbName, 1);
await engine.createTable(createSchema('t', {
id: { type: 'string', primaryKey: true },
@@ -163,7 +166,7 @@ describe('P1-A — 二级索引恢复', () => {
await engine.insert('t', [{ id: '1', name: 'A' }]);
await engine.close();
const engine2 = new AriaEngine({ storageBackend: 'indexeddb', checkpointInterval: 100000 });
const engine2 = new AriaEngine({ storageBackend: 'opfs', checkpointInterval: 100000 });
await engine2.open(dbName, 1);
await engine2.createIndex('t', 'name'); // 修复前 schema 无标记时正常;此处验证无标记场景
const byName = await engine2.find('t', { table: 't', where: { name: 'A' } });
@@ -177,9 +180,9 @@ describe('P1-A — 二级索引恢复', () => {
// ===================================================================
describe('P1-B — ALTER TABLE 持久化', () => {
it('IndexedDBEngine DROP COLUMN 后重启不复活', async () => {
it('KVStoreEngine DROP COLUMN 后重启不复活', async () => {
const dbName = uniqueDB();
const db = new MetonaSqlark({ name: dbName, mode: 'disk', diskEngine: 'indexeddb' });
const db = new MetonaSqlark({ name: dbName, mode: 'disk', diskEngine: 'opfs' });
await db.init();
await db.defineTable('t', {
id: { type: 'string', primaryKey: true },
@@ -194,7 +197,7 @@ describe('P1-B — ALTER TABLE 持久化', () => {
await db.close();
// 重启:schema 不复活,列定义已持久化
const db2 = new MetonaSqlark({ name: dbName, mode: 'disk', diskEngine: 'indexeddb' });
const db2 = new MetonaSqlark({ name: dbName, mode: 'disk', diskEngine: 'opfs' });
await db2.init();
const schema2 = await db2.getEngine().getTableSchema('t');
expect(schema2!.columns.old_col).toBeUndefined();
@@ -204,13 +207,13 @@ describe('P1-B — ALTER TABLE 持久化', () => {
it('HybridEngine ADD COLUMN 后重启保留', async () => {
const dbName = uniqueDB();
const db = new MetonaSqlark({ name: dbName, mode: 'hybrid', diskEngine: 'indexeddb' });
const db = new MetonaSqlark({ name: dbName, mode: 'hybrid', diskEngine: 'opfs' });
await db.init();
await db.defineTable('t', { id: { type: 'string', primaryKey: true } });
await db.query('ALTER TABLE t ADD COLUMN phone STRING');
await db.close();
const db2 = new MetonaSqlark({ name: dbName, mode: 'hybrid', diskEngine: 'indexeddb' });
const db2 = new MetonaSqlark({ name: dbName, mode: 'hybrid', diskEngine: 'opfs' });
await db2.init();
const schema2 = await db2.getEngine().getTableSchema('t');
expect(schema2!.columns.phone).toBeDefined();
@@ -219,13 +222,13 @@ describe('P1-B — ALTER TABLE 持久化', () => {
});
// ===================================================================
// P1-C: IndexedDB 事务内 DDL
// P1-C: KVStore 事务内 DDL
// ===================================================================
describe('P1-C — IndexedDB 事务内 DDL', () => {
describe('P1-C — KVStore 事务内 DDL', () => {
it('事务内建表 → commit 后磁盘一致(重启表存在且可查)', async () => {
const dbName = uniqueDB();
const db = new MetonaSqlark({ name: dbName, mode: 'hybrid', diskEngine: 'indexeddb' });
const db = new MetonaSqlark({ name: dbName, mode: 'hybrid', diskEngine: 'opfs' });
await db.init();
await db.defineTable('base', { id: { type: 'string', primaryKey: true } });
await db.table('base').insert({ id: '1' });
@@ -240,7 +243,7 @@ describe('P1-C — IndexedDB 事务内 DDL', () => {
expect(await db.table('created_in_tx').count()).toBe(1);
await db.close();
const db2 = new MetonaSqlark({ name: dbName, mode: 'hybrid', diskEngine: 'indexeddb' });
const db2 = new MetonaSqlark({ name: dbName, mode: 'hybrid', diskEngine: 'opfs' });
await db2.init();
expect(await db2.table('created_in_tx').count()).toBe(1);
expect(await db2.table('base').count()).toBe(2);
@@ -249,7 +252,7 @@ describe('P1-C — IndexedDB 事务内 DDL', () => {
it('事务内删表 → commit 后磁盘一致(重启无幽灵表)', async () => {
const dbName = uniqueDB();
const db = new MetonaSqlark({ name: dbName, mode: 'hybrid', diskEngine: 'indexeddb' });
const db = new MetonaSqlark({ name: dbName, mode: 'hybrid', diskEngine: 'opfs' });
await db.init();
await db.defineTable('ghost', { id: { type: 'string', primaryKey: true } });
await db.table('ghost').insert({ id: '1' });
@@ -259,7 +262,7 @@ describe('P1-C — IndexedDB 事务内 DDL', () => {
});
await db.close();
const db2 = new MetonaSqlark({ name: dbName, mode: 'hybrid', diskEngine: 'indexeddb' });
const db2 = new MetonaSqlark({ name: dbName, mode: 'hybrid', diskEngine: 'opfs' });
await db2.init();
const names = await db2.getTableNames();
expect(names).not.toContain('ghost');
@@ -300,7 +303,7 @@ describe('全模式抽查 — 生命周期与元数据', () => {
it('AriaEngine repair 幂等', async () => {
const dbName = uniqueDB();
const engine = new AriaEngine({ storageBackend: 'indexeddb' });
const engine = new AriaEngine({ storageBackend: 'opfs' });
await engine.open(dbName, 1);
await engine.createTable(createSchema('t', { id: { type: 'string', primaryKey: true } }));
await engine.insert('t', [{ id: '1' }]);