fiberx-backend-toolkit 0.0.60 → 0.0.62
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.
- package/dist/code_templates/sequelize_code_template.d.ts +3 -3
- package/dist/code_templates/sequelize_code_template.js +210 -144
- package/dist/database/schema/schema_diff_util.js +27 -30
- package/dist/database/schema/schema_normalizer_util.d.ts +1 -1
- package/dist/database/schema/schema_normalizer_util.js +1 -1
- package/dist/database/scripts/make_migrations_script.js +13 -12
- package/dist/types/schema_type.d.ts +5 -11
- package/dist/utils/input_validator_util.d.ts +1 -0
- package/dist/utils/input_validator_util.js +49 -0
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { SchemaColumnInterface, SchemaDiffInterface } from "../types/schema_type";
|
|
1
|
+
import { SchemaColumnInterface, SchemaDefinitionInterface, SchemaDiffInterface } from "../types/schema_type";
|
|
2
2
|
declare const SEQUELIZE_SCHEMA_CODE_TEMPLATE: (schema_name: string, table_name: string, model_name: string, migration_priority: number) => string;
|
|
3
|
-
declare const SEQUELIZE_CREATE_NEW_SCHEMA_MIGRATION_FILE_CODE_TEMPLATE: (schema_table_name: string, schema_model_name: string, schema_file_name: string) => string;
|
|
4
|
-
declare const SEQUELIZE_UPDATE_EXISTING_SCHEMA_MIGRATION_FILE_CODE_TEMPLATE: (
|
|
3
|
+
declare const SEQUELIZE_CREATE_NEW_SCHEMA_MIGRATION_FILE_CODE_TEMPLATE: (schema_table_name: string, schema_model_name: string, schema_file_name: string, schema_definition: SchemaDefinitionInterface) => string;
|
|
4
|
+
declare const SEQUELIZE_UPDATE_EXISTING_SCHEMA_MIGRATION_FILE_CODE_TEMPLATE: (table_name: string, model_name: string, diff: SchemaDiffInterface, current_schema: SchemaDefinitionInterface, previous_schema: SchemaDefinitionInterface) => string;
|
|
5
5
|
declare const SEQUELIZE_SEEDER_TEMPLATE: (class_name: string, table_name: string) => string;
|
|
6
6
|
declare const SEQUELIZE_MODEL_CODE_TEMPLATE: (schema_model_name: string, schema_file_name: string, schema_columns: Record<string, SchemaColumnInterface>) => string;
|
|
7
7
|
declare const SEQUELIZE_MODELS_INDEX_CODE_TEMPLATE: (model_names: string[], imports: string) => string;
|
|
@@ -1,6 +1,68 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SEQUELIZE_MODELS_INDEX_CODE_TEMPLATE = exports.SEQUELIZE_MODEL_CODE_TEMPLATE = exports.SEQUELIZE_UPDATE_EXISTING_SCHEMA_MIGRATION_FILE_CODE_TEMPLATE = exports.SEQUELIZE_CREATE_NEW_SCHEMA_MIGRATION_FILE_CODE_TEMPLATE = exports.SEQUELIZE_SEEDER_TEMPLATE = exports.SEQUELIZE_SCHEMA_CODE_TEMPLATE = void 0;
|
|
4
|
+
const serializeSequelizeDataType = (type) => {
|
|
5
|
+
if (!type)
|
|
6
|
+
return "undefined";
|
|
7
|
+
if (type.key) {
|
|
8
|
+
const key = type.key;
|
|
9
|
+
if (type.options?.length)
|
|
10
|
+
return `Sequelize.${key}(${type.options.length})`;
|
|
11
|
+
if (type.options?.values)
|
|
12
|
+
return `Sequelize.${key}(${JSON.stringify(type.options.values)})`;
|
|
13
|
+
if (type.options?.precision && type.options?.scale)
|
|
14
|
+
return `Sequelize.${key}(${type.options.precision}, ${type.options.scale})`;
|
|
15
|
+
return `Sequelize.${key}`;
|
|
16
|
+
}
|
|
17
|
+
return "undefined";
|
|
18
|
+
};
|
|
19
|
+
const serializeSequelizeColumnDefinition = (column) => {
|
|
20
|
+
const parts = [];
|
|
21
|
+
for (const [key, value] of Object.entries(column)) {
|
|
22
|
+
if (key === "type") {
|
|
23
|
+
parts.push(`type: ${serializeSequelizeDataType(value)}`);
|
|
24
|
+
}
|
|
25
|
+
else if (typeof value === "string") {
|
|
26
|
+
parts.push(`${key}: "${value}"`);
|
|
27
|
+
}
|
|
28
|
+
else if (typeof value === "boolean" || typeof value === "number") {
|
|
29
|
+
parts.push(`${key}: ${value}`);
|
|
30
|
+
}
|
|
31
|
+
else if (Array.isArray(value)) {
|
|
32
|
+
parts.push(`${key}: ${JSON.stringify(value)}`);
|
|
33
|
+
}
|
|
34
|
+
else if (value && typeof value === "object") {
|
|
35
|
+
parts.push(`${key}: ${JSON.stringify(value, null, 4)}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return `{
|
|
39
|
+
${parts.join(",\n ")}
|
|
40
|
+
}`;
|
|
41
|
+
};
|
|
42
|
+
const serializeIndexDefinition = (index) => {
|
|
43
|
+
const clone = { ...index };
|
|
44
|
+
delete clone.name;
|
|
45
|
+
return JSON.stringify(clone, null, 4);
|
|
46
|
+
};
|
|
47
|
+
const serializeSequelizeColumnsObject = (columns) => {
|
|
48
|
+
const rendered = Object.entries(columns)
|
|
49
|
+
.map(([col_name, col_Def]) => {
|
|
50
|
+
return `"${col_name}": ${serializeSequelizeColumnDefinition(col_Def)}`;
|
|
51
|
+
})
|
|
52
|
+
.join(",\n ");
|
|
53
|
+
return `{
|
|
54
|
+
${rendered}
|
|
55
|
+
}`;
|
|
56
|
+
};
|
|
57
|
+
const serializeSequelizeIndexesArray = (indexes) => {
|
|
58
|
+
return indexes.map(index => {
|
|
59
|
+
return `{
|
|
60
|
+
name: "${index.name}",
|
|
61
|
+
fields: ${JSON.stringify(index.fields)},
|
|
62
|
+
${serializeIndexDefinition(index).replace(/^{|}$/g, "")}
|
|
63
|
+
}`;
|
|
64
|
+
}).join(",\n ");
|
|
65
|
+
};
|
|
4
66
|
const SEQUELIZE_SCHEMA_CODE_TEMPLATE = (schema_name, table_name, model_name, migration_priority) => {
|
|
5
67
|
return `
|
|
6
68
|
import { DataTypes } from "sequelize";
|
|
@@ -47,239 +109,242 @@ export default ${schema_name};
|
|
|
47
109
|
`;
|
|
48
110
|
};
|
|
49
111
|
exports.SEQUELIZE_SCHEMA_CODE_TEMPLATE = SEQUELIZE_SCHEMA_CODE_TEMPLATE;
|
|
50
|
-
const SEQUELIZE_CREATE_NEW_SCHEMA_MIGRATION_FILE_CODE_TEMPLATE = (schema_table_name, schema_model_name, schema_file_name) =>
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
import
|
|
112
|
+
const SEQUELIZE_CREATE_NEW_SCHEMA_MIGRATION_FILE_CODE_TEMPLATE = (schema_table_name, schema_model_name, schema_file_name, schema_definition) => {
|
|
113
|
+
const rendered_columns = serializeSequelizeColumnsObject(schema_definition.columns);
|
|
114
|
+
const rendered_indexes = serializeSequelizeIndexesArray(schema_definition.indexes || []);
|
|
115
|
+
return `
|
|
116
|
+
import { QueryInterface, Sequelize } from "sequelize";
|
|
117
|
+
import { LoggerUtil } from "fiberx-backend-toolkit/dist/utils/main";
|
|
55
118
|
|
|
56
119
|
class Create${schema_model_name}TableMigration {
|
|
57
120
|
private name: string = "create_table_${schema_table_name}_migration_file";
|
|
58
|
-
private schema: SchemaDefinitionInterface = ${schema_model_name}Schema;
|
|
59
121
|
private readonly logger: LoggerUtil = new LoggerUtil(this.name);
|
|
60
122
|
|
|
61
123
|
constructor() {}
|
|
62
124
|
|
|
63
|
-
|
|
64
|
-
public async up(
|
|
65
|
-
queryInterface: QueryInterface,
|
|
66
|
-
Sequelize: typeof import("sequelize")
|
|
67
|
-
) {
|
|
125
|
+
public async up(queryInterface: QueryInterface, Sequelize: Sequelize) {
|
|
68
126
|
try {
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
await queryInterface.createTable(table_name, columns);
|
|
127
|
+
const table_name = "${schema_table_name}";
|
|
72
128
|
|
|
73
|
-
|
|
129
|
+
await queryInterface.createTable(
|
|
130
|
+
table_name,
|
|
131
|
+
${rendered_columns}
|
|
132
|
+
);
|
|
74
133
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
{
|
|
82
|
-
|
|
83
|
-
|
|
134
|
+
${schema_definition.indexes?.map(index => `
|
|
135
|
+
await queryInterface.addIndex(
|
|
136
|
+
table_name,
|
|
137
|
+
${JSON.stringify(index.fields)},
|
|
138
|
+
{
|
|
139
|
+
name: "${index.name}",
|
|
140
|
+
${serializeIndexDefinition(index).replace(/^{|}$/g, "")}
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
`).join("") || ""}
|
|
84
144
|
|
|
85
|
-
this.logger.success(\`✅ Table "\${table_name}"
|
|
145
|
+
this.logger.success(\`✅ Table "\${table_name}" created successfully.\`);
|
|
86
146
|
}
|
|
87
147
|
catch (error: any) {
|
|
88
148
|
this.logger.error(\`🚫 Migration failed: "\${error.message}"\`, { error });
|
|
89
|
-
throw
|
|
149
|
+
throw error;
|
|
90
150
|
}
|
|
91
151
|
}
|
|
92
152
|
|
|
93
|
-
|
|
94
|
-
public async down(
|
|
95
|
-
queryInterface: QueryInterface,
|
|
96
|
-
Sequelize: typeof import("sequelize")
|
|
97
|
-
) {
|
|
153
|
+
public async down(queryInterface: QueryInterface, Sequelize: Sequelize) {
|
|
98
154
|
try {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
this.logger.success(\`🗑️ Table "\${table_name}" dropped successfully.\`);
|
|
155
|
+
await queryInterface.dropTable("${schema_table_name}");
|
|
156
|
+
this.logger.success(\`🗑️ Table "${schema_table_name}" dropped successfully.\`);
|
|
102
157
|
} catch (error: any) {
|
|
103
|
-
this.logger.error(\`🚫
|
|
104
|
-
throw
|
|
158
|
+
this.logger.error(\`🚫 Rollback failed: "\${error.message}"\`, { error });
|
|
159
|
+
throw error;
|
|
105
160
|
}
|
|
106
161
|
}
|
|
107
162
|
}
|
|
108
163
|
|
|
109
164
|
export default Create${schema_model_name}TableMigration;
|
|
110
165
|
`;
|
|
166
|
+
};
|
|
111
167
|
exports.SEQUELIZE_CREATE_NEW_SCHEMA_MIGRATION_FILE_CODE_TEMPLATE = SEQUELIZE_CREATE_NEW_SCHEMA_MIGRATION_FILE_CODE_TEMPLATE;
|
|
112
|
-
const SEQUELIZE_UPDATE_EXISTING_SCHEMA_MIGRATION_FILE_CODE_TEMPLATE = (
|
|
113
|
-
|
|
114
|
-
import {
|
|
115
|
-
import {
|
|
116
|
-
import ${schema_model_name}Schema from "@/database/schemas/${schema_file_name.replace(".ts", "")}";
|
|
168
|
+
const SEQUELIZE_UPDATE_EXISTING_SCHEMA_MIGRATION_FILE_CODE_TEMPLATE = (table_name, model_name, diff, current_schema, previous_schema) => {
|
|
169
|
+
return `
|
|
170
|
+
import { QueryInterface, Sequelize } from "sequelize";
|
|
171
|
+
import { LoggerUtil } from "fiberx-backend-toolkit/dist/utils/main";
|
|
117
172
|
|
|
118
|
-
class Update${
|
|
119
|
-
private name: string = "update_table_${
|
|
120
|
-
private schema: SchemaDefinitionInterface = ${schema_model_name}Schema;
|
|
173
|
+
class Update${model_name}TableMigration {
|
|
174
|
+
private readonly name: string = "update_table_${table_name}_migration_file";
|
|
121
175
|
private readonly logger: LoggerUtil = new LoggerUtil(this.name);
|
|
122
176
|
|
|
123
177
|
constructor() {}
|
|
124
178
|
|
|
125
|
-
public async up(
|
|
126
|
-
queryInterface: QueryInterface,
|
|
127
|
-
Sequelize: typeof import("sequelize")
|
|
128
|
-
) {
|
|
179
|
+
public async up(queryInterface: QueryInterface, Sequelize: Sequelize) {
|
|
129
180
|
try {
|
|
130
|
-
|
|
181
|
+
let table_name = "${table_name}";
|
|
131
182
|
|
|
132
|
-
|
|
183
|
+
${diff.table_renamed ? `
|
|
133
184
|
await queryInterface.renameTable(
|
|
134
185
|
"${diff.table_renamed.from}",
|
|
135
186
|
"${diff.table_renamed.to}"
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
|
|
187
|
+
);
|
|
188
|
+
table_name = "${diff.table_renamed.to}";
|
|
189
|
+
` : ""}
|
|
139
190
|
|
|
140
|
-
|
|
141
|
-
|
|
191
|
+
// ----------- ADD COLUMNS -----------
|
|
192
|
+
${diff.columns.added.map(col => `
|
|
142
193
|
await queryInterface.addColumn(
|
|
143
194
|
table_name,
|
|
144
|
-
"${
|
|
145
|
-
columns[
|
|
195
|
+
"${col}",
|
|
196
|
+
${serializeSequelizeColumnDefinition(current_schema.columns[col])}
|
|
146
197
|
);
|
|
147
|
-
|
|
198
|
+
`).join("")}
|
|
148
199
|
|
|
149
|
-
|
|
150
|
-
|
|
200
|
+
// ----------- MODIFY COLUMNS -----------
|
|
201
|
+
${diff.columns.modified.map(col => `
|
|
151
202
|
await queryInterface.changeColumn(
|
|
152
|
-
table_name,
|
|
153
|
-
"${
|
|
154
|
-
${
|
|
203
|
+
table_name,
|
|
204
|
+
"${col}",
|
|
205
|
+
${serializeSequelizeColumnDefinition(current_schema.columns[col])}
|
|
155
206
|
);
|
|
156
207
|
`).join("")}
|
|
157
208
|
|
|
158
|
-
|
|
159
|
-
|
|
209
|
+
// ----------- REMOVE COLUMNS -----------
|
|
210
|
+
${diff.columns.removed.map(col => `
|
|
160
211
|
await queryInterface.removeColumn(
|
|
161
|
-
table_name,
|
|
162
|
-
"${
|
|
212
|
+
table_name,
|
|
213
|
+
"${col}"
|
|
163
214
|
);
|
|
164
215
|
`).join("")}
|
|
165
216
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
217
|
+
// ----------- ADD INDEXES -----------
|
|
218
|
+
${diff.indexes.added.map(name => {
|
|
219
|
+
const curr_indxs = current_schema?.indexes || [];
|
|
220
|
+
const idx = curr_indxs.find(i => i.name === name);
|
|
221
|
+
return idx ? `
|
|
169
222
|
await queryInterface.addIndex(
|
|
170
|
-
table_name,
|
|
171
|
-
${JSON.stringify(idx.fields)},
|
|
172
|
-
|
|
223
|
+
table_name,
|
|
224
|
+
${JSON.stringify(idx.fields)},
|
|
225
|
+
{
|
|
226
|
+
name: "${idx.name}",
|
|
227
|
+
${serializeIndexDefinition(idx).replace(/^{|}$/g, "")}
|
|
228
|
+
}
|
|
173
229
|
);
|
|
174
|
-
|
|
230
|
+
` : "";
|
|
231
|
+
}).join("")}
|
|
175
232
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
);
|
|
233
|
+
// ----------- MODIFY INDEXES -----------
|
|
234
|
+
${diff.indexes.modified.map(name => {
|
|
235
|
+
const curr_indxs = current_schema?.indexes || [];
|
|
236
|
+
const idx = curr_indxs.find(i => i.name === name);
|
|
237
|
+
return idx ? `
|
|
238
|
+
await queryInterface.removeIndex(table_name, "${name}");
|
|
182
239
|
|
|
183
240
|
await queryInterface.addIndex(
|
|
184
|
-
table_name,
|
|
185
|
-
${JSON.stringify(
|
|
186
|
-
|
|
241
|
+
table_name,
|
|
242
|
+
${JSON.stringify(idx.fields)},
|
|
243
|
+
{
|
|
244
|
+
name: "${idx.name}",
|
|
245
|
+
${serializeIndexDefinition(idx).replace(/^{|}$/g, "")}
|
|
246
|
+
}
|
|
187
247
|
);
|
|
188
|
-
|
|
248
|
+
` : "";
|
|
249
|
+
}).join("")}
|
|
189
250
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
await queryInterface.removeIndex(
|
|
193
|
-
table_name,
|
|
194
|
-
${JSON.stringify(idx.name)}
|
|
195
|
-
);
|
|
251
|
+
// ----------- REMOVE INDEXES -----------
|
|
252
|
+
${diff.indexes.removed.map(name => `
|
|
253
|
+
await queryInterface.removeIndex(table_name, "${name}");
|
|
196
254
|
`).join("")}
|
|
197
255
|
|
|
198
|
-
|
|
199
|
-
this.logger.success(\`✅ Table "\${table_name}" for schema "\${model_name}Schema" updated successfully.\`);
|
|
256
|
+
this.logger.success(\`✅ Table "\${table_name}" updated successfully.\`);
|
|
200
257
|
}
|
|
201
258
|
catch (error: any) {
|
|
202
259
|
this.logger.error(\`🚫 Update migration failed: "\${error.message}"\`, { error });
|
|
203
|
-
throw
|
|
260
|
+
throw error;
|
|
204
261
|
}
|
|
205
262
|
}
|
|
206
263
|
|
|
207
|
-
public async down(
|
|
208
|
-
queryInterface: QueryInterface,
|
|
209
|
-
Sequelize: typeof import("sequelize")
|
|
210
|
-
) {
|
|
264
|
+
public async down(queryInterface: QueryInterface, Sequelize: Sequelize) {
|
|
211
265
|
try {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
// Revert Added indexes
|
|
215
|
-
${diff.indexes.added.map((idx) => `
|
|
216
|
-
await queryInterface.removeIndex(
|
|
217
|
-
table_name,
|
|
218
|
-
${JSON.stringify(idx.name)}
|
|
219
|
-
);`).join("")}
|
|
220
|
-
|
|
221
|
-
// Revert Modified Indexes
|
|
222
|
-
${Object.entries(diff.indexes.modified).map(([idx_name, idx_modified]) => `
|
|
223
|
-
await queryInterface.removeIndex(
|
|
224
|
-
table_name,
|
|
225
|
-
${JSON.stringify(idx_modified.after.name)}
|
|
226
|
-
);
|
|
266
|
+
let table_name = "${table_name}";
|
|
227
267
|
|
|
268
|
+
// ----------- REVERT INDEXES -----------
|
|
269
|
+
${diff.indexes.added.map(name => `
|
|
270
|
+
await queryInterface.removeIndex(table_name, "${name}");
|
|
271
|
+
`).join("")}
|
|
272
|
+
|
|
273
|
+
${diff.indexes.modified.map(name => {
|
|
274
|
+
const prev_indxs = previous_schema?.indexes || [];
|
|
275
|
+
const idx = prev_indxs.find(i => i.name === name);
|
|
276
|
+
return idx ? `
|
|
277
|
+
await queryInterface.removeIndex(table_name, "${name}");
|
|
228
278
|
|
|
229
279
|
await queryInterface.addIndex(
|
|
230
|
-
table_name,
|
|
231
|
-
${JSON.stringify(
|
|
232
|
-
|
|
233
|
-
|
|
280
|
+
table_name,
|
|
281
|
+
${JSON.stringify(idx.fields)},
|
|
282
|
+
{
|
|
283
|
+
name: "${idx.name}",
|
|
284
|
+
${serializeIndexDefinition(idx).replace(/^{|}$/g, "")}
|
|
285
|
+
}
|
|
286
|
+
);
|
|
287
|
+
` : "";
|
|
288
|
+
}).join("")}
|
|
234
289
|
|
|
235
|
-
|
|
236
|
-
|
|
290
|
+
${diff.indexes.removed.map(name => {
|
|
291
|
+
const prev_indxs = previous_schema?.indexes || [];
|
|
292
|
+
const idx = prev_indxs.find(i => i.name === name);
|
|
293
|
+
return idx ? `
|
|
237
294
|
await queryInterface.addIndex(
|
|
238
|
-
table_name,
|
|
239
|
-
${JSON.stringify(idx.fields)},
|
|
240
|
-
|
|
241
|
-
|
|
295
|
+
table_name,
|
|
296
|
+
${JSON.stringify(idx.fields)},
|
|
297
|
+
{
|
|
298
|
+
name: "${idx.name}",
|
|
299
|
+
${serializeIndexDefinition(idx).replace(/^{|}$/g, "")}
|
|
300
|
+
}
|
|
301
|
+
);
|
|
302
|
+
` : "";
|
|
303
|
+
}).join("")}
|
|
242
304
|
|
|
243
|
-
|
|
244
|
-
|
|
305
|
+
// ----------- REVERT COLUMNS -----------
|
|
306
|
+
${diff.columns.modified.map(col => `
|
|
245
307
|
await queryInterface.changeColumn(
|
|
246
|
-
table_name,
|
|
247
|
-
"${
|
|
248
|
-
${
|
|
249
|
-
)
|
|
308
|
+
table_name,
|
|
309
|
+
"${col}",
|
|
310
|
+
${serializeSequelizeColumnDefinition(previous_schema.columns[col])}
|
|
311
|
+
);
|
|
312
|
+
`).join("")}
|
|
250
313
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
`).join("")}
|
|
314
|
+
${diff.columns.added.map(col => `
|
|
315
|
+
await queryInterface.removeColumn(table_name, "${col}");
|
|
316
|
+
`).join("")}
|
|
255
317
|
|
|
256
|
-
|
|
318
|
+
${diff.columns.removed.map(col => `
|
|
257
319
|
await queryInterface.addColumn(
|
|
258
|
-
table_name,
|
|
259
|
-
"${
|
|
260
|
-
${
|
|
320
|
+
table_name,
|
|
321
|
+
"${col}",
|
|
322
|
+
${serializeSequelizeColumnDefinition(previous_schema.columns[col])}
|
|
261
323
|
);
|
|
262
|
-
|
|
324
|
+
`).join("")}
|
|
263
325
|
|
|
264
|
-
|
|
326
|
+
${diff.table_renamed ? `
|
|
265
327
|
await queryInterface.renameTable(
|
|
266
328
|
"${diff.table_renamed.to}",
|
|
267
329
|
"${diff.table_renamed.from}"
|
|
268
|
-
)
|
|
330
|
+
);
|
|
331
|
+
` : ""}
|
|
269
332
|
|
|
270
|
-
this.logger.success(\`🗑️ Rollback
|
|
333
|
+
this.logger.success(\`🗑️ Rollback completed successfully.\`);
|
|
271
334
|
}
|
|
272
335
|
catch (error: any) {
|
|
273
336
|
this.logger.error(\`🚫 Rollback migration failed: "\${error.message}"\`, { error });
|
|
274
|
-
throw
|
|
337
|
+
throw error;
|
|
275
338
|
}
|
|
276
339
|
}
|
|
277
340
|
}
|
|
278
341
|
|
|
279
|
-
export default Update${
|
|
342
|
+
export default Update${model_name}TableMigration;
|
|
280
343
|
`;
|
|
344
|
+
};
|
|
281
345
|
exports.SEQUELIZE_UPDATE_EXISTING_SCHEMA_MIGRATION_FILE_CODE_TEMPLATE = SEQUELIZE_UPDATE_EXISTING_SCHEMA_MIGRATION_FILE_CODE_TEMPLATE;
|
|
282
|
-
const SEQUELIZE_SEEDER_TEMPLATE = (class_name, table_name) =>
|
|
346
|
+
const SEQUELIZE_SEEDER_TEMPLATE = (class_name, table_name) => {
|
|
347
|
+
return `
|
|
283
348
|
'use strict';
|
|
284
349
|
|
|
285
350
|
import { QueryInterface } from 'sequelize';
|
|
@@ -324,6 +389,7 @@ class ${class_name}Seeder {
|
|
|
324
389
|
|
|
325
390
|
export default ${class_name}Seeder;
|
|
326
391
|
`;
|
|
392
|
+
};
|
|
327
393
|
exports.SEQUELIZE_SEEDER_TEMPLATE = SEQUELIZE_SEEDER_TEMPLATE;
|
|
328
394
|
const SEQUELIZE_MODEL_CODE_TEMPLATE = (schema_model_name, schema_file_name, schema_columns) => {
|
|
329
395
|
const attributes = Object.keys(schema_columns)
|
|
@@ -349,7 +415,7 @@ ${attributes}
|
|
|
349
415
|
${schema_model_name}Schema.columns,
|
|
350
416
|
{
|
|
351
417
|
sequelize,
|
|
352
|
-
|
|
418
|
+
table_name: ${schema_model_name}Schema.table_name,
|
|
353
419
|
modelName: ${schema_model_name}Schema.model_name,
|
|
354
420
|
timestamps: ${schema_model_name}Schema.timestamps,
|
|
355
421
|
indexes: ${schema_model_name}Schema.indexes,
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const input_validator_util_1 = __importDefault(require("../../utils/input_validator_util"));
|
|
3
7
|
class SchemaDiffUtil {
|
|
4
8
|
static indexKey(i) {
|
|
5
9
|
return JSON.stringify({
|
|
@@ -11,8 +15,8 @@ class SchemaDiffUtil {
|
|
|
11
15
|
}
|
|
12
16
|
static getSchemaDifference(prev, curr) {
|
|
13
17
|
const diff = {
|
|
14
|
-
columns: { added: [], removed:
|
|
15
|
-
indexes: { added: [], removed: [], modified:
|
|
18
|
+
columns: { added: [], removed: [], modified: [] },
|
|
19
|
+
indexes: { added: [], removed: [], modified: [] }
|
|
16
20
|
};
|
|
17
21
|
// Table rename
|
|
18
22
|
if (prev.table_name !== curr.table_name) {
|
|
@@ -22,46 +26,39 @@ class SchemaDiffUtil {
|
|
|
22
26
|
};
|
|
23
27
|
}
|
|
24
28
|
// Columns
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
const prev_cols = prev.columns;
|
|
30
|
+
const curr_cols = curr.columns;
|
|
31
|
+
for (const [col_name, col_def] of Object.entries(curr_cols)) {
|
|
32
|
+
const prev_col_def = prev_cols[col_name];
|
|
33
|
+
if (!prev_col_def) {
|
|
28
34
|
diff.columns.added.push(col_name);
|
|
29
35
|
}
|
|
30
|
-
else if (
|
|
31
|
-
diff.columns.modified
|
|
32
|
-
before: prev_col,
|
|
33
|
-
after: col_def
|
|
34
|
-
};
|
|
36
|
+
else if (!input_validator_util_1.default.isDeepEqual(prev_col_def, col_def)) {
|
|
37
|
+
diff.columns.modified.push(col_name);
|
|
35
38
|
}
|
|
36
39
|
}
|
|
37
|
-
for (const
|
|
38
|
-
if (!
|
|
39
|
-
diff.columns.removed
|
|
40
|
+
for (const col_name of Object.keys(prev_cols)) {
|
|
41
|
+
if (!curr_cols[col_name]) {
|
|
42
|
+
diff.columns.removed.push(col_name);
|
|
40
43
|
}
|
|
41
44
|
}
|
|
42
45
|
// Indexes
|
|
43
46
|
const prev_indexes = prev?.indexes || [];
|
|
44
47
|
const curr_indexes = curr?.indexes || [];
|
|
45
|
-
const prev_map = new Map(prev_indexes.map(i => [
|
|
46
|
-
const curr_map = new Map(curr_indexes.map(i => [
|
|
47
|
-
for (const [
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
const prev_map = new Map(prev_indexes.map(i => [i.name, i]));
|
|
49
|
+
const curr_map = new Map(curr_indexes.map(i => [i.name, i]));
|
|
50
|
+
for (const [name, curr_idx] of curr_map) {
|
|
51
|
+
const prev_idx = prev_map.get(name);
|
|
52
|
+
if (!prev_idx) {
|
|
53
|
+
diff.indexes.added.push(name);
|
|
50
54
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (!curr_map.has(key)) {
|
|
54
|
-
diff.indexes.removed.push(idx);
|
|
55
|
+
else if (!input_validator_util_1.default.isDeepEqual(prev_idx, curr_idx)) {
|
|
56
|
+
diff.indexes.modified.push(name);
|
|
55
57
|
}
|
|
56
58
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (prev_idx && JSON.stringify(prev_idx) !== JSON.stringify(curr_idx)) {
|
|
61
|
-
diff.indexes.modified[curr_idx?.name] = {
|
|
62
|
-
before: prev_idx,
|
|
63
|
-
after: curr_idx
|
|
64
|
-
};
|
|
59
|
+
for (const [name] of prev_map) {
|
|
60
|
+
if (!curr_map.has(name)) {
|
|
61
|
+
diff.indexes.removed.push(name);
|
|
65
62
|
}
|
|
66
63
|
}
|
|
67
64
|
return diff;
|
|
@@ -2,7 +2,7 @@ import { SchemaDefinitionInterface, SchemaSnapshotInterface } from "../../types/
|
|
|
2
2
|
declare class SchemaNormalizerUtil {
|
|
3
3
|
static normalizeColumn(column: any): {
|
|
4
4
|
type: any;
|
|
5
|
-
|
|
5
|
+
allowNull: any;
|
|
6
6
|
primary_key: boolean;
|
|
7
7
|
auto_increment: boolean;
|
|
8
8
|
unique: boolean;
|
|
@@ -4,7 +4,7 @@ class SchemaNormalizerUtil {
|
|
|
4
4
|
static normalizeColumn(column) {
|
|
5
5
|
return {
|
|
6
6
|
type: column.type?.key ?? column.type?.toString(),
|
|
7
|
-
|
|
7
|
+
allowNull: column.allowNull ?? true,
|
|
8
8
|
primary_key: !!column.primaryKey,
|
|
9
9
|
auto_increment: !!column.autoIncrement,
|
|
10
10
|
unique: !!column.unique,
|
|
@@ -17,7 +17,7 @@ class MakeMigrationsScript {
|
|
|
17
17
|
logger = new main_1.LoggerUtil(this.name);
|
|
18
18
|
constructor() { }
|
|
19
19
|
getSnapshotPath(model_name) {
|
|
20
|
-
return path_1.default.join(this.snapshot_dir, `${main_1.InputTransformerUtil.toSnakeCase(model_name)}.schema_snapshot.
|
|
20
|
+
return path_1.default.join(this.snapshot_dir, `${main_1.InputTransformerUtil.toSnakeCase(model_name)}.schema_snapshot.ts`);
|
|
21
21
|
}
|
|
22
22
|
// Method to handle generating migration file name
|
|
23
23
|
generateMigrationFilename(table_name, type, migration_priority) {
|
|
@@ -26,13 +26,14 @@ class MakeMigrationsScript {
|
|
|
26
26
|
}
|
|
27
27
|
;
|
|
28
28
|
// Methos to write schema snapshot
|
|
29
|
-
writeSchemaSnapshot(model_name,
|
|
29
|
+
writeSchemaSnapshot(model_name, schema_file_path) {
|
|
30
30
|
const snapshot_path = this.getSnapshotPath(model_name);
|
|
31
31
|
try {
|
|
32
32
|
// Ensure snapshot directory exists
|
|
33
33
|
main_1.InputValidatorUtil.dirExists(this.snapshot_dir, true);
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
// Copy schema file exactly as-is
|
|
35
|
+
fs_1.default.copyFileSync(schema_file_path, snapshot_path);
|
|
36
|
+
this.logger.success(`📸 Schema snapshot copied to: ${snapshot_path}`);
|
|
36
37
|
return true;
|
|
37
38
|
}
|
|
38
39
|
catch (error) {
|
|
@@ -42,10 +43,10 @@ class MakeMigrationsScript {
|
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
45
|
// Method to handle create update schema table migration file
|
|
45
|
-
createUpdateSchemaMigrationFile(file_path, schema_file_path, schema_content, schema_diff) {
|
|
46
|
+
createUpdateSchemaMigrationFile(file_path, schema_file_path, schema_content, prev_schema_content, schema_diff) {
|
|
46
47
|
const { table_name, model_name } = schema_content;
|
|
47
48
|
const schema_file_name = path_1.default.basename(schema_file_path);
|
|
48
|
-
const template = (0, sequelize_code_template_1.SEQUELIZE_UPDATE_EXISTING_SCHEMA_MIGRATION_FILE_CODE_TEMPLATE)(table_name, model_name, schema_diff,
|
|
49
|
+
const template = (0, sequelize_code_template_1.SEQUELIZE_UPDATE_EXISTING_SCHEMA_MIGRATION_FILE_CODE_TEMPLATE)(table_name, model_name, schema_diff, schema_content, prev_schema_content);
|
|
49
50
|
try {
|
|
50
51
|
fs_1.default.writeFileSync(file_path, template, { encoding: "utf-8" });
|
|
51
52
|
}
|
|
@@ -59,7 +60,7 @@ class MakeMigrationsScript {
|
|
|
59
60
|
createNewSchemaMigrationFile(file_path, schema_file_path, schema_content) {
|
|
60
61
|
const { table_name, model_name } = schema_content;
|
|
61
62
|
const schema_file_name = path_1.default.basename(schema_file_path);
|
|
62
|
-
const template = (0, sequelize_code_template_1.SEQUELIZE_CREATE_NEW_SCHEMA_MIGRATION_FILE_CODE_TEMPLATE)(table_name, model_name, schema_file_name);
|
|
63
|
+
const template = (0, sequelize_code_template_1.SEQUELIZE_CREATE_NEW_SCHEMA_MIGRATION_FILE_CODE_TEMPLATE)(table_name, model_name, schema_file_name, schema_content);
|
|
63
64
|
try {
|
|
64
65
|
fs_1.default.writeFileSync(file_path, template, { encoding: "utf-8" });
|
|
65
66
|
}
|
|
@@ -77,7 +78,6 @@ class MakeMigrationsScript {
|
|
|
77
78
|
this.logger.error(`❌ Schema has no table_name: ${schema_file_path}`);
|
|
78
79
|
return;
|
|
79
80
|
}
|
|
80
|
-
const normalized_schema = main_2.SchemaNormalizerUtil.normalizeSchema(schema_content);
|
|
81
81
|
const snapshot_path = this.getSnapshotPath(model_name);
|
|
82
82
|
const operation = !fs_1.default.existsSync(snapshot_path) ? "create" : "update";
|
|
83
83
|
const migration_file_name = this.generateMigrationFilename(table_name, operation, migration_priority);
|
|
@@ -90,8 +90,9 @@ class MakeMigrationsScript {
|
|
|
90
90
|
else {
|
|
91
91
|
// Existing migration → UPDATE
|
|
92
92
|
this.logger.info(`🔄 Found existing migration snapshots: ${snapshot_path} → checking for updates...`);
|
|
93
|
-
|
|
94
|
-
const
|
|
93
|
+
delete require.cache[require.resolve(snapshot_path)];
|
|
94
|
+
const previous_snapshot = require(snapshot_path).default;
|
|
95
|
+
const diff = main_2.SchemaDiffUtil.getSchemaDifference(previous_snapshot, schema_content);
|
|
95
96
|
if (!diff.table_renamed &&
|
|
96
97
|
!diff.columns.added.length &&
|
|
97
98
|
!diff.columns.removed.length &&
|
|
@@ -101,12 +102,12 @@ class MakeMigrationsScript {
|
|
|
101
102
|
this.logger.info("ℹ️ No schema changes detected");
|
|
102
103
|
return false;
|
|
103
104
|
}
|
|
104
|
-
migration_file_created = this.createUpdateSchemaMigrationFile(migration_file_path, schema_file_path, schema_content, diff);
|
|
105
|
+
migration_file_created = this.createUpdateSchemaMigrationFile(migration_file_path, schema_file_path, schema_content, previous_snapshot, diff);
|
|
105
106
|
}
|
|
106
107
|
if (migration_file_created) {
|
|
107
108
|
this.logger.success(`✅ Migration file created: ${migration_file_name}`);
|
|
108
109
|
// Update snapshot AFTER successful migration creation
|
|
109
|
-
const snapshot_written = this.writeSchemaSnapshot(model_name,
|
|
110
|
+
const snapshot_written = this.writeSchemaSnapshot(model_name, schema_file_path);
|
|
110
111
|
if (!snapshot_written) {
|
|
111
112
|
this.logger.error(`⚠️ Migration created but snapshot update failed for "${model_name}"`);
|
|
112
113
|
}
|
|
@@ -69,18 +69,12 @@ export interface SchemaDiffInterface {
|
|
|
69
69
|
};
|
|
70
70
|
columns: {
|
|
71
71
|
added: string[];
|
|
72
|
-
removed:
|
|
73
|
-
modified:
|
|
74
|
-
before: SchemaColumnInterface;
|
|
75
|
-
after: SchemaColumnInterface;
|
|
76
|
-
}>;
|
|
72
|
+
removed: string[];
|
|
73
|
+
modified: string[];
|
|
77
74
|
};
|
|
78
75
|
indexes: {
|
|
79
|
-
added:
|
|
80
|
-
removed:
|
|
81
|
-
modified:
|
|
82
|
-
before: IndexFieldOptionsInterface;
|
|
83
|
-
after: IndexFieldOptionsInterface;
|
|
84
|
-
}>;
|
|
76
|
+
added: string[];
|
|
77
|
+
removed: string[];
|
|
78
|
+
modified: string[];
|
|
85
79
|
};
|
|
86
80
|
}
|
|
@@ -50,5 +50,6 @@ declare class InputValidatorUtil {
|
|
|
50
50
|
static isValidReCaptcha(recpatcha_token: string): Promise<any>;
|
|
51
51
|
static dirExists(directory: string, create_directory?: boolean, return_dir_path?: boolean): boolean | string;
|
|
52
52
|
static isSafeHashCompare(a: string, b: string): boolean;
|
|
53
|
+
static isDeepEqual(a: any, b: any): boolean;
|
|
53
54
|
}
|
|
54
55
|
export default InputValidatorUtil;
|
|
@@ -125,5 +125,54 @@ class InputValidatorUtil {
|
|
|
125
125
|
}
|
|
126
126
|
return crypto_1.default.timingSafeEqual(buf1, buf2);
|
|
127
127
|
}
|
|
128
|
+
static isDeepEqual(a, b) {
|
|
129
|
+
// Strict equal handles primitives + same reference
|
|
130
|
+
if (a === b)
|
|
131
|
+
return true;
|
|
132
|
+
// Handle null / undefined
|
|
133
|
+
if (a == null || b == null)
|
|
134
|
+
return a === b;
|
|
135
|
+
// Handle Date
|
|
136
|
+
if (a instanceof Date && b instanceof Date) {
|
|
137
|
+
return a.getTime() === b.getTime();
|
|
138
|
+
}
|
|
139
|
+
// Handle functions (compare source)
|
|
140
|
+
if (typeof a === "function" && typeof b === "function") {
|
|
141
|
+
return a.toString() === b.toString();
|
|
142
|
+
}
|
|
143
|
+
// Handle Sequelize DataTypes
|
|
144
|
+
if (a?.key && b?.key) {
|
|
145
|
+
if (a.key !== b.key)
|
|
146
|
+
return false;
|
|
147
|
+
const aOpts = a.options || {};
|
|
148
|
+
const bOpts = b.options || {};
|
|
149
|
+
return InputValidatorUtil.isDeepEqual(aOpts, bOpts);
|
|
150
|
+
}
|
|
151
|
+
// Handle arrays
|
|
152
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
153
|
+
if (a.length !== b.length)
|
|
154
|
+
return false;
|
|
155
|
+
for (let i = 0; i < a.length; i++) {
|
|
156
|
+
if (!InputValidatorUtil.isDeepEqual(a[i], b[i]))
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
// Handle objects
|
|
162
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
163
|
+
const aKeys = Object.keys(a);
|
|
164
|
+
const bKeys = Object.keys(b);
|
|
165
|
+
if (aKeys.length !== bKeys.length)
|
|
166
|
+
return false;
|
|
167
|
+
for (const key of aKeys) {
|
|
168
|
+
if (!bKeys.includes(key))
|
|
169
|
+
return false;
|
|
170
|
+
if (!InputValidatorUtil.isDeepEqual(a[key], b[key]))
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
128
177
|
}
|
|
129
178
|
exports.default = InputValidatorUtil;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fiberx-backend-toolkit",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.62",
|
|
4
4
|
"description": "A TypeScript backend toolkit providing shared domain logic, infrastructure helpers, and utilities for FiberX server-side applications and services.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./dist/index.js",
|