Files
inkreach-agent/apps/desktop/src/main/plugins/storage-worker.mjs
T

103 lines
3.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 插件存储 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) })
}
})