304 lines
8.7 KiB
TypeScript
304 lines
8.7 KiB
TypeScript
/**
|
|
* 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 { AriaEngine } from './engine/aria/index';
|
|
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();
|
|
|
|
/** 查询结果行数上限 */
|
|
get maxRowsPerQuery(): number { return this.config.maxRowsPerQuery ?? 0; }
|
|
|
|
/** 调试模式 */
|
|
get debug(): boolean { return this.config.debug ?? false; }
|
|
|
|
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();
|
|
const startTime = this.debug ? Date.now() : 0;
|
|
|
|
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);
|
|
|
|
if (this.debug) {
|
|
const elapsed = Date.now() - startTime;
|
|
const rows = Array.isArray(result) ? (result as any[]).length : 0;
|
|
this._debug(`query [${elapsed}ms] ${rows} rows: ${sql.slice(0, 100)}`);
|
|
}
|
|
|
|
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 'aria':
|
|
return new AriaEngine({ storageBackend: diskEngine === 'opfs' ? 'memory' : 'indexeddb' });
|
|
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');
|
|
}
|
|
}
|
|
|
|
/** 错误回调分发 */
|
|
private _onError(error: Error): void {
|
|
if (this.config.onError) {
|
|
try { this.config.onError(error); } catch { /* 避免回调自身异常影响主流程 */ }
|
|
}
|
|
}
|
|
|
|
/** 调试日志 */
|
|
private _debug(msg: string, ...args: unknown[]): void {
|
|
if (this.debug) {
|
|
console.debug(`[MetonaSqlark:${this.name}] ${msg}`, ...args);
|
|
}
|
|
}
|
|
}
|