@sparkleideas/plugins 3.0.0-alpha.10

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.
Files changed (80) hide show
  1. package/README.md +401 -0
  2. package/__tests__/collection-manager.test.ts +332 -0
  3. package/__tests__/dependency-graph.test.ts +434 -0
  4. package/__tests__/enhanced-plugin-registry.test.ts +488 -0
  5. package/__tests__/plugin-registry.test.ts +368 -0
  6. package/__tests__/ruvector-bridge.test.ts +2429 -0
  7. package/__tests__/ruvector-integration.test.ts +1602 -0
  8. package/__tests__/ruvector-migrations.test.ts +1099 -0
  9. package/__tests__/ruvector-quantization.test.ts +846 -0
  10. package/__tests__/ruvector-streaming.test.ts +1088 -0
  11. package/__tests__/sdk.test.ts +325 -0
  12. package/__tests__/security.test.ts +348 -0
  13. package/__tests__/utils/ruvector-test-utils.ts +860 -0
  14. package/examples/plugin-creator/index.ts +636 -0
  15. package/examples/plugin-creator/plugin-creator.test.ts +312 -0
  16. package/examples/ruvector/README.md +288 -0
  17. package/examples/ruvector/attention-patterns.ts +394 -0
  18. package/examples/ruvector/basic-usage.ts +288 -0
  19. package/examples/ruvector/docker-compose.yml +75 -0
  20. package/examples/ruvector/gnn-analysis.ts +501 -0
  21. package/examples/ruvector/hyperbolic-hierarchies.ts +557 -0
  22. package/examples/ruvector/init-db.sql +119 -0
  23. package/examples/ruvector/quantization.ts +680 -0
  24. package/examples/ruvector/self-learning.ts +447 -0
  25. package/examples/ruvector/semantic-search.ts +576 -0
  26. package/examples/ruvector/streaming-large-data.ts +507 -0
  27. package/examples/ruvector/transactions.ts +594 -0
  28. package/examples/ruvector-plugins/hook-pattern-library.ts +486 -0
  29. package/examples/ruvector-plugins/index.ts +79 -0
  30. package/examples/ruvector-plugins/intent-router.ts +354 -0
  31. package/examples/ruvector-plugins/mcp-tool-optimizer.ts +424 -0
  32. package/examples/ruvector-plugins/reasoning-bank.ts +657 -0
  33. package/examples/ruvector-plugins/ruvector-plugins.test.ts +518 -0
  34. package/examples/ruvector-plugins/semantic-code-search.ts +498 -0
  35. package/examples/ruvector-plugins/shared/index.ts +20 -0
  36. package/examples/ruvector-plugins/shared/vector-utils.ts +257 -0
  37. package/examples/ruvector-plugins/sona-learning.ts +445 -0
  38. package/package.json +97 -0
  39. package/src/collections/collection-manager.ts +661 -0
  40. package/src/collections/index.ts +56 -0
  41. package/src/collections/official/index.ts +1040 -0
  42. package/src/core/base-plugin.ts +416 -0
  43. package/src/core/plugin-interface.ts +215 -0
  44. package/src/hooks/index.ts +685 -0
  45. package/src/index.ts +378 -0
  46. package/src/integrations/agentic-flow.ts +743 -0
  47. package/src/integrations/index.ts +88 -0
  48. package/src/integrations/ruvector/ARCHITECTURE.md +1245 -0
  49. package/src/integrations/ruvector/attention-advanced.ts +1040 -0
  50. package/src/integrations/ruvector/attention-executor.ts +782 -0
  51. package/src/integrations/ruvector/attention-mechanisms.ts +757 -0
  52. package/src/integrations/ruvector/attention.ts +1063 -0
  53. package/src/integrations/ruvector/gnn.ts +3050 -0
  54. package/src/integrations/ruvector/hyperbolic.ts +1948 -0
  55. package/src/integrations/ruvector/index.ts +394 -0
  56. package/src/integrations/ruvector/migrations/001_create_extension.sql +135 -0
  57. package/src/integrations/ruvector/migrations/002_create_vector_tables.sql +259 -0
  58. package/src/integrations/ruvector/migrations/003_create_indices.sql +328 -0
  59. package/src/integrations/ruvector/migrations/004_create_functions.sql +598 -0
  60. package/src/integrations/ruvector/migrations/005_create_attention_functions.sql +654 -0
  61. package/src/integrations/ruvector/migrations/006_create_gnn_functions.sql +728 -0
  62. package/src/integrations/ruvector/migrations/007_create_hyperbolic_functions.sql +762 -0
  63. package/src/integrations/ruvector/migrations/index.ts +35 -0
  64. package/src/integrations/ruvector/migrations/migrations.ts +647 -0
  65. package/src/integrations/ruvector/quantization.ts +2036 -0
  66. package/src/integrations/ruvector/ruvector-bridge.ts +2000 -0
  67. package/src/integrations/ruvector/self-learning.ts +2376 -0
  68. package/src/integrations/ruvector/streaming.ts +1737 -0
  69. package/src/integrations/ruvector/types.ts +1945 -0
  70. package/src/providers/index.ts +643 -0
  71. package/src/registry/dependency-graph.ts +568 -0
  72. package/src/registry/enhanced-plugin-registry.ts +994 -0
  73. package/src/registry/plugin-registry.ts +604 -0
  74. package/src/sdk/index.ts +563 -0
  75. package/src/security/index.ts +594 -0
  76. package/src/types/index.ts +446 -0
  77. package/src/workers/index.ts +700 -0
  78. package/tmp.json +0 -0
  79. package/tsconfig.json +25 -0
  80. package/vitest.config.ts +23 -0
@@ -0,0 +1,35 @@
1
+ /**
2
+ * RuVector PostgreSQL Bridge - Migrations Module
3
+ *
4
+ * Exports migration manager and utilities for database schema management.
5
+ *
6
+ * @module @sparkleideas/plugins/integrations/ruvector/migrations
7
+ */
8
+
9
+ export {
10
+ MigrationManager,
11
+ createMigrationManager,
12
+ runMigrationsFromCLI,
13
+ } from './migrations.js';
14
+
15
+ export type {
16
+ MigrationFile,
17
+ AppliedMigration,
18
+ MigrationResult,
19
+ MigrationManagerOptions,
20
+ DatabaseClient,
21
+ Logger,
22
+ } from './migrations.js';
23
+
24
+ // Migration file list (in order)
25
+ export const MIGRATION_FILES = [
26
+ '001_create_extension.sql',
27
+ '002_create_vector_tables.sql',
28
+ '003_create_indices.sql',
29
+ '004_create_functions.sql',
30
+ '005_create_attention_functions.sql',
31
+ '006_create_gnn_functions.sql',
32
+ '007_create_hyperbolic_functions.sql',
33
+ ] as const;
34
+
35
+ export type MigrationName = typeof MIGRATION_FILES[number];
@@ -0,0 +1,647 @@
1
+ /**
2
+ * RuVector PostgreSQL Bridge - Migration Manager
3
+ *
4
+ * Handles database migrations for the RuVector PostgreSQL integration.
5
+ * Supports up/down migrations, rollback, and migration tracking.
6
+ *
7
+ * @module @sparkleideas/plugins/integrations/ruvector/migrations
8
+ * @version 1.0.0
9
+ */
10
+
11
+ import { readFile, readdir } from 'fs/promises';
12
+ import { join, basename } from 'path';
13
+
14
+ // ============================================================================
15
+ // Types
16
+ // ============================================================================
17
+
18
+ /**
19
+ * Migration file metadata
20
+ */
21
+ export interface MigrationFile {
22
+ /** Migration number (e.g., 001, 002) */
23
+ readonly number: number;
24
+ /** Migration name (e.g., 'create_extension') */
25
+ readonly name: string;
26
+ /** Full filename */
27
+ readonly filename: string;
28
+ /** SQL content for up migration */
29
+ readonly upSql: string;
30
+ /** SQL content for down migration (extracted from comments) */
31
+ readonly downSql: string | null;
32
+ /** MD5 checksum of the file */
33
+ readonly checksum: string;
34
+ }
35
+
36
+ /**
37
+ * Applied migration record from database
38
+ */
39
+ export interface AppliedMigration {
40
+ readonly id: number;
41
+ readonly name: string;
42
+ readonly appliedAt: Date;
43
+ readonly checksum: string | null;
44
+ readonly executionTimeMs: number | null;
45
+ readonly rolledBackAt: Date | null;
46
+ }
47
+
48
+ /**
49
+ * Migration result
50
+ */
51
+ export interface MigrationResult {
52
+ readonly success: boolean;
53
+ readonly migration: string;
54
+ readonly direction: 'up' | 'down';
55
+ readonly executionTimeMs: number;
56
+ readonly error?: string;
57
+ }
58
+
59
+ /**
60
+ * Database client interface (minimal subset needed for migrations)
61
+ */
62
+ export interface DatabaseClient {
63
+ query<T = unknown>(sql: string, params?: unknown[]): Promise<{ rows: T[] }>;
64
+ connect?(): Promise<void>;
65
+ end?(): Promise<void>;
66
+ }
67
+
68
+ /**
69
+ * Migration manager options
70
+ */
71
+ export interface MigrationManagerOptions {
72
+ /** Directory containing migration files */
73
+ readonly migrationsDir?: string;
74
+ /** Schema name for migrations table */
75
+ readonly schema?: string;
76
+ /** Table name for tracking migrations */
77
+ readonly tableName?: string;
78
+ /** Enable verbose logging */
79
+ readonly verbose?: boolean;
80
+ /** Custom logger */
81
+ readonly logger?: Logger;
82
+ }
83
+
84
+ /**
85
+ * Logger interface
86
+ */
87
+ export interface Logger {
88
+ info(message: string, ...args: unknown[]): void;
89
+ warn(message: string, ...args: unknown[]): void;
90
+ error(message: string, ...args: unknown[]): void;
91
+ debug(message: string, ...args: unknown[]): void;
92
+ }
93
+
94
+ // ============================================================================
95
+ // Default Logger
96
+ // ============================================================================
97
+
98
+ const defaultLogger: Logger = {
99
+ info: (msg, ...args) => console.log(`[INFO] ${msg}`, ...args),
100
+ warn: (msg, ...args) => console.warn(`[WARN] ${msg}`, ...args),
101
+ error: (msg, ...args) => console.error(`[ERROR] ${msg}`, ...args),
102
+ debug: (msg, ...args) => console.debug(`[DEBUG] ${msg}`, ...args),
103
+ };
104
+
105
+ // ============================================================================
106
+ // Utility Functions
107
+ // ============================================================================
108
+
109
+ /**
110
+ * Calculate MD5 checksum of a string
111
+ */
112
+ function md5(content: string): string {
113
+ // Simple hash implementation for checksum
114
+ let hash = 0;
115
+ for (let i = 0; i < content.length; i++) {
116
+ const char = content.charCodeAt(i);
117
+ hash = ((hash << 5) - hash) + char;
118
+ hash = hash & hash; // Convert to 32bit integer
119
+ }
120
+ return Math.abs(hash).toString(16).padStart(8, '0');
121
+ }
122
+
123
+ /**
124
+ * Extract rollback SQL from migration file comments
125
+ */
126
+ function extractRollbackSql(content: string): string | null {
127
+ const rollbackMatch = content.match(
128
+ /-- ={10,}\s*\n-- Rollback Script\s*\n-- ={10,}\s*\n([\s\S]*?)$/
129
+ );
130
+
131
+ if (rollbackMatch) {
132
+ // Remove comment prefixes and extract SQL
133
+ const rollbackContent = rollbackMatch[1]
134
+ .split('\n')
135
+ .map(line => line.replace(/^--\s?/, ''))
136
+ .join('\n')
137
+ .trim();
138
+
139
+ return rollbackContent || null;
140
+ }
141
+
142
+ return null;
143
+ }
144
+
145
+ /**
146
+ * Parse migration filename
147
+ */
148
+ function parseMigrationFilename(filename: string): { number: number; name: string } | null {
149
+ const match = filename.match(/^(\d{3})_(.+)\.sql$/);
150
+ if (!match) return null;
151
+
152
+ return {
153
+ number: parseInt(match[1], 10),
154
+ name: match[2],
155
+ };
156
+ }
157
+
158
+ // ============================================================================
159
+ // Migration Manager Class
160
+ // ============================================================================
161
+
162
+ /**
163
+ * Manages database migrations for RuVector PostgreSQL Bridge
164
+ */
165
+ export class MigrationManager {
166
+ private readonly client: DatabaseClient;
167
+ private readonly migrationsDir: string;
168
+ private readonly schema: string;
169
+ private readonly tableName: string;
170
+ private readonly verbose: boolean;
171
+ private readonly logger: Logger;
172
+
173
+ constructor(client: DatabaseClient, options: MigrationManagerOptions = {}) {
174
+ this.client = client;
175
+ this.migrationsDir = options.migrationsDir ?? join(__dirname, '.');
176
+ this.schema = options.schema ?? 'claude_flow';
177
+ this.tableName = options.tableName ?? 'migrations';
178
+ this.verbose = options.verbose ?? false;
179
+ this.logger = options.logger ?? defaultLogger;
180
+ }
181
+
182
+ /**
183
+ * Initialize the migrations tracking table
184
+ */
185
+ async initialize(): Promise<void> {
186
+ this.log('Initializing migrations table...');
187
+
188
+ // Create schema if not exists
189
+ await this.client.query(`CREATE SCHEMA IF NOT EXISTS ${this.schema}`);
190
+
191
+ // Create migrations tracking table
192
+ await this.client.query(`
193
+ CREATE TABLE IF NOT EXISTS ${this.schema}.${this.tableName} (
194
+ id SERIAL PRIMARY KEY,
195
+ name TEXT NOT NULL UNIQUE,
196
+ applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
197
+ checksum TEXT,
198
+ execution_time_ms INTEGER,
199
+ rolled_back_at TIMESTAMPTZ
200
+ )
201
+ `);
202
+
203
+ this.log('Migrations table initialized');
204
+ }
205
+
206
+ /**
207
+ * Load all migration files from the migrations directory
208
+ */
209
+ async loadMigrations(): Promise<MigrationFile[]> {
210
+ this.log(`Loading migrations from ${this.migrationsDir}`);
211
+
212
+ const files = await readdir(this.migrationsDir);
213
+ const sqlFiles = files.filter(f => f.endsWith('.sql')).sort();
214
+
215
+ const migrations: MigrationFile[] = [];
216
+
217
+ for (const filename of sqlFiles) {
218
+ const parsed = parseMigrationFilename(filename);
219
+ if (!parsed) {
220
+ this.logger.warn(`Skipping invalid migration filename: ${filename}`);
221
+ continue;
222
+ }
223
+
224
+ const filepath = join(this.migrationsDir, filename);
225
+ const content = await readFile(filepath, 'utf-8');
226
+
227
+ migrations.push({
228
+ number: parsed.number,
229
+ name: `${parsed.number.toString().padStart(3, '0')}_${parsed.name}`,
230
+ filename,
231
+ upSql: content,
232
+ downSql: extractRollbackSql(content),
233
+ checksum: md5(content),
234
+ });
235
+ }
236
+
237
+ this.log(`Found ${migrations.length} migration files`);
238
+ return migrations;
239
+ }
240
+
241
+ /**
242
+ * Get list of applied migrations from database
243
+ */
244
+ async getAppliedMigrations(): Promise<AppliedMigration[]> {
245
+ try {
246
+ const result = await this.client.query<{
247
+ id: number;
248
+ name: string;
249
+ applied_at: Date;
250
+ checksum: string | null;
251
+ execution_time_ms: number | null;
252
+ rolled_back_at: Date | null;
253
+ }>(`
254
+ SELECT id, name, applied_at, checksum, execution_time_ms, rolled_back_at
255
+ FROM ${this.schema}.${this.tableName}
256
+ WHERE rolled_back_at IS NULL
257
+ ORDER BY id
258
+ `);
259
+
260
+ return result.rows.map(row => ({
261
+ id: row.id,
262
+ name: row.name,
263
+ appliedAt: row.applied_at,
264
+ checksum: row.checksum,
265
+ executionTimeMs: row.execution_time_ms,
266
+ rolledBackAt: row.rolled_back_at,
267
+ }));
268
+ } catch (error) {
269
+ // Table might not exist yet
270
+ return [];
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Get pending migrations (not yet applied)
276
+ */
277
+ async getPendingMigrations(): Promise<MigrationFile[]> {
278
+ const allMigrations = await this.loadMigrations();
279
+ const appliedMigrations = await this.getAppliedMigrations();
280
+ const appliedNames = new Set(appliedMigrations.map(m => m.name));
281
+
282
+ return allMigrations.filter(m => !appliedNames.has(m.name));
283
+ }
284
+
285
+ /**
286
+ * Run a single migration (up)
287
+ */
288
+ async runMigration(migration: MigrationFile): Promise<MigrationResult> {
289
+ const startTime = Date.now();
290
+ this.log(`Running migration: ${migration.name}`);
291
+
292
+ try {
293
+ // Execute the migration SQL
294
+ await this.client.query(migration.upSql);
295
+
296
+ const executionTimeMs = Date.now() - startTime;
297
+
298
+ // Record the migration (ignore if already recorded by the migration itself)
299
+ await this.client.query(`
300
+ INSERT INTO ${this.schema}.${this.tableName} (name, checksum, execution_time_ms)
301
+ VALUES ($1, $2, $3)
302
+ ON CONFLICT (name) DO UPDATE SET
303
+ checksum = EXCLUDED.checksum,
304
+ execution_time_ms = EXCLUDED.execution_time_ms
305
+ `, [migration.name, migration.checksum, executionTimeMs]);
306
+
307
+ this.log(`Migration ${migration.name} completed in ${executionTimeMs}ms`);
308
+
309
+ return {
310
+ success: true,
311
+ migration: migration.name,
312
+ direction: 'up',
313
+ executionTimeMs,
314
+ };
315
+ } catch (error) {
316
+ const executionTimeMs = Date.now() - startTime;
317
+ const errorMessage = error instanceof Error ? error.message : String(error);
318
+
319
+ this.logger.error(`Migration ${migration.name} failed: ${errorMessage}`);
320
+
321
+ return {
322
+ success: false,
323
+ migration: migration.name,
324
+ direction: 'up',
325
+ executionTimeMs,
326
+ error: errorMessage,
327
+ };
328
+ }
329
+ }
330
+
331
+ /**
332
+ * Rollback a single migration (down)
333
+ */
334
+ async rollbackMigration(migration: MigrationFile): Promise<MigrationResult> {
335
+ const startTime = Date.now();
336
+ this.log(`Rolling back migration: ${migration.name}`);
337
+
338
+ if (!migration.downSql) {
339
+ return {
340
+ success: false,
341
+ migration: migration.name,
342
+ direction: 'down',
343
+ executionTimeMs: 0,
344
+ error: 'No rollback SQL available for this migration',
345
+ };
346
+ }
347
+
348
+ try {
349
+ // Execute the rollback SQL
350
+ await this.client.query(migration.downSql);
351
+
352
+ const executionTimeMs = Date.now() - startTime;
353
+
354
+ // Mark the migration as rolled back
355
+ await this.client.query(`
356
+ UPDATE ${this.schema}.${this.tableName}
357
+ SET rolled_back_at = NOW()
358
+ WHERE name = $1
359
+ `, [migration.name]);
360
+
361
+ this.log(`Rollback of ${migration.name} completed in ${executionTimeMs}ms`);
362
+
363
+ return {
364
+ success: true,
365
+ migration: migration.name,
366
+ direction: 'down',
367
+ executionTimeMs,
368
+ };
369
+ } catch (error) {
370
+ const executionTimeMs = Date.now() - startTime;
371
+ const errorMessage = error instanceof Error ? error.message : String(error);
372
+
373
+ this.logger.error(`Rollback of ${migration.name} failed: ${errorMessage}`);
374
+
375
+ return {
376
+ success: false,
377
+ migration: migration.name,
378
+ direction: 'down',
379
+ executionTimeMs,
380
+ error: errorMessage,
381
+ };
382
+ }
383
+ }
384
+
385
+ /**
386
+ * Run all pending migrations
387
+ */
388
+ async up(): Promise<MigrationResult[]> {
389
+ await this.initialize();
390
+
391
+ const pending = await this.getPendingMigrations();
392
+
393
+ if (pending.length === 0) {
394
+ this.log('No pending migrations');
395
+ return [];
396
+ }
397
+
398
+ this.log(`Running ${pending.length} pending migrations`);
399
+
400
+ const results: MigrationResult[] = [];
401
+
402
+ for (const migration of pending) {
403
+ const result = await this.runMigration(migration);
404
+ results.push(result);
405
+
406
+ if (!result.success) {
407
+ this.logger.error('Migration failed, stopping');
408
+ break;
409
+ }
410
+ }
411
+
412
+ return results;
413
+ }
414
+
415
+ /**
416
+ * Rollback the last N migrations
417
+ */
418
+ async down(count: number = 1): Promise<MigrationResult[]> {
419
+ const allMigrations = await this.loadMigrations();
420
+ const applied = await this.getAppliedMigrations();
421
+
422
+ if (applied.length === 0) {
423
+ this.log('No migrations to rollback');
424
+ return [];
425
+ }
426
+
427
+ // Get the last N applied migrations in reverse order
428
+ const toRollback = applied
429
+ .slice(-count)
430
+ .reverse();
431
+
432
+ this.log(`Rolling back ${toRollback.length} migrations`);
433
+
434
+ const results: MigrationResult[] = [];
435
+
436
+ for (const appliedMigration of toRollback) {
437
+ const migration = allMigrations.find(m => m.name === appliedMigration.name);
438
+
439
+ if (!migration) {
440
+ results.push({
441
+ success: false,
442
+ migration: appliedMigration.name,
443
+ direction: 'down',
444
+ executionTimeMs: 0,
445
+ error: 'Migration file not found',
446
+ });
447
+ continue;
448
+ }
449
+
450
+ const result = await this.rollbackMigration(migration);
451
+ results.push(result);
452
+
453
+ if (!result.success) {
454
+ this.logger.error('Rollback failed, stopping');
455
+ break;
456
+ }
457
+ }
458
+
459
+ return results;
460
+ }
461
+
462
+ /**
463
+ * Rollback all migrations
464
+ */
465
+ async reset(): Promise<MigrationResult[]> {
466
+ const applied = await this.getAppliedMigrations();
467
+ return this.down(applied.length);
468
+ }
469
+
470
+ /**
471
+ * Get migration status
472
+ */
473
+ async status(): Promise<{
474
+ applied: AppliedMigration[];
475
+ pending: MigrationFile[];
476
+ total: number;
477
+ }> {
478
+ const allMigrations = await this.loadMigrations();
479
+ const applied = await this.getAppliedMigrations();
480
+ const pending = await this.getPendingMigrations();
481
+
482
+ return {
483
+ applied,
484
+ pending,
485
+ total: allMigrations.length,
486
+ };
487
+ }
488
+
489
+ /**
490
+ * Verify migration checksums
491
+ */
492
+ async verify(): Promise<{
493
+ valid: boolean;
494
+ mismatches: Array<{ name: string; expected: string; actual: string }>;
495
+ }> {
496
+ const allMigrations = await this.loadMigrations();
497
+ const applied = await this.getAppliedMigrations();
498
+
499
+ const mismatches: Array<{ name: string; expected: string; actual: string }> = [];
500
+
501
+ for (const appliedMigration of applied) {
502
+ const migration = allMigrations.find(m => m.name === appliedMigration.name);
503
+
504
+ if (migration && appliedMigration.checksum && migration.checksum !== appliedMigration.checksum) {
505
+ mismatches.push({
506
+ name: appliedMigration.name,
507
+ expected: appliedMigration.checksum,
508
+ actual: migration.checksum,
509
+ });
510
+ }
511
+ }
512
+
513
+ return {
514
+ valid: mismatches.length === 0,
515
+ mismatches,
516
+ };
517
+ }
518
+
519
+ /**
520
+ * Force re-run a specific migration
521
+ */
522
+ async rerun(migrationName: string): Promise<MigrationResult> {
523
+ const allMigrations = await this.loadMigrations();
524
+ const migration = allMigrations.find(m => m.name === migrationName || m.filename === migrationName);
525
+
526
+ if (!migration) {
527
+ return {
528
+ success: false,
529
+ migration: migrationName,
530
+ direction: 'up',
531
+ executionTimeMs: 0,
532
+ error: 'Migration not found',
533
+ };
534
+ }
535
+
536
+ // First rollback if applied
537
+ const applied = await this.getAppliedMigrations();
538
+ const isApplied = applied.some(a => a.name === migration.name);
539
+
540
+ if (isApplied) {
541
+ const rollbackResult = await this.rollbackMigration(migration);
542
+ if (!rollbackResult.success) {
543
+ return rollbackResult;
544
+ }
545
+ }
546
+
547
+ // Then run again
548
+ return this.runMigration(migration);
549
+ }
550
+
551
+ private log(message: string): void {
552
+ if (this.verbose) {
553
+ this.logger.info(message);
554
+ }
555
+ }
556
+ }
557
+
558
+ // ============================================================================
559
+ // CLI Helper Functions
560
+ // ============================================================================
561
+
562
+ /**
563
+ * Run migrations from command line
564
+ */
565
+ export async function runMigrationsFromCLI(
566
+ client: DatabaseClient,
567
+ command: 'up' | 'down' | 'reset' | 'status' | 'verify',
568
+ options: MigrationManagerOptions & { count?: number } = {}
569
+ ): Promise<void> {
570
+ const manager = new MigrationManager(client, { ...options, verbose: true });
571
+
572
+ switch (command) {
573
+ case 'up': {
574
+ const results = await manager.up();
575
+ const successful = results.filter(r => r.success).length;
576
+ const failed = results.filter(r => !r.success).length;
577
+ console.log(`\nMigrations complete: ${successful} successful, ${failed} failed`);
578
+ break;
579
+ }
580
+
581
+ case 'down': {
582
+ const results = await manager.down(options.count ?? 1);
583
+ const successful = results.filter(r => r.success).length;
584
+ const failed = results.filter(r => !r.success).length;
585
+ console.log(`\nRollbacks complete: ${successful} successful, ${failed} failed`);
586
+ break;
587
+ }
588
+
589
+ case 'reset': {
590
+ const results = await manager.reset();
591
+ console.log(`\nReset complete: ${results.length} migrations rolled back`);
592
+ break;
593
+ }
594
+
595
+ case 'status': {
596
+ const status = await manager.status();
597
+ console.log('\nMigration Status:');
598
+ console.log(` Total: ${status.total}`);
599
+ console.log(` Applied: ${status.applied.length}`);
600
+ console.log(` Pending: ${status.pending.length}`);
601
+
602
+ if (status.applied.length > 0) {
603
+ console.log('\nApplied Migrations:');
604
+ for (const m of status.applied) {
605
+ console.log(` - ${m.name} (${m.appliedAt.toISOString()})`);
606
+ }
607
+ }
608
+
609
+ if (status.pending.length > 0) {
610
+ console.log('\nPending Migrations:');
611
+ for (const m of status.pending) {
612
+ console.log(` - ${m.name}`);
613
+ }
614
+ }
615
+ break;
616
+ }
617
+
618
+ case 'verify': {
619
+ const verification = await manager.verify();
620
+ if (verification.valid) {
621
+ console.log('\nAll migration checksums are valid');
622
+ } else {
623
+ console.log('\nChecksum mismatches found:');
624
+ for (const m of verification.mismatches) {
625
+ console.log(` - ${m.name}: expected ${m.expected}, got ${m.actual}`);
626
+ }
627
+ }
628
+ break;
629
+ }
630
+ }
631
+ }
632
+
633
+ // ============================================================================
634
+ // Export Default Instance Factory
635
+ // ============================================================================
636
+
637
+ /**
638
+ * Create a new MigrationManager instance
639
+ */
640
+ export function createMigrationManager(
641
+ client: DatabaseClient,
642
+ options?: MigrationManagerOptions
643
+ ): MigrationManager {
644
+ return new MigrationManager(client, options);
645
+ }
646
+
647
+ export default MigrationManager;