@javalabs/prisma-client 1.0.27 → 1.0.29

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 (50) hide show
  1. package/.github/CODEOWNERS +1 -1
  2. package/README.md +269 -269
  3. package/migration-config.json +63 -63
  4. package/migration-config.json.bk +95 -95
  5. package/migrations/add_reserved_amount.sql +7 -7
  6. package/package.json +44 -44
  7. package/prisma/migrations/add_uuid_to_transactions.sql +13 -13
  8. package/prisma/schema.prisma +609 -601
  9. package/src/index.ts +23 -23
  10. package/src/prisma-factory.service.ts +40 -40
  11. package/src/prisma.module.ts +9 -9
  12. package/src/prisma.service.ts +16 -16
  13. package/src/scripts/add-uuid-to-table.ts +138 -138
  14. package/src/scripts/create-tenant-schemas.ts +145 -145
  15. package/src/scripts/data-migration/batch-migrator.ts +248 -248
  16. package/src/scripts/data-migration/data-transformer.ts +426 -426
  17. package/src/scripts/data-migration/db-connector.ts +120 -120
  18. package/src/scripts/data-migration/dependency-resolver.ts +174 -174
  19. package/src/scripts/data-migration/entity-discovery.ts +196 -196
  20. package/src/scripts/data-migration/foreign-key-manager.ts +277 -277
  21. package/src/scripts/data-migration/migration-config.json +63 -63
  22. package/src/scripts/data-migration/migration-tool.ts +509 -509
  23. package/src/scripts/data-migration/schema-utils.ts +248 -248
  24. package/src/scripts/data-migration/tenant-migrator.ts +201 -201
  25. package/src/scripts/data-migration/typecast-manager.ts +193 -193
  26. package/src/scripts/data-migration/types.ts +113 -113
  27. package/src/scripts/database-initializer.ts +49 -49
  28. package/src/scripts/drop-database.ts +104 -104
  29. package/src/scripts/dump-source-db.sh +61 -61
  30. package/src/scripts/encrypt-user-passwords.ts +36 -36
  31. package/src/scripts/error-handler.ts +117 -117
  32. package/src/scripts/fix-data-types.ts +241 -241
  33. package/src/scripts/fix-enum-values.ts +357 -357
  34. package/src/scripts/fix-schema-discrepancies.ts +317 -317
  35. package/src/scripts/fix-table-indexes.ts +601 -601
  36. package/src/scripts/migrate-schema-structure.ts +90 -90
  37. package/src/scripts/migrate-uuid.ts +76 -76
  38. package/src/scripts/post-migration-validator.ts +526 -526
  39. package/src/scripts/pre-migration-validator.ts +610 -610
  40. package/src/scripts/reset-database.ts +263 -263
  41. package/src/scripts/retry-failed-migrations.ts +416 -416
  42. package/src/scripts/run-migration.ts +707 -707
  43. package/src/scripts/schema-sync.ts +128 -128
  44. package/src/scripts/sequence-sync-cli.ts +416 -416
  45. package/src/scripts/sequence-synchronizer.ts +127 -127
  46. package/src/scripts/sync-enum-types.ts +170 -170
  47. package/src/scripts/sync-enum-values.ts +563 -563
  48. package/src/scripts/truncate-database.ts +123 -123
  49. package/src/scripts/verify-migration-setup.ts +135 -135
  50. package/tsconfig.json +17 -17
@@ -1,120 +1,120 @@
1
- import { PrismaClient } from "@prisma/client";
2
- import * as pg from "pg";
3
- import * as dotenv from "dotenv";
4
- import { DatabaseConnections, DatabaseConnection } from "./types";
5
-
6
- dotenv.config();
7
-
8
- export interface SourceDatabase {
9
- id: string;
10
- url: string;
11
- name: string;
12
- }
13
-
14
- export class DatabaseConnector {
15
- static createConnections(
16
- sourceDatabases?: SourceDatabase[]
17
- ): DatabaseConnections {
18
- // Crear conexiones para cada base de datos de origen
19
- const sourceConnections: DatabaseConnection[] = (sourceDatabases || []).map(
20
- (source): DatabaseConnection => {
21
- // Conexión de pool para la base de datos de origen
22
- const pool = new pg.Pool({
23
- connectionString: source.url,
24
- });
25
-
26
- // Cliente Prisma para la base de datos de origen
27
- const prisma = new PrismaClient({
28
- datasources: {
29
- db: {
30
- url: source.url,
31
- },
32
- },
33
- });
34
-
35
- const connection: DatabaseConnection = {
36
- pool,
37
- prisma,
38
- sourceId: source.id,
39
- async query(sql: string, params?: any[]) {
40
- return pool.query(sql, params);
41
- },
42
- };
43
-
44
- return connection;
45
- }
46
- );
47
-
48
- const sourcePool = new pg.Pool({
49
- connectionString: process.env.SOURCE_DATABASE_URL,
50
- max: 20,
51
- idleTimeoutMillis: 30000,
52
- });
53
-
54
- // Source Prisma client
55
- const sourcePrisma = new PrismaClient({
56
- datasources: {
57
- db: {
58
- url: process.env.SOURCE_DATABASE_URL,
59
- },
60
- },
61
- });
62
-
63
- // Target database connection (our multi-tenant database)
64
- const targetPool = new pg.Pool({
65
- connectionString: process.env.DATABASE_URL,
66
- max: 20,
67
- idleTimeoutMillis: 30000,
68
- });
69
-
70
- // Target Prisma client
71
- const targetPrisma = new PrismaClient({
72
- datasources: {
73
- db: {
74
- url: process.env.DATABASE_URL,
75
- },
76
- },
77
- });
78
-
79
- return {
80
- sourceConnections,
81
- targetPrisma,
82
- targetPool,
83
- sourcePool,
84
- sourcePrisma,
85
- };
86
- }
87
-
88
- static createTenantPrismaClient(tenantId: string): PrismaClient {
89
- return new PrismaClient({
90
- datasources: {
91
- db: {
92
- url: `${process.env.DATABASE_URL}?schema=${tenantId}`,
93
- },
94
- },
95
- // Increase transaction timeout to 2 minutes
96
- transactionOptions: {
97
- maxWait: 120000, // 2 minutes max wait time
98
- timeout: 120000, // 2 minutes timeout
99
- },
100
- });
101
- }
102
-
103
- static async cleanup(connections: DatabaseConnections): Promise<void> {
104
- // Desconectar todas las conexiones de origen
105
- if (connections.sourceConnections) {
106
- for (const sourceConn of connections.sourceConnections) {
107
- if (sourceConn.pool) await sourceConn.pool.end();
108
- if (sourceConn.prisma) await sourceConn.prisma.$disconnect();
109
- }
110
- }
111
-
112
- // Desconectar conexiones de destino
113
- await connections.targetPrisma.$disconnect();
114
- await connections.targetPool.end();
115
-
116
- // Desconectar conexiones principales de origen si existen
117
- if (connections.sourcePrisma) await connections.sourcePrisma.$disconnect();
118
- if (connections.sourcePool) await connections.sourcePool.end();
119
- }
120
- }
1
+ import { PrismaClient } from "@prisma/client";
2
+ import * as pg from "pg";
3
+ import * as dotenv from "dotenv";
4
+ import { DatabaseConnections, DatabaseConnection } from "./types";
5
+
6
+ dotenv.config();
7
+
8
+ export interface SourceDatabase {
9
+ id: string;
10
+ url: string;
11
+ name: string;
12
+ }
13
+
14
+ export class DatabaseConnector {
15
+ static createConnections(
16
+ sourceDatabases?: SourceDatabase[]
17
+ ): DatabaseConnections {
18
+ // Crear conexiones para cada base de datos de origen
19
+ const sourceConnections: DatabaseConnection[] = (sourceDatabases || []).map(
20
+ (source): DatabaseConnection => {
21
+ // Conexión de pool para la base de datos de origen
22
+ const pool = new pg.Pool({
23
+ connectionString: source.url,
24
+ });
25
+
26
+ // Cliente Prisma para la base de datos de origen
27
+ const prisma = new PrismaClient({
28
+ datasources: {
29
+ db: {
30
+ url: source.url,
31
+ },
32
+ },
33
+ });
34
+
35
+ const connection: DatabaseConnection = {
36
+ pool,
37
+ prisma,
38
+ sourceId: source.id,
39
+ async query(sql: string, params?: any[]) {
40
+ return pool.query(sql, params);
41
+ },
42
+ };
43
+
44
+ return connection;
45
+ }
46
+ );
47
+
48
+ const sourcePool = new pg.Pool({
49
+ connectionString: process.env.SOURCE_DATABASE_URL,
50
+ max: 20,
51
+ idleTimeoutMillis: 30000,
52
+ });
53
+
54
+ // Source Prisma client
55
+ const sourcePrisma = new PrismaClient({
56
+ datasources: {
57
+ db: {
58
+ url: process.env.SOURCE_DATABASE_URL,
59
+ },
60
+ },
61
+ });
62
+
63
+ // Target database connection (our multi-tenant database)
64
+ const targetPool = new pg.Pool({
65
+ connectionString: process.env.DATABASE_URL,
66
+ max: 20,
67
+ idleTimeoutMillis: 30000,
68
+ });
69
+
70
+ // Target Prisma client
71
+ const targetPrisma = new PrismaClient({
72
+ datasources: {
73
+ db: {
74
+ url: process.env.DATABASE_URL,
75
+ },
76
+ },
77
+ });
78
+
79
+ return {
80
+ sourceConnections,
81
+ targetPrisma,
82
+ targetPool,
83
+ sourcePool,
84
+ sourcePrisma,
85
+ };
86
+ }
87
+
88
+ static createTenantPrismaClient(tenantId: string): PrismaClient {
89
+ return new PrismaClient({
90
+ datasources: {
91
+ db: {
92
+ url: `${process.env.DATABASE_URL}?schema=${tenantId}`,
93
+ },
94
+ },
95
+ // Increase transaction timeout to 2 minutes
96
+ transactionOptions: {
97
+ maxWait: 120000, // 2 minutes max wait time
98
+ timeout: 120000, // 2 minutes timeout
99
+ },
100
+ });
101
+ }
102
+
103
+ static async cleanup(connections: DatabaseConnections): Promise<void> {
104
+ // Desconectar todas las conexiones de origen
105
+ if (connections.sourceConnections) {
106
+ for (const sourceConn of connections.sourceConnections) {
107
+ if (sourceConn.pool) await sourceConn.pool.end();
108
+ if (sourceConn.prisma) await sourceConn.prisma.$disconnect();
109
+ }
110
+ }
111
+
112
+ // Desconectar conexiones de destino
113
+ await connections.targetPrisma.$disconnect();
114
+ await connections.targetPool.end();
115
+
116
+ // Desconectar conexiones principales de origen si existen
117
+ if (connections.sourcePrisma) await connections.sourcePrisma.$disconnect();
118
+ if (connections.sourcePool) await connections.sourcePool.end();
119
+ }
120
+ }
@@ -1,174 +1,174 @@
1
- import { Logger } from "@nestjs/common";
2
- import { Pool } from "pg";
3
-
4
- export class DependencyResolver {
5
- private readonly logger = new Logger("DependencyResolver");
6
- private readonly visitedTables = new Set<string>();
7
- private readonly temporaryMark = new Set<string>();
8
- private readonly tableOrder: string[] = [];
9
- private config: any;
10
-
11
- constructor(
12
- private readonly sourcePool: Pool,
13
- private readonly targetPool: Pool
14
- ) {}
15
-
16
- setConfig(config: any) {
17
- this.config = config;
18
- }
19
-
20
- async analyzeDependencies(): Promise<string[]> {
21
- try {
22
- this.logger.log("Iniciando análisis de dependencias...");
23
-
24
- // Obtener todas las tablas del esquema
25
- const tables = await this.getAllTables();
26
-
27
- // Resetear estado
28
- this.visitedTables.clear();
29
- this.temporaryMark.clear();
30
- this.tableOrder.length = 0;
31
-
32
- // Ordenar tablas por prioridad primero
33
- const priorityOrder = this.getPriorityOrder(tables);
34
-
35
- // Realizar ordenamiento topológico para cada grupo de prioridad
36
- for (const priorityGroup of priorityOrder) {
37
- for (const table of priorityGroup) {
38
- if (!this.visitedTables.has(table)) {
39
- await this.visit(table);
40
- }
41
- }
42
- }
43
-
44
- this.logger.log(
45
- `Orden de migración determinado: ${this.tableOrder.join(" -> ")}`
46
- );
47
- return this.tableOrder;
48
- } catch (error) {
49
- this.logger.error(`Error analizando dependencias: ${error.message}`);
50
- throw error;
51
- }
52
- }
53
-
54
- private getPriorityOrder(tables: string[]): string[][] {
55
- const priorities = {
56
- high: new Set(this.config?.migrationPriorities?.high || []),
57
- medium: new Set(this.config?.migrationPriorities?.medium || []),
58
- low: new Set(this.config?.migrationPriorities?.low || []),
59
- };
60
-
61
- // Agrupar tablas por prioridad
62
- const highPriority = tables.filter((t) => priorities.high.has(t));
63
- const mediumPriority = tables.filter((t) => priorities.medium.has(t));
64
- const lowPriority = tables.filter((t) => priorities.low.has(t));
65
- const noPriority = tables.filter(
66
- (t) =>
67
- !priorities.high.has(t) &&
68
- !priorities.medium.has(t) &&
69
- !priorities.low.has(t)
70
- );
71
-
72
- return [highPriority, mediumPriority, noPriority, lowPriority];
73
- }
74
-
75
- private async visit(table: string, path: string[] = []): Promise<void> {
76
- // Detectar ciclos
77
- if (this.temporaryMark.has(table)) {
78
- const cycle = [...path, table].join(" -> ");
79
- this.logger.warn(`Detectado ciclo de dependencias: ${cycle}`);
80
- return;
81
- }
82
-
83
- if (this.visitedTables.has(table)) {
84
- return;
85
- }
86
-
87
- this.temporaryMark.add(table);
88
- path.push(table);
89
-
90
- // Obtener dependencias tanto de la configuración como de la base de datos
91
- const dependencies = await this.getTableDependencies(table);
92
-
93
- for (const dep of dependencies) {
94
- if (!this.visitedTables.has(dep)) {
95
- await this.visit(dep, [...path]);
96
- }
97
- }
98
-
99
- this.temporaryMark.delete(table);
100
- this.visitedTables.add(table);
101
- this.tableOrder.push(table);
102
- }
103
-
104
- private async getAllTables(): Promise<string[]> {
105
- const query = `
106
- SELECT table_name
107
- FROM information_schema.tables
108
- WHERE table_schema = 'public'
109
- AND table_type = 'BASE TABLE'
110
- `;
111
-
112
- const result = await this.sourcePool.query(query);
113
- return result.rows.map((row) => row.table_name);
114
- }
115
-
116
- private async getTableDependencies(table: string): Promise<string[]> {
117
- const dependencies = new Set<string>();
118
-
119
- // Obtener dependencias de la configuración
120
- const configDeps = this.config?.tables?.[table]?.dependencies || [];
121
- configDeps.forEach((dep) => dependencies.add(dep));
122
-
123
- // Obtener dependencias de la base de datos
124
- const query = `
125
- SELECT
126
- ccu.table_name AS foreign_table
127
- FROM
128
- information_schema.table_constraints tc
129
- JOIN information_schema.constraint_column_usage ccu
130
- ON ccu.constraint_name = tc.constraint_name
131
- WHERE
132
- tc.constraint_type = 'FOREIGN KEY'
133
- AND tc.table_name = $1
134
- AND tc.table_schema = 'public'
135
- `;
136
-
137
- try {
138
- const result = await this.sourcePool.query(query, [table]);
139
- result.rows.forEach((row) => dependencies.add(row.foreign_table));
140
- } catch (error) {
141
- this.logger.warn(
142
- `Error obteniendo dependencias para ${table}: ${error.message}`
143
- );
144
- }
145
-
146
- return Array.from(dependencies);
147
- }
148
-
149
- async validateDependencies(
150
- table: string,
151
- dependencies: string[]
152
- ): Promise<boolean> {
153
- for (const dep of dependencies) {
154
- const query = `
155
- SELECT EXISTS (
156
- SELECT 1
157
- FROM information_schema.tables
158
- WHERE table_schema = 'public'
159
- AND table_name = $1
160
- )
161
- `;
162
-
163
- const result = await this.sourcePool.query(query, [dep]);
164
- if (!result.rows[0].exists) {
165
- this.logger.warn(
166
- `Dependencia ${dep} no encontrada para tabla ${table}`
167
- );
168
- return false;
169
- }
170
- }
171
-
172
- return true;
173
- }
174
- }
1
+ import { Logger } from "@nestjs/common";
2
+ import { Pool } from "pg";
3
+
4
+ export class DependencyResolver {
5
+ private readonly logger = new Logger("DependencyResolver");
6
+ private readonly visitedTables = new Set<string>();
7
+ private readonly temporaryMark = new Set<string>();
8
+ private readonly tableOrder: string[] = [];
9
+ private config: any;
10
+
11
+ constructor(
12
+ private readonly sourcePool: Pool,
13
+ private readonly targetPool: Pool
14
+ ) {}
15
+
16
+ setConfig(config: any) {
17
+ this.config = config;
18
+ }
19
+
20
+ async analyzeDependencies(): Promise<string[]> {
21
+ try {
22
+ this.logger.log("Iniciando análisis de dependencias...");
23
+
24
+ // Obtener todas las tablas del esquema
25
+ const tables = await this.getAllTables();
26
+
27
+ // Resetear estado
28
+ this.visitedTables.clear();
29
+ this.temporaryMark.clear();
30
+ this.tableOrder.length = 0;
31
+
32
+ // Ordenar tablas por prioridad primero
33
+ const priorityOrder = this.getPriorityOrder(tables);
34
+
35
+ // Realizar ordenamiento topológico para cada grupo de prioridad
36
+ for (const priorityGroup of priorityOrder) {
37
+ for (const table of priorityGroup) {
38
+ if (!this.visitedTables.has(table)) {
39
+ await this.visit(table);
40
+ }
41
+ }
42
+ }
43
+
44
+ this.logger.log(
45
+ `Orden de migración determinado: ${this.tableOrder.join(" -> ")}`
46
+ );
47
+ return this.tableOrder;
48
+ } catch (error) {
49
+ this.logger.error(`Error analizando dependencias: ${error.message}`);
50
+ throw error;
51
+ }
52
+ }
53
+
54
+ private getPriorityOrder(tables: string[]): string[][] {
55
+ const priorities = {
56
+ high: new Set(this.config?.migrationPriorities?.high || []),
57
+ medium: new Set(this.config?.migrationPriorities?.medium || []),
58
+ low: new Set(this.config?.migrationPriorities?.low || []),
59
+ };
60
+
61
+ // Agrupar tablas por prioridad
62
+ const highPriority = tables.filter((t) => priorities.high.has(t));
63
+ const mediumPriority = tables.filter((t) => priorities.medium.has(t));
64
+ const lowPriority = tables.filter((t) => priorities.low.has(t));
65
+ const noPriority = tables.filter(
66
+ (t) =>
67
+ !priorities.high.has(t) &&
68
+ !priorities.medium.has(t) &&
69
+ !priorities.low.has(t)
70
+ );
71
+
72
+ return [highPriority, mediumPriority, noPriority, lowPriority];
73
+ }
74
+
75
+ private async visit(table: string, path: string[] = []): Promise<void> {
76
+ // Detectar ciclos
77
+ if (this.temporaryMark.has(table)) {
78
+ const cycle = [...path, table].join(" -> ");
79
+ this.logger.warn(`Detectado ciclo de dependencias: ${cycle}`);
80
+ return;
81
+ }
82
+
83
+ if (this.visitedTables.has(table)) {
84
+ return;
85
+ }
86
+
87
+ this.temporaryMark.add(table);
88
+ path.push(table);
89
+
90
+ // Obtener dependencias tanto de la configuración como de la base de datos
91
+ const dependencies = await this.getTableDependencies(table);
92
+
93
+ for (const dep of dependencies) {
94
+ if (!this.visitedTables.has(dep)) {
95
+ await this.visit(dep, [...path]);
96
+ }
97
+ }
98
+
99
+ this.temporaryMark.delete(table);
100
+ this.visitedTables.add(table);
101
+ this.tableOrder.push(table);
102
+ }
103
+
104
+ private async getAllTables(): Promise<string[]> {
105
+ const query = `
106
+ SELECT table_name
107
+ FROM information_schema.tables
108
+ WHERE table_schema = 'public'
109
+ AND table_type = 'BASE TABLE'
110
+ `;
111
+
112
+ const result = await this.sourcePool.query(query);
113
+ return result.rows.map((row) => row.table_name);
114
+ }
115
+
116
+ private async getTableDependencies(table: string): Promise<string[]> {
117
+ const dependencies = new Set<string>();
118
+
119
+ // Obtener dependencias de la configuración
120
+ const configDeps = this.config?.tables?.[table]?.dependencies || [];
121
+ configDeps.forEach((dep) => dependencies.add(dep));
122
+
123
+ // Obtener dependencias de la base de datos
124
+ const query = `
125
+ SELECT
126
+ ccu.table_name AS foreign_table
127
+ FROM
128
+ information_schema.table_constraints tc
129
+ JOIN information_schema.constraint_column_usage ccu
130
+ ON ccu.constraint_name = tc.constraint_name
131
+ WHERE
132
+ tc.constraint_type = 'FOREIGN KEY'
133
+ AND tc.table_name = $1
134
+ AND tc.table_schema = 'public'
135
+ `;
136
+
137
+ try {
138
+ const result = await this.sourcePool.query(query, [table]);
139
+ result.rows.forEach((row) => dependencies.add(row.foreign_table));
140
+ } catch (error) {
141
+ this.logger.warn(
142
+ `Error obteniendo dependencias para ${table}: ${error.message}`
143
+ );
144
+ }
145
+
146
+ return Array.from(dependencies);
147
+ }
148
+
149
+ async validateDependencies(
150
+ table: string,
151
+ dependencies: string[]
152
+ ): Promise<boolean> {
153
+ for (const dep of dependencies) {
154
+ const query = `
155
+ SELECT EXISTS (
156
+ SELECT 1
157
+ FROM information_schema.tables
158
+ WHERE table_schema = 'public'
159
+ AND table_name = $1
160
+ )
161
+ `;
162
+
163
+ const result = await this.sourcePool.query(query, [dep]);
164
+ if (!result.rows[0].exists) {
165
+ this.logger.warn(
166
+ `Dependencia ${dep} no encontrada para tabla ${table}`
167
+ );
168
+ return false;
169
+ }
170
+ }
171
+
172
+ return true;
173
+ }
174
+ }