@javalabs/prisma-client 1.0.0

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 (150) hide show
  1. package/README.md +220 -0
  2. package/dist/index.d.ts +7 -0
  3. package/dist/index.js +34 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/prisma-factory.service.d.ts +9 -0
  6. package/dist/prisma-factory.service.js +47 -0
  7. package/dist/prisma-factory.service.js.map +1 -0
  8. package/dist/prisma.module.d.ts +2 -0
  9. package/dist/prisma.module.js +23 -0
  10. package/dist/prisma.module.js.map +1 -0
  11. package/dist/prisma.service.d.ts +6 -0
  12. package/dist/prisma.service.js +27 -0
  13. package/dist/prisma.service.js.map +1 -0
  14. package/dist/scripts/create-tenant-schemas.d.ts +1 -0
  15. package/dist/scripts/create-tenant-schemas.js +117 -0
  16. package/dist/scripts/create-tenant-schemas.js.map +1 -0
  17. package/dist/scripts/data-migration/batch-migrator.d.ts +25 -0
  18. package/dist/scripts/data-migration/batch-migrator.js +333 -0
  19. package/dist/scripts/data-migration/batch-migrator.js.map +1 -0
  20. package/dist/scripts/data-migration/data-transformer.d.ts +17 -0
  21. package/dist/scripts/data-migration/data-transformer.js +242 -0
  22. package/dist/scripts/data-migration/data-transformer.js.map +1 -0
  23. package/dist/scripts/data-migration/db-connector.d.ts +7 -0
  24. package/dist/scripts/data-migration/db-connector.js +58 -0
  25. package/dist/scripts/data-migration/db-connector.js.map +1 -0
  26. package/dist/scripts/data-migration/dependency-manager.d.ts +9 -0
  27. package/dist/scripts/data-migration/dependency-manager.js +86 -0
  28. package/dist/scripts/data-migration/dependency-manager.js.map +1 -0
  29. package/dist/scripts/data-migration/dependency-resolver.d.ts +18 -0
  30. package/dist/scripts/data-migration/dependency-resolver.js +251 -0
  31. package/dist/scripts/data-migration/dependency-resolver.js.map +1 -0
  32. package/dist/scripts/data-migration/entity-discovery.d.ts +11 -0
  33. package/dist/scripts/data-migration/entity-discovery.js +152 -0
  34. package/dist/scripts/data-migration/entity-discovery.js.map +1 -0
  35. package/dist/scripts/data-migration/foreign-key-manager.d.ts +17 -0
  36. package/dist/scripts/data-migration/foreign-key-manager.js +70 -0
  37. package/dist/scripts/data-migration/foreign-key-manager.js.map +1 -0
  38. package/dist/scripts/data-migration/migration-phases.d.ts +5 -0
  39. package/dist/scripts/data-migration/migration-phases.js +55 -0
  40. package/dist/scripts/data-migration/migration-phases.js.map +1 -0
  41. package/dist/scripts/data-migration/migration-tool.d.ts +29 -0
  42. package/dist/scripts/data-migration/migration-tool.js +250 -0
  43. package/dist/scripts/data-migration/migration-tool.js.map +1 -0
  44. package/dist/scripts/data-migration/phase-generator.d.ts +15 -0
  45. package/dist/scripts/data-migration/phase-generator.js +187 -0
  46. package/dist/scripts/data-migration/phase-generator.js.map +1 -0
  47. package/dist/scripts/data-migration/schema-utils.d.ts +18 -0
  48. package/dist/scripts/data-migration/schema-utils.js +164 -0
  49. package/dist/scripts/data-migration/schema-utils.js.map +1 -0
  50. package/dist/scripts/data-migration/tenant-migrator.d.ts +15 -0
  51. package/dist/scripts/data-migration/tenant-migrator.js +110 -0
  52. package/dist/scripts/data-migration/tenant-migrator.js.map +1 -0
  53. package/dist/scripts/data-migration/typecast-manager.d.ts +5 -0
  54. package/dist/scripts/data-migration/typecast-manager.js +35 -0
  55. package/dist/scripts/data-migration/typecast-manager.js.map +1 -0
  56. package/dist/scripts/data-migration/types.d.ts +34 -0
  57. package/dist/scripts/data-migration/types.js +3 -0
  58. package/dist/scripts/data-migration/types.js.map +1 -0
  59. package/dist/scripts/data-migration.d.ts +22 -0
  60. package/dist/scripts/data-migration.js +593 -0
  61. package/dist/scripts/data-migration.js.map +1 -0
  62. package/dist/scripts/drop-database.d.ts +10 -0
  63. package/dist/scripts/drop-database.js +81 -0
  64. package/dist/scripts/drop-database.js.map +1 -0
  65. package/dist/scripts/error-handler.d.ts +12 -0
  66. package/dist/scripts/error-handler.js +82 -0
  67. package/dist/scripts/error-handler.js.map +1 -0
  68. package/dist/scripts/fix-data-types.d.ts +10 -0
  69. package/dist/scripts/fix-data-types.js +185 -0
  70. package/dist/scripts/fix-data-types.js.map +1 -0
  71. package/dist/scripts/fix-enum-values.d.ts +17 -0
  72. package/dist/scripts/fix-enum-values.js +234 -0
  73. package/dist/scripts/fix-enum-values.js.map +1 -0
  74. package/dist/scripts/fix-schema-discrepancies.d.ts +21 -0
  75. package/dist/scripts/fix-schema-discrepancies.js +240 -0
  76. package/dist/scripts/fix-schema-discrepancies.js.map +1 -0
  77. package/dist/scripts/migrate-schema-structure.d.ts +1 -0
  78. package/dist/scripts/migrate-schema-structure.js +76 -0
  79. package/dist/scripts/migrate-schema-structure.js.map +1 -0
  80. package/dist/scripts/post-migration-validator.d.ts +21 -0
  81. package/dist/scripts/post-migration-validator.js +341 -0
  82. package/dist/scripts/post-migration-validator.js.map +1 -0
  83. package/dist/scripts/pre-migration-validator.d.ts +25 -0
  84. package/dist/scripts/pre-migration-validator.js +491 -0
  85. package/dist/scripts/pre-migration-validator.js.map +1 -0
  86. package/dist/scripts/reset-database.d.ts +17 -0
  87. package/dist/scripts/reset-database.js +202 -0
  88. package/dist/scripts/reset-database.js.map +1 -0
  89. package/dist/scripts/retry-failed-migrations.d.ts +14 -0
  90. package/dist/scripts/retry-failed-migrations.js +301 -0
  91. package/dist/scripts/retry-failed-migrations.js.map +1 -0
  92. package/dist/scripts/run-migration.d.ts +1 -0
  93. package/dist/scripts/run-migration.js +525 -0
  94. package/dist/scripts/run-migration.js.map +1 -0
  95. package/dist/scripts/schema-sync.d.ts +1 -0
  96. package/dist/scripts/schema-sync.js +85 -0
  97. package/dist/scripts/schema-sync.js.map +1 -0
  98. package/dist/scripts/sync-enum-types.d.ts +13 -0
  99. package/dist/scripts/sync-enum-types.js +139 -0
  100. package/dist/scripts/sync-enum-types.js.map +1 -0
  101. package/dist/scripts/sync-enum-values.d.ts +20 -0
  102. package/dist/scripts/sync-enum-values.js +336 -0
  103. package/dist/scripts/sync-enum-values.js.map +1 -0
  104. package/dist/scripts/truncate-database.d.ts +10 -0
  105. package/dist/scripts/truncate-database.js +100 -0
  106. package/dist/scripts/truncate-database.js.map +1 -0
  107. package/dist/scripts/verify-migration-setup.d.ts +11 -0
  108. package/dist/scripts/verify-migration-setup.js +120 -0
  109. package/dist/scripts/verify-migration-setup.js.map +1 -0
  110. package/dist/tsconfig.tsbuildinfo +1 -0
  111. package/migration-config-public.json +95 -0
  112. package/migration-config.json +95 -0
  113. package/package.json +33 -0
  114. package/prisma/migrations/migration_lock.toml +3 -0
  115. package/prisma/schema.prisma +360 -0
  116. package/src/index.ts +23 -0
  117. package/src/prisma-factory.service.ts +41 -0
  118. package/src/prisma.module.ts +10 -0
  119. package/src/prisma.service.ts +17 -0
  120. package/src/scripts/create-tenant-schemas.ts +146 -0
  121. package/src/scripts/data-migration/batch-migrator.ts +569 -0
  122. package/src/scripts/data-migration/data-transformer.ts +377 -0
  123. package/src/scripts/data-migration/db-connector.ts +67 -0
  124. package/src/scripts/data-migration/dependency-resolver.ts +319 -0
  125. package/src/scripts/data-migration/entity-discovery.ts +197 -0
  126. package/src/scripts/data-migration/foreign-key-manager.ts +95 -0
  127. package/src/scripts/data-migration/migration-tool.ts +357 -0
  128. package/src/scripts/data-migration/schema-utils.ts +186 -0
  129. package/src/scripts/data-migration/tenant-migrator.ts +194 -0
  130. package/src/scripts/data-migration/typecast-manager.ts +38 -0
  131. package/src/scripts/data-migration/types.ts +40 -0
  132. package/src/scripts/drop-database.ts +105 -0
  133. package/src/scripts/dump-source-db.sh +62 -0
  134. package/src/scripts/dumps/source_dump_20250413_112626.sql +1527 -0
  135. package/src/scripts/error-handler.ts +118 -0
  136. package/src/scripts/fix-data-types.ts +242 -0
  137. package/src/scripts/fix-enum-values.ts +357 -0
  138. package/src/scripts/fix-schema-discrepancies.ts +318 -0
  139. package/src/scripts/migrate-schema-structure.ts +90 -0
  140. package/src/scripts/post-migration-validator.ts +427 -0
  141. package/src/scripts/pre-migration-validator.ts +611 -0
  142. package/src/scripts/reset-database.ts +264 -0
  143. package/src/scripts/retry-failed-migrations.ts +416 -0
  144. package/src/scripts/run-migration.ts +691 -0
  145. package/src/scripts/schema-sync.ts +129 -0
  146. package/src/scripts/sync-enum-types.ts +171 -0
  147. package/src/scripts/sync-enum-values.ts +563 -0
  148. package/src/scripts/truncate-database.ts +124 -0
  149. package/src/scripts/verify-migration-setup.ts +136 -0
  150. package/tsconfig.json +18 -0
@@ -0,0 +1,264 @@
1
+ import * as pg from "pg";
2
+ import * as dotenv from "dotenv";
3
+ import { Logger } from "@nestjs/common";
4
+ import { execSync } from "child_process";
5
+ import { PrismaClient } from "@prisma/client";
6
+
7
+ dotenv.config();
8
+
9
+ export class DatabaseResetTool {
10
+ private readonly logger = new Logger("DatabaseResetTool");
11
+ private readonly pool: pg.Pool;
12
+ private readonly adminPool: pg.Pool;
13
+ private readonly prisma: PrismaClient;
14
+
15
+ constructor(private readonly databaseUrl: string) {
16
+ // Conexión a la base de datos específica
17
+ this.pool = new pg.Pool({
18
+ connectionString: this.databaseUrl,
19
+ });
20
+
21
+ // Conexión a la base de datos postgres (admin)
22
+ const adminUrl = this.databaseUrl.replace(/\/[^/]+$/, "/postgres");
23
+ this.adminPool = new pg.Pool({
24
+ connectionString: adminUrl,
25
+ });
26
+
27
+ // Cliente Prisma
28
+ this.prisma = new PrismaClient({
29
+ datasources: {
30
+ db: {
31
+ url: this.databaseUrl,
32
+ },
33
+ },
34
+ });
35
+ }
36
+
37
+ async resetDatabase(recreateDb = false) {
38
+ try {
39
+ this.logger.log("Starting complete database reset process");
40
+
41
+ // Extraer el nombre de la base de datos de la URL
42
+ const dbName = this.extractDatabaseName(this.databaseUrl);
43
+
44
+ if (!dbName) {
45
+ throw new Error("Could not extract database name from connection URL");
46
+ }
47
+
48
+ // 1. Eliminar todos los schemas personalizados y sus contenidos
49
+ await this.dropAllSchemas();
50
+
51
+ // 2. Eliminar todas las tablas del schema public
52
+ await this.dropAllPublicTables();
53
+
54
+ // 3. Eliminar todos los tipos personalizados (enums, etc.)
55
+ await this.dropAllCustomTypes();
56
+
57
+ if (recreateDb) {
58
+ // 4. Terminar todas las conexiones y eliminar la base de datos
59
+ await this.terminateConnections(dbName);
60
+ await this.adminPool.query(`DROP DATABASE IF EXISTS "${dbName}"`);
61
+
62
+ // 5. Recrear la base de datos
63
+ this.logger.log(`Recreating database: ${dbName}`);
64
+ await this.adminPool.query(`CREATE DATABASE "${dbName}"`);
65
+
66
+ // 6. Ejecutar prisma migrate para recrear el esquema base
67
+ this.logger.log("Running prisma migrate to recreate base schema");
68
+ try {
69
+ execSync(`DATABASE_URL="${this.databaseUrl}" npx prisma migrate deploy`, {
70
+ stdio: 'inherit'
71
+ });
72
+ } catch (migrationError) {
73
+ this.logger.error(`Error running prisma migrate: ${migrationError.message}`);
74
+ }
75
+ }
76
+
77
+ this.logger.log("Database has been completely reset");
78
+ return true;
79
+ } catch (error) {
80
+ this.logger.error(
81
+ `Error resetting database: ${error.message}`,
82
+ error.stack
83
+ );
84
+ return false;
85
+ } finally {
86
+ await this.cleanup();
87
+ }
88
+ }
89
+
90
+ private async dropAllSchemas() {
91
+ this.logger.log("Dropping all custom schemas");
92
+
93
+ try {
94
+ // Obtener todos los schemas excepto los del sistema
95
+ const schemas = await this.getCustomSchemas();
96
+ this.logger.log(`Found ${schemas.length} custom schemas to drop`);
97
+
98
+ // Desactivar restricciones de clave foránea temporalmente
99
+ await this.pool.query(`SET session_replication_role = 'replica'`);
100
+
101
+ // Eliminar cada schema y su contenido
102
+ for (const schema of schemas) {
103
+ try {
104
+ await this.pool.query(`DROP SCHEMA IF EXISTS "${schema}" CASCADE`);
105
+ this.logger.log(`Dropped schema: ${schema}`);
106
+ } catch (error) {
107
+ this.logger.error(`Error dropping schema ${schema}: ${error.message}`);
108
+ }
109
+ }
110
+
111
+ // Reactivar restricciones de clave foránea
112
+ await this.pool.query(`SET session_replication_role = 'origin'`);
113
+ } catch (error) {
114
+ this.logger.error(`Error dropping schemas: ${error.message}`);
115
+ }
116
+ }
117
+
118
+ private async dropAllPublicTables() {
119
+ this.logger.log("Dropping all tables in public schema");
120
+
121
+ try {
122
+ // Obtener todas las tablas del schema public
123
+ const tables = await this.getPublicTables();
124
+ this.logger.log(`Found ${tables.length} tables in public schema to drop`);
125
+
126
+ if (tables.length > 0) {
127
+ // Desactivar restricciones de clave foránea temporalmente
128
+ await this.pool.query(`SET session_replication_role = 'replica'`);
129
+
130
+ // Eliminar todas las tablas en una sola operación
131
+ const tableList = tables.map(table => `"${table}"`).join(", ");
132
+ if (tableList) {
133
+ await this.pool.query(`DROP TABLE IF EXISTS ${tableList} CASCADE`);
134
+ this.logger.log("Dropped all tables in public schema");
135
+ }
136
+
137
+ // Reactivar restricciones de clave foránea
138
+ await this.pool.query(`SET session_replication_role = 'origin'`);
139
+ }
140
+ } catch (error) {
141
+ this.logger.error(`Error dropping public tables: ${error.message}`);
142
+ }
143
+ }
144
+
145
+ private async dropAllCustomTypes() {
146
+ this.logger.log("Dropping all custom types (enums, domains, etc.)");
147
+
148
+ try {
149
+ // Obtener todos los tipos personalizados
150
+ const typesQuery = `
151
+ SELECT t.typname AS name
152
+ FROM pg_type t
153
+ JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
154
+ WHERE n.nspname = 'public'
155
+ AND t.typtype = 'e' -- enum types
156
+ `;
157
+
158
+ const typesResult = await this.pool.query(typesQuery);
159
+ const types = typesResult.rows.map(row => row.name);
160
+
161
+ this.logger.log(`Found ${types.length} custom types to drop`);
162
+
163
+ // Eliminar cada tipo
164
+ for (const type of types) {
165
+ try {
166
+ await this.pool.query(`DROP TYPE IF EXISTS "${type}" CASCADE`);
167
+ this.logger.log(`Dropped type: ${type}`);
168
+ } catch (error) {
169
+ this.logger.error(`Error dropping type ${type}: ${error.message}`);
170
+ }
171
+ }
172
+ } catch (error) {
173
+ this.logger.error(`Error dropping custom types: ${error.message}`);
174
+ }
175
+ }
176
+
177
+ private async getCustomSchemas(): Promise<string[]> {
178
+ const result = await this.pool.query(`
179
+ SELECT schema_name
180
+ FROM information_schema.schemata
181
+ WHERE schema_name NOT IN ('public', 'information_schema', 'pg_catalog', 'pg_toast')
182
+ AND schema_name NOT LIKE 'pg_%'
183
+ `);
184
+
185
+ return result.rows.map(row => row.schema_name);
186
+ }
187
+
188
+ private async getPublicTables(): Promise<string[]> {
189
+ const result = await this.pool.query(`
190
+ SELECT table_name
191
+ FROM information_schema.tables
192
+ WHERE table_schema = 'public'
193
+ AND table_type = 'BASE TABLE'
194
+ `);
195
+
196
+ return result.rows.map(row => row.table_name);
197
+ }
198
+
199
+ private extractDatabaseName(url: string): string | null {
200
+ // Extraer el nombre de la base de datos de la URL de conexión
201
+ const matches = url.match(/\/([^/?]+)($|\?)/);
202
+ return matches ? matches[1] : null;
203
+ }
204
+
205
+ private async terminateConnections(dbName: string) {
206
+ this.logger.log(`Terminating all connections to database: ${dbName}`);
207
+
208
+ try {
209
+ await this.adminPool.query(`
210
+ SELECT pg_terminate_backend(pg_stat_activity.pid)
211
+ FROM pg_stat_activity
212
+ WHERE pg_stat_activity.datname = $1
213
+ AND pid <> pg_backend_pid()
214
+ `, [dbName]);
215
+
216
+ this.logger.log("All connections terminated successfully");
217
+ } catch (error) {
218
+ this.logger.error(`Error terminating connections: ${error.message}`);
219
+ }
220
+ }
221
+
222
+ private async cleanup() {
223
+ this.logger.log("Cleaning up database connections");
224
+ await this.prisma.$disconnect();
225
+ await this.pool.end();
226
+ await this.adminPool.end();
227
+ }
228
+ }
229
+
230
+ // Script para ejecutar desde línea de comandos
231
+ if (require.main === module) {
232
+ const run = async () => {
233
+ try {
234
+ // Load environment variables first
235
+ dotenv.config();
236
+
237
+ // Get database URL from command line args or .env
238
+ const databaseUrl = process.argv[2] || process.env.DATABASE_URL;
239
+
240
+ if (!databaseUrl) {
241
+ console.error("Error: DATABASE_URL not found in environment variables or command line arguments");
242
+ process.exit(1);
243
+ }
244
+
245
+ // Check for recreate flag
246
+ const recreateFlag = process.argv.includes('--recreate') || process.argv.includes('-r');
247
+
248
+ const resetTool = new DatabaseResetTool(databaseUrl);
249
+ const success = await resetTool.resetDatabase(recreateFlag);
250
+
251
+ if (!success) {
252
+ console.error("Database reset failed");
253
+ process.exit(1);
254
+ }
255
+
256
+ process.exit(0);
257
+ } catch (error) {
258
+ console.error("Error:", error.message);
259
+ process.exit(1);
260
+ }
261
+ };
262
+
263
+ run();
264
+ }
@@ -0,0 +1,416 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { PrismaClient } from "@prisma/client";
4
+ import * as dotenv from "dotenv";
5
+ import { Logger } from "@nestjs/common";
6
+ import * as pg from "pg";
7
+ import { MigrationErrorHandler } from "./error-handler";
8
+
9
+ dotenv.config();
10
+
11
+ export class FailedMigrationRetry {
12
+ private readonly logger = new Logger("FailedMigrationRetry");
13
+ private readonly targetPrisma: PrismaClient;
14
+ private readonly targetPool: pg.Pool;
15
+ private readonly errorHandler: MigrationErrorHandler;
16
+ private schemaCache: Record<string, any> = {};
17
+
18
+ constructor(private readonly errorLogPath: string) {
19
+ // Target database connection
20
+ this.targetPool = new pg.Pool({
21
+ connectionString: process.env.DATABASE_URL,
22
+ });
23
+
24
+ // Target Prisma client
25
+ this.targetPrisma = new PrismaClient({
26
+ datasources: {
27
+ db: {
28
+ url: process.env.DATABASE_URL,
29
+ },
30
+ },
31
+ });
32
+
33
+ // Inicializar el manejador de errores
34
+ this.errorHandler = new MigrationErrorHandler();
35
+ }
36
+
37
+ async retryFailedMigrations() {
38
+ try {
39
+ this.logger.log(`Loading error log from: ${this.errorLogPath}`);
40
+
41
+ // Cargar el archivo de log de errores
42
+ if (!fs.existsSync(this.errorLogPath)) {
43
+ this.logger.error(`Error log file not found: ${this.errorLogPath}`);
44
+ return;
45
+ }
46
+
47
+ const errorLogContent = fs.readFileSync(this.errorLogPath, "utf8");
48
+ const errorLog: Record<string, any[]> = JSON.parse(errorLogContent);
49
+
50
+ let totalErrors = 0;
51
+ let retrySuccessCount = 0;
52
+ let retryFailCount = 0;
53
+
54
+ // Procesar cada tenant
55
+ for (const tenantId in errorLog) {
56
+ const tenantErrors = errorLog[tenantId];
57
+ if (tenantErrors.length === 0) continue;
58
+
59
+ totalErrors += tenantErrors.length;
60
+ this.logger.log(
61
+ `Processing ${tenantErrors.length} errors for tenant: ${tenantId}`
62
+ );
63
+
64
+ // Crear cliente Prisma específico para este tenant
65
+ const tenantPrisma = new PrismaClient({
66
+ datasources: {
67
+ db: {
68
+ url: `${process.env.DATABASE_URL}?schema=${tenantId}`,
69
+ },
70
+ },
71
+ transactionOptions: {
72
+ maxWait: 120000,
73
+ timeout: 120000,
74
+ },
75
+ });
76
+
77
+ // Procesar cada error
78
+ for (const errorEntry of tenantErrors) {
79
+ const { entity, recordId, data } = errorEntry;
80
+
81
+ this.logger.log(`Retrying ${entity} with ID ${recordId}`);
82
+
83
+ try {
84
+ // Obtener el esquema de la tabla si no está en caché
85
+ if (!this.schemaCache[`${tenantId}.${entity}`]) {
86
+ this.schemaCache[`${tenantId}.${entity}`] =
87
+ await this.getTableSchema(entity, tenantId);
88
+ }
89
+
90
+ const tableSchema = this.schemaCache[`${tenantId}.${entity}`];
91
+
92
+ if (!tableSchema || tableSchema.length === 0) {
93
+ this.logger.warn(
94
+ `Schema not found for ${entity} in tenant ${tenantId}. Skipping.`
95
+ );
96
+ retryFailCount++;
97
+ continue;
98
+ }
99
+
100
+ // Preparar los datos para la inserción/actualización
101
+ const createData: Record<string, any> = {};
102
+ const updateData: Record<string, any> = {};
103
+
104
+ // Obtener la lista de columnas
105
+ const columnNames = tableSchema.map((col) => col.column_name);
106
+
107
+ // Si no tenemos los datos originales, no podemos reintentar
108
+ if (!data) {
109
+ this.logger.warn(
110
+ `No data available for ${entity} with ID ${recordId}. Skipping.`
111
+ );
112
+ retryFailCount++;
113
+ continue;
114
+ }
115
+
116
+ // Procesar cada columna
117
+ for (const column of tableSchema) {
118
+ const columnName = column.column_name;
119
+
120
+ // Omitir columnas que no están en los datos
121
+ if (!Object.prototype.hasOwnProperty.call(data, columnName)) {
122
+ continue;
123
+ }
124
+
125
+ // Manejar tipos especiales
126
+ if (column.data_type === "numeric" && data[columnName] !== null) {
127
+ // Convertir a número
128
+ const numericValue = parseFloat(data[columnName]);
129
+ if (!isNaN(numericValue)) {
130
+ createData[columnName] = numericValue;
131
+ updateData[columnName] = numericValue;
132
+ } else {
133
+ createData[columnName] = null;
134
+ updateData[columnName] = null;
135
+ }
136
+ }
137
+ // Manejar tipos enum
138
+ else if (
139
+ column.data_type === "USER-DEFINED" &&
140
+ column.udt_name.startsWith("enum_")
141
+ ) {
142
+ // Obtener valores válidos para el enum
143
+ const enumValues = await this.getEnumValues(column.udt_name);
144
+
145
+ // Inicializar _enumTypes si no existe
146
+ if (!createData._enumTypes) createData._enumTypes = {};
147
+ if (!updateData._enumTypes) updateData._enumTypes = {};
148
+
149
+ // Siempre guardar el tipo de enum para esta columna
150
+ createData._enumTypes[columnName] = column.udt_name;
151
+ updateData._enumTypes[columnName] = column.udt_name;
152
+
153
+ if (data[columnName] === null || data[columnName] === "") {
154
+ createData[columnName] = null;
155
+ updateData[columnName] = null;
156
+ }
157
+ // Si el valor es un string, intentar encontrar una coincidencia en los valores del enum
158
+ else if (typeof data[columnName] === "string") {
159
+ const matchingValue = this.findMatchingEnumValue(
160
+ data[columnName],
161
+ enumValues
162
+ );
163
+ if (matchingValue) {
164
+ createData[columnName] = matchingValue;
165
+ updateData[columnName] = matchingValue;
166
+ } else {
167
+ // Si no hay coincidencia, usar null
168
+ createData[columnName] = null;
169
+ updateData[columnName] = null;
170
+ this.logger.warn(
171
+ `No matching enum value found for ${columnName}="${data[columnName]}" in ${entity}. Using NULL.`
172
+ );
173
+ }
174
+ } else {
175
+ // Para otros tipos, usar el valor tal cual
176
+ const enumValue = data[columnName];
177
+ createData[columnName] = data[columnName];
178
+ updateData[columnName] = data[columnName];
179
+ }
180
+ } else {
181
+ // Para otros tipos de datos, usar el valor tal cual
182
+ createData[columnName] = data[columnName];
183
+ updateData[columnName] = data[columnName];
184
+ }
185
+ }
186
+
187
+ // Asegurarse de incluir el ID
188
+ if (columnNames.includes(recordId)) {
189
+ createData[recordId] = data[recordId];
190
+ }
191
+
192
+ // Ejecutar la operación upsert usando SQL raw en lugar de acceso directo al modelo
193
+ // Verificar primero si el registro existe
194
+ const checkExistQuery = `
195
+ SELECT 1 FROM "${tenantId}"."${entity}"
196
+ WHERE "${recordId}" = $1
197
+ LIMIT 1
198
+ `;
199
+
200
+ const existResult = await this.targetPool.query(checkExistQuery, [
201
+ data[recordId],
202
+ ]);
203
+ const recordExists = existResult.rows.length > 0;
204
+
205
+ if (recordExists) {
206
+ // Construir la consulta UPDATE
207
+ const updateFields = Object.keys(updateData)
208
+ .filter((key) => key !== recordId && key !== '_enumTypes') // Excluir el campo ID y _enumTypes
209
+ .map((key, index) => {
210
+ // Aplicar casting explícito para tipos enum
211
+ if (updateData._enumTypes && updateData._enumTypes[key]) {
212
+ return `"${key}" = $${index + 2}::${updateData._enumTypes[key]}`;
213
+ }
214
+ return `"${key}" = $${index + 2}`;
215
+ });
216
+
217
+ if (updateFields.length > 0) {
218
+ const updateQuery = `
219
+ UPDATE "${tenantId}"."${entity}"
220
+ SET ${updateFields.join(", ")}
221
+ WHERE "${recordId}" = $1
222
+ `;
223
+
224
+ const updateValues = [
225
+ data[recordId],
226
+ ...Object.keys(updateData)
227
+ .filter((key) => key !== recordId && key !== '_enumTypes')
228
+ .map((key) => updateData[key]),
229
+ ];
230
+
231
+ await this.targetPool.query(updateQuery, updateValues);
232
+ }
233
+ } else {
234
+ // Construir la consulta INSERT
235
+ const insertFields = Object.keys(createData)
236
+ .filter(key => key !== '_enumTypes')
237
+ .map((key) => `"${key}"`);
238
+
239
+ const insertPlaceholders = Object.keys(createData)
240
+ .filter(key => key !== '_enumTypes')
241
+ .map((key, index) => {
242
+ // Aplicar casting explícito para tipos enum
243
+ if (createData._enumTypes && createData._enumTypes[key]) {
244
+ return `$${index + 1}::${createData._enumTypes[key]}`;
245
+ }
246
+ return `$${index + 1}`;
247
+ });
248
+
249
+ const insertQuery = `
250
+ INSERT INTO "${tenantId}"."${entity}" (${insertFields.join(", ")})
251
+ VALUES (${insertPlaceholders.join(", ")})
252
+ `;
253
+
254
+ const insertValues = Object.keys(createData)
255
+ .filter(key => key !== '_enumTypes')
256
+ .map((key) => createData[key]);
257
+
258
+ await this.targetPool.query(insertQuery, insertValues);
259
+ }
260
+
261
+ this.logger.log(
262
+ `Successfully migrated ${entity} with ID ${recordId}`
263
+ );
264
+ retrySuccessCount++;
265
+ } catch (error) {
266
+ this.logger.error(
267
+ `Error retrying ${entity} with ID ${recordId}: ${error.message}`
268
+ );
269
+ // Registrar el error en el nuevo log
270
+ this.errorHandler.logError(tenantId, entity, recordId, error, data);
271
+ retryFailCount++;
272
+ }
273
+ }
274
+
275
+ // Cerrar la conexión del cliente tenant
276
+ await tenantPrisma.$disconnect();
277
+ }
278
+
279
+ // Mostrar resumen
280
+ this.logger.log(`
281
+ Retry Summary:
282
+ - Total errors: ${totalErrors}
283
+ - Successfully retried: ${retrySuccessCount}
284
+ - Failed retries: ${retryFailCount}
285
+ `);
286
+
287
+ // Generar informe de errores restantes
288
+ const remainingErrorsReport = this.errorHandler.generateErrorReport();
289
+ this.logger.log("Remaining Errors Report:\n" + remainingErrorsReport);
290
+ } catch (error) {
291
+ this.logger.error(
292
+ `Error in retry process: ${error.message}`,
293
+ error.stack
294
+ );
295
+ } finally {
296
+ await this.cleanup();
297
+ }
298
+ }
299
+
300
+ private async getTableSchema(
301
+ tableName: string,
302
+ schemaName: string
303
+ ): Promise<any[]> {
304
+ try {
305
+ const schemaQuery = `
306
+ SELECT column_name, data_type, column_default, is_nullable, udt_name
307
+ FROM information_schema.columns
308
+ WHERE table_name = $1
309
+ AND table_schema = $2
310
+ ORDER BY ordinal_position
311
+ `;
312
+
313
+ const result = await this.targetPool.query(schemaQuery, [
314
+ tableName,
315
+ schemaName,
316
+ ]);
317
+ return result.rows;
318
+ } catch (error) {
319
+ this.logger.error(
320
+ `Error getting schema for table ${tableName} in schema ${schemaName}: ${error.message}`
321
+ );
322
+ return [];
323
+ }
324
+ }
325
+
326
+ private async getEnumValues(enumTypeName: string): Promise<string[]> {
327
+ try {
328
+ const enumQuery = `
329
+ SELECT e.enumlabel
330
+ FROM pg_enum e
331
+ JOIN pg_type t ON e.enumtypid = t.oid
332
+ WHERE t.typname = $1
333
+ ORDER BY e.enumsortorder
334
+ `;
335
+
336
+ const result = await this.targetPool.query(enumQuery, [enumTypeName]);
337
+ return result.rows.map((row) => row.enumlabel);
338
+ } catch (error) {
339
+ this.logger.error(
340
+ `Error getting enum values for ${enumTypeName}: ${error.message}`
341
+ );
342
+ return [];
343
+ }
344
+ }
345
+
346
+ private findMatchingEnumValue(
347
+ value: string,
348
+ enumValues: string[]
349
+ ): string | null {
350
+ // Buscar coincidencia exacta
351
+ if (enumValues.includes(value)) {
352
+ return value;
353
+ }
354
+
355
+ // Buscar coincidencia sin distinción de mayúsculas/minúsculas
356
+ const lowerValue = value.toLowerCase();
357
+ for (const enumValue of enumValues) {
358
+ if (enumValue.toLowerCase() === lowerValue) {
359
+ return enumValue;
360
+ }
361
+ }
362
+
363
+ // Buscar coincidencia parcial
364
+ const similarValues = enumValues.filter(
365
+ (v) =>
366
+ v.toLowerCase().includes(lowerValue) ||
367
+ lowerValue.includes(v.toLowerCase())
368
+ );
369
+
370
+ if (similarValues.length === 1) {
371
+ return similarValues[0];
372
+ } else if (similarValues.length > 1) {
373
+ // Si hay múltiples coincidencias, usar la más cercana por longitud
374
+ return similarValues.reduce((prev, curr) =>
375
+ Math.abs(curr.length - value.length) <
376
+ Math.abs(prev.length - value.length)
377
+ ? curr
378
+ : prev
379
+ );
380
+ }
381
+
382
+ return null;
383
+ }
384
+
385
+ private async cleanup() {
386
+ this.logger.log("Cleaning up database connections");
387
+ await this.targetPrisma.$disconnect();
388
+ await this.targetPool.end();
389
+ }
390
+ }
391
+
392
+ // Script para ejecutar desde línea de comandos
393
+ if (require.main === module) {
394
+ const run = async () => {
395
+ try {
396
+ // Obtener la ruta del archivo de log de errores
397
+ const errorLogPath = process.argv[2];
398
+
399
+ if (!errorLogPath) {
400
+ console.error(
401
+ "Error: No error log file path provided. Please provide the path to the error log file."
402
+ );
403
+ process.exit(1);
404
+ }
405
+
406
+ const retryTool = new FailedMigrationRetry(errorLogPath);
407
+ await retryTool.retryFailedMigrations();
408
+ process.exit(0);
409
+ } catch (error) {
410
+ console.error("Error:", error.message);
411
+ process.exit(1);
412
+ }
413
+ };
414
+
415
+ run();
416
+ }