mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-10-26 02:04:37 +08:00
feat(merge): migrate merge routes from IPC to shared HTTP package
Unify the merge workflow (parse/conflicts/execute/clear/export-for-merge) across CLI, Electron, and web-serve into a single set of shared routes in @openchatlab/http-routes. Key changes: - Move MergeSessionCache from CLI to node-runtime shared package - Add dual-mode parse route (multipart upload for web, JSON filePath for Electron) - Integrate merge routes into Electron internal API server with @fastify/multipart - Create installMergeShims() with filePath-to-handle mapping for frontend - Remove Electron merge IPC handlers, merger module, and preload bridges (~790 lines deleted) - Address CodeX review: CHAT_DB_TABLES in openDatabase, no chatApi patching, outputName sanitization, resp.ok checks, @fastify/multipart in desktop deps Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
65fb5179cd
commit
ae90db63a5
22 files changed
+432
-790
No files matched your search
@@ -332,7 +332,7 @@ export * as memberService from './services/member-service'
|
||||
export * as sessionIndexService from './services/session-index-service'
|
||||
export * as summaryService from './services/summary-service'
|
||||
export * as exportService from './services/export-service'
|
||||
export { createDatabaseManagerAdapter } from './services'
|
||||
export { createDatabaseManagerAdapter, MergeSessionCache } from './services'
|
||||
export type {
|
||||
SessionRuntimeAdapter,
|
||||
AnalysisSessionDTO,
|
||||
|
||||
@@ -41,3 +41,6 @@ export type { LlmConfig, SummaryServiceDeps } from './summary-service'
|
||||
|
||||
// Export service
|
||||
export { exportMarkdown } from './export-service'
|
||||
|
||||
// Merge cache
|
||||
export { MergeSessionCache } from './merge-cache'
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* Server-side merge session cache.
|
||||
*
|
||||
* Manages temporary databases for the multi-file merge workflow.
|
||||
* Each uploaded/parsed file gets a temp DB handle that persists
|
||||
* across HTTP requests until merge completes or cache is cleared.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import * as crypto from 'crypto'
|
||||
import type { PathProvider, DatabaseAdapter } from '@openchatlab/core'
|
||||
import { openBetterSqliteDatabase } from '../better-sqlite3-adapter'
|
||||
import { TempDbReader, deleteTempDatabase, cleanupTempDatabases } from '../merger/temp-db'
|
||||
|
||||
interface CacheEntry {
|
||||
tempDbPath: string
|
||||
filename: string
|
||||
createdAt: number
|
||||
}
|
||||
|
||||
export class MergeSessionCache {
|
||||
private cache = new Map<string, CacheEntry>()
|
||||
private tempDir: string
|
||||
private nativeBinding?: string
|
||||
|
||||
constructor(pathProvider: PathProvider, options?: { nativeBinding?: string }) {
|
||||
this.tempDir = pathProvider.getTempDir()
|
||||
this.nativeBinding = options?.nativeBinding
|
||||
if (!fs.existsSync(this.tempDir)) {
|
||||
fs.mkdirSync(this.tempDir, { recursive: true })
|
||||
}
|
||||
}
|
||||
|
||||
generateTempDbPath(filename: string): string {
|
||||
const ts = Date.now()
|
||||
const rand = Math.random().toString(36).slice(2, 8)
|
||||
const safeName = filename.replace(/[/\\?%*:|"<>]/g, '_').slice(0, 40)
|
||||
return path.join(this.tempDir, `merge_${safeName}_${ts}_${rand}.db`)
|
||||
}
|
||||
|
||||
/** Store a parsed file's temp DB and return a handle for subsequent requests. */
|
||||
store(filename: string, tempDbPath: string): string {
|
||||
const handle = crypto.randomUUID()
|
||||
this.cache.set(handle, { tempDbPath, filename, createdAt: Date.now() })
|
||||
return handle
|
||||
}
|
||||
|
||||
/** Open a TempDbReader for a given handle. Caller must close the reader. */
|
||||
openReader(handle: string): { reader: TempDbReader; filename: string } | null {
|
||||
const entry = this.cache.get(handle)
|
||||
if (!entry || !fs.existsSync(entry.tempDbPath)) return null
|
||||
const adapter = openBetterSqliteDatabase(entry.tempDbPath, {
|
||||
readonly: true,
|
||||
nativeBinding: this.nativeBinding,
|
||||
})
|
||||
return { reader: new TempDbReader(adapter), filename: entry.filename }
|
||||
}
|
||||
|
||||
/** Create a writable temp DB for streaming parse. */
|
||||
createTempDatabase(filename: string): { db: DatabaseAdapter; tempDbPath: string } {
|
||||
const tempDbPath = this.generateTempDbPath(filename)
|
||||
const adapter = openBetterSqliteDatabase(tempDbPath, { nativeBinding: this.nativeBinding })
|
||||
return { db: adapter, tempDbPath }
|
||||
}
|
||||
|
||||
/** Remove a single entry and its temp DB file. */
|
||||
delete(handle: string): void {
|
||||
const entry = this.cache.get(handle)
|
||||
if (entry) {
|
||||
deleteTempDatabase(entry.tempDbPath)
|
||||
this.cache.delete(handle)
|
||||
}
|
||||
}
|
||||
|
||||
/** Clear all entries and delete their temp DB files. */
|
||||
clear(): void {
|
||||
for (const entry of this.cache.values()) {
|
||||
deleteTempDatabase(entry.tempDbPath)
|
||||
}
|
||||
this.cache.clear()
|
||||
}
|
||||
|
||||
/** Cleanup orphan temp DBs from previous runs. */
|
||||
cleanupOrphans(): void {
|
||||
cleanupTempDatabases(this.tempDir)
|
||||
}
|
||||
|
||||
/** Auto-cleanup entries older than maxAge (default 1 hour). */
|
||||
cleanupExpired(maxAgeMs = 3600_000): void {
|
||||
const now = Date.now()
|
||||
for (const [handle, entry] of this.cache) {
|
||||
if (now - entry.createdAt > maxAgeMs) {
|
||||
deleteTempDatabase(entry.tempDbPath)
|
||||
this.cache.delete(handle)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user