跳转到内容

数据库与迁移

数据层基于 Drizzle ORM(对象化类型安全查询)+ Rust 侧自建 sqlx 通道执行。

在插件 frontend/schema.ts 定义表(JPA Entity 的等价物),自动聚合,无需手动注册 (Vite 构建期扫描 plugins/*/frontend/schema.ts):

import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core';
import { sql } from 'drizzle-orm';
export const myItems = sqliteTable('my_items', {
id: integer('id').primaryKey({ autoIncrement: true }),
title: text('title').notNull(),
createdAt: text('created_at')
.notNull()
.default(sql`(datetime('now','localtime'))`),
});
export type MyItem = typeof myItems.$inferSelect; // 行类型,勿手写 interface

在插件 backend/migrations.rs 追加迁移。scope = 插件 id,version 在作用域内从 1 递增

migration("my-tools", 1, "CREATE TABLE my_items (...)")

构建期自动聚合,启动时按 (scope, version) 幂等执行。建表 DDL 的列默认值要与 schema 对齐。

若插件此前用过旧的全局版本号,在 plugin.jsonlegacyMigrations 声明 「旧全局版本 → 新本地版本」,启动时按映射登记进 plugin_migrations(不重复执行 DDL):

{ "legacyMigrations": { "1": 1, "2": 2 } }
import { kdb } from '@/core/db';
import { eq, desc } from 'drizzle-orm';
import { myItems } from './schema';
const rows = await kdb
.select()
.from(myItems)
.where(eq(myItems.title, 'x'))
.orderBy(desc(myItems.id));
const [{ id }] = await kdb
.insert(myItems)
.values({ title: 'x' })
.returning({ id: myItems.id });
await kdb.update(myItems).set({ title: 'y' }).where(eq(myItems.id, id));
await kdb.delete(myItems).where(eq(myItems.id, id));
import { db } from '@/core/db';
await db.insert('notes', { content: 'hi' }); // 标识符白名单校验
const rows = await db.findAll<Row>('notes', { where: { ok: 1 }, orderBy: 'id DESC', limit: 20 });
const rows2 = await db.select<Row>('SELECT * FROM t WHERE a > $1', [value]);

复杂聚合 / JOIN 退回手写 SQL。

要点说明
标识符表名 / 列名做白名单校验,值一律参数化
自增 id.returning() 拿返回值——proxy 的 run 路径不回传 lastInsertId
时间戳schema 的 default 与迁移 DDL 对齐
BLOB经 sqlx 通道以 base64 字符串返回,前端自行 atob 解码
原子写入多语句用 runInTransaction([{ sql, params }]),Rust 侧单事务执行,任一失败整体回滚