@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.
- package/README.md +401 -0
- package/__tests__/collection-manager.test.ts +332 -0
- package/__tests__/dependency-graph.test.ts +434 -0
- package/__tests__/enhanced-plugin-registry.test.ts +488 -0
- package/__tests__/plugin-registry.test.ts +368 -0
- package/__tests__/ruvector-bridge.test.ts +2429 -0
- package/__tests__/ruvector-integration.test.ts +1602 -0
- package/__tests__/ruvector-migrations.test.ts +1099 -0
- package/__tests__/ruvector-quantization.test.ts +846 -0
- package/__tests__/ruvector-streaming.test.ts +1088 -0
- package/__tests__/sdk.test.ts +325 -0
- package/__tests__/security.test.ts +348 -0
- package/__tests__/utils/ruvector-test-utils.ts +860 -0
- package/examples/plugin-creator/index.ts +636 -0
- package/examples/plugin-creator/plugin-creator.test.ts +312 -0
- package/examples/ruvector/README.md +288 -0
- package/examples/ruvector/attention-patterns.ts +394 -0
- package/examples/ruvector/basic-usage.ts +288 -0
- package/examples/ruvector/docker-compose.yml +75 -0
- package/examples/ruvector/gnn-analysis.ts +501 -0
- package/examples/ruvector/hyperbolic-hierarchies.ts +557 -0
- package/examples/ruvector/init-db.sql +119 -0
- package/examples/ruvector/quantization.ts +680 -0
- package/examples/ruvector/self-learning.ts +447 -0
- package/examples/ruvector/semantic-search.ts +576 -0
- package/examples/ruvector/streaming-large-data.ts +507 -0
- package/examples/ruvector/transactions.ts +594 -0
- package/examples/ruvector-plugins/hook-pattern-library.ts +486 -0
- package/examples/ruvector-plugins/index.ts +79 -0
- package/examples/ruvector-plugins/intent-router.ts +354 -0
- package/examples/ruvector-plugins/mcp-tool-optimizer.ts +424 -0
- package/examples/ruvector-plugins/reasoning-bank.ts +657 -0
- package/examples/ruvector-plugins/ruvector-plugins.test.ts +518 -0
- package/examples/ruvector-plugins/semantic-code-search.ts +498 -0
- package/examples/ruvector-plugins/shared/index.ts +20 -0
- package/examples/ruvector-plugins/shared/vector-utils.ts +257 -0
- package/examples/ruvector-plugins/sona-learning.ts +445 -0
- package/package.json +97 -0
- package/src/collections/collection-manager.ts +661 -0
- package/src/collections/index.ts +56 -0
- package/src/collections/official/index.ts +1040 -0
- package/src/core/base-plugin.ts +416 -0
- package/src/core/plugin-interface.ts +215 -0
- package/src/hooks/index.ts +685 -0
- package/src/index.ts +378 -0
- package/src/integrations/agentic-flow.ts +743 -0
- package/src/integrations/index.ts +88 -0
- package/src/integrations/ruvector/ARCHITECTURE.md +1245 -0
- package/src/integrations/ruvector/attention-advanced.ts +1040 -0
- package/src/integrations/ruvector/attention-executor.ts +782 -0
- package/src/integrations/ruvector/attention-mechanisms.ts +757 -0
- package/src/integrations/ruvector/attention.ts +1063 -0
- package/src/integrations/ruvector/gnn.ts +3050 -0
- package/src/integrations/ruvector/hyperbolic.ts +1948 -0
- package/src/integrations/ruvector/index.ts +394 -0
- package/src/integrations/ruvector/migrations/001_create_extension.sql +135 -0
- package/src/integrations/ruvector/migrations/002_create_vector_tables.sql +259 -0
- package/src/integrations/ruvector/migrations/003_create_indices.sql +328 -0
- package/src/integrations/ruvector/migrations/004_create_functions.sql +598 -0
- package/src/integrations/ruvector/migrations/005_create_attention_functions.sql +654 -0
- package/src/integrations/ruvector/migrations/006_create_gnn_functions.sql +728 -0
- package/src/integrations/ruvector/migrations/007_create_hyperbolic_functions.sql +762 -0
- package/src/integrations/ruvector/migrations/index.ts +35 -0
- package/src/integrations/ruvector/migrations/migrations.ts +647 -0
- package/src/integrations/ruvector/quantization.ts +2036 -0
- package/src/integrations/ruvector/ruvector-bridge.ts +2000 -0
- package/src/integrations/ruvector/self-learning.ts +2376 -0
- package/src/integrations/ruvector/streaming.ts +1737 -0
- package/src/integrations/ruvector/types.ts +1945 -0
- package/src/providers/index.ts +643 -0
- package/src/registry/dependency-graph.ts +568 -0
- package/src/registry/enhanced-plugin-registry.ts +994 -0
- package/src/registry/plugin-registry.ts +604 -0
- package/src/sdk/index.ts +563 -0
- package/src/security/index.ts +594 -0
- package/src/types/index.ts +446 -0
- package/src/workers/index.ts +700 -0
- package/tmp.json +0 -0
- package/tsconfig.json +25 -0
- 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;
|