chore: remove all better-sqlite3 references from codebase (#357)
* fix: export/import scripts now use API instead of direct DB access Export script fix: - Add format=json parameter to SearchManager for raw data output - Add getSdkSessionsBySessionIds method to SessionStore - Add POST /api/sdk-sessions/batch endpoint to DataRoutes - Refactor export-memories.ts to use HTTP API Import script fix: - Add import methods to SessionStore with duplicate detection - Add POST /api/import endpoint to DataRoutes - Refactor import-memories.ts to use HTTP API 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: update analyze-transformations-smart.js to use bun:sqlite Replace better-sqlite3 import with bun:sqlite to align with v7.1.0 migration. * chore: remove all better-sqlite3 references from codebase - Updated scripts/analyze-transformations-smart.js to use bun:sqlite - Merged PR #332: Refactored import/export scripts to use worker API instead of direct DB access - Updated PM2-to-Bun migration documentation All better-sqlite3 references have been removed from source code. Documentation references remain as appropriate historical context. * build: update plugin artifacts with merged changes Include built artifacts from PR #332 merge and analyze-transformations-smart.js update. --------- Co-authored-by: lee <loyalpartner@163.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1039,6 +1039,36 @@ export class SessionStore {
|
||||
return stmt.get(id) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SDK sessions by SDK session IDs
|
||||
* Used for exporting session metadata
|
||||
*/
|
||||
getSdkSessionsBySessionIds(sdkSessionIds: string[]): {
|
||||
id: number;
|
||||
claude_session_id: string;
|
||||
sdk_session_id: string;
|
||||
project: string;
|
||||
user_prompt: string;
|
||||
started_at: string;
|
||||
started_at_epoch: number;
|
||||
completed_at: string | null;
|
||||
completed_at_epoch: number | null;
|
||||
status: string;
|
||||
}[] {
|
||||
if (sdkSessionIds.length === 0) return [];
|
||||
|
||||
const placeholders = sdkSessionIds.map(() => '?').join(',');
|
||||
const stmt = this.db.prepare(`
|
||||
SELECT id, claude_session_id, sdk_session_id, project, user_prompt,
|
||||
started_at, started_at_epoch, completed_at, completed_at_epoch, status
|
||||
FROM sdk_sessions
|
||||
WHERE sdk_session_id IN (${placeholders})
|
||||
ORDER BY started_at_epoch DESC
|
||||
`);
|
||||
|
||||
return stmt.all(...sdkSessionIds) as any[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Find active SDK session for a Claude session
|
||||
*/
|
||||
@@ -1795,4 +1825,212 @@ export class SessionStore {
|
||||
close(): void {
|
||||
this.db.close();
|
||||
}
|
||||
|
||||
// ===========================================
|
||||
// Import Methods (for import-memories script)
|
||||
// ===========================================
|
||||
|
||||
/**
|
||||
* Import SDK session with duplicate checking
|
||||
* Returns: { imported: boolean, id: number }
|
||||
*/
|
||||
importSdkSession(session: {
|
||||
claude_session_id: string;
|
||||
sdk_session_id: string;
|
||||
project: string;
|
||||
user_prompt: string;
|
||||
started_at: string;
|
||||
started_at_epoch: number;
|
||||
completed_at: string | null;
|
||||
completed_at_epoch: number | null;
|
||||
status: string;
|
||||
}): { imported: boolean; id: number } {
|
||||
// Check if session already exists
|
||||
const existing = this.db.prepare(
|
||||
'SELECT id FROM sdk_sessions WHERE claude_session_id = ?'
|
||||
).get(session.claude_session_id) as { id: number } | undefined;
|
||||
|
||||
if (existing) {
|
||||
return { imported: false, id: existing.id };
|
||||
}
|
||||
|
||||
const stmt = this.db.prepare(`
|
||||
INSERT INTO sdk_sessions (
|
||||
claude_session_id, sdk_session_id, project, user_prompt,
|
||||
started_at, started_at_epoch, completed_at, completed_at_epoch, status
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
const result = stmt.run(
|
||||
session.claude_session_id,
|
||||
session.sdk_session_id,
|
||||
session.project,
|
||||
session.user_prompt,
|
||||
session.started_at,
|
||||
session.started_at_epoch,
|
||||
session.completed_at,
|
||||
session.completed_at_epoch,
|
||||
session.status
|
||||
);
|
||||
|
||||
return { imported: true, id: result.lastInsertRowid as number };
|
||||
}
|
||||
|
||||
/**
|
||||
* Import session summary with duplicate checking
|
||||
* Returns: { imported: boolean, id: number }
|
||||
*/
|
||||
importSessionSummary(summary: {
|
||||
sdk_session_id: string;
|
||||
project: string;
|
||||
request: string | null;
|
||||
investigated: string | null;
|
||||
learned: string | null;
|
||||
completed: string | null;
|
||||
next_steps: string | null;
|
||||
files_read: string | null;
|
||||
files_edited: string | null;
|
||||
notes: string | null;
|
||||
prompt_number: number | null;
|
||||
discovery_tokens: number;
|
||||
created_at: string;
|
||||
created_at_epoch: number;
|
||||
}): { imported: boolean; id: number } {
|
||||
// Check if summary already exists for this session
|
||||
const existing = this.db.prepare(
|
||||
'SELECT id FROM session_summaries WHERE sdk_session_id = ?'
|
||||
).get(summary.sdk_session_id) as { id: number } | undefined;
|
||||
|
||||
if (existing) {
|
||||
return { imported: false, id: existing.id };
|
||||
}
|
||||
|
||||
const stmt = this.db.prepare(`
|
||||
INSERT INTO session_summaries (
|
||||
sdk_session_id, project, request, investigated, learned,
|
||||
completed, next_steps, files_read, files_edited, notes,
|
||||
prompt_number, discovery_tokens, created_at, created_at_epoch
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
const result = stmt.run(
|
||||
summary.sdk_session_id,
|
||||
summary.project,
|
||||
summary.request,
|
||||
summary.investigated,
|
||||
summary.learned,
|
||||
summary.completed,
|
||||
summary.next_steps,
|
||||
summary.files_read,
|
||||
summary.files_edited,
|
||||
summary.notes,
|
||||
summary.prompt_number,
|
||||
summary.discovery_tokens || 0,
|
||||
summary.created_at,
|
||||
summary.created_at_epoch
|
||||
);
|
||||
|
||||
return { imported: true, id: result.lastInsertRowid as number };
|
||||
}
|
||||
|
||||
/**
|
||||
* Import observation with duplicate checking
|
||||
* Duplicates are identified by sdk_session_id + title + created_at_epoch
|
||||
* Returns: { imported: boolean, id: number }
|
||||
*/
|
||||
importObservation(obs: {
|
||||
sdk_session_id: string;
|
||||
project: string;
|
||||
text: string | null;
|
||||
type: string;
|
||||
title: string | null;
|
||||
subtitle: string | null;
|
||||
facts: string | null;
|
||||
narrative: string | null;
|
||||
concepts: string | null;
|
||||
files_read: string | null;
|
||||
files_modified: string | null;
|
||||
prompt_number: number | null;
|
||||
discovery_tokens: number;
|
||||
created_at: string;
|
||||
created_at_epoch: number;
|
||||
}): { imported: boolean; id: number } {
|
||||
// Check if observation already exists
|
||||
const existing = this.db.prepare(`
|
||||
SELECT id FROM observations
|
||||
WHERE sdk_session_id = ? AND title = ? AND created_at_epoch = ?
|
||||
`).get(obs.sdk_session_id, obs.title, obs.created_at_epoch) as { id: number } | undefined;
|
||||
|
||||
if (existing) {
|
||||
return { imported: false, id: existing.id };
|
||||
}
|
||||
|
||||
const stmt = this.db.prepare(`
|
||||
INSERT INTO observations (
|
||||
sdk_session_id, project, text, type, title, subtitle,
|
||||
facts, narrative, concepts, files_read, files_modified,
|
||||
prompt_number, discovery_tokens, created_at, created_at_epoch
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
const result = stmt.run(
|
||||
obs.sdk_session_id,
|
||||
obs.project,
|
||||
obs.text,
|
||||
obs.type,
|
||||
obs.title,
|
||||
obs.subtitle,
|
||||
obs.facts,
|
||||
obs.narrative,
|
||||
obs.concepts,
|
||||
obs.files_read,
|
||||
obs.files_modified,
|
||||
obs.prompt_number,
|
||||
obs.discovery_tokens || 0,
|
||||
obs.created_at,
|
||||
obs.created_at_epoch
|
||||
);
|
||||
|
||||
return { imported: true, id: result.lastInsertRowid as number };
|
||||
}
|
||||
|
||||
/**
|
||||
* Import user prompt with duplicate checking
|
||||
* Duplicates are identified by claude_session_id + prompt_number
|
||||
* Returns: { imported: boolean, id: number }
|
||||
*/
|
||||
importUserPrompt(prompt: {
|
||||
claude_session_id: string;
|
||||
prompt_number: number;
|
||||
prompt_text: string;
|
||||
created_at: string;
|
||||
created_at_epoch: number;
|
||||
}): { imported: boolean; id: number } {
|
||||
// Check if prompt already exists
|
||||
const existing = this.db.prepare(`
|
||||
SELECT id FROM user_prompts
|
||||
WHERE claude_session_id = ? AND prompt_number = ?
|
||||
`).get(prompt.claude_session_id, prompt.prompt_number) as { id: number } | undefined;
|
||||
|
||||
if (existing) {
|
||||
return { imported: false, id: existing.id };
|
||||
}
|
||||
|
||||
const stmt = this.db.prepare(`
|
||||
INSERT INTO user_prompts (
|
||||
claude_session_id, prompt_number, prompt_text,
|
||||
created_at, created_at_epoch
|
||||
) VALUES (?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
const result = stmt.run(
|
||||
prompt.claude_session_id,
|
||||
prompt.prompt_number,
|
||||
prompt.prompt_text,
|
||||
prompt.created_at,
|
||||
prompt.created_at_epoch
|
||||
);
|
||||
|
||||
return { imported: true, id: result.lastInsertRowid as number };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ export class SearchManager {
|
||||
try {
|
||||
// Normalize URL-friendly params to internal format
|
||||
const normalized = this.normalizeParams(args);
|
||||
const { query, type, obs_type, concepts, files, ...options } = normalized;
|
||||
const { query, type, obs_type, concepts, files, format, ...options } = normalized;
|
||||
let observations: ObservationSearchResult[] = [];
|
||||
let sessions: SessionSummarySearchResult[] = [];
|
||||
let prompts: UserPromptSearchResult[] = [];
|
||||
@@ -200,6 +200,17 @@ export class SearchManager {
|
||||
|
||||
const totalResults = observations.length + sessions.length + prompts.length;
|
||||
|
||||
// JSON format: return raw data for programmatic access (e.g., export scripts)
|
||||
if (format === 'json') {
|
||||
return {
|
||||
observations,
|
||||
sessions,
|
||||
prompts,
|
||||
totalResults,
|
||||
query: query || ''
|
||||
};
|
||||
}
|
||||
|
||||
if (totalResults === 0) {
|
||||
return {
|
||||
content: [{
|
||||
|
||||
@@ -40,6 +40,7 @@ export class DataRoutes extends BaseRouteHandler {
|
||||
app.get('/api/observation/:id', this.handleGetObservationById.bind(this));
|
||||
app.post('/api/observations/batch', this.handleGetObservationsByIds.bind(this));
|
||||
app.get('/api/session/:id', this.handleGetSessionById.bind(this));
|
||||
app.post('/api/sdk-sessions/batch', this.handleGetSdkSessionsByIds.bind(this));
|
||||
app.get('/api/prompt/:id', this.handleGetPromptById.bind(this));
|
||||
|
||||
// Metadata endpoints
|
||||
@@ -49,6 +50,9 @@ export class DataRoutes extends BaseRouteHandler {
|
||||
// Processing status endpoints
|
||||
app.get('/api/processing-status', this.handleGetProcessingStatus.bind(this));
|
||||
app.post('/api/processing', this.handleSetProcessing.bind(this));
|
||||
|
||||
// Import endpoint
|
||||
app.post('/api/import', this.handleImport.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,6 +150,24 @@ export class DataRoutes extends BaseRouteHandler {
|
||||
res.json(sessions[0]);
|
||||
});
|
||||
|
||||
/**
|
||||
* Get SDK sessions by SDK session IDs
|
||||
* POST /api/sdk-sessions/batch
|
||||
* Body: { sdkSessionIds: string[] }
|
||||
*/
|
||||
private handleGetSdkSessionsByIds = this.wrapHandler((req: Request, res: Response): void => {
|
||||
const { sdkSessionIds } = req.body;
|
||||
|
||||
if (!Array.isArray(sdkSessionIds)) {
|
||||
this.badRequest(res, 'sdkSessionIds must be an array');
|
||||
return;
|
||||
}
|
||||
|
||||
const store = this.dbManager.getSessionStore();
|
||||
const sessions = store.getSdkSessionsBySessionIds(sdkSessionIds);
|
||||
res.json(sessions);
|
||||
});
|
||||
|
||||
/**
|
||||
* Get user prompt by ID
|
||||
* GET /api/prompt/:id
|
||||
@@ -267,4 +289,79 @@ export class DataRoutes extends BaseRouteHandler {
|
||||
|
||||
return { offset, limit, project };
|
||||
}
|
||||
|
||||
/**
|
||||
* Import memories from export file
|
||||
* POST /api/import
|
||||
* Body: { sessions: [], summaries: [], observations: [], prompts: [] }
|
||||
*/
|
||||
private handleImport = this.wrapHandler((req: Request, res: Response): void => {
|
||||
const { sessions, summaries, observations, prompts } = req.body;
|
||||
|
||||
const stats = {
|
||||
sessionsImported: 0,
|
||||
sessionsSkipped: 0,
|
||||
summariesImported: 0,
|
||||
summariesSkipped: 0,
|
||||
observationsImported: 0,
|
||||
observationsSkipped: 0,
|
||||
promptsImported: 0,
|
||||
promptsSkipped: 0
|
||||
};
|
||||
|
||||
const store = this.dbManager.getSessionStore();
|
||||
|
||||
// Import sessions first (dependency for everything else)
|
||||
if (Array.isArray(sessions)) {
|
||||
for (const session of sessions) {
|
||||
const result = store.importSdkSession(session);
|
||||
if (result.imported) {
|
||||
stats.sessionsImported++;
|
||||
} else {
|
||||
stats.sessionsSkipped++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Import summaries (depends on sessions)
|
||||
if (Array.isArray(summaries)) {
|
||||
for (const summary of summaries) {
|
||||
const result = store.importSessionSummary(summary);
|
||||
if (result.imported) {
|
||||
stats.summariesImported++;
|
||||
} else {
|
||||
stats.summariesSkipped++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Import observations (depends on sessions)
|
||||
if (Array.isArray(observations)) {
|
||||
for (const obs of observations) {
|
||||
const result = store.importObservation(obs);
|
||||
if (result.imported) {
|
||||
stats.observationsImported++;
|
||||
} else {
|
||||
stats.observationsSkipped++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Import prompts (depends on sessions)
|
||||
if (Array.isArray(prompts)) {
|
||||
for (const prompt of prompts) {
|
||||
const result = store.importUserPrompt(prompt);
|
||||
if (result.imported) {
|
||||
stats.promptsImported++;
|
||||
} else {
|
||||
stats.promptsSkipped++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
stats
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user