@javalabs/prisma-client 1.0.30 → 1.0.31

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 (80) hide show
  1. package/.github/CODEOWNERS +1 -1
  2. package/README.md +269 -269
  3. package/dist/scripts/add-uuid-to-table.js +32 -32
  4. package/dist/scripts/data-migration/batch-migrator.js +12 -12
  5. package/dist/scripts/data-migration/data-transformer.js +14 -14
  6. package/dist/scripts/data-migration/dependency-resolver.js +23 -23
  7. package/dist/scripts/data-migration/entity-discovery.js +68 -68
  8. package/dist/scripts/data-migration/foreign-key-manager.js +23 -23
  9. package/dist/scripts/data-migration/migration-tool.js +5 -5
  10. package/dist/scripts/data-migration/schema-utils.js +74 -74
  11. package/dist/scripts/data-migration/typecast-manager.js +4 -4
  12. package/dist/scripts/database-initializer.js +5 -5
  13. package/dist/scripts/drop-database.js +5 -5
  14. package/dist/scripts/fix-data-types.js +53 -53
  15. package/dist/scripts/fix-enum-values.js +34 -34
  16. package/dist/scripts/fix-schema-discrepancies.js +40 -40
  17. package/dist/scripts/fix-table-indexes.js +81 -81
  18. package/dist/scripts/migrate-schema-structure.js +4 -4
  19. package/dist/scripts/migrate-uuid.js +19 -19
  20. package/dist/scripts/post-migration-validator.js +49 -49
  21. package/dist/scripts/pre-migration-validator.js +107 -107
  22. package/dist/scripts/reset-database.js +21 -21
  23. package/dist/scripts/retry-failed-migrations.js +28 -28
  24. package/dist/scripts/run-migration.js +5 -5
  25. package/dist/scripts/schema-sync.js +18 -18
  26. package/dist/scripts/sequence-sync-cli.js +55 -55
  27. package/dist/scripts/sequence-synchronizer.js +20 -20
  28. package/dist/scripts/sync-enum-types.js +30 -30
  29. package/dist/scripts/sync-enum-values.js +52 -52
  30. package/dist/scripts/truncate-database.js +10 -10
  31. package/dist/scripts/verify-migration-setup.js +10 -10
  32. package/dist/tsconfig.tsbuildinfo +1 -1
  33. package/migration-config.json +63 -63
  34. package/migration-config.json.bk +95 -95
  35. package/migrations/add_reserved_amount.sql +7 -7
  36. package/package.json +44 -44
  37. package/prisma/migrations/add_uuid_to_transactions.sql +13 -13
  38. package/prisma/schema.prisma +636 -609
  39. package/src/index.ts +23 -23
  40. package/src/prisma-factory.service.ts +40 -40
  41. package/src/prisma.module.ts +9 -9
  42. package/src/prisma.service.ts +16 -16
  43. package/src/scripts/add-uuid-to-table.ts +138 -138
  44. package/src/scripts/create-tenant-schemas.ts +145 -145
  45. package/src/scripts/data-migration/batch-migrator.ts +248 -248
  46. package/src/scripts/data-migration/data-transformer.ts +426 -426
  47. package/src/scripts/data-migration/db-connector.ts +120 -120
  48. package/src/scripts/data-migration/dependency-resolver.ts +174 -174
  49. package/src/scripts/data-migration/entity-discovery.ts +196 -196
  50. package/src/scripts/data-migration/foreign-key-manager.ts +277 -277
  51. package/src/scripts/data-migration/migration-config.json +63 -63
  52. package/src/scripts/data-migration/migration-tool.ts +509 -509
  53. package/src/scripts/data-migration/schema-utils.ts +248 -248
  54. package/src/scripts/data-migration/tenant-migrator.ts +201 -201
  55. package/src/scripts/data-migration/typecast-manager.ts +193 -193
  56. package/src/scripts/data-migration/types.ts +113 -113
  57. package/src/scripts/database-initializer.ts +49 -49
  58. package/src/scripts/drop-database.ts +104 -104
  59. package/src/scripts/dump-source-db.sh +61 -61
  60. package/src/scripts/encrypt-user-passwords.ts +36 -36
  61. package/src/scripts/error-handler.ts +117 -117
  62. package/src/scripts/fix-data-types.ts +241 -241
  63. package/src/scripts/fix-enum-values.ts +357 -357
  64. package/src/scripts/fix-schema-discrepancies.ts +317 -317
  65. package/src/scripts/fix-table-indexes.ts +601 -601
  66. package/src/scripts/migrate-schema-structure.ts +90 -90
  67. package/src/scripts/migrate-uuid.ts +76 -76
  68. package/src/scripts/post-migration-validator.ts +526 -526
  69. package/src/scripts/pre-migration-validator.ts +610 -610
  70. package/src/scripts/reset-database.ts +263 -263
  71. package/src/scripts/retry-failed-migrations.ts +416 -416
  72. package/src/scripts/run-migration.ts +707 -707
  73. package/src/scripts/schema-sync.ts +128 -128
  74. package/src/scripts/sequence-sync-cli.ts +416 -416
  75. package/src/scripts/sequence-synchronizer.ts +127 -127
  76. package/src/scripts/sync-enum-types.ts +170 -170
  77. package/src/scripts/sync-enum-values.ts +563 -563
  78. package/src/scripts/truncate-database.ts +123 -123
  79. package/src/scripts/verify-migration-setup.ts +135 -135
  80. package/tsconfig.json +17 -17
@@ -1,426 +1,426 @@
1
- import { Logger } from "@nestjs/common";
2
- import { ColumnSchema, EnumCastValue } from "./types";
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";
11
-
12
- export class DataTransformer {
13
- private readonly logger = new Logger("DataTransformer");
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
- }
32
-
33
- async validateEnumValue(
34
- tableName: string,
35
- columnName: string,
36
- value: string | null,
37
- isNullable: boolean = true
38
- ): Promise<string | null> {
39
- try {
40
- if (value === null) {
41
- return isNullable
42
- ? null
43
- : await this.getDefaultEnumValue(tableName, columnName);
44
- }
45
-
46
- const enumTypeName = await this.getEnumTypeName(tableName, columnName);
47
- const normalizedValue = value.toString().trim().toLowerCase();
48
-
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);
52
-
53
- if (!validValues || !validValues.rows) {
54
- this.logger.warn(
55
- `No se pudieron obtener valores válidos para el enum ${enumTypeName}`
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();
67
- }
68
-
69
- // Verificar coincidencia parcial
70
- const partialMatch = enumValues.find(
71
- (enumValue) =>
72
- enumValue.includes(normalizedValue) ||
73
- normalizedValue.includes(enumValue)
74
- );
75
-
76
- if (partialMatch) {
77
- this.logger.debug(
78
- `Valor enum corregido: ${value} -> ${partialMatch} para ${tableName}.${columnName}`
79
- );
80
- return partialMatch;
81
- }
82
-
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;
90
- }
91
-
92
- if (!isNullable) {
93
- throw new Error(
94
- `No se pudo encontrar un valor válido para el enum ${enumTypeName}`
95
- );
96
- }
97
-
98
- return null;
99
- } catch (error) {
100
- this.logger.error(
101
- `Error validating enum value for ${tableName}.${columnName}: ${error.message}`
102
- );
103
- if (!isNullable) {
104
- return await this.getDefaultEnumValue(tableName, columnName);
105
- }
106
- return null;
107
- }
108
- }
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
-
134
- transformToNumeric(value: any): number | null {
135
- if (value === null || value === "") {
136
- return null;
137
- }
138
-
139
- const numericValue = parseFloat(value);
140
- if (isNaN(numericValue)) {
141
- return null;
142
- }
143
-
144
- return numericValue;
145
- }
146
-
147
- private async getDefaultEnumValue(
148
- enumType: string,
149
- tenantId: string = "public"
150
- ): Promise<string | null> {
151
- try {
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
165
- const query = `
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
- `;
174
-
175
- const result = await this.schemaUtils.queryTargetDb(query, [
176
- enumType,
177
- tenantId,
178
- ]);
179
-
180
- if (result.rows.length > 0) {
181
- return result.rows[0].enumlabel;
182
- }
183
-
184
- return null;
185
- } catch (error) {
186
- this.logger.error(
187
- `Error getting default enum value for ${enumType}: ${error.message}`
188
- );
189
- return null;
190
- }
191
- }
192
-
193
- async prepareEnumValue(
194
- tenantId: string,
195
- enumType: string,
196
- value: string
197
- ): Promise<EnumCastValue | null> {
198
- if (value === null || value === undefined || value === "") {
199
- return null;
200
- }
201
-
202
- const validatedValue = await this.validateEnumValue(
203
- tenantId,
204
- enumType,
205
- value
206
- );
207
-
208
- if (validatedValue === null) {
209
- // Obtener valor por defecto dinámicamente
210
- const defaultValue = await this.getDefaultEnumValue(enumType, tenantId);
211
- if (defaultValue) {
212
- this.logger.warn(
213
- `Using first enum value '${defaultValue}' as default for invalid value '${value}' of type ${enumType}`
214
- );
215
- return {
216
- needsEnumCast: true,
217
- value: defaultValue,
218
- enumType,
219
- };
220
- }
221
- return null;
222
- }
223
-
224
- return {
225
- needsEnumCast: true,
226
- value: validatedValue,
227
- enumType,
228
- };
229
- }
230
-
231
- async transformColumnValue(
232
- value: any,
233
- columnName: string,
234
- targetColumn: ColumnSchema & { source_type?: string },
235
- tenantId: string
236
- ): Promise<any> {
237
- if (value === null || value === undefined) {
238
- return targetColumn.is_nullable === "YES"
239
- ? null
240
- : this.getDefaultValue(targetColumn);
241
- }
242
-
243
- try {
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
250
- );
251
- }
252
-
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";
267
- }
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;
305
- }
306
- } catch (error) {
307
- this.logger.warn(
308
- `Error transformando valor para columna ${columnName}: ${error.message}`
309
- );
310
- return targetColumn.is_nullable === "YES"
311
- ? null
312
- : this.getDefaultValue(targetColumn);
313
- }
314
- }
315
-
316
- private getDefaultValue(column: ColumnSchema): any {
317
- switch (column.data_type.toLowerCase()) {
318
- case "integer":
319
- case "bigint":
320
- case "smallint":
321
- return 0;
322
- case "numeric":
323
- case "decimal":
324
- case "real":
325
- case "double precision":
326
- return 0.0;
327
- case "boolean":
328
- return false;
329
- case "text":
330
- case "character varying":
331
- case "varchar":
332
- case "char":
333
- return "";
334
- case "json":
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];
344
- default:
345
- return null;
346
- }
347
- }
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
-
387
- async transformRecord(
388
- record: any,
389
- sourceSchema: ColumnSchema[],
390
- targetSchema: ColumnSchema[],
391
- tenantId: string
392
- ): Promise<any> {
393
- const transformedData: any = {};
394
-
395
- for (const [key, value] of Object.entries(record)) {
396
- // Primero intentar encontrar una coincidencia exacta
397
- let targetColumn = targetSchema.find((col) => col.column_name === key);
398
-
399
- // Si no se encuentra coincidencia exacta, intentar case-insensitive
400
- if (!targetColumn) {
401
- targetColumn = targetSchema.find(
402
- (col) => col.column_name.toLowerCase() === key.toLowerCase()
403
- );
404
- }
405
-
406
- if (!targetColumn) {
407
- this.logger.warn(`Column ${key} not found in target schema, skipping`);
408
- continue;
409
- }
410
-
411
- // Usar el nombre exacto de la columna del schema destino
412
- const columnName = targetColumn.column_name;
413
-
414
- const transformedValue = await this.transformColumnValue(
415
- value,
416
- columnName,
417
- targetColumn,
418
- tenantId
419
- );
420
-
421
- transformedData[columnName] = transformedValue;
422
- }
423
-
424
- return transformedData;
425
- }
426
- }
1
+ import { Logger } from "@nestjs/common";
2
+ import { ColumnSchema, EnumCastValue } from "./types";
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";
11
+
12
+ export class DataTransformer {
13
+ private readonly logger = new Logger("DataTransformer");
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
+ }
32
+
33
+ async validateEnumValue(
34
+ tableName: string,
35
+ columnName: string,
36
+ value: string | null,
37
+ isNullable: boolean = true
38
+ ): Promise<string | null> {
39
+ try {
40
+ if (value === null) {
41
+ return isNullable
42
+ ? null
43
+ : await this.getDefaultEnumValue(tableName, columnName);
44
+ }
45
+
46
+ const enumTypeName = await this.getEnumTypeName(tableName, columnName);
47
+ const normalizedValue = value.toString().trim().toLowerCase();
48
+
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);
52
+
53
+ if (!validValues || !validValues.rows) {
54
+ this.logger.warn(
55
+ `No se pudieron obtener valores válidos para el enum ${enumTypeName}`
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();
67
+ }
68
+
69
+ // Verificar coincidencia parcial
70
+ const partialMatch = enumValues.find(
71
+ (enumValue) =>
72
+ enumValue.includes(normalizedValue) ||
73
+ normalizedValue.includes(enumValue)
74
+ );
75
+
76
+ if (partialMatch) {
77
+ this.logger.debug(
78
+ `Valor enum corregido: ${value} -> ${partialMatch} para ${tableName}.${columnName}`
79
+ );
80
+ return partialMatch;
81
+ }
82
+
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;
90
+ }
91
+
92
+ if (!isNullable) {
93
+ throw new Error(
94
+ `No se pudo encontrar un valor válido para el enum ${enumTypeName}`
95
+ );
96
+ }
97
+
98
+ return null;
99
+ } catch (error) {
100
+ this.logger.error(
101
+ `Error validating enum value for ${tableName}.${columnName}: ${error.message}`
102
+ );
103
+ if (!isNullable) {
104
+ return await this.getDefaultEnumValue(tableName, columnName);
105
+ }
106
+ return null;
107
+ }
108
+ }
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
+
134
+ transformToNumeric(value: any): number | null {
135
+ if (value === null || value === "") {
136
+ return null;
137
+ }
138
+
139
+ const numericValue = parseFloat(value);
140
+ if (isNaN(numericValue)) {
141
+ return null;
142
+ }
143
+
144
+ return numericValue;
145
+ }
146
+
147
+ private async getDefaultEnumValue(
148
+ enumType: string,
149
+ tenantId: string = "public"
150
+ ): Promise<string | null> {
151
+ try {
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
165
+ const query = `
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
+ `;
174
+
175
+ const result = await this.schemaUtils.queryTargetDb(query, [
176
+ enumType,
177
+ tenantId,
178
+ ]);
179
+
180
+ if (result.rows.length > 0) {
181
+ return result.rows[0].enumlabel;
182
+ }
183
+
184
+ return null;
185
+ } catch (error) {
186
+ this.logger.error(
187
+ `Error getting default enum value for ${enumType}: ${error.message}`
188
+ );
189
+ return null;
190
+ }
191
+ }
192
+
193
+ async prepareEnumValue(
194
+ tenantId: string,
195
+ enumType: string,
196
+ value: string
197
+ ): Promise<EnumCastValue | null> {
198
+ if (value === null || value === undefined || value === "") {
199
+ return null;
200
+ }
201
+
202
+ const validatedValue = await this.validateEnumValue(
203
+ tenantId,
204
+ enumType,
205
+ value
206
+ );
207
+
208
+ if (validatedValue === null) {
209
+ // Obtener valor por defecto dinámicamente
210
+ const defaultValue = await this.getDefaultEnumValue(enumType, tenantId);
211
+ if (defaultValue) {
212
+ this.logger.warn(
213
+ `Using first enum value '${defaultValue}' as default for invalid value '${value}' of type ${enumType}`
214
+ );
215
+ return {
216
+ needsEnumCast: true,
217
+ value: defaultValue,
218
+ enumType,
219
+ };
220
+ }
221
+ return null;
222
+ }
223
+
224
+ return {
225
+ needsEnumCast: true,
226
+ value: validatedValue,
227
+ enumType,
228
+ };
229
+ }
230
+
231
+ async transformColumnValue(
232
+ value: any,
233
+ columnName: string,
234
+ targetColumn: ColumnSchema & { source_type?: string },
235
+ tenantId: string
236
+ ): Promise<any> {
237
+ if (value === null || value === undefined) {
238
+ return targetColumn.is_nullable === "YES"
239
+ ? null
240
+ : this.getDefaultValue(targetColumn);
241
+ }
242
+
243
+ try {
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
250
+ );
251
+ }
252
+
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";
267
+ }
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;
305
+ }
306
+ } catch (error) {
307
+ this.logger.warn(
308
+ `Error transformando valor para columna ${columnName}: ${error.message}`
309
+ );
310
+ return targetColumn.is_nullable === "YES"
311
+ ? null
312
+ : this.getDefaultValue(targetColumn);
313
+ }
314
+ }
315
+
316
+ private getDefaultValue(column: ColumnSchema): any {
317
+ switch (column.data_type.toLowerCase()) {
318
+ case "integer":
319
+ case "bigint":
320
+ case "smallint":
321
+ return 0;
322
+ case "numeric":
323
+ case "decimal":
324
+ case "real":
325
+ case "double precision":
326
+ return 0.0;
327
+ case "boolean":
328
+ return false;
329
+ case "text":
330
+ case "character varying":
331
+ case "varchar":
332
+ case "char":
333
+ return "";
334
+ case "json":
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];
344
+ default:
345
+ return null;
346
+ }
347
+ }
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
+
387
+ async transformRecord(
388
+ record: any,
389
+ sourceSchema: ColumnSchema[],
390
+ targetSchema: ColumnSchema[],
391
+ tenantId: string
392
+ ): Promise<any> {
393
+ const transformedData: any = {};
394
+
395
+ for (const [key, value] of Object.entries(record)) {
396
+ // Primero intentar encontrar una coincidencia exacta
397
+ let targetColumn = targetSchema.find((col) => col.column_name === key);
398
+
399
+ // Si no se encuentra coincidencia exacta, intentar case-insensitive
400
+ if (!targetColumn) {
401
+ targetColumn = targetSchema.find(
402
+ (col) => col.column_name.toLowerCase() === key.toLowerCase()
403
+ );
404
+ }
405
+
406
+ if (!targetColumn) {
407
+ this.logger.warn(`Column ${key} not found in target schema, skipping`);
408
+ continue;
409
+ }
410
+
411
+ // Usar el nombre exacto de la columna del schema destino
412
+ const columnName = targetColumn.column_name;
413
+
414
+ const transformedValue = await this.transformColumnValue(
415
+ value,
416
+ columnName,
417
+ targetColumn,
418
+ tenantId
419
+ );
420
+
421
+ transformedData[columnName] = transformedValue;
422
+ }
423
+
424
+ return transformedData;
425
+ }
426
+ }