feat(server): isolate better-sqlite3 native binary for Electron/Node coexistence

Add rebuild-native.sh script that compiles better-sqlite3 in an isolated
temp directory for system Node.js, storing the binary at packages/server/native/.
DatabaseManager and openBetterSqliteDatabase now accept nativeBinding option.
CLI auto-detects non-Electron environment and uses the isolated binary.
This commit is contained in:
digua committed 2026-05-11 20:24:39 +08:00
1 parent 0d0dbdb6ce
commit d040aaaa98
6 files changed
+68 -3

No files matched your search

+1
View File
@@ -17,6 +17,7 @@ error-log-*.txt
# Project
dist/
packages/server/native/
.history/
out
.obskey
@@ -61,13 +61,17 @@ export class BetterSqliteAdapter implements DatabaseAdapter {
/**
* 从文件路径打开数据库并返回适配器
*
* @param options.nativeBinding - 指定 better-sqlite3 原生模块路径,
* 用于在独立 Node.js 环境中加载与 Electron 隔离的二进制。
*/
export function openBetterSqliteDatabase(
filePath: string,
options?: { readonly?: boolean }
options?: { readonly?: boolean; nativeBinding?: string }
): BetterSqliteAdapter {
const db = new Database(filePath, {
readonly: options?.readonly ?? false,
nativeBinding: options?.nativeBinding,
})
db.pragma('journal_mode = WAL')
return new BetterSqliteAdapter(db)
@@ -14,8 +14,14 @@ import { openBetterSqliteDatabase } from './better-sqlite3-adapter'
export class DatabaseManager {
private cache = new Map<string, DatabaseAdapter>()
private nativeBinding?: string
constructor(private pathProvider: PathProvider) {}
constructor(
private pathProvider: PathProvider,
options?: { nativeBinding?: string }
) {
this.nativeBinding = options?.nativeBinding
}
/**
* 打开指定会话的数据库(带缓存)
@@ -30,6 +36,7 @@ export class DatabaseManager {
const adapter = openBetterSqliteDatabase(dbPath, {
readonly: options?.readonly ?? true,
nativeBinding: this.nativeBinding,
})
this.cache.set(sessionId, adapter)
return adapter
+1
View File
@@ -10,6 +10,7 @@
"types": "./src/index.ts",
"scripts": {
"cli": "tsx src/cli.ts",
"rebuild-native": "bash scripts/rebuild-native.sh",
"rebuild-sqlite": "npm rebuild better-sqlite3"
},
"dependencies": {
+38
View File
@@ -0,0 +1,38 @@
#!/bin/bash
#
# 在隔离目录中编译 better-sqlite3 的系统 Node.js 原生模块,
# 避免与 electron-rebuild 产生冲突。
#
# 产物:packages/server/native/better_sqlite3.node
#
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SERVER_DIR="$(dirname "$SCRIPT_DIR")"
TARGET_DIR="$SERVER_DIR/native"
# 获取当前 workspace 中 better-sqlite3 的版本
BS3_VERSION=$(node -e "const p = require('better-sqlite3/package.json'); console.log(p.version)")
echo "Building better-sqlite3@${BS3_VERSION} for system Node.js ($(node -v))..."
TEMP_DIR=$(mktemp -d)
trap "rm -rf $TEMP_DIR" EXIT
cd "$TEMP_DIR"
npm init -y > /dev/null 2>&1
npm install "better-sqlite3@${BS3_VERSION}" --ignore-scripts > /dev/null 2>&1
cd "node_modules/better-sqlite3"
npx --yes prebuild-install -r napi || npm run build-release 2>/dev/null || npx --yes node-gyp rebuild --release
BUILT="$TEMP_DIR/node_modules/better-sqlite3/build/Release/better_sqlite3.node"
if [ ! -f "$BUILT" ]; then
echo "Error: native binary not found at $BUILT"
exit 1
fi
mkdir -p "$TARGET_DIR"
cp "$BUILT" "$TARGET_DIR/better_sqlite3.node"
echo "Done. Native binary saved to $TARGET_DIR/better_sqlite3.node"
echo " ABI: $(node -e 'console.log(process.versions.modules)')"
+15 -1
View File
@@ -5,6 +5,8 @@
* 开发阶段运行方式:pnpm --filter chatlab cli -- sessions
*/
import * as fs from 'fs'
import * as path from 'path'
import { Command } from 'commander'
import { loadConfig, getConfigPath } from '@openchatlab/config'
import { NodePathProvider, DatabaseManager } from '@openchatlab/node-runtime'
@@ -227,12 +229,24 @@ configCmd
// --- 工具函数 ---
/**
* 查找独立编译的 better-sqlite3 原生模块路径。
* 在非 Electron 环境下使用,避免与 electron-rebuild 冲突。
*/
function resolveNativeBinding(): string | undefined {
if (process.versions.electron) return undefined
const nativePath = path.resolve(__dirname, '../native/better_sqlite3.node')
if (fs.existsSync(nativePath)) return nativePath
return undefined
}
function initRuntime() {
const config = loadConfig()
const dataDir = config.data.dir || undefined
const pathProvider = new NodePathProvider(dataDir)
pathProvider.ensureAllDirs()
const dbManager = new DatabaseManager(pathProvider)
const nativeBinding = resolveNativeBinding()
const dbManager = new DatabaseManager(pathProvider, { nativeBinding })
return { config, pathProvider, dbManager }
}