feat: metona-sqlark v0.1.12 — 前端TypeScript关系型数据库
- 4种存储引擎:Memory / IndexedDB / OPFS / Hybrid - 完整SQL支持:SELECT/INSERT/UPDATE/DELETE/JOIN/GROUP BY/HAVING/DISTINCT - Query Builder链式API + TypeScript泛型支持 - 聚合函数:COUNT/SUM/AVG/MIN/MAX - 事务、插件系统(14 hooks)、发布订阅、数据迁移、导入导出 - React/Vue框架集成 - 264个测试用例,93.46%覆盖率 - 零运行时依赖
This commit is contained in:
+273
@@ -0,0 +1,273 @@
|
||||
/**
|
||||
* metona-sqlark Core — 数据库主类
|
||||
* @module core
|
||||
*
|
||||
* 管理数据库生命周期、引擎调度、表操作、SQL 查询、事务和插件。
|
||||
*/
|
||||
|
||||
import type { IStorageEngine } from './engine/interface';
|
||||
import type { DatabaseConfig, ColumnDef } from './constants';
|
||||
import { DB_DEFAULTS, DatabaseError } from './constants';
|
||||
import { MemoryEngine } from './engine/memory';
|
||||
import { IndexedDBEngine } from './engine/indexeddb';
|
||||
import { OPFSEngine } from './engine/opfs';
|
||||
import { HybridEngine } from './hybrid/index';
|
||||
import { Table } from './table/table';
|
||||
import { createSchema } from './table/schema';
|
||||
import { QueryExecutor } from './query/executor';
|
||||
import { parse } from './sql/parser';
|
||||
import { TransactionManager } from './transaction/index';
|
||||
import { PluginManager } from './plugin/index';
|
||||
import type { Statement } from './query/ast';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// MetonaSqlark
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export class MetonaSqlark {
|
||||
/** 数据库名称 */
|
||||
readonly name: string;
|
||||
|
||||
/** 存储模式 */
|
||||
readonly mode: string;
|
||||
|
||||
/** 版本号 */
|
||||
private _version: number;
|
||||
|
||||
/** 获取版本号 */
|
||||
get version(): number { return this._version; }
|
||||
|
||||
private engine!: IStorageEngine;
|
||||
private executor!: QueryExecutor;
|
||||
private transactionManager!: TransactionManager;
|
||||
private pluginManager: PluginManager;
|
||||
private config: DatabaseConfig;
|
||||
private ready = false;
|
||||
|
||||
private tableCache: Map<string, Table> = new Map();
|
||||
|
||||
constructor(config: DatabaseConfig) {
|
||||
this.config = config;
|
||||
this.name = config.name ?? DB_DEFAULTS.name;
|
||||
this.mode = config.mode ?? DB_DEFAULTS.mode;
|
||||
this._version = config.version ?? DB_DEFAULTS.version;
|
||||
this.pluginManager = new PluginManager();
|
||||
}
|
||||
|
||||
// ---- 初始化 ----
|
||||
|
||||
/** 初始化数据库(创建引擎、打开连接) */
|
||||
async init(): Promise<void> {
|
||||
// 创建引擎
|
||||
this.engine = this.createEngine();
|
||||
|
||||
// 打开连接
|
||||
await this.engine.open(this.name, this.version);
|
||||
|
||||
// 初始化执行器和事务管理器
|
||||
this.executor = new QueryExecutor(this.engine);
|
||||
this.transactionManager = new TransactionManager(this.engine);
|
||||
|
||||
// 注册插件
|
||||
if (this.config.plugins) {
|
||||
for (const plugin of this.config.plugins) {
|
||||
this.pluginManager.register(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
this.ready = true;
|
||||
|
||||
// 回调
|
||||
if (this.config.onReady) {
|
||||
this.config.onReady(this);
|
||||
}
|
||||
}
|
||||
|
||||
/** 检查是否就绪 */
|
||||
isReady(): boolean {
|
||||
return this.ready;
|
||||
}
|
||||
|
||||
// ---- 表管理 ----
|
||||
|
||||
/** 创建表 */
|
||||
async defineTable(name: string, columns: Record<string, ColumnDef>): Promise<void> {
|
||||
this.ensureReady();
|
||||
const schema = createSchema(name, columns);
|
||||
|
||||
await this.pluginManager.trigger('beforeCreateTable', schema);
|
||||
await this.engine.createTable(schema);
|
||||
await this.pluginManager.trigger('afterCreateTable', schema);
|
||||
|
||||
// 清除缓存
|
||||
this.tableCache.delete(name);
|
||||
}
|
||||
|
||||
/** 获取表操作对象 */
|
||||
table(name: string): Table {
|
||||
this.ensureReady();
|
||||
|
||||
let t = this.tableCache.get(name);
|
||||
if (!t) {
|
||||
t = new Table(this.engine, name, this.executor);
|
||||
this.tableCache.set(name, t);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
/** 删除表 */
|
||||
async dropTable(name: string): Promise<void> {
|
||||
this.ensureReady();
|
||||
await this.pluginManager.trigger('beforeDropTable', name);
|
||||
await this.engine.dropTable(name);
|
||||
await this.pluginManager.trigger('afterDropTable', name);
|
||||
this.tableCache.delete(name);
|
||||
}
|
||||
|
||||
/** 获取所有表名 */
|
||||
async getTableNames(): Promise<string[]> {
|
||||
this.ensureReady();
|
||||
return this.engine.getTableNames();
|
||||
}
|
||||
|
||||
// ---- SQL 查询 ----
|
||||
|
||||
/** 执行 SQL 字符串查询 */
|
||||
async query(sql: string): Promise<unknown> {
|
||||
this.ensureReady();
|
||||
|
||||
await this.pluginManager.trigger('beforeQuery', sql);
|
||||
|
||||
const stmt: Statement = parse(sql);
|
||||
const result = await this.executor.execute(stmt);
|
||||
|
||||
await this.pluginManager.trigger('afterQuery', sql, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ---- 事务 ----
|
||||
|
||||
/** 执行事务 */
|
||||
async transaction<T>(fn: (trx: import('./transaction/index').Transaction) => Promise<T>): Promise<T> {
|
||||
this.ensureReady();
|
||||
await this.pluginManager.trigger('beforeTransaction');
|
||||
const result = await this.transactionManager.execute(fn);
|
||||
await this.pluginManager.trigger('afterTransaction');
|
||||
return result;
|
||||
}
|
||||
|
||||
// ---- 导入导出 ----
|
||||
|
||||
/** 导出表数据为 JSON */
|
||||
async exportTable(tableName: string): Promise<Record<string, unknown>[]> {
|
||||
this.ensureReady();
|
||||
return this.engine.find(tableName, { table: tableName });
|
||||
}
|
||||
|
||||
/** 导入 JSON 数据到表 */
|
||||
async importTable(tableName: string, data: Record<string, unknown>[]): Promise<string[]> {
|
||||
this.ensureReady();
|
||||
return this.engine.insert(tableName, data);
|
||||
}
|
||||
|
||||
/** 导出整个数据库为 JSON */
|
||||
async exportAll(): Promise<Record<string, Record<string, unknown>[]>> {
|
||||
this.ensureReady();
|
||||
const result: Record<string, Record<string, unknown>[]> = {};
|
||||
const names = await this.engine.getTableNames();
|
||||
for (const name of names) {
|
||||
result[name] = await this.engine.find(name, { table: name });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ---- 发布订阅 ----
|
||||
|
||||
private listeners: Map<string, Set<(data: unknown) => void>> = new Map();
|
||||
|
||||
/** 订阅表变更 */
|
||||
subscribe(tableName: string, callback: (event: { type: string; row?: unknown }) => void): () => void {
|
||||
const key = `change:${tableName}`;
|
||||
if (!this.listeners.has(key)) this.listeners.set(key, new Set());
|
||||
this.listeners.get(key)!.add(callback as (data: unknown) => void);
|
||||
return () => this.listeners.get(key)?.delete(callback as (data: unknown) => void);
|
||||
}
|
||||
|
||||
/** 触发变更事件 */
|
||||
emit(tableName: string, event: { type: string; row?: unknown }): void {
|
||||
const key = `change:${tableName}`;
|
||||
this.listeners.get(key)?.forEach((cb) => cb(event));
|
||||
}
|
||||
|
||||
// ---- 迁移 ----
|
||||
|
||||
private migrations: Map<number, (db: MetonaSqlark) => Promise<void>> = new Map();
|
||||
|
||||
/** 注册迁移 */
|
||||
addMigration(version: number, up: (db: MetonaSqlark) => Promise<void>): void {
|
||||
this.migrations.set(version, up);
|
||||
}
|
||||
|
||||
/** 执行迁移到指定版本 */
|
||||
async migrateTo(targetVersion: number): Promise<void> {
|
||||
this.ensureReady();
|
||||
for (const [version, up] of [...this.migrations.entries()].sort((a, b) => a[0] - b[0])) {
|
||||
if (version <= targetVersion && version > this.version) {
|
||||
await up(this);
|
||||
this._version = version;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 插件 ----
|
||||
|
||||
/** 获取插件管理器 */
|
||||
getPluginManager(): PluginManager {
|
||||
return this.pluginManager;
|
||||
}
|
||||
|
||||
/** 注册钩子 */
|
||||
on(hook: import('./constants').HookName, callback: import('./plugin/index').HookCallback): void {
|
||||
this.pluginManager.on(hook, callback);
|
||||
}
|
||||
|
||||
// ---- 生命周期 ----
|
||||
|
||||
/** 关闭数据库 */
|
||||
async close(): Promise<void> {
|
||||
this.pluginManager.destroy();
|
||||
await this.engine.close();
|
||||
this.tableCache.clear();
|
||||
this.ready = false;
|
||||
}
|
||||
|
||||
/** 获取底层引擎 */
|
||||
getEngine(): IStorageEngine {
|
||||
return this.engine;
|
||||
}
|
||||
|
||||
// ---- 内部 ----
|
||||
|
||||
private createEngine(): IStorageEngine {
|
||||
const mode = this.mode;
|
||||
const diskEngine = this.config.diskEngine ?? 'indexeddb';
|
||||
|
||||
switch (mode) {
|
||||
case 'memory':
|
||||
return new MemoryEngine();
|
||||
case 'disk':
|
||||
return diskEngine === 'opfs' ? new OPFSEngine() : new IndexedDBEngine();
|
||||
case 'hybrid':
|
||||
return new HybridEngine(diskEngine);
|
||||
default:
|
||||
throw new DatabaseError(`Unknown storage mode: ${mode}`, 'CONFIG_ERROR');
|
||||
}
|
||||
}
|
||||
|
||||
private ensureReady(): void {
|
||||
if (!this.ready) {
|
||||
throw new DatabaseError('Database not initialized. Call await db.init() first.', 'DB_NOT_READY');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user