feat(core): init @openchatlab/core package with abstract interfaces

Phase 1.1 of CLI/Web transformation: create the platform-agnostic core
package with DatabaseAdapter, PathProvider, NotificationBus interfaces
and query filter utilities extracted from worker/core/dbCore.ts.
This commit is contained in:
digua committed 2026-05-11 19:44:24 +08:00
1 parent 24bdcc0a0e
commit d1e160fe23
9 files changed
+217 -1

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
{
"name": "@openchatlab/core",
"version": "0.0.0",
"private": true,
"description": "ChatLab 平台无关的共享核心:抽象接口、查询逻辑、分析算法",
"main": "./src/index.ts",
"types": "./src/index.ts"
}
+19
View File
@@ -0,0 +1,19 @@
/**
* @openchatlab/core
*
* 平台无关的 ChatLab 共享核心。
* 提供抽象接口、查询工具、分析算法,不依赖任何特定运行时(Electron / Node / 浏览器)。
*/
// 抽象接口
export type {
DatabaseAdapter,
PreparedStatement,
RunResult,
PathProvider,
NotificationBus,
NotificationPayload,
} from './interfaces'
// 查询工具
export { buildTimeFilter, buildSystemMessageFilter } from './query'
@@ -0,0 +1,68 @@
/**
* 数据库适配器抽象接口
*
* 定义平台无关的 SQLite 数据库访问契约。
* 接口设计贴合 better-sqlite3 API(因为现有查询代码基于此编写),
* sql.js 等其他实现只需编写薄适配层。
*/
/**
* 预编译语句接口
*/
export interface PreparedStatement {
/**
* 执行查询并返回第一行结果
*/
get(...params: unknown[]): Record<string, unknown> | undefined
/**
* 执行查询并返回所有行
*/
all(...params: unknown[]): Record<string, unknown>[]
/**
* 执行写操作(INSERT / UPDATE / DELETE)
*/
run(...params: unknown[]): RunResult
}
/**
* 写操作的返回结果
*/
export interface RunResult {
changes: number
lastInsertRowid?: number | bigint
}
/**
* 数据库适配器接口
*
* 调用方通过此接口操作 SQLite 数据库,不关心底层使用
* better-sqlite3(Node.js)还是 sql.js(浏览器 WASM)。
*/
export interface DatabaseAdapter {
/**
* 执行原始 SQL(不返回结果,用于 DDL 或批量语句)
*/
exec(sql: string): void
/**
* 预编译 SQL 语句
*/
prepare(sql: string): PreparedStatement
/**
* 在事务中执行操作
*/
transaction<T>(fn: () => T): T
/**
* 关闭数据库连接
*/
close(): void
/**
* 数据库是否处于只读模式
*/
readonly?: boolean
}
+3
View File
@@ -0,0 +1,3 @@
export type { DatabaseAdapter, PreparedStatement, RunResult } from './database-adapter'
export type { PathProvider } from './path-provider'
export type { NotificationBus, NotificationPayload } from './notification-bus'
@@ -0,0 +1,21 @@
/**
* 通知总线抽象接口
*
* 统一不同运行环境下的异步事件通知方式:
* - Electron:通过 BrowserWindow.webContents.send(IPC)通知前端
* - Node 独立运行:通过 EventEmitter 或 SSE 通知消费者
* - 浏览器:通过 postMessage / CustomEvent 通知 UI
*/
export type NotificationPayload = Record<string, unknown>
export interface NotificationBus {
/** 发送通知事件 */
emit(event: string, payload?: NotificationPayload): void
/** 监听通知事件 */
on(event: string, handler: (payload?: NotificationPayload) => void): void
/** 取消监听 */
off(event: string, handler: (payload?: NotificationPayload) => void): void
}
@@ -0,0 +1,41 @@
/**
* 路径提供器抽象接口
*
* 统一不同运行环境下的目录路径获取方式:
* - Electron:通过 app.getPath('userData') 驱动
* - Node 独立运行:通过 ~/.chatlab/ 或 CHATLAB_DATA_DIR 环境变量驱动
*
* 子目录结构在所有环境下保持一致:
* {dataDir}/
* ├── databases/ 聊天记录 SQLite 文件({uuid}.db)
* ├── ai/ AI 对话历史、LLM 配置
* ├── settings/ 用户设置
* ├── cache/ 派生数据缓存(可再生)
* ├── temp/ 临时文件
* └── logs/ 日志
*/
export interface PathProvider {
/** 数据根目录 */
getDataDir(): string
/** 数据库文件目录(存放 {uuid}.db) */
getDatabaseDir(): string
/** AI 数据目录(对话历史、LLM 配置) */
getAiDataDir(): string
/** 设置目录 */
getSettingsDir(): string
/** 缓存目录(存放可再生的派生数据) */
getCacheDir(): string
/** 临时文件目录 */
getTempDir(): string
/** 日志目录 */
getLogsDir(): string
/** 下载目录(导出文件的默认保存位置) */
getDownloadsDir(): string
}
+54
View File
@@ -0,0 +1,54 @@
/**
* SQL 查询过滤条件构建工具
*
* 平台无关的 WHERE 子句构建器,供所有查询模块复用。
*/
import type { TimeFilter } from '@openchatlab/shared-types'
/**
* 构建时间过滤 WHERE 子句
* @param filter 时间过滤器(包含时间范围和成员筛选)
* @param tableAlias 表别名,用于多表 JOIN 场景避免列名歧义(如 'msg')
*/
export function buildTimeFilter(
filter?: TimeFilter,
tableAlias?: string
): { clause: string; params: (number | string)[] } {
const conditions: string[] = []
const params: (number | string)[] = []
const tsColumn = tableAlias ? `${tableAlias}.ts` : 'ts'
const senderIdColumn = tableAlias ? `${tableAlias}.sender_id` : 'sender_id'
if (filter?.startTs !== undefined) {
conditions.push(`${tsColumn} >= ?`)
params.push(filter.startTs)
}
if (filter?.endTs !== undefined) {
conditions.push(`${tsColumn} <= ?`)
params.push(filter.endTs)
}
if (filter?.memberId !== undefined && filter?.memberId !== null) {
conditions.push(`${senderIdColumn} = ?`)
params.push(filter.memberId)
}
return {
clause: conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : '',
params,
}
}
/**
* 构建排除系统消息的过滤条件
*/
export function buildSystemMessageFilter(existingClause: string): string {
const systemFilter = "COALESCE(m.account_name, '') != '系统消息'"
if (existingClause.includes('WHERE')) {
return existingClause + ' AND ' + systemFilter
} else {
return ' WHERE ' + systemFilter
}
}
+1
View File
@@ -0,0 +1 @@
export { buildTimeFilter, buildSystemMessageFilter } from './filters'
+2 -1
View File
@@ -7,7 +7,8 @@
"electron/preload/**/*",
"electron/shared/**/*",
"src/types/**/*",
"packages/shared-types/**/*"
"packages/shared-types/**/*",
"packages/core/**/*"
],
"compilerOptions": {
"composite": true,