feat(plugins): 依赖互调 + SQLite 存储,契约 0.3.0

This commit is contained in:
Rick Ye
2026-09-11 14:56:30 +08:00
parent e958784d10
commit 1cb2b554c0
14 changed files with 807 additions and 114 deletions
@@ -0,0 +1,102 @@
/**
* 插件存储 worker(免打包纯 ESM,只依赖 node 内置模块;构建期拷到 out/main/ 旁)。
* 持有全部插件的 SQLite 连接(node:sqlite DatabaseSync),按消息逐个执行——
* 同步 API 全部留在本线程,主进程只做异步 RPC,插件查询永不阻塞主进程事件循环。
*
* 协议:主进程 → { reqId, op, ...payload };worker → { reqId, ok, result? | error? }。
* 首条消息必须是 init(下发 dataRoot,避免 worker 依赖 electron app 路径)。
*/
import { DatabaseSync } from 'node:sqlite'
import { mkdirSync } from 'node:fs'
import { join } from 'node:path'
import { parentPort } from 'node:worker_threads'
/** dataRoot(userData/plugin-data);由 init 消息下发 */
let dataRoot = null
/** pluginId → 已打开连接(跨消息复用;close/clearData 时移除) */
const dbs = new Map()
function openDb(pluginId) {
let db = dbs.get(pluginId)
if (db) return db
// pluginId 已由宿主按 manifest 规则校验(小写字母开头,字母/数字/连字符),可安全作路径段
mkdirSync(join(dataRoot, pluginId), { recursive: true })
db = new DatabaseSync(join(dataRoot, pluginId, 'data.db'))
db.exec('PRAGMA journal_mode = WAL;')
// 宿主管理的 KV 表:api.storage.kv 的底层(value JSON 编码)
db.exec('CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value TEXT NOT NULL)')
dbs.set(pluginId, db)
return db
}
function handle(op, msg) {
switch (op) {
case 'init':
dataRoot = msg.dataRoot
return null
case 'close': {
const db = dbs.get(msg.pluginId)
if (db) {
dbs.delete(msg.pluginId)
db.close()
}
return null
}
case 'kvGet': {
const row = openDb(msg.pluginId).prepare('SELECT value FROM kv WHERE key = ?').get(msg.key)
return row ? JSON.parse(row.value) : null
}
case 'kvSet':
openDb(msg.pluginId)
.prepare('INSERT INTO kv (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value')
.run(msg.key, JSON.stringify(msg.value ?? null))
return null
case 'kvDelete':
openDb(msg.pluginId).prepare('DELETE FROM kv WHERE key = ?').run(msg.key)
return null
case 'kvKeys':
return openDb(msg.pluginId)
.prepare('SELECT key FROM kv ORDER BY key')
.all()
.map((r) => r.key)
case 'exec':
openDb(msg.pluginId).exec(msg.sql)
return null
case 'run':
openDb(msg.pluginId).prepare(msg.sql).run(...(msg.params ?? []))
return null
case 'query':
return openDb(msg.pluginId).prepare(msg.sql).all(...(msg.params ?? []))
case 'batch': {
const db = openDb(msg.pluginId)
db.exec('BEGIN')
try {
for (const st of msg.statements ?? []) db.prepare(st.sql).run(...(st.params ?? []))
db.exec('COMMIT')
} catch (err) {
try {
db.exec('ROLLBACK')
} catch {
// 连接已坏时 ROLLBACK 自身会失败——原始错误优先
}
throw err
}
return null
}
default:
throw new Error(`未知存储操作: ${op}`)
}
}
parentPort.on('message', (msg) => {
if (!dataRoot && msg.op !== 'init') {
parentPort.postMessage({ reqId: msg.reqId, ok: false, error: '存储 worker 未初始化' })
return
}
try {
parentPort.postMessage({ reqId: msg.reqId, ok: true, result: handle(msg.op, msg) ?? null })
} catch (err) {
parentPort.postMessage({ reqId: msg.reqId, ok: false, error: err instanceof Error ? err.message : String(err) })
}
})