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:
@@ -0,0 +1,293 @@
|
||||
/**
|
||||
* MetonaSqlark 核心集成测试
|
||||
*/
|
||||
|
||||
import { MetonaSqlark } from '../src/core';
|
||||
import 'fake-indexeddb/auto';
|
||||
|
||||
let diskCounter = 0;
|
||||
|
||||
// ===================================================================
|
||||
// Memory 模式
|
||||
// ===================================================================
|
||||
|
||||
describe('MetonaSqlark Memory 模式', () => {
|
||||
let db: MetonaSqlark;
|
||||
|
||||
beforeEach(async () => {
|
||||
db = new MetonaSqlark({ name: 'test-db-mem', mode: 'memory' });
|
||||
await db.init();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await db.close();
|
||||
});
|
||||
|
||||
it('创建数据库并初始化', () => {
|
||||
expect(db.isReady()).toBe(true);
|
||||
expect(db.name).toBe('test-db-mem');
|
||||
expect(db.mode).toBe('memory');
|
||||
});
|
||||
|
||||
it('定义表', async () => {
|
||||
await db.defineTable('users', {
|
||||
id: { type: 'string', primaryKey: true },
|
||||
name: { type: 'string', required: true },
|
||||
age: { type: 'number' },
|
||||
});
|
||||
const tables = await db.getTableNames();
|
||||
expect(tables).toContain('users');
|
||||
});
|
||||
|
||||
describe('Query Builder API', () => {
|
||||
beforeEach(async () => {
|
||||
await db.defineTable('users', {
|
||||
id: { type: 'string', primaryKey: true },
|
||||
name: { type: 'string', required: true },
|
||||
age: { type: 'number' },
|
||||
});
|
||||
});
|
||||
|
||||
it('insert + select', async () => {
|
||||
const table = db.table('users');
|
||||
await table.insert({ id: '1', name: 'Alice', age: 30 });
|
||||
await table.insert({ id: '2', name: 'Bob', age: 25 });
|
||||
const results = await table.select().where({ age: { $gt: 20 } }).execute();
|
||||
expect(results).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('insertMany', async () => {
|
||||
const table = db.table('users');
|
||||
await table.insertMany([
|
||||
{ id: '1', name: 'Alice', age: 30 },
|
||||
{ id: '2', name: 'Bob', age: 25 },
|
||||
]);
|
||||
const count = await table.count();
|
||||
expect(count).toBe(2);
|
||||
});
|
||||
|
||||
it('update', async () => {
|
||||
const table = db.table('users');
|
||||
await table.insert({ id: '1', name: 'Alice', age: 30 });
|
||||
const count = await table.update({ age: 31 }).where({ id: '1' }).execute();
|
||||
expect(count).toBe(1);
|
||||
const results = await table.select().where({ id: '1' }).execute();
|
||||
expect(results[0].age).toBe(31);
|
||||
});
|
||||
|
||||
it('delete', async () => {
|
||||
const table = db.table('users');
|
||||
await table.insert({ id: '1', name: 'Alice', age: 30 });
|
||||
const count = await table.delete().where({ id: '1' }).execute();
|
||||
expect(count).toBe(1);
|
||||
expect(await table.count()).toBe(0);
|
||||
});
|
||||
|
||||
it('orderBy + limit + offset', async () => {
|
||||
const table = db.table('users');
|
||||
await table.insertMany([
|
||||
{ id: '1', name: 'Alice', age: 30 },
|
||||
{ id: '2', name: 'Bob', age: 25 },
|
||||
{ id: '3', name: 'Charlie', age: 35 },
|
||||
]);
|
||||
const results = await table.select().orderBy('age', 'desc').limit(2).offset(1).execute();
|
||||
expect(results).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('dropTable', async () => {
|
||||
await db.dropTable('users');
|
||||
const tables = await db.getTableNames();
|
||||
expect(tables).not.toContain('users');
|
||||
});
|
||||
});
|
||||
|
||||
describe('SQL API', () => {
|
||||
beforeEach(async () => {
|
||||
await db.query('CREATE TABLE users (id STRING PRIMARY KEY, name STRING, age NUMBER)');
|
||||
});
|
||||
|
||||
it('SQL INSERT + SELECT', async () => {
|
||||
await db.query("INSERT INTO users (id, name, age) VALUES ('1', 'Alice', 30)");
|
||||
await db.query("INSERT INTO users VALUES ('2', 'Bob', 25)");
|
||||
const result = await db.query('SELECT * FROM users') as Record<string, unknown>[];
|
||||
expect(result).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('SQL SELECT WHERE', async () => {
|
||||
await db.query("INSERT INTO users VALUES ('1', 'Alice', 30)");
|
||||
await db.query("INSERT INTO users VALUES ('2', 'Bob', 25)");
|
||||
const result = await db.query("SELECT * FROM users WHERE age > 28") as Record<string, unknown>[];
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].name).toBe('Alice');
|
||||
});
|
||||
|
||||
it('SQL UPDATE', async () => {
|
||||
await db.query("INSERT INTO users VALUES ('1', 'Alice', 30)");
|
||||
await db.query("UPDATE users SET age = 31 WHERE id = '1'");
|
||||
const result = await db.query("SELECT * FROM users WHERE id = '1'") as Record<string, unknown>[];
|
||||
expect(result[0].age).toBe(31);
|
||||
});
|
||||
|
||||
it('SQL DELETE', async () => {
|
||||
await db.query("INSERT INTO users VALUES ('1', 'Alice', 30)");
|
||||
await db.query("DELETE FROM users WHERE id = '1'");
|
||||
const result = await db.query('SELECT * FROM users') as Record<string, unknown>[];
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('SQL DROP TABLE', async () => {
|
||||
await db.query('DROP TABLE users');
|
||||
const tables = await db.getTableNames();
|
||||
expect(tables).not.toContain('users');
|
||||
});
|
||||
});
|
||||
|
||||
describe('事务', () => {
|
||||
beforeEach(async () => {
|
||||
await db.defineTable('users', {
|
||||
id: { type: 'string', primaryKey: true },
|
||||
name: { type: 'string', required: true },
|
||||
balance: { type: 'number', default: 0 },
|
||||
});
|
||||
});
|
||||
|
||||
it('事务中执行多个操作', async () => {
|
||||
await db.transaction(async (trx) => {
|
||||
await trx.table('users').insert({ id: '1', name: 'Alice', balance: 100 });
|
||||
await trx.table('users').insert({ id: '2', name: 'Bob', balance: 50 });
|
||||
});
|
||||
const table = db.table('users');
|
||||
expect(await table.count()).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('表管理高级', () => {
|
||||
beforeEach(async () => {
|
||||
await db.defineTable('users', {
|
||||
id: { type: 'string', primaryKey: true },
|
||||
name: { type: 'string', required: true },
|
||||
});
|
||||
});
|
||||
|
||||
it('getTableNames 返回所有表', async () => {
|
||||
const names = await db.getTableNames();
|
||||
expect(names).toContain('users');
|
||||
});
|
||||
|
||||
it('dropTable 后表不再存在', async () => {
|
||||
await db.dropTable('users');
|
||||
const names = await db.getTableNames();
|
||||
expect(names).not.toContain('users');
|
||||
});
|
||||
|
||||
it('Table.getSchema 返回表结构', async () => {
|
||||
const table = db.table('users');
|
||||
const schema = await table.getSchema();
|
||||
expect(schema.name).toBe('users');
|
||||
expect(schema.columns.id.primaryKey).toBe(true);
|
||||
});
|
||||
|
||||
it('Table.clear 清空数据', async () => {
|
||||
const table = db.table('users');
|
||||
await table.insert({ id: '1', name: 'Alice' });
|
||||
await table.insert({ id: '2', name: 'Bob' });
|
||||
await table.clear();
|
||||
expect(await table.count()).toBe(0);
|
||||
expect(await db.getTableNames()).toContain('users');
|
||||
});
|
||||
|
||||
it('Table.drop 删除表', async () => {
|
||||
const table = db.table('users');
|
||||
await table.drop();
|
||||
const names = await db.getTableNames();
|
||||
expect(names).not.toContain('users');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ===================================================================
|
||||
// Disk 模式
|
||||
// ===================================================================
|
||||
|
||||
describe('MetonaSqlark Disk 模式', () => {
|
||||
let db: MetonaSqlark;
|
||||
let dbName: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
dbName = `test-disk-${++diskCounter}`;
|
||||
db = new MetonaSqlark({ name: dbName, mode: 'disk', diskEngine: 'indexeddb' });
|
||||
await db.init();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await db.close();
|
||||
try { indexedDB.deleteDatabase(dbName); } catch {}
|
||||
});
|
||||
|
||||
it('创建 disk 模式数据库', () => {
|
||||
expect(db.isReady()).toBe(true);
|
||||
expect(db.mode).toBe('disk');
|
||||
});
|
||||
|
||||
it('定义表并插入数据', async () => {
|
||||
await db.defineTable('users', {
|
||||
id: { type: 'string', primaryKey: true },
|
||||
name: { type: 'string', required: true },
|
||||
});
|
||||
await db.table('users').insert({ id: '1', name: 'Alice' });
|
||||
const rows = await db.table('users').select().execute();
|
||||
expect(rows).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
// ===================================================================
|
||||
// Hybrid 模式
|
||||
// ===================================================================
|
||||
|
||||
describe('MetonaSqlark Hybrid 模式', () => {
|
||||
let db: MetonaSqlark;
|
||||
let dbName: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
dbName = `test-hybrid-core-${++diskCounter}`;
|
||||
db = new MetonaSqlark({ name: dbName, mode: 'hybrid', diskEngine: 'indexeddb' });
|
||||
await db.init();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await db.close();
|
||||
try { indexedDB.deleteDatabase(dbName); } catch {}
|
||||
});
|
||||
|
||||
it('创建 hybrid 模式数据库', () => {
|
||||
expect(db.isReady()).toBe(true);
|
||||
expect(db.mode).toBe('hybrid');
|
||||
});
|
||||
|
||||
it('基本 CRUD', async () => {
|
||||
await db.defineTable('users', {
|
||||
id: { type: 'string', primaryKey: true },
|
||||
name: { type: 'string', required: true },
|
||||
});
|
||||
const table = db.table('users');
|
||||
await table.insert({ id: '1', name: 'Alice' });
|
||||
expect(await table.count()).toBe(1);
|
||||
|
||||
await table.update({ name: 'Alice2' }).where({ id: '1' }).execute();
|
||||
const rows = await table.select().where({ id: '1' }).execute();
|
||||
expect(rows[0].name).toBe('Alice2');
|
||||
|
||||
await table.delete().where({ id: '1' }).execute();
|
||||
expect(await table.count()).toBe(0);
|
||||
});
|
||||
|
||||
it('SQL 查询', async () => {
|
||||
await db.defineTable('users', {
|
||||
id: { type: 'string', primaryKey: true },
|
||||
name: { type: 'string', required: true },
|
||||
});
|
||||
await db.query("INSERT INTO users VALUES ('1', 'Alice')");
|
||||
const result = await db.query('SELECT * FROM users') as Record<string, unknown>[];
|
||||
expect(result).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user