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
+10 -90
View File
@@ -9,10 +9,12 @@
import { AriaEngine } from '../src/engine/aria/index';
import { createSchema } from '../src/table/schema';
import { OPFSEngine } from '../src/engine/opfs';
import { MemTable } from '../src/engine/aria/index/memtable';
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 {
@@ -73,7 +75,7 @@ describe('P0 — 事务进行中 checkpoint 不得截断 WAL', () => {
it('事务中触发 checkpoint → 崩溃恢复不丢事务数据', async () => {
const dbName = uniqueDB();
const engine = new AriaEngine({
storageBackend: 'indexeddb',
storageBackend: 'opfs',
checkpointInterval: 2, // 每 2 次操作即触发 checkpoint(事务中途)
});
await engine.open(dbName, 1);
@@ -91,7 +93,7 @@ describe('P0 — 事务进行中 checkpoint 不得截断 WAL', () => {
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);
// 修复前:checkpoint 截断了事务的 WAL 记录 → 恢复后数据丢失
expect(await engine2.count('t')).toBe(2);
@@ -101,7 +103,7 @@ describe('P0 — 事务进行中 checkpoint 不得截断 WAL', () => {
it('事务回滚后再 checkpoint 正常截断', async () => {
const dbName = uniqueDB();
const engine = new AriaEngine({
storageBackend: 'indexeddb',
storageBackend: 'opfs',
checkpointInterval: 1,
});
await engine.open(dbName, 1);
@@ -115,7 +117,7 @@ describe('P0 — 事务进行中 checkpoint 不得截断 WAL', () => {
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);
expect(await engine2.count('t')).toBe(1);
await engine2.close();
@@ -168,7 +170,7 @@ describe('P1 — Aria DDL 索引清理', () => {
it('DROP_TABLE 崩溃恢复同样清理索引', 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 },
@@ -180,7 +182,7 @@ describe('P1 — Aria DDL 索引清理', () => {
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);
// 表不存在,且无索引残留
expect(await engine2.hasTable('t')).toBe(false);
@@ -189,88 +191,6 @@ describe('P1 — Aria DDL 索引清理', () => {
});
});
// ===================================================================
// P1: OPFS 持久化与并发
// ===================================================================
describe('P1 — OPFS 生产加固', () => {
it('并发写不丢数据(内存快照总是最新,多写内容一致)', async () => {
// 模拟真实 I/O 延迟:内存写同步、持久化异步 → 并发写内容均基于最新内存快照
const files = mockOPFS(undefined, {
ioDelay: 10,
writeDelayFor: (data) => (data.length < 40 ? 30 : 5),
});
const engine = new OPFSEngine();
await engine.open('opfs-race', 1);
await engine.createTable(createSchema('users', {
id: { type: 'string', primaryKey: true },
name: { type: 'string' },
}));
// 两个写并发:内存同步写保证两个快照一致 → 无论完成顺序文件内容完整
const p1 = engine.insert('users', [{ id: '1', name: 'A' }]);
const p2 = engine.insert('users', [{ id: '2', name: 'B' }]);
await Promise.all([p1, p2]);
const rows = await engine.find('users', { table: 'users' });
expect(rows).toHaveLength(2);
// 文件内容完整(重启不丢)
expect(files.get('users.json')).toContain('"B"');
await engine.close();
});
it('空表重启后保留(schema 持久化)', async () => {
mockOPFS();
const e1 = new OPFSEngine();
await e1.open('opfs-schema', 1);
await e1.createTable(createSchema('users', {
id: { type: 'string', primaryKey: true },
email: { type: 'string', index: true },
}));
await e1.close();
// 重启:空表也应恢复(修复前空表消失)+ 索引标记恢复
const e2 = new OPFSEngine();
await e2.open('opfs-schema', 1);
expect(await e2.hasTable('users')).toBe(true);
const schema = await e2.getTableSchema('users');
expect(schema!.columns.email.index).toBe(true);
await e2.close();
});
it('有数据重启后索引查询可用', async () => {
mockOPFS();
const e1 = new OPFSEngine();
await e1.open('opfs-idx', 1);
await e1.createTable(createSchema('users', {
id: { type: 'string', primaryKey: true },
email: { type: 'string', index: true },
}));
await e1.insert('users', [{ id: '1', email: 'a@x.com' }]);
await e1.close();
const e2 = new OPFSEngine();
await e2.open('opfs-idx', 1);
const byEmail = await e2.find('users', { table: 'users', where: { email: 'a@x.com' } });
expect(byEmail).toHaveLength(1);
await e2.close();
});
it('dropTable 后重启无幽灵表', async () => {
mockOPFS();
const e1 = new OPFSEngine();
await e1.open('opfs-drop', 1);
await e1.createTable(createSchema('t', { id: { type: 'string', primaryKey: true } }));
await e1.insert('t', [{ id: '1' }]);
await e1.dropTable('t');
await e1.close();
const e2 = new OPFSEngine();
await e2.open('opfs-drop', 1);
expect(await e2.hasTable('t')).toBe(false);
expect(await e2.getTableNames()).toEqual([]);
await e2.close();
});
});
// ===================================================================
// P2: Aria 内部状态
// ===================================================================