release: v0.1.14 生产加固 — IDB事务原子性/多标签页感知/幂等init
CI / test (18.x) (push) Successful in 9m54s
CI / test (20.x) (push) Successful in 9m52s
CI / test (22.x) (push) Successful in 9m52s
CI / test (24.x) (push) Successful in 9m48s

This commit is contained in:
thzxx
2026-07-26 17:20:31 +08:00
parent 71e238a5cc
commit 014e92884c
16 changed files with 165 additions and 52 deletions
+1 -1
View File
@@ -203,4 +203,4 @@ export class DatabaseError extends Error {
// 版本
// ---------------------------------------------------------------------------
export const VERSION = '0.1.13';
export const VERSION = '0.1.14';
+30 -8
View File
@@ -27,14 +27,30 @@ export class IndexedDBEngine implements IStorageEngine {
await this.memoryCache.open(dbName, version);
return new Promise((resolve, reject) => {
const request = indexedDB.open(dbName, version);
request.onsuccess = () => { this.db = request.result; resolve(); };
request.onsuccess = () => {
this.db = request.result;
// 多标签页冲突处理:其他标签页升级版本时自动关闭当前连接
this.db.onversionchange = () => {
if (this.db) {
this.db.close();
this.db = null;
// eslint-disable-next-line no-console
console.warn(`[metona-sqlark] Database "${dbName}" was upgraded in another tab. Connection closed. Please re-open.`);
}
};
resolve();
};
request.onerror = () => reject(new DatabaseError(`Failed to open IndexedDB "${dbName}"`, 'IDB_OPEN_ERROR', request.error));
request.onblocked = () => reject(new DatabaseError(`IndexedDB "${dbName}" is blocked`, 'IDB_BLOCKED'));
});
}
async close(): Promise<void> {
if (this.db) { this.db.close(); this.db = null; }
if (this.db) {
this.db.onversionchange = null; // 清理监听器
this.db.close();
this.db = null;
}
await this.memoryCache.close();
}
@@ -243,16 +259,22 @@ export class IndexedDBEngine implements IStorageEngine {
});
}
/** 将内存缓存中的所有表数据刷新到 IndexedDB */
/** 将内存缓存中的所有表数据原子性刷新到 IndexedDB */
private async flushToIDB(): Promise<void> {
const tableNames = await this.memoryCache.getTableNames();
const db = this.ensureDB();
// 每个表在一个单独的 IDB 事务中完成 clear+insert,保证原子性
for (const tableName of tableNames) {
const rows = await this.memoryCache.find(tableName, { table: tableName });
// Clear + bulk insert for simplicity
await this.idbClear(tableName);
if (rows.length > 0) {
await this.idbInsert(tableName, rows);
}
await new Promise<void>((resolve, reject) => {
const tx = db.transaction(tableName, 'readwrite');
const store = tx.objectStore(tableName);
store.clear(); // 清空
for (const row of rows) store.add(row); // 批量写入
tx.oncomplete = () => resolve();
tx.onerror = () => reject(new DatabaseError(`Flush failed for "${tableName}"`, 'IDB_FLUSH_ERROR', tx.error));
});
}
}
+7 -1
View File
@@ -24,7 +24,13 @@ export class MemoryEngine implements IStorageEngine {
} | null = null;
// ---- 生命周期 ----
async open(_dbName: string, _version: number): Promise<void> { this.opened = true; }
async open(_dbName: string, _version: number): Promise<void> {
if (this.opened) {
// 幂等:已打开则忽略
return;
}
this.opened = true;
}
async close(): Promise<void> {
this.tables.clear(); this.schemas.clear(); this.indexes.clear(); this.opened = false;
}