fix: v0.7.1 API 修复与防御统一 — static create / 未 open 防护 / 原型污染 / lint 清零
- MetonaSqlark.create 静态工厂(README 示例在 ESM/Node 下此前 TypeError), 独立 create 函数委托静态实现 - close 未初始化防御(engine undefined 不再崩溃) - AriaEngine hasTable/getTableNames/getTableSchema 统一 ensureOpen - 事务回滚失败不掩盖原始错误 - __proto__ 列名防护:Executor 列映射 Object.create(null) + schema 校验拒绝 - lint 清零(移除 5 处未使用导入) 测试 1147 → 1155(73 套件);行覆盖率 89.8%;版本 0.7.1
This commit is contained in:
+1
-1
@@ -214,4 +214,4 @@ export class DatabaseError extends Error {
|
||||
// 版本
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const VERSION = '0.7.0';
|
||||
export const VERSION = '0.7.1';
|
||||
|
||||
+16
-1
@@ -29,6 +29,18 @@ export class MetonaSqlark {
|
||||
/** 数据库名称 */
|
||||
readonly name: string;
|
||||
|
||||
/**
|
||||
* v0.7.1: 静态工厂(与 connect/disconnect 同一入口风格)。
|
||||
* 此前 create 仅存在于 api 对象 / window 挂载 —— README/站点示例的
|
||||
* `MetonaSqlark.create({...})` 在 ESM/Node 下是 undefined(TypeError)。
|
||||
*/
|
||||
static async create(config: DatabaseConfig): Promise<MetonaSqlark> {
|
||||
const db = new MetonaSqlark(config);
|
||||
await db.init();
|
||||
return db;
|
||||
}
|
||||
|
||||
|
||||
/** 存储模式 */
|
||||
readonly mode: string;
|
||||
|
||||
@@ -540,7 +552,10 @@ export class MetonaSqlark {
|
||||
this.channel = null;
|
||||
}
|
||||
this.pluginManager.destroy();
|
||||
await this.engine.close();
|
||||
// v0.7.1: init 失败/未调用时 close 不应崩溃(此前 this.engine undefined → TypeError)
|
||||
if (this.engine) {
|
||||
await this.engine.close();
|
||||
}
|
||||
this.tableCache.clear();
|
||||
this.ready = false;
|
||||
}
|
||||
|
||||
@@ -512,14 +512,18 @@ export class AriaEngine implements IStorageEngine {
|
||||
}
|
||||
|
||||
async hasTable(tableName: string): Promise<boolean> {
|
||||
// v0.7.1: 未 open 防护统一(此前与 KVStoreEngine 不一致:返回空而非报错)
|
||||
this.ensureOpen();
|
||||
return this.schemas.has(tableName);
|
||||
}
|
||||
|
||||
async getTableNames(): Promise<string[]> {
|
||||
this.ensureOpen();
|
||||
return Array.from(this.schemas.keys());
|
||||
}
|
||||
|
||||
async getTableSchema(tableName: string): Promise<TableSchema | null> {
|
||||
this.ensureOpen();
|
||||
return this.schemas.get(tableName) ?? null;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
* 供 Buffer Pool 的 PageIO 和 WAL 的 WALStore 使用。
|
||||
*/
|
||||
|
||||
import { DatabaseError } from '../../../constants';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// StorageBackend 接口
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
|
||||
import type { IStorageBackend } from './backend';
|
||||
import type { PageIO } from '../buffer/pool';
|
||||
import { PAGE_SIZE, PageType } from '../types';
|
||||
import { initPageHeader } from '../page/header';
|
||||
import { PAGE_SIZE } from '../types';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// FileManager (implements PageIO)
|
||||
|
||||
@@ -22,7 +22,6 @@ import type { QueryPlan, TableSchema } from '../constants';
|
||||
import { DatabaseError } from '../constants';
|
||||
import { MemoryEngine } from './memory';
|
||||
import { KVStore } from './kvstore/index';
|
||||
import { SharedMemoryBackend } from './kvstore/shared_memory_medium';
|
||||
import type { IStorageBackend } from './aria/store/backend';
|
||||
|
||||
const SCHEMA_KEY = '__schema';
|
||||
|
||||
+2
-3
@@ -38,9 +38,8 @@ import './connection-manager';
|
||||
* ```
|
||||
*/
|
||||
async function create(config: DatabaseConfig): Promise<MetonaSqlark> {
|
||||
const db = new MetonaSqlark(config);
|
||||
await db.init();
|
||||
return db;
|
||||
// v0.7.1: 委托静态 create(类与独立函数行为一致,README 两种写法均可)
|
||||
return MetonaSqlark.create(config);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -712,7 +712,9 @@ export class QueryExecutor {
|
||||
const exists = await this.engine.hasTable(stmt.name);
|
||||
if (exists) return;
|
||||
}
|
||||
const columns: Record<string, any> = {};
|
||||
// v0.7.1: Object.create(null) —— 防止 '__proto__' 列名触发原型 setter 静默丢列
|
||||
// (createSchema 校验会显式拒绝该列名)
|
||||
const columns: Record<string, any> = Object.create(null) as Record<string, any>;
|
||||
for (const col of stmt.columns) columns[col.name] = astColumnToColumnDef(col);
|
||||
return this.engine.createTable(createSchema(stmt.name, columns));
|
||||
}
|
||||
|
||||
+1325
-1326
File diff suppressed because it is too large
Load Diff
@@ -26,6 +26,12 @@ export function validateColumns(columns: Record<string, ColumnDef>): void {
|
||||
let primaryKeyCount = 0;
|
||||
|
||||
for (const [colName, colDef] of Object.entries(columns)) {
|
||||
// v0.7.1: '__proto__' 作为列名会触发对象原型 setter(列静默丢失);
|
||||
// 显式拒绝避免原型污染类攻击面
|
||||
if (colName === '__proto__') {
|
||||
throw new DatabaseError('Column name "__proto__" is not allowed', 'SCHEMA_ERROR');
|
||||
}
|
||||
|
||||
// 类型校验
|
||||
if (!FIELD_TYPES.includes(colDef.type)) {
|
||||
throw new DatabaseError(
|
||||
|
||||
@@ -64,8 +64,11 @@ export class TransactionManager {
|
||||
trx._markCompleted();
|
||||
return result;
|
||||
} catch (error) {
|
||||
// 失败 → 回滚
|
||||
await this.engine.rollbackTransaction();
|
||||
// 失败 → 回滚。v0.7.1: 回滚自身失败不掩盖原始事务错误
|
||||
// (此前 rollback 抛错会替换掉真正导致失败的异常,定位困难)
|
||||
try {
|
||||
await this.engine.rollbackTransaction();
|
||||
} catch { /* 回滚失败保留原始错误 */ }
|
||||
if (error instanceof DatabaseError) throw error;
|
||||
throw new DatabaseError(`Transaction failed: ${(error as Error).message}`, 'TRANSACTION_ERROR', error);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user