@javalabs/prisma-client 1.0.4 → 1.0.6

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 (58) hide show
  1. package/dist/scripts/data-migration/batch-migrator.d.ts +14 -19
  2. package/dist/scripts/data-migration/batch-migrator.js +98 -297
  3. package/dist/scripts/data-migration/batch-migrator.js.map +1 -1
  4. package/dist/scripts/data-migration/data-transformer.d.ts +16 -7
  5. package/dist/scripts/data-migration/data-transformer.js +169 -133
  6. package/dist/scripts/data-migration/data-transformer.js.map +1 -1
  7. package/dist/scripts/data-migration/db-connector.d.ts +6 -1
  8. package/dist/scripts/data-migration/db-connector.js +44 -8
  9. package/dist/scripts/data-migration/db-connector.js.map +1 -1
  10. package/dist/scripts/data-migration/dependency-resolver.d.ts +10 -10
  11. package/dist/scripts/data-migration/dependency-resolver.js +92 -211
  12. package/dist/scripts/data-migration/dependency-resolver.js.map +1 -1
  13. package/dist/scripts/data-migration/foreign-key-manager.d.ts +6 -5
  14. package/dist/scripts/data-migration/foreign-key-manager.js +108 -18
  15. package/dist/scripts/data-migration/foreign-key-manager.js.map +1 -1
  16. package/dist/scripts/data-migration/migration-config.json +63 -0
  17. package/dist/scripts/data-migration/migration-tool.d.ts +25 -6
  18. package/dist/scripts/data-migration/migration-tool.js +78 -38
  19. package/dist/scripts/data-migration/migration-tool.js.map +1 -1
  20. package/dist/scripts/data-migration/multi-source-migrator.d.ts +17 -0
  21. package/dist/scripts/data-migration/multi-source-migrator.js +130 -0
  22. package/dist/scripts/data-migration/multi-source-migrator.js.map +1 -0
  23. package/dist/scripts/data-migration/schema-utils.d.ts +3 -3
  24. package/dist/scripts/data-migration/schema-utils.js +62 -19
  25. package/dist/scripts/data-migration/schema-utils.js.map +1 -1
  26. package/dist/scripts/data-migration/tenant-migrator.js +9 -2
  27. package/dist/scripts/data-migration/tenant-migrator.js.map +1 -1
  28. package/dist/scripts/data-migration/typecast-manager.d.ts +7 -3
  29. package/dist/scripts/data-migration/typecast-manager.js +169 -25
  30. package/dist/scripts/data-migration/typecast-manager.js.map +1 -1
  31. package/dist/scripts/data-migration/types.d.ts +68 -2
  32. package/dist/scripts/fix-table-indexes.d.ts +26 -0
  33. package/dist/scripts/fix-table-indexes.js +460 -0
  34. package/dist/scripts/fix-table-indexes.js.map +1 -0
  35. package/dist/scripts/multi-db-migration.d.ts +1 -0
  36. package/dist/scripts/multi-db-migration.js +55 -0
  37. package/dist/scripts/multi-db-migration.js.map +1 -0
  38. package/dist/scripts/run-migration.js +41 -75
  39. package/dist/scripts/run-migration.js.map +1 -1
  40. package/dist/tsconfig.tsbuildinfo +1 -1
  41. package/migration-config.json +40 -72
  42. package/{migration-config-public.json → migration-config.json.bk} +14 -14
  43. package/package.json +6 -3
  44. package/src/scripts/data-migration/batch-migrator.ts +192 -513
  45. package/src/scripts/data-migration/data-transformer.ts +252 -203
  46. package/src/scripts/data-migration/db-connector.ts +66 -13
  47. package/src/scripts/data-migration/dependency-resolver.ts +121 -266
  48. package/src/scripts/data-migration/foreign-key-manager.ts +214 -32
  49. package/src/scripts/data-migration/migration-config.json +63 -0
  50. package/src/scripts/data-migration/migration-tool.ts +377 -225
  51. package/src/scripts/data-migration/schema-utils.ts +94 -32
  52. package/src/scripts/data-migration/tenant-migrator.ts +12 -5
  53. package/src/scripts/data-migration/typecast-manager.ts +186 -31
  54. package/src/scripts/data-migration/types.ts +78 -5
  55. package/src/scripts/dumps/source_dump_20250428_145606.sql +323 -0
  56. package/src/scripts/fix-table-indexes.ts +602 -0
  57. package/src/scripts/post-migration-validator.ts +206 -107
  58. package/src/scripts/run-migration.ts +87 -101
@@ -1,89 +1,136 @@
1
1
  import { Logger } from "@nestjs/common";
2
2
  import { ColumnSchema, EnumCastValue } from "./types";
3
3
  import { SchemaUtils } from "./schema-utils";
4
+ import {
5
+ DatabaseConnections,
6
+ MigrationConfig,
7
+ MigrationOptions,
8
+ } from "./types";
9
+ import { ForeignKeyManager } from "./foreign-key-manager";
10
+ import { TypecastManager } from "./typecast-manager";
4
11
 
5
12
  export class DataTransformer {
6
13
  private readonly logger = new Logger("DataTransformer");
7
- private enumValuesCache: Map<string, any> = new Map();
8
- private columnValuesCache: Map<string, Map<string, any>> = new Map();
9
-
10
- constructor(public readonly schemaUtils: SchemaUtils) {}
14
+ private readonly schemaUtils: SchemaUtils;
15
+ private readonly sourceConnection: any;
16
+ private connections: DatabaseConnections;
17
+ private migrationConfig: MigrationConfig;
18
+ private options: MigrationOptions;
19
+ private foreignKeyManager: ForeignKeyManager;
20
+ private readonly typecastManager: TypecastManager;
21
+
22
+ constructor(
23
+ schemaUtils: SchemaUtils,
24
+ sourceConnection: any,
25
+ connections: DatabaseConnections
26
+ ) {
27
+ this.schemaUtils = schemaUtils;
28
+ this.sourceConnection = sourceConnection;
29
+ this.connections = connections;
30
+ this.typecastManager = new TypecastManager();
31
+ }
11
32
 
12
33
  async validateEnumValue(
13
- schemaName: string,
14
- enumType: string,
15
- value: string
34
+ tableName: string,
35
+ columnName: string,
36
+ value: string | null,
37
+ isNullable: boolean = true
16
38
  ): Promise<string | null> {
17
39
  try {
18
- const cacheKey = `${schemaName}.${enumType}`;
19
- if (!this.enumValuesCache.has(cacheKey)) {
20
- const query = `
21
- SELECT e.enumlabel
22
- FROM pg_type t
23
- JOIN pg_enum e ON t.oid = e.enumtypid
24
- JOIN pg_namespace n ON n.oid = t.typnamespace
25
- WHERE t.typname = $1 AND n.nspname = $2
26
- `;
27
-
28
- const result = await this.schemaUtils.queryTargetDb(query, [
29
- enumType,
30
- schemaName,
31
- ]);
32
- const uniqueValues: Set<string> = new Set(
33
- result.rows.map((row): string => String(row.enumlabel))
34
- );
40
+ if (value === null) {
41
+ return isNullable
42
+ ? null
43
+ : await this.getDefaultEnumValue(tableName, columnName);
44
+ }
35
45
 
36
- // También almacenar un mapa de valores normalizados a valores originales
37
- const normalizedMap = new Map<string, string>();
38
- result.rows.forEach((row) => {
39
- const original = String(row.enumlabel);
40
- const normalized = original.toLowerCase().trim();
41
- normalizedMap.set(normalized, original);
42
- });
46
+ const enumTypeName = await this.getEnumTypeName(tableName, columnName);
47
+ const normalizedValue = value.toString().trim().toLowerCase();
43
48
 
44
- this.enumValuesCache.set(cacheKey, uniqueValues);
45
- this.enumValuesCache.set(`${cacheKey}_normalized`, normalizedMap);
49
+ // Obtener valores válidos del enum desde la base de datos
50
+ const query = `SELECT unnest(enum_range(NULL::${enumTypeName})) as enum_value`;
51
+ const validValues = await this.connections.sourcePool.query(query);
46
52
 
47
- this.logger.log(
48
- `Valid values for ${enumType}: ${[...uniqueValues].join(", ")}`
53
+ if (!validValues || !validValues.rows) {
54
+ this.logger.warn(
55
+ `No se pudieron obtener valores válidos para el enum ${enumTypeName}`
49
56
  );
57
+ return await this.getDefaultEnumValue(enumTypeName);
58
+ }
59
+
60
+ const enumValues = validValues.rows.map((row: any) =>
61
+ row.enum_value.toLowerCase()
62
+ );
63
+
64
+ // Verificar coincidencia exacta
65
+ if (enumValues.includes(normalizedValue)) {
66
+ return value.toString().trim();
50
67
  }
51
68
 
52
- const validValues = this.enumValuesCache.get(cacheKey);
53
- const normalizedMap = this.enumValuesCache.get(
54
- `${cacheKey}_normalized`
55
- ) as unknown as Map<string, string>;
69
+ // Verificar coincidencia parcial
70
+ const partialMatch = enumValues.find(
71
+ (enumValue) =>
72
+ enumValue.includes(normalizedValue) ||
73
+ normalizedValue.includes(enumValue)
74
+ );
56
75
 
57
- if (!validValues || !normalizedMap) {
58
- throw new Error(`No valid values found for enum ${enumType}`);
76
+ if (partialMatch) {
77
+ this.logger.debug(
78
+ `Valor enum corregido: ${value} -> ${partialMatch} para ${tableName}.${columnName}`
79
+ );
80
+ return partialMatch;
59
81
  }
60
82
 
61
- // Verificar coincidencia exacta primero
62
- if (validValues.has(value)) {
63
- return value;
83
+ // Si no se encuentra coincidencia, usar valor por defecto
84
+ const defaultValue = await this.getDefaultEnumValue(enumTypeName);
85
+ if (defaultValue) {
86
+ this.logger.warn(
87
+ `Usando valor por defecto ${defaultValue} para ${tableName}.${columnName}`
88
+ );
89
+ return defaultValue;
64
90
  }
65
91
 
66
- // Luego intentar con valor normalizado
67
- const normalizedValue = value.toLowerCase().trim();
68
- if (normalizedMap.has(normalizedValue)) {
69
- // Devolver el valor original correcto, no el normalizado
70
- return normalizedMap.get(normalizedValue);
92
+ if (!isNullable) {
93
+ throw new Error(
94
+ `No se pudo encontrar un valor válido para el enum ${enumTypeName}`
95
+ );
71
96
  }
72
97
 
73
- this.logger.warn(
74
- `Invalid enum value "${value}" for type ${enumType}. Valid values are: ${[
75
- ...validValues,
76
- ].join(", ")}`
77
- );
78
98
  return null;
79
99
  } catch (error) {
80
100
  this.logger.error(
81
- `Error validating enum value "${value}" for type ${enumType}: ${error.message}`
101
+ `Error validating enum value for ${tableName}.${columnName}: ${error.message}`
82
102
  );
103
+ if (!isNullable) {
104
+ return await this.getDefaultEnumValue(tableName, columnName);
105
+ }
83
106
  return null;
84
107
  }
85
108
  }
86
109
 
110
+ private async getEnumTypeName(
111
+ tableName: string,
112
+ columnName: string
113
+ ): Promise<string> {
114
+ const result = await this.connections.sourcePool.query(
115
+ `
116
+ SELECT udt_name
117
+ FROM information_schema.columns
118
+ WHERE table_name = $1
119
+ AND column_name = $2
120
+ AND table_schema = 'public'
121
+ `,
122
+ [tableName.replace(/^public\./, ""), columnName]
123
+ );
124
+
125
+ if (result.rows.length === 0) {
126
+ throw new Error(
127
+ `No se encontró el tipo de enum para ${tableName}.${columnName}`
128
+ );
129
+ }
130
+
131
+ return result.rows[0].udt_name;
132
+ }
133
+
87
134
  transformToNumeric(value: any): number | null {
88
135
  if (value === null || value === "") {
89
136
  return null;
@@ -102,23 +149,34 @@ export class DataTransformer {
102
149
  tenantId: string = "public"
103
150
  ): Promise<string | null> {
104
151
  try {
105
- // Obtener todos los valores posibles del enum desde la base de datos
152
+ // Mapeo específico para tipos de enum conocidos
153
+ const enumDefaults = {
154
+ enum_users_role: "user",
155
+ enum_account_type: "savings",
156
+ enum_transaction_type: "payin",
157
+ enum_transaction_status: "pending",
158
+ };
159
+
160
+ if (enumDefaults[enumType]) {
161
+ return enumDefaults[enumType];
162
+ }
163
+
164
+ // Si no hay un valor por defecto específico, obtener el primer valor del enum
106
165
  const query = `
107
- SELECT e.enumlabel
108
- FROM pg_type t
109
- JOIN pg_enum e ON t.oid = e.enumtypid
110
- JOIN pg_namespace n ON n.oid = t.typnamespace
111
- WHERE t.typname = $1 AND n.nspname = $2
112
- ORDER BY e.enumsortorder
113
- LIMIT 1
114
- `;
166
+ SELECT e.enumlabel
167
+ FROM pg_type t
168
+ JOIN pg_enum e ON t.oid = e.enumtypid
169
+ JOIN pg_namespace n ON n.oid = t.typnamespace
170
+ WHERE t.typname = $1 AND n.nspname = $2
171
+ ORDER BY e.enumsortorder
172
+ LIMIT 1
173
+ `;
115
174
 
116
175
  const result = await this.schemaUtils.queryTargetDb(query, [
117
176
  enumType,
118
177
  tenantId,
119
178
  ]);
120
179
 
121
- // Retornar el primer valor del enum como valor por defecto
122
180
  if (result.rows.length > 0) {
123
181
  return result.rows[0].enumlabel;
124
182
  }
@@ -173,166 +231,159 @@ export class DataTransformer {
173
231
  async transformColumnValue(
174
232
  value: any,
175
233
  columnName: string,
176
- targetColumn: ColumnSchema,
234
+ targetColumn: ColumnSchema & { source_type?: string },
177
235
  tenantId: string
178
236
  ): Promise<any> {
179
- // Log input values for debugging
180
- this.logger.debug(
181
- `Transforming column ${columnName}, value: ${JSON.stringify(
182
- value
183
- )}, type: ${targetColumn.udt_name}`
184
- );
185
-
186
- if (value === null && targetColumn.is_nullable === "YES") {
187
- return null;
237
+ if (value === null || value === undefined) {
238
+ return targetColumn.is_nullable === "YES"
239
+ ? null
240
+ : this.getDefaultValue(targetColumn);
188
241
  }
189
242
 
190
243
  try {
191
- // Handle enum types
192
- if (targetColumn.udt_name.startsWith("enum_")) {
193
- // Handle JSONB/object to enum conversion
194
- if (typeof value === "object" && value !== null) {
195
- const stringValue = this.extractEnumValueFromObject(value);
196
- if (stringValue) {
197
- return await this.prepareEnumValue(
198
- tenantId,
199
- targetColumn.udt_name,
200
- stringValue
201
- );
202
- }
203
- }
204
-
205
- // Handle direct string values
206
- if (typeof value === "string") {
207
- return await this.prepareEnumValue(
208
- tenantId,
209
- targetColumn.udt_name,
210
- value
211
- );
212
- }
213
-
214
- // If no valid value found, get default
215
- const defaultValue = await this.getDefaultEnumValue(
216
- targetColumn.udt_name,
217
- tenantId
244
+ // Si tenemos el tipo de origen, usar el TypecastManager
245
+ if (targetColumn.source_type) {
246
+ return this.typecastManager.castValue(
247
+ value,
248
+ targetColumn.source_type,
249
+ targetColumn.data_type
218
250
  );
219
- if (defaultValue) {
220
- this.logger.warn(
221
- `Using default enum value '${defaultValue}' for column ${columnName}`
222
- );
223
- return {
224
- needsEnumCast: true,
225
- value: defaultValue,
226
- enumType: targetColumn.udt_name,
227
- };
228
- }
229
251
  }
230
252
 
231
- // Handle numeric types
232
- if (
233
- targetColumn.udt_name === "numeric" ||
234
- targetColumn.data_type === "decimal"
235
- ) {
236
- const numericValue = this.transformToNumeric(value);
237
- if (numericValue !== null || targetColumn.is_nullable === "YES") {
238
- return numericValue;
239
- }
240
- return 0; // Default value for non-nullable numeric fields
241
- }
242
-
243
- // Handle boolean types
244
- if (
245
- targetColumn.udt_name === "bool" ||
246
- targetColumn.data_type === "boolean"
247
- ) {
248
- if (typeof value === "string") {
249
- return value.toLowerCase() === "true" || value === "1";
250
- }
251
- return Boolean(value);
252
- }
253
-
254
- // Handle date/timestamp types
255
- if (
256
- targetColumn.data_type.includes("timestamp") ||
257
- targetColumn.data_type.includes("date")
258
- ) {
259
- if (!value) return null;
260
- const date = new Date(value);
261
- return isNaN(date.getTime()) ? null : date.toISOString();
262
- }
263
-
264
- // Handle JSON/JSONB types
265
- if (
266
- targetColumn.data_type === "json" ||
267
- targetColumn.data_type === "jsonb"
268
- ) {
269
- if (typeof value === "string") {
270
- try {
271
- return JSON.parse(value);
272
- } catch {
273
- return value;
253
+ // Si no tenemos el tipo de origen, intentar convertir basado en el tipo destino
254
+ switch (targetColumn.data_type.toLowerCase()) {
255
+ case "integer":
256
+ case "bigint":
257
+ case "smallint":
258
+ return typeof value === "string" ? parseInt(value, 10) : value;
259
+ case "numeric":
260
+ case "decimal":
261
+ case "real":
262
+ case "double precision":
263
+ return typeof value === "string" ? parseFloat(value) : value;
264
+ case "boolean":
265
+ if (typeof value === "string") {
266
+ return value.toLowerCase() === "true" || value === "1";
274
267
  }
275
- }
276
- return value;
268
+ return Boolean(value);
269
+ case "json":
270
+ case "jsonb":
271
+ if (typeof value === "string") {
272
+ try {
273
+ return JSON.parse(value);
274
+ } catch {
275
+ return value;
276
+ }
277
+ }
278
+ return value;
279
+ case "timestamp":
280
+ case "timestamptz":
281
+ case "timestamp with time zone":
282
+ case "timestamp without time zone":
283
+ if (value instanceof Date) {
284
+ return value.toISOString();
285
+ }
286
+ if (typeof value === "string" || typeof value === "number") {
287
+ return new Date(value).toISOString();
288
+ }
289
+ return value;
290
+ case "date":
291
+ if (value instanceof Date) {
292
+ return value.toISOString().split("T")[0];
293
+ }
294
+ if (typeof value === "string" || typeof value === "number") {
295
+ return new Date(value).toISOString().split("T")[0];
296
+ }
297
+ return value;
298
+ case "text":
299
+ case "character varying":
300
+ case "varchar":
301
+ case "char":
302
+ return String(value);
303
+ default:
304
+ return value;
277
305
  }
278
-
279
- // Default case: return value as-is
280
- return value;
281
306
  } catch (error) {
282
- this.logger.error(
283
- `Error transforming value for column ${columnName}: ${error.message}`
307
+ this.logger.warn(
308
+ `Error transformando valor para columna ${columnName}: ${error.message}`
284
309
  );
285
-
286
- // Return appropriate default values based on column type
287
- if (targetColumn.is_nullable === "YES") {
288
- return null;
289
- }
290
-
291
- return this.getDefaultValueForType(targetColumn.udt_name);
292
- }
293
- }
294
-
295
- private extractEnumValueFromObject(value: any): string | null {
296
- const possibleKeys = ["type", "value", "document_type", "status", "state"];
297
-
298
- for (const key of possibleKeys) {
299
- if (value[key] && typeof value[key] === "string") {
300
- return value[key];
301
- }
302
- }
303
-
304
- // Try to find any string value in the object
305
- for (const val of Object.values(value)) {
306
- if (typeof val === "string") {
307
- return val;
308
- }
310
+ return targetColumn.is_nullable === "YES"
311
+ ? null
312
+ : this.getDefaultValue(targetColumn);
309
313
  }
310
-
311
- return null;
312
314
  }
313
315
 
314
- private getDefaultValueForType(udtName: string): any {
315
- switch (udtName) {
316
- case "int4":
317
- case "int8":
318
- case "numeric":
316
+ private getDefaultValue(column: ColumnSchema): any {
317
+ switch (column.data_type.toLowerCase()) {
318
+ case "integer":
319
+ case "bigint":
320
+ case "smallint":
319
321
  return 0;
320
- case "bool":
322
+ case "numeric":
323
+ case "decimal":
324
+ case "real":
325
+ case "double precision":
326
+ return 0.0;
327
+ case "boolean":
321
328
  return false;
322
329
  case "text":
330
+ case "character varying":
323
331
  case "varchar":
332
+ case "char":
324
333
  return "";
325
- case "jsonb":
326
334
  case "json":
327
- return {};
335
+ case "jsonb":
336
+ return "{}";
337
+ case "timestamp":
338
+ case "timestamptz":
339
+ case "timestamp with time zone":
340
+ case "timestamp without time zone":
341
+ return new Date().toISOString();
342
+ case "date":
343
+ return new Date().toISOString().split("T")[0];
328
344
  default:
329
- if (udtName.startsWith("enum_")) {
330
- return null; // Enums should be handled by prepareEnumValue
331
- }
332
345
  return null;
333
346
  }
334
347
  }
335
348
 
349
+ private extractEnumValueFromObject(value: any): string | null {
350
+ if (typeof value === "object" && value !== null) {
351
+ // Intentar extraer el valor de propiedades comunes que podrían contener el valor enum
352
+ const possibleProperties = ["value", "type", "name", "code"];
353
+ for (const prop of possibleProperties) {
354
+ if (value[prop] && typeof value[prop] === "string") {
355
+ return value[prop].trim();
356
+ }
357
+ }
358
+ }
359
+ return null;
360
+ }
361
+
362
+ private async handleEnumType(
363
+ value: any,
364
+ columnName: string,
365
+ targetColumn: ColumnSchema,
366
+ tenantId: string
367
+ ): Promise<string | null> {
368
+ // Si el valor es null y la columna permite null
369
+ if (value === null && targetColumn.is_nullable === "YES") {
370
+ return null;
371
+ }
372
+
373
+ // Obtener el tipo específico de enum
374
+ const enumType = targetColumn.udt_name;
375
+
376
+ // Validar el valor según el tipo específico de enum
377
+ const validatedValue = await this.validateEnumValue(
378
+ tenantId,
379
+ columnName,
380
+ value,
381
+ targetColumn.is_nullable === "YES"
382
+ );
383
+
384
+ return validatedValue;
385
+ }
386
+
336
387
  async transformRecord(
337
388
  record: any,
338
389
  sourceSchema: ColumnSchema[],
@@ -343,9 +394,7 @@ export class DataTransformer {
343
394
 
344
395
  for (const [key, value] of Object.entries(record)) {
345
396
  // Primero intentar encontrar una coincidencia exacta
346
- let targetColumn = targetSchema.find(
347
- (col) => col.column_name === key
348
- );
397
+ let targetColumn = targetSchema.find((col) => col.column_name === key);
349
398
 
350
399
  // Si no se encuentra coincidencia exacta, intentar case-insensitive
351
400
  if (!targetColumn) {
@@ -361,7 +410,7 @@ export class DataTransformer {
361
410
 
362
411
  // Usar el nombre exacto de la columna del schema destino
363
412
  const columnName = targetColumn.column_name;
364
-
413
+
365
414
  const transformedValue = await this.transformColumnValue(
366
415
  value,
367
416
  columnName,
@@ -1,20 +1,54 @@
1
1
  import { PrismaClient } from "@prisma/client";
2
2
  import * as pg from "pg";
3
3
  import * as dotenv from "dotenv";
4
- import { DatabaseConnections } from "./types";
4
+ import { DatabaseConnections, DatabaseConnection } from "./types";
5
5
 
6
6
  dotenv.config();
7
7
 
8
+ export interface SourceDatabase {
9
+ id: string;
10
+ url: string;
11
+ name: string;
12
+ }
13
+
8
14
  export class DatabaseConnector {
9
- static createConnections(): DatabaseConnections {
10
- // Source database connection (the one we're migrating from)
15
+ static createConnections(
16
+ sourceDatabases?: SourceDatabase[]
17
+ ): DatabaseConnections {
18
+ // Crear conexiones para cada base de datos de origen
19
+ const sourceConnections: DatabaseConnection[] = (sourceDatabases || []).map(
20
+ (source): DatabaseConnection => {
21
+ // Conexión de pool para la base de datos de origen
22
+ const pool = new pg.Pool({
23
+ connectionString: source.url,
24
+ });
25
+
26
+ // Cliente Prisma para la base de datos de origen
27
+ const prisma = new PrismaClient({
28
+ datasources: {
29
+ db: {
30
+ url: source.url,
31
+ },
32
+ },
33
+ });
34
+
35
+ const connection: DatabaseConnection = {
36
+ pool,
37
+ prisma,
38
+ sourceId: source.id,
39
+ async query(sql: string, params?: any[]) {
40
+ return pool.query(sql, params);
41
+ },
42
+ };
43
+
44
+ return connection;
45
+ }
46
+ );
47
+
11
48
  const sourcePool = new pg.Pool({
12
49
  connectionString: process.env.SOURCE_DATABASE_URL,
13
- });
14
-
15
- // Target database connection (our multi-tenant database)
16
- const targetPool = new pg.Pool({
17
- connectionString: process.env.DATABASE_URL,
50
+ max: 20,
51
+ idleTimeoutMillis: 30000,
18
52
  });
19
53
 
20
54
  // Source Prisma client
@@ -26,6 +60,13 @@ export class DatabaseConnector {
26
60
  },
27
61
  });
28
62
 
63
+ // Target database connection (our multi-tenant database)
64
+ const targetPool = new pg.Pool({
65
+ connectionString: process.env.DATABASE_URL,
66
+ max: 20,
67
+ idleTimeoutMillis: 30000,
68
+ });
69
+
29
70
  // Target Prisma client
30
71
  const targetPrisma = new PrismaClient({
31
72
  datasources: {
@@ -36,10 +77,11 @@ export class DatabaseConnector {
36
77
  });
37
78
 
38
79
  return {
39
- sourcePrisma,
80
+ sourceConnections,
40
81
  targetPrisma,
41
- sourcePool,
42
82
  targetPool,
83
+ sourcePool,
84
+ sourcePrisma,
43
85
  };
44
86
  }
45
87
 
@@ -59,9 +101,20 @@ export class DatabaseConnector {
59
101
  }
60
102
 
61
103
  static async cleanup(connections: DatabaseConnections): Promise<void> {
62
- await connections.sourcePrisma.$disconnect();
104
+ // Desconectar todas las conexiones de origen
105
+ if (connections.sourceConnections) {
106
+ for (const sourceConn of connections.sourceConnections) {
107
+ if (sourceConn.pool) await sourceConn.pool.end();
108
+ if (sourceConn.prisma) await sourceConn.prisma.$disconnect();
109
+ }
110
+ }
111
+
112
+ // Desconectar conexiones de destino
63
113
  await connections.targetPrisma.$disconnect();
64
- await connections.sourcePool.end();
65
114
  await connections.targetPool.end();
115
+
116
+ // Desconectar conexiones principales de origen si existen
117
+ if (connections.sourcePrisma) await connections.sourcePrisma.$disconnect();
118
+ if (connections.sourcePool) await connections.sourcePool.end();
66
119
  }
67
- }
120
+ }