@sylphx/flow 1.8.0 → 1.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +72 -0
- package/assets/output-styles/silent.md +145 -8
- package/assets/rules/core.md +19 -2
- package/package.json +2 -12
- package/src/commands/flow/execute.ts +470 -0
- package/src/commands/flow/index.ts +11 -0
- package/src/commands/flow/prompt.ts +35 -0
- package/src/commands/flow/setup.ts +312 -0
- package/src/commands/flow/targets.ts +18 -0
- package/src/commands/flow/types.ts +47 -0
- package/src/commands/flow-command.ts +18 -967
- package/src/commands/flow-orchestrator.ts +14 -5
- package/src/commands/hook-command.ts +1 -1
- package/src/commands/init-core.ts +12 -3
- package/src/commands/run-command.ts +1 -1
- package/src/config/rules.ts +1 -1
- package/src/core/error-handling.ts +1 -1
- package/src/core/loop-controller.ts +1 -1
- package/src/core/state-detector.ts +1 -1
- package/src/core/target-manager.ts +1 -1
- package/src/index.ts +1 -1
- package/src/shared/files/index.ts +1 -1
- package/src/shared/processing/index.ts +1 -1
- package/src/targets/claude-code.ts +3 -3
- package/src/targets/opencode.ts +3 -3
- package/src/utils/agent-enhancer.ts +2 -2
- package/src/utils/{mcp-config.ts → config/mcp-config.ts} +4 -4
- package/src/utils/{paths.ts → config/paths.ts} +1 -1
- package/src/utils/{settings.ts → config/settings.ts} +1 -1
- package/src/utils/{target-config.ts → config/target-config.ts} +5 -5
- package/src/utils/{target-utils.ts → config/target-utils.ts} +3 -3
- package/src/utils/display/banner.ts +25 -0
- package/src/utils/display/status.ts +55 -0
- package/src/utils/{file-operations.ts → files/file-operations.ts} +2 -2
- package/src/utils/files/jsonc.ts +36 -0
- package/src/utils/{sync-utils.ts → files/sync-utils.ts} +3 -3
- package/src/utils/index.ts +42 -61
- package/src/utils/version.ts +47 -0
- package/src/components/benchmark-monitor.tsx +0 -331
- package/src/components/reindex-progress.tsx +0 -261
- package/src/composables/functional/index.ts +0 -14
- package/src/composables/functional/useEnvironment.ts +0 -171
- package/src/composables/functional/useFileSystem.ts +0 -139
- package/src/composables/index.ts +0 -4
- package/src/composables/useEnv.ts +0 -13
- package/src/composables/useRuntimeConfig.ts +0 -27
- package/src/core/ai-sdk.ts +0 -603
- package/src/core/app-factory.ts +0 -381
- package/src/core/builtin-agents.ts +0 -9
- package/src/core/command-system.ts +0 -550
- package/src/core/config-system.ts +0 -550
- package/src/core/connection-pool.ts +0 -390
- package/src/core/di-container.ts +0 -155
- package/src/core/headless-display.ts +0 -96
- package/src/core/interfaces/index.ts +0 -22
- package/src/core/interfaces/repository.interface.ts +0 -91
- package/src/core/interfaces/service.interface.ts +0 -133
- package/src/core/interfaces.ts +0 -96
- package/src/core/result.ts +0 -351
- package/src/core/service-config.ts +0 -252
- package/src/core/session-service.ts +0 -121
- package/src/core/storage-factory.ts +0 -115
- package/src/core/stream-handler.ts +0 -288
- package/src/core/type-utils.ts +0 -427
- package/src/core/unified-storage.ts +0 -456
- package/src/core/validation/limit.ts +0 -46
- package/src/core/validation/query.ts +0 -20
- package/src/db/auto-migrate.ts +0 -322
- package/src/db/base-database-client.ts +0 -144
- package/src/db/cache-db.ts +0 -218
- package/src/db/cache-schema.ts +0 -75
- package/src/db/database.ts +0 -70
- package/src/db/index.ts +0 -252
- package/src/db/memory-db.ts +0 -153
- package/src/db/memory-schema.ts +0 -29
- package/src/db/schema.ts +0 -289
- package/src/db/session-repository.ts +0 -733
- package/src/domains/index.ts +0 -6
- package/src/domains/utilities/index.ts +0 -6
- package/src/domains/utilities/time/index.ts +0 -5
- package/src/domains/utilities/time/tools.ts +0 -291
- package/src/services/agent-service.ts +0 -273
- package/src/services/evaluation-service.ts +0 -271
- package/src/services/functional/evaluation-logic.ts +0 -296
- package/src/services/functional/file-processor.ts +0 -273
- package/src/services/functional/index.ts +0 -12
- package/src/services/memory.service.ts +0 -476
- package/src/types/api/batch.ts +0 -108
- package/src/types/api/errors.ts +0 -118
- package/src/types/api/index.ts +0 -55
- package/src/types/api/requests.ts +0 -76
- package/src/types/api/responses.ts +0 -180
- package/src/types/api/websockets.ts +0 -85
- package/src/types/benchmark.ts +0 -49
- package/src/types/database.types.ts +0 -510
- package/src/types/memory-types.ts +0 -63
- package/src/utils/advanced-tokenizer.ts +0 -191
- package/src/utils/ai-model-fetcher.ts +0 -19
- package/src/utils/async-file-operations.ts +0 -516
- package/src/utils/audio-player.ts +0 -345
- package/src/utils/codebase-helpers.ts +0 -211
- package/src/utils/console-ui.ts +0 -79
- package/src/utils/database-errors.ts +0 -140
- package/src/utils/debug-logger.ts +0 -49
- package/src/utils/file-scanner.ts +0 -259
- package/src/utils/help.ts +0 -20
- package/src/utils/immutable-cache.ts +0 -106
- package/src/utils/jsonc.ts +0 -158
- package/src/utils/memory-tui.ts +0 -414
- package/src/utils/models-dev.ts +0 -91
- package/src/utils/parallel-operations.ts +0 -487
- package/src/utils/process-manager.ts +0 -155
- package/src/utils/prompts.ts +0 -120
- package/src/utils/search-tool-builder.ts +0 -214
- package/src/utils/session-manager.ts +0 -168
- package/src/utils/session-title.ts +0 -87
- package/src/utils/simplified-errors.ts +0 -410
- package/src/utils/template-engine.ts +0 -94
- package/src/utils/test-audio.ts +0 -71
- package/src/utils/todo-context.ts +0 -46
- package/src/utils/token-counter.ts +0 -288
- /package/src/utils/{cli-output.ts → display/cli-output.ts} +0 -0
- /package/src/utils/{logger.ts → display/logger.ts} +0 -0
- /package/src/utils/{notifications.ts → display/notifications.ts} +0 -0
- /package/src/utils/{secret-utils.ts → security/secret-utils.ts} +0 -0
- /package/src/utils/{security.ts → security/security.ts} +0 -0
package/src/db/auto-migrate.ts
DELETED
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Auto Migration System
|
|
3
|
-
* Automatically migrates from file-based to database on first app start
|
|
4
|
-
*
|
|
5
|
-
* Design:
|
|
6
|
-
* 1. Check if database has sessions table
|
|
7
|
-
* 2. If not → Run migrations automatically
|
|
8
|
-
* 3. Check if JSON files exist but not in database
|
|
9
|
-
* 4. If yes → Auto-migrate files to database
|
|
10
|
-
* 5. Completely transparent to user
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { join } from 'node:path';
|
|
14
|
-
import { homedir } from 'node:os';
|
|
15
|
-
import { readdir, mkdir, readFile as fsReadFile, unlink } from 'node:fs/promises';
|
|
16
|
-
import { existsSync } from 'node:fs';
|
|
17
|
-
import { createClient } from '@libsql/client';
|
|
18
|
-
import { drizzle } from 'drizzle-orm/libsql';
|
|
19
|
-
import { migrate } from 'drizzle-orm/libsql/migrator';
|
|
20
|
-
import { sql } from 'drizzle-orm';
|
|
21
|
-
import { SessionRepository } from './session-repository.js';
|
|
22
|
-
import { loadSession } from '../utils/session-manager.js';
|
|
23
|
-
import { findPackageRoot } from '../utils/paths.js';
|
|
24
|
-
|
|
25
|
-
const SESSION_DIR = join(homedir(), '.sylphx', 'sessions');
|
|
26
|
-
const DB_DIR = join(homedir(), '.sylphx-flow');
|
|
27
|
-
const DB_PATH = join(DB_DIR, 'memory.db');
|
|
28
|
-
const MIGRATION_FLAG = join(DB_DIR, '.session-migrated');
|
|
29
|
-
|
|
30
|
-
export interface MigrationProgress {
|
|
31
|
-
total: number;
|
|
32
|
-
current: number;
|
|
33
|
-
status: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export type ProgressCallback = (progress: MigrationProgress) => void;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Check if JSON session files need migration to database
|
|
40
|
-
* Returns true if there are JSON files that need to be migrated
|
|
41
|
-
*
|
|
42
|
-
* Note: Schema migrations are handled automatically by Drizzle's migrate()
|
|
43
|
-
* We only need to check for JSON file migration here
|
|
44
|
-
*/
|
|
45
|
-
async function needsFileMigration(db: any): Promise<boolean> {
|
|
46
|
-
try {
|
|
47
|
-
// Check if migration flag exists
|
|
48
|
-
if (existsSync(MIGRATION_FLAG)) {
|
|
49
|
-
// Already migrated - cleanup any remaining JSON files
|
|
50
|
-
await cleanupOldJSONFiles();
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Check if JSON files exist that need migration
|
|
55
|
-
try {
|
|
56
|
-
const files = await readdir(SESSION_DIR);
|
|
57
|
-
const sessionFiles = files.filter((f) => f.endsWith('.json') && !f.startsWith('.'));
|
|
58
|
-
|
|
59
|
-
if (sessionFiles.length === 0) {
|
|
60
|
-
return false; // No files to migrate
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Check if any files not in database
|
|
64
|
-
const repository = new SessionRepository(db);
|
|
65
|
-
const dbCount = await repository.getSessionCount();
|
|
66
|
-
|
|
67
|
-
// If database is empty but files exist, need migration
|
|
68
|
-
return dbCount === 0 && sessionFiles.length > 0;
|
|
69
|
-
} catch {
|
|
70
|
-
return false; // Session directory doesn't exist
|
|
71
|
-
}
|
|
72
|
-
} catch {
|
|
73
|
-
return false;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Cleanup old JSON files after migration
|
|
79
|
-
* Called when migration flag exists (already migrated)
|
|
80
|
-
*/
|
|
81
|
-
async function cleanupOldJSONFiles(): Promise<void> {
|
|
82
|
-
try {
|
|
83
|
-
const files = await readdir(SESSION_DIR);
|
|
84
|
-
const sessionFiles = files.filter((f) => f.endsWith('.json') && !f.startsWith('.'));
|
|
85
|
-
|
|
86
|
-
if (sessionFiles.length === 0) {
|
|
87
|
-
return; // No files to cleanup
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
console.log(`Cleaning up ${sessionFiles.length} old JSON files...`);
|
|
91
|
-
let deletedCount = 0;
|
|
92
|
-
|
|
93
|
-
for (const file of sessionFiles) {
|
|
94
|
-
try {
|
|
95
|
-
await unlink(join(SESSION_DIR, file));
|
|
96
|
-
deletedCount++;
|
|
97
|
-
} catch (error) {
|
|
98
|
-
console.warn(`Failed to delete ${file}:`, error);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
console.log(`Cleanup complete: deleted ${deletedCount}/${sessionFiles.length} JSON files`);
|
|
103
|
-
} catch {
|
|
104
|
-
// Session directory doesn't exist or other error - ignore
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Run database schema migrations
|
|
110
|
-
*/
|
|
111
|
-
async function runSchemaMigrations(db: any): Promise<void> {
|
|
112
|
-
// Ensure database directory exists
|
|
113
|
-
await mkdir(DB_DIR, { recursive: true });
|
|
114
|
-
|
|
115
|
-
// Run Drizzle migrations using package root to find migrations
|
|
116
|
-
// This works with npm global install
|
|
117
|
-
const packageRoot = findPackageRoot('drizzle migrations');
|
|
118
|
-
const migrationsPath = join(packageRoot, 'drizzle');
|
|
119
|
-
|
|
120
|
-
if (!existsSync(migrationsPath)) {
|
|
121
|
-
throw new Error(`Drizzle migrations not found at ${migrationsPath}`);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
await migrate(db, { migrationsFolder: migrationsPath });
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Migrate session files to database
|
|
129
|
-
*/
|
|
130
|
-
async function migrateSessionFiles(
|
|
131
|
-
db: any,
|
|
132
|
-
onProgress?: ProgressCallback
|
|
133
|
-
): Promise<{ success: number; errors: number }> {
|
|
134
|
-
const repository = new SessionRepository(db);
|
|
135
|
-
|
|
136
|
-
// Get all session files
|
|
137
|
-
const files = await readdir(SESSION_DIR);
|
|
138
|
-
const sessionFiles = files.filter((f) => f.endsWith('.json') && !f.startsWith('.'));
|
|
139
|
-
|
|
140
|
-
let successCount = 0;
|
|
141
|
-
let errorCount = 0;
|
|
142
|
-
|
|
143
|
-
onProgress?.({
|
|
144
|
-
total: sessionFiles.length,
|
|
145
|
-
current: 0,
|
|
146
|
-
status: `Found ${sessionFiles.length} sessions to migrate`,
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
for (let i = 0; i < sessionFiles.length; i++) {
|
|
150
|
-
const file = sessionFiles[i];
|
|
151
|
-
const sessionId = file.replace('.json', '');
|
|
152
|
-
|
|
153
|
-
try {
|
|
154
|
-
onProgress?.({
|
|
155
|
-
total: sessionFiles.length,
|
|
156
|
-
current: i + 1,
|
|
157
|
-
status: `Migrating session ${i + 1}/${sessionFiles.length}`,
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
// Load session from file
|
|
161
|
-
const session = await loadSession(sessionId);
|
|
162
|
-
|
|
163
|
-
if (!session) {
|
|
164
|
-
errorCount++;
|
|
165
|
-
continue;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Check if already exists
|
|
169
|
-
const existing = await repository.getSessionById(session.id);
|
|
170
|
-
if (existing) {
|
|
171
|
-
continue; // Skip duplicates
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Create session in database with existing ID and metadata
|
|
175
|
-
await repository.createSessionFromData({
|
|
176
|
-
id: session.id,
|
|
177
|
-
provider: session.provider,
|
|
178
|
-
model: session.model,
|
|
179
|
-
title: session.title,
|
|
180
|
-
nextTodoId: session.nextTodoId || 1,
|
|
181
|
-
created: session.created,
|
|
182
|
-
updated: session.updated,
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
// Add all messages
|
|
186
|
-
// Normalize attachments from old JSON files (might have corrupt data like {})
|
|
187
|
-
for (const message of session.messages) {
|
|
188
|
-
// Normalize attachments: must be array or undefined
|
|
189
|
-
let normalizedAttachments: typeof message.attachments = undefined;
|
|
190
|
-
if (message.attachments && Array.isArray(message.attachments) && message.attachments.length > 0) {
|
|
191
|
-
const validAttachments = message.attachments.filter((a: any) =>
|
|
192
|
-
a && typeof a === 'object' && a.path && a.relativePath
|
|
193
|
-
);
|
|
194
|
-
if (validAttachments.length > 0) {
|
|
195
|
-
normalizedAttachments = validAttachments;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
await repository.addMessage(
|
|
200
|
-
session.id,
|
|
201
|
-
message.role,
|
|
202
|
-
message.content,
|
|
203
|
-
normalizedAttachments,
|
|
204
|
-
message.usage,
|
|
205
|
-
message.finishReason,
|
|
206
|
-
message.metadata,
|
|
207
|
-
message.todoSnapshot
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Update todos
|
|
212
|
-
if (session.todos && session.todos.length > 0) {
|
|
213
|
-
await repository.updateTodos(session.id, session.todos, session.nextTodoId);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Migration successful - delete the JSON file to free space and avoid confusion
|
|
217
|
-
const filePath = join(SESSION_DIR, file);
|
|
218
|
-
try {
|
|
219
|
-
await unlink(filePath);
|
|
220
|
-
} catch (unlinkError) {
|
|
221
|
-
console.warn(`Successfully migrated ${sessionId} but failed to delete JSON file:`, unlinkError);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
successCount++;
|
|
225
|
-
} catch (error) {
|
|
226
|
-
console.error(`Error migrating ${sessionId}:`, error);
|
|
227
|
-
errorCount++;
|
|
228
|
-
// Keep JSON file on error for debugging
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Create migration flag
|
|
233
|
-
await fsReadFile(MIGRATION_FLAG, 'utf8').catch(() =>
|
|
234
|
-
require('fs').writeFileSync(MIGRATION_FLAG, new Date().toISOString())
|
|
235
|
-
);
|
|
236
|
-
|
|
237
|
-
return { success: successCount, errors: errorCount };
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Auto-migrate on app startup
|
|
242
|
-
* Returns database instance ready to use
|
|
243
|
-
*
|
|
244
|
-
* Design: Always run schema migrations (Drizzle handles detection)
|
|
245
|
-
* 1. Run Drizzle migrate() - automatically applies only new migrations
|
|
246
|
-
* 2. Check and migrate JSON files if needed
|
|
247
|
-
*/
|
|
248
|
-
export async function autoMigrate(onProgress?: ProgressCallback): Promise<any> {
|
|
249
|
-
const DATABASE_URL = process.env.DATABASE_URL || `file:${DB_PATH}`;
|
|
250
|
-
|
|
251
|
-
// Initialize database with optimized settings for concurrency
|
|
252
|
-
const client = createClient({ url: DATABASE_URL });
|
|
253
|
-
const db = drizzle(client);
|
|
254
|
-
|
|
255
|
-
// Configure SQLite for better concurrency
|
|
256
|
-
// WAL mode allows concurrent reads while writing
|
|
257
|
-
// Busy timeout retries when database is locked (5 seconds)
|
|
258
|
-
await client.execute('PRAGMA journal_mode = WAL');
|
|
259
|
-
await client.execute('PRAGMA busy_timeout = 5000');
|
|
260
|
-
await client.execute('PRAGMA synchronous = NORMAL');
|
|
261
|
-
|
|
262
|
-
onProgress?.({
|
|
263
|
-
total: 2,
|
|
264
|
-
current: 0,
|
|
265
|
-
status: 'Running database migrations...',
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
// Always run schema migrations
|
|
269
|
-
// Drizzle's migrate() automatically detects and applies only new migrations
|
|
270
|
-
await runSchemaMigrations(db);
|
|
271
|
-
|
|
272
|
-
onProgress?.({
|
|
273
|
-
total: 2,
|
|
274
|
-
current: 1,
|
|
275
|
-
status: 'Database schema up to date',
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
// Check if JSON file migration needed
|
|
279
|
-
const needsFiles = await needsFileMigration(db);
|
|
280
|
-
|
|
281
|
-
if (needsFiles) {
|
|
282
|
-
try {
|
|
283
|
-
const files = await readdir(SESSION_DIR);
|
|
284
|
-
const sessionFiles = files.filter((f) => f.endsWith('.json') && !f.startsWith('.'));
|
|
285
|
-
|
|
286
|
-
if (sessionFiles.length > 0) {
|
|
287
|
-
onProgress?.({
|
|
288
|
-
total: 2 + sessionFiles.length,
|
|
289
|
-
current: 1,
|
|
290
|
-
status: `Migrating ${sessionFiles.length} sessions from files...`,
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
// Migrate files to database
|
|
294
|
-
const result = await migrateSessionFiles(db, (fileProgress) => {
|
|
295
|
-
onProgress?.({
|
|
296
|
-
total: 2 + fileProgress.total,
|
|
297
|
-
current: 1 + fileProgress.current,
|
|
298
|
-
status: fileProgress.status,
|
|
299
|
-
});
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
onProgress?.({
|
|
303
|
-
total: 2 + sessionFiles.length,
|
|
304
|
-
current: 2 + sessionFiles.length,
|
|
305
|
-
status: `Migration complete! ${result.success} sessions migrated`,
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
} catch {
|
|
309
|
-
// No session directory, skip file migration
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
return db;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
* Initialize database with auto-migration
|
|
318
|
-
* Call this on app startup
|
|
319
|
-
*/
|
|
320
|
-
export async function initializeDatabase(onProgress?: ProgressCallback): Promise<any> {
|
|
321
|
-
return autoMigrate(onProgress);
|
|
322
|
-
}
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Base database client - 基礎數據庫客戶端
|
|
3
|
-
* 提供共用的數據庫連接和管理功能
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import * as fs from 'node:fs';
|
|
7
|
-
import * as path from 'node:path';
|
|
8
|
-
import { createClient } from '@libsql/client';
|
|
9
|
-
import { drizzle } from 'drizzle-orm/libsql';
|
|
10
|
-
import { ConnectionError, DatabaseError } from '../utils/database-errors.js';
|
|
11
|
-
|
|
12
|
-
export abstract class BaseDatabaseClient<TSchema extends Record<string, unknown>> {
|
|
13
|
-
protected client: ReturnType<typeof createClient>;
|
|
14
|
-
public db: ReturnType<typeof drizzle<TSchema>>;
|
|
15
|
-
protected dbName: string;
|
|
16
|
-
|
|
17
|
-
constructor(dbName: string, schema: TSchema) {
|
|
18
|
-
this.dbName = dbName;
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
const dbDir = path.join(process.cwd(), '.sylphx-flow');
|
|
22
|
-
|
|
23
|
-
// Ensure directory exists with proper error handling
|
|
24
|
-
try {
|
|
25
|
-
if (!fs.existsSync(dbDir)) {
|
|
26
|
-
fs.mkdirSync(dbDir, { recursive: true });
|
|
27
|
-
}
|
|
28
|
-
} catch (dirError) {
|
|
29
|
-
throw new Error(
|
|
30
|
-
`Failed to create database directory: ${dbDir}. ` +
|
|
31
|
-
`Error: ${(dirError as Error).message}`
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const dbPath = path.join(dbDir, `${dbName}.db`);
|
|
36
|
-
|
|
37
|
-
// Use local path directly without file: URL scheme
|
|
38
|
-
// libSQL will automatically create the file if it doesn't exist
|
|
39
|
-
this.client = createClient({
|
|
40
|
-
url: dbPath,
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
this.db = drizzle(this.client, { schema });
|
|
44
|
-
} catch (error) {
|
|
45
|
-
const dbPath = path.join(process.cwd(), '.sylphx-flow', `${dbName}.db`);
|
|
46
|
-
|
|
47
|
-
throw new ConnectionError(
|
|
48
|
-
`Failed to initialize ${dbName} database connection`,
|
|
49
|
-
{
|
|
50
|
-
url: dbPath,
|
|
51
|
-
dbPath,
|
|
52
|
-
cwd: process.cwd(),
|
|
53
|
-
platform: process.platform,
|
|
54
|
-
},
|
|
55
|
-
error as Error
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Initialize database schema - 子類必須實現
|
|
62
|
-
*/
|
|
63
|
-
abstract initialize(): Promise<void>;
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Get migration status - 子類必須實現
|
|
67
|
-
*/
|
|
68
|
-
abstract getMigrationStatus(): Promise<{
|
|
69
|
-
isMigrated: boolean;
|
|
70
|
-
migrationCount: number;
|
|
71
|
-
}>;
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Perform database health check - 子類必須實現
|
|
75
|
-
*/
|
|
76
|
-
abstract healthCheck(): Promise<{
|
|
77
|
-
healthy: boolean;
|
|
78
|
-
error?: string;
|
|
79
|
-
details?: Record<string, unknown>;
|
|
80
|
-
}>;
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Close database connection
|
|
84
|
-
*/
|
|
85
|
-
async close(): Promise<void> {
|
|
86
|
-
// libSQL client doesn't have explicit close for file-based databases
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Get database path for debugging
|
|
91
|
-
*/
|
|
92
|
-
getDatabasePath(): string {
|
|
93
|
-
return path.join(process.cwd(), '.sylphx-flow', `${this.dbName}.db`);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Create tables directly with common pattern
|
|
98
|
-
*/
|
|
99
|
-
protected async createTable(definition: string): Promise<void> {
|
|
100
|
-
try {
|
|
101
|
-
await this.client.execute(definition);
|
|
102
|
-
} catch (error) {
|
|
103
|
-
throw new DatabaseError(
|
|
104
|
-
`Failed to create table for ${this.dbName}`,
|
|
105
|
-
`${this.dbName}.createTable`,
|
|
106
|
-
error as Error
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Create index with common pattern
|
|
113
|
-
*/
|
|
114
|
-
protected async createIndex(definition: string): Promise<void> {
|
|
115
|
-
try {
|
|
116
|
-
await this.client.execute(definition);
|
|
117
|
-
} catch (error) {
|
|
118
|
-
throw new DatabaseError(
|
|
119
|
-
`Failed to create index for ${this.dbName}`,
|
|
120
|
-
`${this.dbName}.createIndex`,
|
|
121
|
-
error as Error
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Check if table exists
|
|
128
|
-
*/
|
|
129
|
-
protected async tableExists(tableName: string): Promise<boolean> {
|
|
130
|
-
try {
|
|
131
|
-
const result = await this.client.execute(`
|
|
132
|
-
SELECT name FROM sqlite_master
|
|
133
|
-
WHERE type='table' AND name='${tableName}'
|
|
134
|
-
`);
|
|
135
|
-
return result.rows.length > 0;
|
|
136
|
-
} catch (error) {
|
|
137
|
-
throw new DatabaseError(
|
|
138
|
-
`Failed to check if table ${tableName} exists`,
|
|
139
|
-
`${this.dbName}.tableExists`,
|
|
140
|
-
error as Error
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
package/src/db/cache-db.ts
DELETED
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cache database client - 臨時索引數據庫
|
|
3
|
-
* 負責管理可以重新生成的緩存數據(代碼索引、搜索詞彙等)
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import * as path from 'node:path';
|
|
7
|
-
import type { drizzle } from 'drizzle-orm/libsql';
|
|
8
|
-
import { DatabaseError } from '../utils/database-errors.js';
|
|
9
|
-
import { BaseDatabaseClient } from './base-database-client.js';
|
|
10
|
-
import * as schema from './cache-schema.js';
|
|
11
|
-
|
|
12
|
-
export type CacheDatabase = ReturnType<typeof drizzle<typeof schema>>;
|
|
13
|
-
|
|
14
|
-
export class CacheDatabaseClient extends BaseDatabaseClient<typeof schema> {
|
|
15
|
-
constructor() {
|
|
16
|
-
super('cache', schema);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Initialize cache database schema
|
|
21
|
-
*/
|
|
22
|
-
async initialize(): Promise<void> {
|
|
23
|
-
try {
|
|
24
|
-
// Check if tables already exist
|
|
25
|
-
const migrationStatus = await this.getMigrationStatus();
|
|
26
|
-
|
|
27
|
-
if (migrationStatus.isMigrated) {
|
|
28
|
-
// Tables already exist, skip logging to reduce noise
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// For now, create tables directly since we don't have migration files yet
|
|
33
|
-
await this.createTables();
|
|
34
|
-
console.error('[INFO] Cache database tables created');
|
|
35
|
-
} catch (error) {
|
|
36
|
-
throw new DatabaseError(
|
|
37
|
-
'Failed to initialize cache database',
|
|
38
|
-
'cache.initialize',
|
|
39
|
-
error as Error
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Create tables directly (fallback)
|
|
46
|
-
*/
|
|
47
|
-
private async createTables(): Promise<void> {
|
|
48
|
-
// Create codebase_files table
|
|
49
|
-
await this.createTable(`
|
|
50
|
-
CREATE TABLE IF NOT EXISTS codebase_files_table (
|
|
51
|
-
path TEXT PRIMARY KEY,
|
|
52
|
-
mtime INTEGER NOT NULL,
|
|
53
|
-
hash TEXT NOT NULL,
|
|
54
|
-
content TEXT,
|
|
55
|
-
language TEXT,
|
|
56
|
-
size INTEGER,
|
|
57
|
-
indexed_at TEXT NOT NULL
|
|
58
|
-
)
|
|
59
|
-
`);
|
|
60
|
-
|
|
61
|
-
// Create codebase_metadata table
|
|
62
|
-
await this.createTable(`
|
|
63
|
-
CREATE TABLE IF NOT EXISTS codebase_metadata_table (
|
|
64
|
-
key TEXT PRIMARY KEY,
|
|
65
|
-
value TEXT NOT NULL
|
|
66
|
-
)
|
|
67
|
-
`);
|
|
68
|
-
|
|
69
|
-
// Create tfidf_documents table
|
|
70
|
-
await this.createTable(`
|
|
71
|
-
CREATE TABLE IF NOT EXISTS tfidf_documents_table (
|
|
72
|
-
file_path TEXT PRIMARY KEY,
|
|
73
|
-
magnitude REAL NOT NULL,
|
|
74
|
-
term_count INTEGER NOT NULL,
|
|
75
|
-
raw_terms TEXT NOT NULL,
|
|
76
|
-
FOREIGN KEY (file_path) REFERENCES codebase_files_table(path) ON DELETE CASCADE
|
|
77
|
-
)
|
|
78
|
-
`);
|
|
79
|
-
|
|
80
|
-
// Create tfidf_idf table
|
|
81
|
-
await this.createTable(`
|
|
82
|
-
CREATE TABLE IF NOT EXISTS tfidf_idf_table (
|
|
83
|
-
term TEXT PRIMARY KEY,
|
|
84
|
-
idf_value REAL NOT NULL
|
|
85
|
-
)
|
|
86
|
-
`);
|
|
87
|
-
|
|
88
|
-
// Create tfidf_terms table
|
|
89
|
-
await this.createTable(`
|
|
90
|
-
CREATE TABLE IF NOT EXISTS tfidf_terms_table (
|
|
91
|
-
file_path TEXT NOT NULL,
|
|
92
|
-
term TEXT NOT NULL,
|
|
93
|
-
frequency REAL NOT NULL,
|
|
94
|
-
PRIMARY KEY (file_path, term),
|
|
95
|
-
FOREIGN KEY (file_path) REFERENCES codebase_files_table(path) ON DELETE CASCADE
|
|
96
|
-
)
|
|
97
|
-
`);
|
|
98
|
-
|
|
99
|
-
// Create indexes
|
|
100
|
-
await this.createIndex(`
|
|
101
|
-
CREATE INDEX IF NOT EXISTS idx_codebase_files_mtime ON codebase_files_table (mtime)
|
|
102
|
-
`);
|
|
103
|
-
|
|
104
|
-
await this.createIndex(`
|
|
105
|
-
CREATE INDEX IF NOT EXISTS idx_codebase_files_hash ON codebase_files_table (hash)
|
|
106
|
-
`);
|
|
107
|
-
|
|
108
|
-
await this.createIndex(`
|
|
109
|
-
CREATE INDEX IF NOT EXISTS idx_tfidf_terms_term ON tfidf_terms_table (term)
|
|
110
|
-
`);
|
|
111
|
-
|
|
112
|
-
await this.createIndex(`
|
|
113
|
-
CREATE INDEX IF NOT EXISTS idx_tfidf_terms_file ON tfidf_terms_table (file_path)
|
|
114
|
-
`);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Get migration status
|
|
119
|
-
*/
|
|
120
|
-
async getMigrationStatus(): Promise<{
|
|
121
|
-
isMigrated: boolean;
|
|
122
|
-
migrationCount: number;
|
|
123
|
-
}> {
|
|
124
|
-
try {
|
|
125
|
-
const tables = ['codebase_files_table', 'tfidf_terms_table', 'tfidf_documents_table'];
|
|
126
|
-
let existingCount = 0;
|
|
127
|
-
|
|
128
|
-
for (const table of tables) {
|
|
129
|
-
if (await this.tableExists(table)) {
|
|
130
|
-
existingCount++;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return {
|
|
135
|
-
isMigrated: existingCount >= 2, // At least codebase_files and tfidf_terms
|
|
136
|
-
migrationCount: existingCount,
|
|
137
|
-
};
|
|
138
|
-
} catch (error) {
|
|
139
|
-
throw new DatabaseError(
|
|
140
|
-
'Failed to check cache database migration status',
|
|
141
|
-
'cache.getMigrationStatus',
|
|
142
|
-
error as Error
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Clear all cache data (useful for rebuilding)
|
|
149
|
-
*/
|
|
150
|
-
async clearCache(): Promise<void> {
|
|
151
|
-
try {
|
|
152
|
-
await this.client.execute('DELETE FROM tfidf_terms_table');
|
|
153
|
-
await this.client.execute('DELETE FROM tfidf_documents_table');
|
|
154
|
-
await this.client.execute('DELETE FROM tfidf_idf_table');
|
|
155
|
-
await this.client.execute('DELETE FROM codebase_files_table');
|
|
156
|
-
await this.client.execute('DELETE FROM codebase_metadata_table');
|
|
157
|
-
|
|
158
|
-
console.error('[INFO] Cache database cleared');
|
|
159
|
-
} catch (error) {
|
|
160
|
-
throw new DatabaseError('Failed to clear cache database', 'cache.clearCache', error as Error);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Perform database health check
|
|
166
|
-
*/
|
|
167
|
-
async healthCheck(): Promise<{
|
|
168
|
-
healthy: boolean;
|
|
169
|
-
error?: string;
|
|
170
|
-
details?: Record<string, unknown>;
|
|
171
|
-
}> {
|
|
172
|
-
try {
|
|
173
|
-
// Test basic connectivity
|
|
174
|
-
await this.client.execute('SELECT 1');
|
|
175
|
-
|
|
176
|
-
// Check if cache tables exist
|
|
177
|
-
const migrationStatus = await this.getMigrationStatus();
|
|
178
|
-
|
|
179
|
-
// Test basic read/write operation
|
|
180
|
-
const testResult = await this.client.execute(`
|
|
181
|
-
SELECT count(*) as count FROM codebase_files_table
|
|
182
|
-
`);
|
|
183
|
-
|
|
184
|
-
return {
|
|
185
|
-
healthy: true,
|
|
186
|
-
details: {
|
|
187
|
-
tablesExist: migrationStatus.isMigrated,
|
|
188
|
-
tableCount: migrationStatus.migrationCount,
|
|
189
|
-
cachedFiles: testResult.rows[0]?.count || 0,
|
|
190
|
-
},
|
|
191
|
-
};
|
|
192
|
-
} catch (error) {
|
|
193
|
-
return {
|
|
194
|
-
healthy: false,
|
|
195
|
-
error: (error as Error).message,
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Close database connection
|
|
202
|
-
*/
|
|
203
|
-
async close(): Promise<void> {
|
|
204
|
-
// libSQL client doesn't have explicit close for file-based databases
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Get database path for debugging
|
|
209
|
-
*/
|
|
210
|
-
getDatabasePath(): string {
|
|
211
|
-
const cacheDir = path.join(process.cwd(), '.sylphx-flow');
|
|
212
|
-
return path.join(cacheDir, 'cache.db');
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Export schema and types
|
|
217
|
-
export * from './cache-schema.js';
|
|
218
|
-
export { schema };
|