@cheetah.js/orm 0.1.8 → 0.1.10

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.
@@ -0,0 +1,287 @@
1
+ import { EntityStorage } from '../domain/entities';
2
+ import { ColDiff, ColumnsInfo, SnapshotTable, TableDiff } from '@cheetah.js/orm/driver/driver.interface';
3
+
4
+ export class DiffCalculator {
5
+ private entities: EntityStorage;
6
+
7
+ constructor(entities: EntityStorage) {
8
+ this.entities = entities;
9
+ }
10
+
11
+ diff(snapshotBd: SnapshotTable[], snapshotEntities: SnapshotTable[]): TableDiff[] {
12
+ let diffs: TableDiff[] = [];
13
+ // Cria um mapa (dicionário) para facilitar o acesso por nome da tabela
14
+ const bdTablesMap = new Map(snapshotBd.map(table => [table.tableName, table]));
15
+ const entityTablesMap = new Map(snapshotEntities.map(table => [table.tableName, table]));
16
+
17
+ // Junta todos os nomes de tabelas
18
+ const allTableNames = new Set([...bdTablesMap.keys(), ...entityTablesMap.keys()]);
19
+
20
+ allTableNames.forEach(tableName => {
21
+ const bdTable = bdTablesMap.get(tableName);
22
+ const entityTable = entityTablesMap.get(tableName);
23
+
24
+ if (!entityTable) {
25
+ // Se a tabela só está no banco de dados, precisamos deletá-la
26
+ diffs.push({
27
+ tableName,
28
+ colDiffs: [{actionType: 'DELETE', colName: '*'}], // Indica que todas as colunas devem ser deletadas (ou seja, a tabela inteira)
29
+ });
30
+ } else if (!bdTable) {
31
+ const colDiffs: ColDiff[] = entityTable.columns.flatMap(c => {
32
+ return this.createNewColumn(c, [])
33
+ });
34
+ this.checkIndexes(bdTable, entityTable, colDiffs)
35
+ // Se a tabela só está nas entidades, precisamos criá-la
36
+ diffs.push({
37
+ tableName,
38
+ newTable: true,
39
+ schema: entityTable.schema ?? 'public',
40
+ colDiffs,// Indica que todas as colunas devem ser criadas
41
+ });
42
+ } else {
43
+ const colDiffs: ColDiff[] = [];
44
+ // Se a tabela está em ambos, precisamos comparar as colunas
45
+ const bdColumnsMap = new Map(bdTable.columns.map(col => [col.name, col]));
46
+ const entityColumnsMap = new Map(entityTable.columns.map(col => [col.name, col]));
47
+ const allColumnNames = new Set([...bdColumnsMap.keys(), ...entityColumnsMap.keys()]);
48
+
49
+ this.checkIndexes(bdTable, entityTable, colDiffs)
50
+
51
+ allColumnNames.forEach(colName => {
52
+ const bdCol = bdColumnsMap.get(colName);
53
+ const entityCol = entityColumnsMap.get(colName);
54
+
55
+ if (!entityCol) {
56
+ colDiffs.push({
57
+ actionType: 'DELETE',
58
+ colName: bdCol!.name,
59
+ });
60
+ } else if (!bdCol) {
61
+ this.createNewColumn(entityCol, colDiffs);
62
+ } else this.diffColumnSql(bdCol, entityCol, colDiffs);
63
+ });
64
+
65
+ if (colDiffs.length > 0) {
66
+ diffs.push({
67
+ tableName: tableName,
68
+ schema: entityTable.schema ?? 'public',
69
+ colDiffs,
70
+ });
71
+ }
72
+ }
73
+ });
74
+
75
+ return diffs;
76
+ }
77
+
78
+ private checkIndexes(bdTable: SnapshotTable | undefined, entityTable: SnapshotTable | undefined, colDiffs: ColDiff[]) {
79
+ if ((bdTable && bdTable.indexes) || (entityTable && entityTable.indexes)) {
80
+ if (!bdTable || !bdTable.indexes) {
81
+ colDiffs.push({
82
+ actionType: 'INDEX',
83
+ colName: '*',
84
+ indexTables: entityTable!.indexes.map(index => ({
85
+ name: index.indexName,
86
+ properties: index.columnName.split(','),
87
+ })),
88
+ });
89
+ }
90
+
91
+ if (!entityTable || !entityTable.indexes) {
92
+ colDiffs.push({
93
+ actionType: 'INDEX',
94
+ colName: '*',
95
+ indexTables: bdTable!.indexes.map(index => ({name: index.indexName})),
96
+ });
97
+ }
98
+ }
99
+
100
+ if ((bdTable && bdTable.indexes) && (entityTable && entityTable.indexes)) {
101
+ const bdIndexesMap = new Map(bdTable.indexes.map(index => [index.indexName, index]));
102
+ const entityIndexesMap = new Map(entityTable.indexes.map(index => [index.indexName, index]));
103
+ const allIndexes = new Set([...bdIndexesMap.keys(), ...entityIndexesMap.keys()]);
104
+ allIndexes.forEach(indexName => {
105
+ const bdIndex = bdIndexesMap.get(indexName);
106
+ const entityIndex = entityIndexesMap.get(indexName);
107
+ if (!entityIndex) {
108
+ colDiffs.push({
109
+ actionType: 'INDEX',
110
+ colName: bdIndex!.columnName,
111
+ indexTables: [{name: indexName}],
112
+ });
113
+ } else if (!bdIndex) {
114
+ colDiffs.push({
115
+ actionType: 'INDEX',
116
+ colName: entityIndex.columnName,
117
+ indexTables: [{name: indexName, properties: entityIndex.columnName.split(',')}],
118
+ });
119
+ }
120
+ });
121
+ }
122
+ }
123
+
124
+ private createNewColumn(entityCol: ColumnsInfo, colDiffs: ColDiff[]): ColDiff[] {
125
+ const colType = this.convertEntityTypeToSqlType(entityCol.type);
126
+
127
+ colDiffs.push({
128
+ actionType: 'CREATE',
129
+ colName: entityCol.name,
130
+ colType: colType.type,
131
+ colLength: entityCol.length ?? colType.len,
132
+ colChanges: {
133
+ autoIncrement: entityCol.autoIncrement,
134
+ default: entityCol.default,
135
+ primary: entityCol.primary,
136
+ unique: entityCol.unique,
137
+ nullable: entityCol.nullable,
138
+ foreignKeys: entityCol.foreignKeys ?? [],
139
+ },
140
+ });
141
+
142
+ return colDiffs;
143
+ }
144
+
145
+ private diffColumnType(bdCol: ColumnsInfo, entityCol: ColumnsInfo, colDiffs: ColDiff[]): void {
146
+ const colT = this.convertEntityTypeToSqlType(entityCol.type);
147
+ const colType = colT.type;
148
+ const length = entityCol.length ?? colT.len;
149
+ if (bdCol.type !== colType || bdCol.length !== length) {
150
+ colDiffs.push({
151
+ actionType: 'ALTER',
152
+ colName: entityCol.name,
153
+ colType: colType,
154
+ colLength: length,
155
+ });
156
+ }
157
+ }
158
+
159
+ private diffColumnDefault(bdCol: ColumnsInfo, entityCol: ColumnsInfo, colDiffs: ColDiff[]): void {
160
+ if (bdCol.default !== entityCol.default) {
161
+ colDiffs.push({
162
+ actionType: 'ALTER',
163
+ colName: entityCol.name,
164
+ colChanges: {default: entityCol.default},
165
+ colLength: entityCol.length,
166
+ });
167
+ }
168
+ }
169
+
170
+ private diffColumnPrimary(bdCol: ColumnsInfo, entityCol: ColumnsInfo, colDiffs: ColDiff[]): void {
171
+ if (bdCol.primary !== entityCol.primary) {
172
+ colDiffs.push({
173
+ actionType: 'ALTER',
174
+ colName: entityCol.name,
175
+ colChanges: {primary: entityCol.primary},
176
+ colLength: entityCol.length,
177
+ });
178
+ }
179
+ }
180
+
181
+ private diffColumnUnique(bdCol: ColumnsInfo, entityCol: ColumnsInfo, colDiffs: ColDiff[]): void {
182
+ if (bdCol.unique !== entityCol.unique) {
183
+
184
+ if (bdCol.unique === false && entityCol.unique === undefined) {
185
+ return;
186
+ }
187
+
188
+ colDiffs.push({
189
+ actionType: 'ALTER',
190
+ colName: entityCol.name,
191
+ colChanges: {unique: entityCol.unique || false},
192
+ colLength: entityCol.length,
193
+ });
194
+ }
195
+ }
196
+
197
+ private diffForeignKey(bdCol: ColumnsInfo, entityCol: ColumnsInfo, colDiffs: ColDiff[]): void {
198
+ if (bdCol.foreignKeys || entityCol.foreignKeys) {
199
+ const bdFKMap = new Map((bdCol.foreignKeys || []).map(fk => [`${fk.referencedTableName}.${fk.referencedColumnName}`, fk]));
200
+ const entityFKMap = new Map((entityCol.foreignKeys || []).map(fk => [`${fk.referencedTableName}.${fk.referencedColumnName}`, fk]));
201
+
202
+ const allFKs = new Set([...bdFKMap.keys(), ...entityFKMap.keys()]);
203
+
204
+ allFKs.forEach(fkName => {
205
+ const bdFK = bdFKMap.get(fkName);
206
+ const entityFK = entityFKMap.get(fkName);
207
+
208
+ if (!entityFK) {
209
+ colDiffs.push({
210
+ actionType: 'ALTER',
211
+ colName: bdCol.name,
212
+ colChanges: {
213
+ foreignKeys: bdCol.foreignKeys?.filter((fk: any) => fk !== bdFK),
214
+ },
215
+ });
216
+ }
217
+ // else if (!bdFK) {
218
+ // console.log(bdFK, 'lu')
219
+ // colDiffs.push({
220
+ // actionType: 'ALTER',
221
+ // colName: entityCol.name,
222
+ // colChanges: {
223
+ // foreignKeys: [...(bdCol.foreignKeys || []), entityFK],
224
+ // },
225
+ // });
226
+ // }
227
+ });
228
+
229
+ }
230
+ }
231
+
232
+ private diffColumnSql(bdCol: ColumnsInfo, entityCol: ColumnsInfo, colDiffs: ColDiff[]) {
233
+ this.diffForeignKey(bdCol, entityCol, colDiffs);
234
+ this.diffColumnType(bdCol, entityCol, colDiffs);
235
+ this.diffColumnDefault(bdCol, entityCol, colDiffs);
236
+ this.diffColumnPrimary(bdCol, entityCol, colDiffs);
237
+ this.diffColumnUnique(bdCol, entityCol, colDiffs);
238
+ this.diffColumnNullable(bdCol, entityCol, colDiffs);
239
+
240
+ return colDiffs;
241
+ }
242
+
243
+ private diffColumnNullable(bdCol: ColumnsInfo, entityCol: ColumnsInfo, colDiffs: ColDiff[]) {
244
+ if (bdCol.nullable !== entityCol.nullable) {
245
+ colDiffs.push({
246
+ actionType: 'ALTER',
247
+ colName: entityCol.name,
248
+ colChanges: {nullable: entityCol.nullable},
249
+ colLength: entityCol.length,
250
+ });
251
+ }
252
+ }
253
+
254
+ // TODO: Precisa ser de acordo com o driver
255
+ // adicionar 'varchar' | 'text' | 'int' | 'bigint' | 'float' | 'double' | 'decimal' | 'date' | 'datetime' | 'time' | 'timestamp' | 'boolean' | 'json' | 'jsonb' | 'enum' | 'array' | 'uuid'
256
+ private convertEntityTypeToSqlType(entityType: string): { type: string, len?: number } {
257
+ switch (entityType) {
258
+ case "Number":
259
+ case 'int':
260
+ return {type: 'numeric', len: 11};
261
+ case 'bigint':
262
+ return {type: 'bigint'};
263
+ case 'float':
264
+ return {type: 'float4'};
265
+ case 'double':
266
+ return {type: 'float8'};
267
+ case 'decimal':
268
+ return {type: 'decimal'};
269
+ case "String":
270
+ case "varchar":
271
+ return {type: 'character varying', len: 255};
272
+ case "Boolean":
273
+ return {type: "boolean"};
274
+ case "Date":
275
+ return {type: "timestamp"};
276
+ case "Object":
277
+ return {type: "json"};
278
+ case 'uuid':
279
+ return {type: 'uuid'};
280
+ case 'text':
281
+ return {type: 'text'};
282
+ default:
283
+ return {type: "character varying", len: 255};
284
+ //... mais casos aqui ...
285
+ }
286
+ }
287
+ }
@@ -0,0 +1,277 @@
1
+ import 'reflect-metadata';
2
+ import { ColDiff, ConnectionSettings, DriverInterface, SnapshotTable } from '../driver/driver.interface';
3
+ import { globby } from 'globby'
4
+ import { Orm, OrmService } from '@cheetah.js/orm';
5
+ import { InjectorService, LoggerService } from '@cheetah.js/core';
6
+ import { EntityStorage } from '../domain/entities';
7
+ import * as path from 'path';
8
+ import * as fs from 'fs';
9
+ import { DiffCalculator } from './diff-calculator';
10
+
11
+ export class Migrator {
12
+ config: ConnectionSettings<any>;
13
+ orm: Orm<any>;
14
+ entities: EntityStorage = new EntityStorage();
15
+
16
+ constructor() {
17
+ }
18
+
19
+ async initConfigFile(basePath: string = process.cwd()) {
20
+ const paths = await globby(['cheetah.config.ts'], {absolute: true, cwd: basePath})
21
+
22
+ if (paths.length === 0) {
23
+ throw new Error('Config file not found');
24
+ }
25
+
26
+ const config = await import(paths[0]);
27
+ this.config = config.default;
28
+
29
+ if (typeof this.config.entities === 'string') {
30
+ const paths = await globby(this.config.entities, {absolute: true, cwd: basePath})
31
+ for (const path of paths) {
32
+ await import(path);
33
+ }
34
+ }
35
+
36
+ this.orm = new Orm(new LoggerService(new InjectorService()));
37
+ this.entities = new EntityStorage();
38
+ const serv = new OrmService(this.orm, this.entities)
39
+ await serv.onInit(this.config);
40
+ }
41
+
42
+ async initMigration() {
43
+ const snapshotBd = await this.snapshotBd();
44
+ const snapshotEntities = await this.snapshotEntities();
45
+ const calculator = new DiffCalculator(this.entities);
46
+
47
+ return calculator.diff(snapshotBd, snapshotEntities);
48
+ }
49
+
50
+ private async snapshotBd(): Promise<SnapshotTable[]> {
51
+ const snapshot = []
52
+ for (let [_, values] of this.entities.entries()) {
53
+ const bd = await this.orm.driverInstance.snapshot(values.tableName);
54
+ if (!bd) {
55
+ continue;
56
+ }
57
+ snapshot.push(bd)
58
+ }
59
+ return snapshot;
60
+ }
61
+
62
+ private async snapshotEntities() {
63
+ const snapshot = []
64
+ for (let [_, values] of this.entities.entries()) {
65
+ snapshot.push(await this.entities.snapshot(values))
66
+ }
67
+ return snapshot;
68
+ }
69
+
70
+
71
+ async createMigration(forceName: string | null = null): Promise<void> {
72
+ const diff = await this.initMigration();
73
+ const migrationDirectory = path.join(process.cwd(), this.config.migrationPath ?? 'database/migrations');
74
+ const migrationFileName = (forceName ?? `migration_${new Date().toISOString().replace(/[^\d]/g, '')}`) + `.sql`;
75
+ const migrationFilePath = path.join(migrationDirectory, migrationFileName);
76
+
77
+ if (!fs.existsSync(migrationDirectory)) {
78
+ fs.mkdirSync(migrationDirectory, {recursive: true});
79
+ }
80
+
81
+ let sqlInstructions: string[] = [];
82
+
83
+ diff.forEach(tableDiff => {
84
+ const tableName = tableDiff.tableName;
85
+ const schema = tableDiff.schema;
86
+
87
+ if (tableDiff.newTable) {
88
+ const indexes = tableDiff.colDiffs.filter(colDiff => colDiff.actionType === 'INDEX');
89
+ const creates = tableDiff.colDiffs.filter(colDiff => colDiff.actionType === 'CREATE');
90
+ const fks = tableDiff.colDiffs.filter(colDiff => colDiff.colChanges?.foreignKeys);
91
+
92
+ sqlInstructions.push(this.orm.driverInstance.getCreateTableInstruction(schema, tableName, creates));
93
+
94
+ indexes.forEach(colDiff => {
95
+ colDiff.indexTables = colDiff.indexTables?.filter(index => index.name !== `${tableName}_pkey`);
96
+ });
97
+
98
+ fks.forEach(colDiff => {
99
+ colDiff.colChanges?.foreignKeys?.forEach(fk => {
100
+ sqlInstructions.push(this.orm.driverInstance.getAlterTableFkInstruction(schema, tableName, colDiff, fk));
101
+ });
102
+ });
103
+
104
+ sqlInstructions = sqlInstructions.map(sql => sql.replace(/' '/g, ''));
105
+ indexes.forEach(colDiff => {
106
+ if (colDiff.indexTables) {
107
+ colDiff.indexTables.forEach(index => {
108
+ if (index.properties) {
109
+ sqlInstructions.push(this.orm.driverInstance.getCreateIndex(index, schema, tableName));
110
+ }
111
+ });
112
+ }
113
+ })
114
+ return;
115
+ }
116
+
117
+ // Agrupar alterações por coluna
118
+ const colChangesMap = new Map<string, ColDiff[]>();
119
+
120
+ tableDiff.colDiffs.reverse().forEach(colDiff => {
121
+ const colName = colDiff.colName;
122
+
123
+ if (!colChangesMap.has(colName)) {
124
+ colChangesMap.set(colName, []);
125
+ }
126
+
127
+ colChangesMap.get(colName)?.push(colDiff);
128
+ });
129
+
130
+ // Gerar instruções SQL agrupadas por coluna
131
+ colChangesMap.forEach((colDiffs, colName) => {
132
+ const colDiffInstructions: string[] = [];
133
+
134
+ colDiffs.forEach(colDiff => {
135
+ switch (colDiff.actionType) {
136
+ case 'CREATE':
137
+ (this.orm.driverInstance as DriverInterface).getAddColumn(schema, tableName, colName, colDiff, colDiffInstructions);
138
+ break;
139
+ case 'DELETE':
140
+ (this.orm.driverInstance as DriverInterface).getDropColumn(colDiffInstructions, schema, tableName, colName);
141
+ break;
142
+ case 'ALTER':
143
+ this.applyColumnChanges(colDiff, colDiffInstructions, schema, tableName, colName);
144
+ break;
145
+ case "INDEX":
146
+ if (colDiff.indexTables) {
147
+ colDiff.indexTables.forEach(index => {
148
+ // if already exists instruction for this index, skip
149
+ if (colDiffInstructions.find(instruction => instruction.includes(index.name))) {
150
+ return;
151
+ }
152
+
153
+ if (this.alreadyConstraint(sqlInstructions, index.name)) {
154
+ return;
155
+ }
156
+
157
+ if (index.properties) {
158
+ colDiffInstructions.push((this.orm.driverInstance as DriverInterface).getCreateIndex(index, schema, tableName));
159
+ } else {
160
+ colDiffInstructions.push((this.orm.driverInstance as DriverInterface).getDropIndex(index, schema, tableName));
161
+ }
162
+ });
163
+ }
164
+ break;
165
+ // Adicione lógica para outros tipos de ação, se necessário
166
+ }
167
+ });
168
+
169
+ // Se houver instruções para a coluna, agrupe-as
170
+ if (colDiffInstructions.length > 0) {
171
+ sqlInstructions.push(colDiffInstructions.join('\n'));
172
+ }
173
+ });
174
+ });
175
+ const migrationContent = sqlInstructions.join('\n');
176
+
177
+ if (migrationContent.length === 0) {
178
+ console.log('No changes detected');
179
+ return;
180
+ } else {
181
+ fs.writeFileSync(migrationFilePath, migrationContent);
182
+
183
+ console.log(`Migration file created: ${migrationFilePath}`);
184
+ }
185
+ }
186
+
187
+ async migrate() {
188
+ const migrationTable = 'cheetah_migrations';
189
+ const migrationDirectory = path.join(process.cwd(), this.config.migrationPath ?? 'database/migrations');
190
+ const migrationFiles = fs
191
+ .readdirSync(migrationDirectory)
192
+ .filter(file => file.endsWith('.sql'))
193
+ .sort();
194
+
195
+ if (migrationFiles.length === 0) {
196
+ console.log('No migration files found');
197
+ return;
198
+ }
199
+
200
+ this.orm.driverInstance.executeSql(`CREATE TABLE IF NOT EXISTS "${migrationTable}" ("migration_file" character varying(255) NOT NULL PRIMARY KEY UNIQUE);`);
201
+ // get the migration fil
202
+ const migrated = await this.orm.driverInstance.executeSql(`SELECT * FROM "${migrationTable}" ORDER BY "migration_file" ASC;`);
203
+ const lastMigration = migrated.rows[migrated.rows.length - 1];
204
+ const lastMigrationIndex = migrationFiles.indexOf(lastMigration?.migration_file ?? '');
205
+ const migrationsToExecute = migrationFiles.slice(lastMigrationIndex + 1);
206
+
207
+ if (migrationsToExecute.length === 0) {
208
+ console.log('Database is up to date');
209
+ return;
210
+ }
211
+
212
+ for (const migrationFile of migrationsToExecute) {
213
+ const migrationFilePath = path.join(migrationDirectory, migrationFile);
214
+ const migrationContent = fs.readFileSync(migrationFilePath, {encoding: 'utf-8'});
215
+ const sqlInstructions = migrationContent.split(';').filter(sql => sql.trim().length > 0);
216
+
217
+ for (const sqlInstruction of sqlInstructions) {
218
+ await this.orm.driverInstance.executeSql(sqlInstruction);
219
+ }
220
+
221
+ await this.orm.driverInstance.executeSql(`INSERT INTO "${migrationTable}" ("migration_file") VALUES ('${migrationFile}');`);
222
+
223
+ console.log(`Migration executed: ${migrationFile}`);
224
+ }
225
+ }
226
+
227
+ private applyColumnChanges(colDiff: ColDiff, sqlInstructions: string[], schema: string | undefined, tableName: string, colName: string) {
228
+ if (colDiff.colType) {
229
+ sqlInstructions.push( (this.orm.driverInstance as DriverInterface).getAlterTableType(schema, tableName, colName, colDiff));
230
+ }
231
+
232
+ if (colDiff.colChanges) {
233
+ if (colDiff.colChanges.default !== undefined) {
234
+ sqlInstructions.push((this.orm.driverInstance as DriverInterface).getAlterTableDefaultInstruction(schema, tableName, colName, colDiff));
235
+ }
236
+
237
+ if (colDiff.colChanges.primary !== undefined) {
238
+ if (colDiff.colChanges.primary) {
239
+ sqlInstructions.push((this.orm.driverInstance as DriverInterface).getAlterTablePrimaryKeyInstruction(schema, tableName, colName, colDiff));
240
+ } else {
241
+ sqlInstructions.push((this.orm.driverInstance as DriverInterface).getDropConstraint({name: `${tableName}_pkey`}, schema, tableName));
242
+ }
243
+ }
244
+
245
+ if (colDiff.colChanges.unique !== undefined && !this.alreadyConstraint(sqlInstructions, `${tableName}_${colName}_key`)) {
246
+ if (colDiff.colChanges.unique) {
247
+ sqlInstructions.push((this.orm.driverInstance as DriverInterface).getAddUniqueConstraint(schema, tableName, colName));
248
+ } else {
249
+ sqlInstructions.push((this.orm.driverInstance as DriverInterface).getDropConstraint({name: `${tableName}_${colName}_key`}, schema, tableName));
250
+ }
251
+ }
252
+
253
+ if(colDiff.colChanges.nullable !== undefined) {
254
+ if (colDiff.colChanges.nullable) {
255
+ sqlInstructions.push((this.orm.driverInstance as DriverInterface).getAlterTableDropNullInstruction(schema, tableName, colName, colDiff));
256
+ } else {
257
+ sqlInstructions.push((this.orm.driverInstance as DriverInterface).getAlterTableDropNotNullInstruction(schema, tableName, colName, colDiff));
258
+ }
259
+ }
260
+
261
+ if (colDiff.colChanges.foreignKeys !== undefined) {
262
+ if (colDiff.colChanges.foreignKeys.length === 0) {
263
+ sqlInstructions.push((this.orm.driverInstance as DriverInterface).getDropConstraint({name: `${tableName}_${colName}_fk`}, schema, tableName));
264
+ }
265
+
266
+ colDiff.colChanges.foreignKeys.forEach(fk => {
267
+ sqlInstructions.push((this.orm.driverInstance as DriverInterface).getAlterTableFkInstruction(schema, tableName, colDiff, fk))
268
+ });
269
+ }
270
+ // Adicione lógica para outras alterações necessárias
271
+ }
272
+ }
273
+
274
+ private alreadyConstraint(sqlInstructions: string[], s: string): boolean {
275
+ return sqlInstructions.some(sql => sql.includes(`"${s}"`));
276
+ }
277
+ }
@@ -0,0 +1,120 @@
1
+ import { Metadata, OnApplicationInit, Service } from '@cheetah.js/core';
2
+ import { EntityStorage, Property } from './domain/entities';
3
+ import { ENTITIES, PROPERTIES_METADATA, PROPERTIES_RELATIONS } from './constants';
4
+ import { globbySync } from 'globby';
5
+ import { Project, SyntaxKind } from 'ts-morph';
6
+ import { Orm } from './orm';
7
+
8
+ @Service()
9
+ export class OrmService {
10
+ private allEntities = new Map<string, { nullables: string[], defaults: { [key: string]: any } }>();
11
+
12
+ constructor(private orm: Orm, private storage: EntityStorage, entityFile?: string) {
13
+ console.log('Preparing entities...')
14
+ const files = new Project({skipLoadingLibFiles: true}).addSourceFilesAtPaths(entityFile ?? this.getSourceFilePaths())
15
+ files.forEach(file => {
16
+ file.getClasses().forEach(classDeclaration => {
17
+ if (classDeclaration.getDecorator('Entity')) {
18
+
19
+ const properties = classDeclaration.getProperties();
20
+ const nullables: string[] = [];
21
+ const defaults: { [key: string]: any } = {};
22
+
23
+ properties.forEach(property => {
24
+ const propertyName = property.getName();
25
+ const isNullable = property.hasQuestionToken();
26
+ const initializer = property.getInitializer();
27
+ if (isNullable) {
28
+ nullables.push(propertyName);
29
+ }
30
+ if (initializer) {
31
+ const initializerKind = initializer.getKind();
32
+
33
+ switch (initializerKind) {
34
+ case SyntaxKind.StringLiteral:
35
+ defaults[propertyName] = initializer.getText();
36
+ break;
37
+ case SyntaxKind.NumericLiteral:
38
+ defaults[propertyName] = parseFloat(initializer.getText());
39
+ break;
40
+ default:
41
+ defaults[propertyName] = () => initializer.getText();
42
+ break;
43
+ }
44
+ }
45
+
46
+ this.allEntities.set(classDeclaration.getName() as string, {nullables, defaults});
47
+ });
48
+ }
49
+ });
50
+ })
51
+ }
52
+
53
+ @OnApplicationInit()
54
+ async onInit(customConfig: any = {}) {
55
+
56
+ const configFile = globbySync('cheetah.config.ts', {absolute: true});
57
+ if (configFile.length === 0) {
58
+ console.log('No config file found!')
59
+ return;
60
+ }
61
+
62
+ const config = await import(configFile[0]);
63
+ const setConfig = Object.keys(customConfig).length > 0 ? customConfig : config.default;
64
+
65
+ this.orm.setConnection(setConfig);
66
+ await this.orm.connect();
67
+
68
+ if (typeof config.default.entities === 'string') {
69
+ const files = globbySync([config.default.entities, '!node_modules'], {gitignore: true, absolute: true})
70
+
71
+ for (const file of files) {
72
+ await import(file)
73
+ }
74
+ }
75
+
76
+ const entities = Metadata.get(ENTITIES, Reflect);
77
+
78
+ if (!entities) {
79
+ console.log('No entities found!')
80
+ return;
81
+ }
82
+
83
+ for (const entity of entities) {
84
+ const nullableDefaultEntity = this.allEntities.get(entity.target.name);
85
+ const properties: { [key: string]: Property } = Metadata.get(PROPERTIES_METADATA, entity.target);
86
+ const relationship = Metadata.get(PROPERTIES_RELATIONS, entity.target);
87
+
88
+ for (const property in properties) {
89
+ if (nullableDefaultEntity?.nullables.includes(property)) {
90
+ properties[property].options.nullable = true;
91
+ }
92
+ if (nullableDefaultEntity?.defaults[property]) {
93
+ properties[property].options.default = nullableDefaultEntity?.defaults[property];
94
+ }
95
+ }
96
+
97
+ this.storage.add(entity, properties, relationship);
98
+ }
99
+ console.log('Entities prepared!')
100
+ }
101
+
102
+
103
+ private getSourceFilePaths(): string[] {
104
+ const projectRoot = process.cwd(); // Ajuste conforme a estrutura do seu projeto
105
+
106
+ const getAllFiles = (dir: string): string[] => {
107
+ const patterns = [`${dir}/**/*.(ts|js)`, "!**/node_modules/**"];
108
+
109
+ try {
110
+ return globbySync(patterns, {gitignore: true});
111
+ } catch (error) {
112
+ console.error('Erro ao obter arquivos:', error);
113
+ return [];
114
+ }
115
+ }
116
+
117
+ // Filtra os arquivos pelo padrão de nomenclatura
118
+ return getAllFiles(projectRoot);
119
+ }
120
+ }