@javalabs/prisma-client 1.0.18 → 1.0.19

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 (106) hide show
  1. package/.dockerignore +14 -0
  2. package/Dockerfile +23 -0
  3. package/README.md +269 -269
  4. package/dist/index.d.ts +1 -1
  5. package/dist/prisma.service.d.ts +1 -1
  6. package/dist/scripts/add-uuid-to-table.js +32 -32
  7. package/dist/scripts/data-migration/batch-migrator.js +12 -12
  8. package/dist/scripts/data-migration/data-transformer.js +14 -14
  9. package/dist/scripts/data-migration/dependency-resolver.js +23 -23
  10. package/dist/scripts/data-migration/entity-discovery.js +68 -68
  11. package/dist/scripts/data-migration/foreign-key-manager.js +23 -23
  12. package/dist/scripts/data-migration/migration-tool.js +5 -5
  13. package/dist/scripts/data-migration/schema-utils.js +74 -74
  14. package/dist/scripts/data-migration/typecast-manager.js +4 -4
  15. package/dist/scripts/database-initializer.js +5 -5
  16. package/dist/scripts/drop-database.js +5 -5
  17. package/dist/scripts/fix-data-types.js +53 -53
  18. package/dist/scripts/fix-enum-values.js +34 -34
  19. package/dist/scripts/fix-schema-discrepancies.js +40 -40
  20. package/dist/scripts/fix-table-indexes.js +81 -81
  21. package/dist/scripts/migrate-schema-structure.js +4 -4
  22. package/dist/scripts/migrate-uuid.js +19 -19
  23. package/dist/scripts/post-migration-validator.js +49 -49
  24. package/dist/scripts/pre-migration-validator.js +107 -107
  25. package/dist/scripts/reset-database.js +21 -21
  26. package/dist/scripts/retry-failed-migrations.js +28 -28
  27. package/dist/scripts/run-migration.js +5 -5
  28. package/dist/scripts/schema-sync.js +18 -18
  29. package/dist/scripts/sequence-sync-cli.js +55 -55
  30. package/dist/scripts/sequence-synchronizer.js +20 -20
  31. package/dist/scripts/sync-enum-types.js +30 -30
  32. package/dist/scripts/sync-enum-values.js +52 -52
  33. package/dist/scripts/truncate-database.js +10 -10
  34. package/dist/scripts/verify-migration-setup.js +10 -10
  35. package/dist/tsconfig.tsbuildinfo +1 -1
  36. package/migration-config.json +63 -63
  37. package/migration-config.json.bk +95 -95
  38. package/package.json +44 -44
  39. package/prisma/migrations/add_accepts_partial_payments_to_users.sql +19 -0
  40. package/prisma/migrations/add_amount_received_to_manual_payments.sql +19 -0
  41. package/prisma/migrations/add_commission_fields.sql +33 -0
  42. package/prisma/migrations/add_uuid_to_transactions.sql +13 -13
  43. package/prisma/migrations/complete_partial_payments_migration.sql +53 -0
  44. package/prisma/migrations/create_settlements_table.sql +60 -0
  45. package/prisma/schema.prisma +47 -1
  46. package/src/index.ts +23 -23
  47. package/src/prisma-factory.service.ts +40 -40
  48. package/src/prisma.module.ts +9 -9
  49. package/src/prisma.service.ts +16 -16
  50. package/src/scripts/add-uuid-to-table.ts +138 -138
  51. package/src/scripts/create-tenant-schemas.ts +145 -145
  52. package/src/scripts/data-migration/batch-migrator.ts +248 -248
  53. package/src/scripts/data-migration/data-transformer.ts +426 -426
  54. package/src/scripts/data-migration/db-connector.ts +120 -120
  55. package/src/scripts/data-migration/dependency-resolver.ts +174 -174
  56. package/src/scripts/data-migration/entity-discovery.ts +196 -196
  57. package/src/scripts/data-migration/foreign-key-manager.ts +277 -277
  58. package/src/scripts/data-migration/migration-config.json +63 -63
  59. package/src/scripts/data-migration/migration-tool.ts +509 -509
  60. package/src/scripts/data-migration/schema-utils.ts +248 -248
  61. package/src/scripts/data-migration/tenant-migrator.ts +201 -201
  62. package/src/scripts/data-migration/typecast-manager.ts +193 -193
  63. package/src/scripts/data-migration/types.ts +113 -113
  64. package/src/scripts/database-initializer.ts +49 -49
  65. package/src/scripts/drop-database.ts +104 -104
  66. package/src/scripts/dump-source-db.sh +61 -61
  67. package/src/scripts/encrypt-user-passwords.ts +36 -36
  68. package/src/scripts/error-handler.ts +117 -117
  69. package/src/scripts/fix-data-types.ts +241 -241
  70. package/src/scripts/fix-enum-values.ts +357 -357
  71. package/src/scripts/fix-schema-discrepancies.ts +317 -317
  72. package/src/scripts/fix-table-indexes.ts +601 -601
  73. package/src/scripts/migrate-schema-structure.ts +90 -90
  74. package/src/scripts/migrate-uuid.ts +76 -76
  75. package/src/scripts/post-migration-validator.ts +526 -526
  76. package/src/scripts/pre-migration-validator.ts +610 -610
  77. package/src/scripts/reset-database.ts +263 -263
  78. package/src/scripts/retry-failed-migrations.ts +416 -416
  79. package/src/scripts/run-migration.ts +707 -707
  80. package/src/scripts/schema-sync.ts +128 -128
  81. package/src/scripts/sequence-sync-cli.ts +416 -416
  82. package/src/scripts/sequence-synchronizer.ts +127 -127
  83. package/src/scripts/sync-enum-types.ts +170 -170
  84. package/src/scripts/sync-enum-values.ts +563 -563
  85. package/src/scripts/truncate-database.ts +123 -123
  86. package/src/scripts/verify-migration-setup.ts +135 -135
  87. package/tsconfig.json +17 -17
  88. package/dist/scripts/data-migration/dependency-manager.d.ts +0 -9
  89. package/dist/scripts/data-migration/dependency-manager.js +0 -86
  90. package/dist/scripts/data-migration/dependency-manager.js.map +0 -1
  91. package/dist/scripts/data-migration/migration-config.json +0 -63
  92. package/dist/scripts/data-migration/migration-phases.d.ts +0 -5
  93. package/dist/scripts/data-migration/migration-phases.js +0 -55
  94. package/dist/scripts/data-migration/migration-phases.js.map +0 -1
  95. package/dist/scripts/data-migration/multi-source-migrator.d.ts +0 -17
  96. package/dist/scripts/data-migration/multi-source-migrator.js +0 -130
  97. package/dist/scripts/data-migration/multi-source-migrator.js.map +0 -1
  98. package/dist/scripts/data-migration/phase-generator.d.ts +0 -15
  99. package/dist/scripts/data-migration/phase-generator.js +0 -187
  100. package/dist/scripts/data-migration/phase-generator.js.map +0 -1
  101. package/dist/scripts/data-migration.d.ts +0 -22
  102. package/dist/scripts/data-migration.js +0 -593
  103. package/dist/scripts/data-migration.js.map +0 -1
  104. package/dist/scripts/multi-db-migration.d.ts +0 -1
  105. package/dist/scripts/multi-db-migration.js +0 -55
  106. package/dist/scripts/multi-db-migration.js.map +0 -1
@@ -1,357 +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
- }
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
+ }