@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,118 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { Logger } from '@nestjs/common';
4
+
5
+ export class MigrationErrorHandler {
6
+ private readonly logger = new Logger('MigrationErrorHandler');
7
+ private errorLog: Record<string, any[]> = {};
8
+ private errorLogPath: string;
9
+
10
+ constructor(private readonly logDir: string = path.join(process.cwd(), 'migration-logs')) {
11
+ // Crear directorio de logs si no existe
12
+ if (!fs.existsSync(this.logDir)) {
13
+ fs.mkdirSync(this.logDir, { recursive: true });
14
+ }
15
+
16
+ // Crear nombre de archivo con timestamp
17
+ const timestamp = new Date().toISOString().replace(/:/g, '-').replace(/\..+/, '');
18
+ this.errorLogPath = path.join(this.logDir, `migration-errors-${timestamp}.json`);
19
+
20
+ // Inicializar archivo de log
21
+ this.saveErrorLog();
22
+ }
23
+
24
+ /**
25
+ * Registra un error durante la migración
26
+ */
27
+ logError(tenantId: string, entityName: string, recordId: any, error: Error, data?: any): void {
28
+ if (!this.errorLog[tenantId]) {
29
+ this.errorLog[tenantId] = [];
30
+ }
31
+
32
+ this.errorLog[tenantId].push({
33
+ timestamp: new Date().toISOString(),
34
+ entity: entityName,
35
+ recordId,
36
+ error: {
37
+ message: error.message,
38
+ stack: error.stack,
39
+ },
40
+ data: data || null,
41
+ });
42
+
43
+ // Guardar el log después de cada error
44
+ this.saveErrorLog();
45
+ }
46
+
47
+ /**
48
+ * Guarda el log de errores en un archivo JSON
49
+ */
50
+ private saveErrorLog(): void {
51
+ try {
52
+ fs.writeFileSync(
53
+ this.errorLogPath,
54
+ JSON.stringify(this.errorLog, null, 2),
55
+ 'utf8'
56
+ );
57
+ } catch (error) {
58
+ this.logger.error(`Error saving error log: ${error.message}`);
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Genera un informe de errores
64
+ */
65
+ generateErrorReport(): string {
66
+ let totalErrors = 0;
67
+ let report = 'Migration Error Report\n';
68
+ report += '=====================\n\n';
69
+
70
+ for (const tenantId in this.errorLog) {
71
+ const tenantErrors = this.errorLog[tenantId];
72
+ if (tenantErrors.length === 0) continue;
73
+
74
+ totalErrors += tenantErrors.length;
75
+ report += `Tenant: ${tenantId}\n`;
76
+ report += `Errors: ${tenantErrors.length}\n\n`;
77
+
78
+ // Agrupar errores por entidad
79
+ const entityErrors: Record<string, number> = {};
80
+ for (const error of tenantErrors) {
81
+ entityErrors[error.entity] = (entityErrors[error.entity] || 0) + 1;
82
+ }
83
+
84
+ report += 'Errors by entity:\n';
85
+ for (const entity in entityErrors) {
86
+ report += ` - ${entity}: ${entityErrors[entity]}\n`;
87
+ }
88
+ report += '\n';
89
+
90
+ // Mostrar los primeros 5 errores como ejemplo
91
+ report += 'Sample errors:\n';
92
+ for (let i = 0; i < Math.min(5, tenantErrors.length); i++) {
93
+ const error = tenantErrors[i];
94
+ report += ` - ${error.entity} (ID: ${error.recordId}): ${error.error.message}\n`;
95
+ }
96
+ report += '\n';
97
+ }
98
+
99
+ report += `Total errors: ${totalErrors}\n`;
100
+ report += `Full error log saved to: ${this.errorLogPath}\n`;
101
+
102
+ return report;
103
+ }
104
+
105
+ /**
106
+ * Obtiene la lista de errores para un tenant específico
107
+ */
108
+ getErrorsForTenant(tenantId: string): any[] {
109
+ return this.errorLog[tenantId] || [];
110
+ }
111
+
112
+ /**
113
+ * Obtiene todos los errores
114
+ */
115
+ getAllErrors(): Record<string, any[]> {
116
+ return this.errorLog;
117
+ }
118
+ }
@@ -0,0 +1,242 @@
1
+ import { PrismaClient } from "@prisma/client";
2
+ import * as dotenv from "dotenv";
3
+ import { Logger } from "@nestjs/common";
4
+ import * as pg from "pg";
5
+
6
+ dotenv.config();
7
+
8
+ export class DataTypeFixer {
9
+ private readonly logger = new Logger("DataTypeFixer");
10
+ private readonly sourcePool: pg.Pool;
11
+ private readonly targetPool: pg.Pool;
12
+
13
+ constructor() {
14
+ // Source database connection
15
+ this.sourcePool = new pg.Pool({
16
+ connectionString: process.env.SOURCE_DATABASE_URL,
17
+ });
18
+
19
+ // Target database connection
20
+ this.targetPool = new pg.Pool({
21
+ connectionString: process.env.DATABASE_URL,
22
+ });
23
+ }
24
+
25
+ async fixDataTypes() {
26
+ try {
27
+ this.logger.log("Starting data type fixing process");
28
+
29
+ // Get all schemas (tenants) from the target database
30
+ const schemasQuery = `
31
+ SELECT schema_name
32
+ FROM information_schema.schemata
33
+ WHERE schema_name NOT IN ('public', 'information_schema', 'pg_catalog', 'pg_toast')
34
+ AND schema_name NOT LIKE 'pg_%';
35
+ `;
36
+
37
+ const schemasResult = await this.targetPool.query(schemasQuery);
38
+ const schemas = schemasResult.rows.map(row => row.schema_name);
39
+
40
+ this.logger.log(`Found ${schemas.length} schemas to process`);
41
+
42
+ // Process each schema
43
+ for (const schema of schemas) {
44
+ await this.fixNumericFieldsForSchema(schema);
45
+ await this.fixEnumFieldsForSchema(schema);
46
+ }
47
+
48
+ this.logger.log("Data type fixing completed successfully");
49
+ } catch (error) {
50
+ this.logger.error(
51
+ `Error during data type fixing: ${error.message}`,
52
+ error.stack
53
+ );
54
+ } finally {
55
+ await this.cleanup();
56
+ }
57
+ }
58
+
59
+ private async fixNumericFieldsForSchema(schema: string) {
60
+ this.logger.log(`Processing numeric fields for schema: ${schema}`);
61
+
62
+ try {
63
+ // Get all tables with numeric columns in this schema
64
+ const numericColumnsQuery = `
65
+ SELECT
66
+ table_name,
67
+ column_name
68
+ FROM
69
+ information_schema.columns
70
+ WHERE
71
+ table_schema = $1
72
+ AND data_type = 'numeric'
73
+ `;
74
+
75
+ const numericColumnsResult = await this.targetPool.query(numericColumnsQuery, [schema]);
76
+
77
+ // Process each table with numeric columns
78
+ for (const row of numericColumnsResult.rows) {
79
+ const tableName = row.table_name;
80
+ const columnName = row.column_name;
81
+
82
+ this.logger.log(`Fixing numeric column ${columnName} in table ${schema}.${tableName}`);
83
+
84
+ // Update the column to cast text values to numeric
85
+ const updateQuery = `
86
+ UPDATE "${schema}"."${tableName}"
87
+ SET "${columnName}" = CASE
88
+ WHEN "${columnName}" IS NULL THEN NULL
89
+ WHEN "${columnName}" = '' THEN NULL
90
+ ELSE CAST("${columnName}" AS NUMERIC)
91
+ END
92
+ WHERE "${columnName}" IS NOT NULL
93
+ AND "${columnName}" != ''
94
+ AND "${columnName}" ~ '^[0-9]+(\.[0-9]+)?$';
95
+ `;
96
+
97
+ try {
98
+ await this.targetPool.query(updateQuery);
99
+ this.logger.log(`Successfully fixed numeric column ${columnName} in table ${schema}.${tableName}`);
100
+ } catch (updateError) {
101
+ this.logger.error(
102
+ `Error fixing numeric column ${columnName} in table ${schema}.${tableName}: ${updateError.message}`
103
+ );
104
+ }
105
+ }
106
+
107
+ this.logger.log(`Completed processing numeric fields for schema: ${schema}`);
108
+ } catch (error) {
109
+ this.logger.error(
110
+ `Error processing numeric fields for schema ${schema}: ${error.message}`,
111
+ error.stack
112
+ );
113
+ }
114
+ }
115
+
116
+ private async fixEnumFieldsForSchema(schema: string) {
117
+ this.logger.log(`Processing enum fields for schema: ${schema}`);
118
+
119
+ try {
120
+ // Get all tables with enum columns in this schema
121
+ const enumColumnsQuery = `
122
+ SELECT
123
+ c.table_name,
124
+ c.column_name,
125
+ c.udt_name
126
+ FROM
127
+ information_schema.columns c
128
+ WHERE
129
+ c.table_schema = $1
130
+ AND c.data_type = 'USER-DEFINED'
131
+ AND c.udt_name LIKE 'enum_%'
132
+ `;
133
+
134
+ const enumColumnsResult = await this.targetPool.query(enumColumnsQuery, [schema]);
135
+
136
+ // Process each table with enum columns
137
+ for (const row of enumColumnsResult.rows) {
138
+ const tableName = row.table_name;
139
+ const columnName = row.column_name;
140
+ const enumTypeName = row.udt_name;
141
+
142
+ this.logger.log(`Fixing enum column ${columnName} (${enumTypeName}) in table ${schema}.${tableName}`);
143
+
144
+ // Get the valid enum values
145
+ const enumValuesQuery = `
146
+ SELECT e.enumlabel
147
+ FROM pg_enum e
148
+ JOIN pg_type t ON e.enumtypid = t.oid
149
+ WHERE t.typname = $1
150
+ ORDER BY e.enumsortorder
151
+ `;
152
+
153
+ const enumValuesResult = await this.targetPool.query(enumValuesQuery, [enumTypeName]);
154
+ const validEnumValues = enumValuesResult.rows.map(r => r.enumlabel);
155
+
156
+ if (validEnumValues.length === 0) {
157
+ this.logger.warn(`No enum values found for type ${enumTypeName}. Skipping.`);
158
+ continue;
159
+ }
160
+
161
+ this.logger.log(`Valid values for ${enumTypeName}: ${validEnumValues.join(', ')}`);
162
+
163
+ // Get current values in the column
164
+ const currentValuesQuery = `
165
+ SELECT DISTINCT "${columnName}"
166
+ FROM "${schema}"."${tableName}"
167
+ WHERE "${columnName}" IS NOT NULL
168
+ `;
169
+
170
+ const currentValuesResult = await this.targetPool.query(currentValuesQuery);
171
+ const currentValues = currentValuesResult.rows.map(r => r[columnName]);
172
+
173
+ // Find invalid values
174
+ const invalidValues = currentValues.filter(val => !validEnumValues.includes(val));
175
+
176
+ if (invalidValues.length > 0) {
177
+ this.logger.log(`Found invalid values for enum ${enumTypeName}: ${invalidValues.join(', ')}`);
178
+
179
+ // For each invalid value, try to map it to a valid one or set to NULL
180
+ for (const invalidValue of invalidValues) {
181
+ // Try to find a case-insensitive match
182
+ const matchingValidValue = validEnumValues.find(
183
+ v => v.toLowerCase() === invalidValue.toLowerCase()
184
+ );
185
+
186
+ let updateQuery;
187
+ if (matchingValidValue) {
188
+ // If we found a case-insensitive match, use it
189
+ updateQuery = `
190
+ UPDATE "${schema}"."${tableName}"
191
+ SET "${columnName}" = $1
192
+ WHERE "${columnName}" = $2
193
+ `;
194
+ await this.targetPool.query(updateQuery, [matchingValidValue, invalidValue]);
195
+ this.logger.log(`Updated invalid value "${invalidValue}" to "${matchingValidValue}"`);
196
+ } else {
197
+ // Otherwise set to NULL or default value
198
+ updateQuery = `
199
+ UPDATE "${schema}"."${tableName}"
200
+ SET "${columnName}" = NULL
201
+ WHERE "${columnName}" = $1
202
+ `;
203
+ await this.targetPool.query(updateQuery, [invalidValue]);
204
+ this.logger.log(`Set invalid value "${invalidValue}" to NULL`);
205
+ }
206
+ }
207
+ } else {
208
+ this.logger.log(`No invalid enum values found for ${columnName} in ${schema}.${tableName}`);
209
+ }
210
+ }
211
+
212
+ this.logger.log(`Completed processing enum fields for schema: ${schema}`);
213
+ } catch (error) {
214
+ this.logger.error(
215
+ `Error processing enum fields for schema ${schema}: ${error.message}`,
216
+ error.stack
217
+ );
218
+ }
219
+ }
220
+
221
+ private async cleanup() {
222
+ this.logger.log("Cleaning up database connections");
223
+ await this.sourcePool.end();
224
+ await this.targetPool.end();
225
+ }
226
+ }
227
+
228
+ // Script para ejecutar desde línea de comandos
229
+ if (require.main === module) {
230
+ const run = async () => {
231
+ try {
232
+ const fixer = new DataTypeFixer();
233
+ await fixer.fixDataTypes();
234
+ process.exit(0);
235
+ } catch (error) {
236
+ console.error("Error:", error.message);
237
+ process.exit(1);
238
+ }
239
+ };
240
+
241
+ run();
242
+ }
@@ -0,0 +1,357 @@
1
+ import * as pg from "pg";
2
+ import * as dotenv from "dotenv";
3
+ import { Logger } from "@nestjs/common";
4
+ import * as fs from "fs";
5
+ import * as path from "path";
6
+
7
+ dotenv.config();
8
+
9
+ export class EnumFixer {
10
+ private readonly logger = new Logger("EnumFixer");
11
+ private readonly pool: pg.Pool;
12
+ private enumValuesCache: Record<string, string[]> = {};
13
+ // Fix the type definition to match how it's actually used
14
+ private mappingsCache: Record<string, string | null> = {};
15
+ private readonly mappingsFilePath: string;
16
+
17
+ constructor(
18
+ private readonly databaseUrl: string = process.env.DATABASE_URL,
19
+ private readonly saveMapping: boolean = true
20
+ ) {
21
+ this.pool = new pg.Pool({
22
+ connectionString: this.databaseUrl,
23
+ });
24
+
25
+ // Crear directorio para mappings si no existe
26
+ const mappingsDir = path.join(process.cwd(), "migration-logs");
27
+ if (!fs.existsSync(mappingsDir)) {
28
+ fs.mkdirSync(mappingsDir, { recursive: true });
29
+ }
30
+
31
+ // Archivo para guardar los mappings
32
+ const timestamp = new Date()
33
+ .toISOString()
34
+ .replace(/:/g, "-")
35
+ .replace(/\..+/, "");
36
+ this.mappingsFilePath = path.join(
37
+ mappingsDir,
38
+ `enum-mappings-${timestamp}.json`
39
+ );
40
+
41
+ // Inicializar archivo de mappings
42
+ if (this.saveMapping) {
43
+ this.saveMappings();
44
+ }
45
+ }
46
+
47
+ async fixEnumValues() {
48
+ try {
49
+ this.logger.log("Starting enum values fixing process");
50
+
51
+ // Obtener todos los schemas excepto los del sistema
52
+ const schemas = await this.getSchemas();
53
+ this.logger.log(`Found ${schemas.length} schemas to process`);
54
+
55
+ // Para cada schema, procesar sus campos enum
56
+ for (const schema of schemas) {
57
+ await this.fixEnumValuesForSchema(schema);
58
+ }
59
+
60
+ // Guardar los mappings finales
61
+ if (this.saveMapping) {
62
+ this.saveMappings();
63
+ this.logger.log(`Enum mappings saved to: ${this.mappingsFilePath}`);
64
+ }
65
+
66
+ this.logger.log("Enum values fixing completed successfully");
67
+ } catch (error) {
68
+ this.logger.error(
69
+ `Error during enum values fixing: ${error.message}`,
70
+ error.stack
71
+ );
72
+ } finally {
73
+ await this.cleanup();
74
+ }
75
+ }
76
+
77
+ private async getSchemas(): Promise<string[]> {
78
+ const result = await this.pool.query(`
79
+ SELECT schema_name
80
+ FROM information_schema.schemata
81
+ WHERE schema_name NOT IN ('information_schema', 'pg_catalog', 'pg_toast')
82
+ AND schema_name NOT LIKE 'pg_%'
83
+ `);
84
+
85
+ return result.rows.map((row) => row.schema_name);
86
+ }
87
+
88
+ private async fixEnumValuesForSchema(schema: string) {
89
+ this.logger.log(`Processing enum fields for schema: ${schema}`);
90
+
91
+ try {
92
+ // Obtener todas las columnas enum en este schema
93
+ const enumColumnsQuery = `
94
+ SELECT
95
+ c.table_name,
96
+ c.column_name,
97
+ c.udt_name
98
+ FROM
99
+ information_schema.columns c
100
+ WHERE
101
+ c.table_schema = $1
102
+ AND c.data_type = 'USER-DEFINED'
103
+ AND c.udt_name LIKE 'enum_%'
104
+ `;
105
+
106
+ const enumColumnsResult = await this.pool.query(enumColumnsQuery, [
107
+ schema,
108
+ ]);
109
+
110
+ if (enumColumnsResult.rows.length === 0) {
111
+ this.logger.log(`No enum fields found in schema: ${schema}`);
112
+ return;
113
+ }
114
+
115
+ this.logger.log(
116
+ `Found ${enumColumnsResult.rows.length} enum fields in schema: ${schema}`
117
+ );
118
+
119
+ // Procesar cada columna enum
120
+ for (const row of enumColumnsResult.rows) {
121
+ const tableName = row.table_name;
122
+ const columnName = row.column_name;
123
+ const enumTypeName = row.udt_name;
124
+
125
+ await this.fixEnumColumn(schema, tableName, columnName, enumTypeName);
126
+ }
127
+
128
+ this.logger.log(`Completed processing enum fields for schema: ${schema}`);
129
+ } catch (error) {
130
+ this.logger.error(
131
+ `Error processing enum fields for schema ${schema}: ${error.message}`,
132
+ error.stack
133
+ );
134
+ }
135
+ }
136
+
137
+ private async fixEnumColumn(
138
+ schema: string,
139
+ tableName: string,
140
+ columnName: string,
141
+ enumTypeName: string
142
+ ) {
143
+ this.logger.log(
144
+ `Fixing enum column ${columnName} (${enumTypeName}) in table ${schema}.${tableName}`
145
+ );
146
+
147
+ try {
148
+ // Obtener los valores válidos para el enum
149
+ let validEnumValues: string[];
150
+ if (this.enumValuesCache[enumTypeName]) {
151
+ validEnumValues = this.enumValuesCache[enumTypeName];
152
+ } else {
153
+ validEnumValues = await this.getEnumValues(enumTypeName);
154
+ this.enumValuesCache[enumTypeName] = validEnumValues;
155
+ }
156
+
157
+ if (validEnumValues.length === 0) {
158
+ this.logger.warn(
159
+ `No enum values found for type ${enumTypeName}. Skipping.`
160
+ );
161
+ return;
162
+ }
163
+
164
+ this.logger.log(
165
+ `Valid values for ${enumTypeName}: ${validEnumValues.join(", ")}`
166
+ );
167
+
168
+ // Obtener valores actuales en la columna
169
+ const currentValuesQuery = `
170
+ SELECT DISTINCT "${columnName}"
171
+ FROM "${schema}"."${tableName}"
172
+ WHERE "${columnName}" IS NOT NULL
173
+ `;
174
+
175
+ const currentValuesResult = await this.pool.query(currentValuesQuery);
176
+ const currentValues = currentValuesResult.rows.map((r) => r[columnName]);
177
+
178
+ // Encontrar valores inválidos
179
+ const invalidValues = currentValues.filter(
180
+ (val) => !validEnumValues.includes(val)
181
+ );
182
+
183
+ if (invalidValues.length === 0) {
184
+ this.logger.log(
185
+ `No invalid values found for enum column ${columnName} in table ${schema}.${tableName}`
186
+ );
187
+ return;
188
+ }
189
+
190
+ this.logger.log(
191
+ `Found ${
192
+ invalidValues.length
193
+ } invalid values for enum ${enumTypeName} in ${schema}.${tableName}: ${invalidValues.join(
194
+ ", "
195
+ )}`
196
+ );
197
+
198
+ // Para cada valor inválido, intentar mapearlo a uno válido o establecerlo a NULL
199
+ for (const invalidValue of invalidValues) {
200
+ // Intentar encontrar un mapping existente
201
+ const mappingKey = `${enumTypeName}:${invalidValue}`;
202
+ let mappedValue: string | null = null;
203
+
204
+ if (this.mappingsCache[mappingKey]) {
205
+ mappedValue = this.mappingsCache[mappingKey];
206
+ this.logger.log(
207
+ `Using cached mapping for ${invalidValue} -> ${mappedValue}`
208
+ );
209
+ } else {
210
+ // Intentar encontrar una coincidencia sin distinción de mayúsculas/minúsculas
211
+ const matchingValidValue = validEnumValues.find(
212
+ (v) => v.toLowerCase() === invalidValue.toLowerCase()
213
+ );
214
+
215
+ if (matchingValidValue) {
216
+ mappedValue = matchingValidValue;
217
+ this.logger.log(
218
+ `Found case-insensitive match for ${invalidValue} -> ${mappedValue}`
219
+ );
220
+ } else {
221
+ // Intentar encontrar una coincidencia parcial
222
+ const similarValues = validEnumValues.filter(
223
+ (v) =>
224
+ v.toLowerCase().includes(invalidValue.toLowerCase()) ||
225
+ invalidValue.toLowerCase().includes(v.toLowerCase())
226
+ );
227
+
228
+ if (similarValues.length === 1) {
229
+ mappedValue = similarValues[0];
230
+ this.logger.log(
231
+ `Found partial match for ${invalidValue} -> ${mappedValue}`
232
+ );
233
+ } else if (similarValues.length > 1) {
234
+ // Si hay múltiples coincidencias, usar la más cercana por longitud
235
+ mappedValue = similarValues.reduce((prev, curr) =>
236
+ Math.abs(curr.length - invalidValue.length) <
237
+ Math.abs(prev.length - invalidValue.length)
238
+ ? curr
239
+ : prev
240
+ );
241
+ this.logger.log(
242
+ `Found multiple partial matches for ${invalidValue}, using closest: ${mappedValue}`
243
+ );
244
+ } else {
245
+ // Si no hay coincidencias, usar NULL
246
+ mappedValue = null;
247
+ this.logger.log(
248
+ `No match found for ${invalidValue}, will set to NULL`
249
+ );
250
+ }
251
+ }
252
+
253
+ // Guardar el mapping para uso futuro
254
+ this.mappingsCache[mappingKey] = mappedValue;
255
+ if (this.saveMapping) {
256
+ this.saveMappings();
257
+ }
258
+ }
259
+
260
+ // Actualizar los registros con el valor mapeado usando CAST explícito
261
+ const updateQuery =
262
+ mappedValue !== null
263
+ ? `
264
+ UPDATE "${schema}"."${tableName}"
265
+ SET "${columnName}" = CAST($1 AS "${enumTypeName}")
266
+ WHERE "${columnName}" = $2
267
+ `
268
+ : `
269
+ UPDATE "${schema}"."${tableName}"
270
+ SET "${columnName}" = NULL
271
+ WHERE "${columnName}" = $1
272
+ `;
273
+
274
+ const updateParams =
275
+ mappedValue !== null ? [mappedValue, invalidValue] : [invalidValue];
276
+
277
+ const updateResult = await this.pool.query(updateQuery, updateParams);
278
+
279
+ this.logger.log(
280
+ `Updated ${
281
+ updateResult.rowCount
282
+ } rows in ${schema}.${tableName} with ${invalidValue} -> ${
283
+ mappedValue || "NULL"
284
+ } with explicit CAST`
285
+ );
286
+ }
287
+ } catch (error) {
288
+ this.logger.error(
289
+ `Error fixing enum column ${columnName} in table ${schema}.${tableName}: ${error.message}`,
290
+ error.stack
291
+ );
292
+ }
293
+ }
294
+
295
+ private async getEnumValues(enumTypeName: string): Promise<string[]> {
296
+ try {
297
+ const enumQuery = `
298
+ SELECT e.enumlabel
299
+ FROM pg_enum e
300
+ JOIN pg_type t ON e.enumtypid = t.oid
301
+ WHERE t.typname = $1
302
+ ORDER BY e.enumsortorder
303
+ `;
304
+
305
+ const result = await this.pool.query(enumQuery, [enumTypeName]);
306
+ return result.rows.map((row) => row.enumlabel);
307
+ } catch (error) {
308
+ this.logger.error(
309
+ `Error getting enum values for ${enumTypeName}: ${error.message}`
310
+ );
311
+ return [];
312
+ }
313
+ }
314
+
315
+ private saveMappings(): void {
316
+ try {
317
+ fs.writeFileSync(
318
+ this.mappingsFilePath,
319
+ JSON.stringify(this.mappingsCache, null, 2),
320
+ "utf8"
321
+ );
322
+ } catch (error) {
323
+ this.logger.error(`Error saving enum mappings: ${error.message}`);
324
+ }
325
+ }
326
+
327
+ private async cleanup() {
328
+ this.logger.log("Cleaning up database connections");
329
+ await this.pool.end();
330
+ }
331
+ }
332
+
333
+ // Script para ejecutar desde línea de comandos
334
+ if (require.main === module) {
335
+ const run = async () => {
336
+ try {
337
+ // Obtener la URL de la base de datos desde los argumentos o .env
338
+ const databaseUrl = process.argv[2] || process.env.DATABASE_URL;
339
+
340
+ if (!databaseUrl) {
341
+ console.error(
342
+ "Error: No database URL provided. Please provide a database URL as an argument or set DATABASE_URL environment variable."
343
+ );
344
+ process.exit(1);
345
+ }
346
+
347
+ const enumFixer = new EnumFixer(databaseUrl);
348
+ await enumFixer.fixEnumValues();
349
+ process.exit(0);
350
+ } catch (error) {
351
+ console.error("Error:", error.message);
352
+ process.exit(1);
353
+ }
354
+ };
355
+
356
+ run();
357
+ }