fiberx-backend-toolkit 0.0.61 → 0.0.63
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 +224 -141
- 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,82 @@
|
|
|
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 INDENT = " ".repeat(4);
|
|
5
|
+
const indentBlock = (code, level) => {
|
|
6
|
+
const indentation = INDENT.repeat(level);
|
|
7
|
+
return code
|
|
8
|
+
.split("\n")
|
|
9
|
+
.map(line => line.trim().length ? indentation + line : line)
|
|
10
|
+
.join("\n");
|
|
11
|
+
};
|
|
12
|
+
const serializeSequelizeDataType = (type) => {
|
|
13
|
+
if (!type)
|
|
14
|
+
return "undefined";
|
|
15
|
+
if (type.key) {
|
|
16
|
+
const key = type.key;
|
|
17
|
+
if (type.options?.length)
|
|
18
|
+
return `DataTypes.${key}(${type.options.length})`;
|
|
19
|
+
if (type.options?.values)
|
|
20
|
+
return `DataTypes.${key}(${JSON.stringify(type.options.values)})`;
|
|
21
|
+
if (type.options?.precision && type.options?.scale)
|
|
22
|
+
return `DataTypes.${key}(${type.options.precision}, ${type.options.scale})`;
|
|
23
|
+
return `DataTypes.${key}`;
|
|
24
|
+
}
|
|
25
|
+
return "undefined";
|
|
26
|
+
};
|
|
27
|
+
const serializeSequelizeColumnDefinition = (column) => {
|
|
28
|
+
const parts = [];
|
|
29
|
+
for (const [key, value] of Object.entries(column)) {
|
|
30
|
+
if (key === "type") {
|
|
31
|
+
parts.push(`type: ${serializeSequelizeDataType(value)}`);
|
|
32
|
+
}
|
|
33
|
+
else if (typeof value === "string") {
|
|
34
|
+
parts.push(`${key}: "${value}"`);
|
|
35
|
+
}
|
|
36
|
+
else if (typeof value === "boolean" || typeof value === "number") {
|
|
37
|
+
parts.push(`${key}: ${value}`);
|
|
38
|
+
}
|
|
39
|
+
else if (Array.isArray(value)) {
|
|
40
|
+
parts.push(`${key}: ${JSON.stringify(value)}`);
|
|
41
|
+
}
|
|
42
|
+
else if (value && typeof value === "object") {
|
|
43
|
+
parts.push(`${key}: ${JSON.stringify(value, null, 4)}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return `{
|
|
47
|
+
${indentBlock(parts.join(",\n"), 1)}
|
|
48
|
+
}`;
|
|
49
|
+
};
|
|
50
|
+
const serializeIndexDefinition = (index) => {
|
|
51
|
+
const clone = { ...index };
|
|
52
|
+
delete clone.name;
|
|
53
|
+
delete clone.fields;
|
|
54
|
+
delete clone.comment;
|
|
55
|
+
const entries = Object.entries(clone)
|
|
56
|
+
.map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
|
|
57
|
+
.join(",\n");
|
|
58
|
+
return entries;
|
|
59
|
+
};
|
|
60
|
+
const serializeSequelizeColumnsObject = (columns) => {
|
|
61
|
+
const rendered = Object.entries(columns)
|
|
62
|
+
.map(([col_name, col_def]) => {
|
|
63
|
+
const colCode = serializeSequelizeColumnDefinition(col_def);
|
|
64
|
+
return `"${col_name}": ${colCode}`;
|
|
65
|
+
})
|
|
66
|
+
.join(",\n");
|
|
67
|
+
return `{
|
|
68
|
+
${indentBlock(rendered, 1)}
|
|
69
|
+
}`;
|
|
70
|
+
};
|
|
71
|
+
const serializeSequelizeIndexesArray = (indexes) => {
|
|
72
|
+
return indexes.map(index => {
|
|
73
|
+
return `{
|
|
74
|
+
name: "${index.name}",
|
|
75
|
+
fields: ${JSON.stringify(index.fields)},
|
|
76
|
+
${serializeIndexDefinition(index).replace(/^{|}$/g, "")}
|
|
77
|
+
}`;
|
|
78
|
+
}).join(",\n ");
|
|
79
|
+
};
|
|
4
80
|
const SEQUELIZE_SCHEMA_CODE_TEMPLATE = (schema_name, table_name, model_name, migration_priority) => {
|
|
5
81
|
return `
|
|
6
82
|
import { DataTypes } from "sequelize";
|
|
@@ -47,239 +123,245 @@ export default ${schema_name};
|
|
|
47
123
|
`;
|
|
48
124
|
};
|
|
49
125
|
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
|
-
|
|
126
|
+
const SEQUELIZE_CREATE_NEW_SCHEMA_MIGRATION_FILE_CODE_TEMPLATE = (schema_table_name, schema_model_name, schema_file_name, schema_definition) => {
|
|
127
|
+
const rendered_columns = serializeSequelizeColumnsObject(schema_definition.columns);
|
|
128
|
+
const rendered_indexes = serializeSequelizeIndexesArray(schema_definition.indexes || []);
|
|
129
|
+
return `
|
|
130
|
+
import { QueryInterface, Sequelize, DataTypes } from "sequelize";
|
|
52
131
|
import { LoggerUtil } from "fiberx-backend-toolkit/dist/utils/main";
|
|
53
|
-
import { SchemaDefinitionInterface } from "fiberx-backend-toolkit";
|
|
54
|
-
import ${schema_model_name}Schema from "@/database/schemas/${schema_file_name.replace(".ts", "")}";
|
|
55
132
|
|
|
56
133
|
class Create${schema_model_name}TableMigration {
|
|
57
134
|
private name: string = "create_table_${schema_table_name}_migration_file";
|
|
58
|
-
private schema: SchemaDefinitionInterface = ${schema_model_name}Schema;
|
|
59
135
|
private readonly logger: LoggerUtil = new LoggerUtil(this.name);
|
|
60
136
|
|
|
61
137
|
constructor() {}
|
|
62
138
|
|
|
63
|
-
|
|
64
|
-
public async up(
|
|
65
|
-
queryInterface: QueryInterface,
|
|
66
|
-
Sequelize: Sequelize
|
|
67
|
-
) {
|
|
139
|
+
public async up(queryInterface: QueryInterface, Sequelize: Sequelize) {
|
|
68
140
|
try {
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
await queryInterface.createTable(table_name, columns);
|
|
141
|
+
const table_name = "${schema_table_name}";
|
|
72
142
|
|
|
73
|
-
|
|
143
|
+
await queryInterface.createTable(
|
|
144
|
+
table_name,
|
|
145
|
+
${indentBlock(rendered_columns, 4)}
|
|
146
|
+
);
|
|
74
147
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
148
|
+
${schema_definition.indexes?.map(index => {
|
|
149
|
+
const indexCode = `
|
|
150
|
+
await queryInterface.addIndex(
|
|
151
|
+
table_name,
|
|
152
|
+
${JSON.stringify(index.fields)},
|
|
153
|
+
{
|
|
154
|
+
name: "${index.name}",
|
|
155
|
+
${indentBlock(serializeIndexDefinition(index), 2)}
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
`;
|
|
159
|
+
return indentBlock(indexCode.trim(), 3);
|
|
160
|
+
}).join("\n\n") || ""}
|
|
84
161
|
|
|
85
|
-
this.logger.success(\`✅ Table "\${table_name}"
|
|
162
|
+
this.logger.success(\`✅ Table "\${table_name}" created successfully.\`);
|
|
86
163
|
}
|
|
87
164
|
catch (error: any) {
|
|
88
165
|
this.logger.error(\`🚫 Migration failed: "\${error.message}"\`, { error });
|
|
89
|
-
throw
|
|
166
|
+
throw error;
|
|
90
167
|
}
|
|
91
168
|
}
|
|
92
169
|
|
|
93
|
-
|
|
94
|
-
public async down(
|
|
95
|
-
queryInterface: QueryInterface,
|
|
96
|
-
Sequelize: Sequelize
|
|
97
|
-
) {
|
|
170
|
+
public async down(queryInterface: QueryInterface, Sequelize: Sequelize) {
|
|
98
171
|
try {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
this.logger.success(\`🗑️ Table "\${table_name}" dropped successfully.\`);
|
|
172
|
+
await queryInterface.dropTable("${schema_table_name}");
|
|
173
|
+
this.logger.success(\`🗑️ Table "${schema_table_name}" dropped successfully.\`);
|
|
102
174
|
} catch (error: any) {
|
|
103
|
-
this.logger.error(\`🚫
|
|
104
|
-
throw
|
|
175
|
+
this.logger.error(\`🚫 Rollback failed: "\${error.message}"\`, { error });
|
|
176
|
+
throw error;
|
|
105
177
|
}
|
|
106
178
|
}
|
|
107
179
|
}
|
|
108
180
|
|
|
109
181
|
export default Create${schema_model_name}TableMigration;
|
|
110
182
|
`;
|
|
183
|
+
};
|
|
111
184
|
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 = (
|
|
185
|
+
const SEQUELIZE_UPDATE_EXISTING_SCHEMA_MIGRATION_FILE_CODE_TEMPLATE = (table_name, model_name, diff, current_schema, previous_schema) => {
|
|
186
|
+
return `
|
|
113
187
|
import { QueryInterface, Sequelize } from "sequelize";
|
|
114
188
|
import { LoggerUtil } from "fiberx-backend-toolkit/dist/utils/main";
|
|
115
|
-
import { SchemaDefinitionInterface } from "fiberx-backend-toolkit";
|
|
116
|
-
import ${schema_model_name}Schema from "@/database/schemas/${schema_file_name.replace(".ts", "")}";
|
|
117
189
|
|
|
118
|
-
class Update${
|
|
119
|
-
private name: string = "update_table_${
|
|
120
|
-
private schema: SchemaDefinitionInterface = ${schema_model_name}Schema;
|
|
190
|
+
class Update${model_name}TableMigration {
|
|
191
|
+
private readonly name: string = "update_table_${table_name}_migration_file";
|
|
121
192
|
private readonly logger: LoggerUtil = new LoggerUtil(this.name);
|
|
122
193
|
|
|
123
194
|
constructor() {}
|
|
124
195
|
|
|
125
|
-
public async up(
|
|
126
|
-
queryInterface: QueryInterface,
|
|
127
|
-
Sequelize: Sequelize
|
|
128
|
-
) {
|
|
196
|
+
public async up(queryInterface: QueryInterface, Sequelize: Sequelize) {
|
|
129
197
|
try {
|
|
130
|
-
|
|
198
|
+
let table_name = "${table_name}";
|
|
131
199
|
|
|
132
|
-
|
|
200
|
+
${diff.table_renamed ? `
|
|
133
201
|
await queryInterface.renameTable(
|
|
134
202
|
"${diff.table_renamed.from}",
|
|
135
203
|
"${diff.table_renamed.to}"
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
|
|
204
|
+
);
|
|
205
|
+
table_name = "${diff.table_renamed.to}";
|
|
206
|
+
` : ""}
|
|
139
207
|
|
|
140
|
-
|
|
141
|
-
|
|
208
|
+
// ----------- ADD COLUMNS -----------
|
|
209
|
+
${diff.columns.added.map(col => `
|
|
142
210
|
await queryInterface.addColumn(
|
|
143
211
|
table_name,
|
|
144
|
-
"${
|
|
145
|
-
columns[
|
|
212
|
+
"${col}",
|
|
213
|
+
${serializeSequelizeColumnDefinition(current_schema.columns[col])}
|
|
146
214
|
);
|
|
147
|
-
|
|
215
|
+
`).join("")}
|
|
148
216
|
|
|
149
|
-
|
|
150
|
-
|
|
217
|
+
// ----------- MODIFY COLUMNS -----------
|
|
218
|
+
${diff.columns.modified.map(col => `
|
|
151
219
|
await queryInterface.changeColumn(
|
|
152
|
-
table_name,
|
|
153
|
-
"${
|
|
154
|
-
${
|
|
220
|
+
table_name,
|
|
221
|
+
"${col}",
|
|
222
|
+
${serializeSequelizeColumnDefinition(current_schema.columns[col])}
|
|
155
223
|
);
|
|
156
224
|
`).join("")}
|
|
157
225
|
|
|
158
|
-
|
|
159
|
-
|
|
226
|
+
// ----------- REMOVE COLUMNS -----------
|
|
227
|
+
${diff.columns.removed.map(col => `
|
|
160
228
|
await queryInterface.removeColumn(
|
|
161
|
-
table_name,
|
|
162
|
-
"${
|
|
229
|
+
table_name,
|
|
230
|
+
"${col}"
|
|
163
231
|
);
|
|
164
232
|
`).join("")}
|
|
165
233
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
234
|
+
// ----------- ADD INDEXES -----------
|
|
235
|
+
${diff.indexes.added.map(name => {
|
|
236
|
+
const curr_indxs = current_schema?.indexes || [];
|
|
237
|
+
const idx = curr_indxs.find(i => i.name === name);
|
|
238
|
+
return idx ? `
|
|
169
239
|
await queryInterface.addIndex(
|
|
170
|
-
table_name,
|
|
171
|
-
${JSON.stringify(idx.fields)},
|
|
172
|
-
|
|
240
|
+
table_name,
|
|
241
|
+
${JSON.stringify(idx.fields)},
|
|
242
|
+
{
|
|
243
|
+
name: "${idx.name}",
|
|
244
|
+
${serializeIndexDefinition(idx).replace(/^{|}$/g, "")}
|
|
245
|
+
}
|
|
173
246
|
);
|
|
174
|
-
|
|
247
|
+
` : "";
|
|
248
|
+
}).join("")}
|
|
175
249
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
);
|
|
250
|
+
// ----------- MODIFY INDEXES -----------
|
|
251
|
+
${diff.indexes.modified.map(name => {
|
|
252
|
+
const curr_indxs = current_schema?.indexes || [];
|
|
253
|
+
const idx = curr_indxs.find(i => i.name === name);
|
|
254
|
+
return idx ? `
|
|
255
|
+
await queryInterface.removeIndex(table_name, "${name}");
|
|
182
256
|
|
|
183
257
|
await queryInterface.addIndex(
|
|
184
|
-
table_name,
|
|
185
|
-
${JSON.stringify(
|
|
186
|
-
|
|
258
|
+
table_name,
|
|
259
|
+
${JSON.stringify(idx.fields)},
|
|
260
|
+
{
|
|
261
|
+
name: "${idx.name}",
|
|
262
|
+
${serializeIndexDefinition(idx).replace(/^{|}$/g, "")}
|
|
263
|
+
}
|
|
187
264
|
);
|
|
188
|
-
|
|
265
|
+
` : "";
|
|
266
|
+
}).join("")}
|
|
189
267
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
await queryInterface.removeIndex(
|
|
193
|
-
table_name,
|
|
194
|
-
${JSON.stringify(idx.name)}
|
|
195
|
-
);
|
|
268
|
+
// ----------- REMOVE INDEXES -----------
|
|
269
|
+
${diff.indexes.removed.map(name => `
|
|
270
|
+
await queryInterface.removeIndex(table_name, "${name}");
|
|
196
271
|
`).join("")}
|
|
197
272
|
|
|
198
|
-
|
|
199
|
-
this.logger.success(\`✅ Table "\${table_name}" for schema "\${model_name}Schema" updated successfully.\`);
|
|
273
|
+
this.logger.success(\`✅ Table "\${table_name}" updated successfully.\`);
|
|
200
274
|
}
|
|
201
275
|
catch (error: any) {
|
|
202
276
|
this.logger.error(\`🚫 Update migration failed: "\${error.message}"\`, { error });
|
|
203
|
-
throw
|
|
277
|
+
throw error;
|
|
204
278
|
}
|
|
205
279
|
}
|
|
206
280
|
|
|
207
|
-
public async down(
|
|
208
|
-
queryInterface: QueryInterface,
|
|
209
|
-
Sequelize: Sequelize
|
|
210
|
-
) {
|
|
281
|
+
public async down(queryInterface: QueryInterface, Sequelize: Sequelize) {
|
|
211
282
|
try {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
await queryInterface.removeIndex(
|
|
217
|
-
|
|
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
|
-
);
|
|
283
|
+
let table_name = "${table_name}";
|
|
284
|
+
|
|
285
|
+
// ----------- REVERT INDEXES -----------
|
|
286
|
+
${diff.indexes.added.map(name => `
|
|
287
|
+
await queryInterface.removeIndex(table_name, "${name}");
|
|
288
|
+
`).join("")}
|
|
227
289
|
|
|
290
|
+
${diff.indexes.modified.map(name => {
|
|
291
|
+
const prev_indxs = previous_schema?.indexes || [];
|
|
292
|
+
const idx = prev_indxs.find(i => i.name === name);
|
|
293
|
+
return idx ? `
|
|
294
|
+
await queryInterface.removeIndex(table_name, "${name}");
|
|
228
295
|
|
|
229
296
|
await queryInterface.addIndex(
|
|
230
|
-
table_name,
|
|
231
|
-
${JSON.stringify(
|
|
232
|
-
|
|
233
|
-
|
|
297
|
+
table_name,
|
|
298
|
+
${JSON.stringify(idx.fields)},
|
|
299
|
+
{
|
|
300
|
+
name: "${idx.name}",
|
|
301
|
+
${serializeIndexDefinition(idx).replace(/^{|}$/g, "")}
|
|
302
|
+
}
|
|
303
|
+
);
|
|
304
|
+
` : "";
|
|
305
|
+
}).join("")}
|
|
234
306
|
|
|
235
|
-
|
|
236
|
-
|
|
307
|
+
${diff.indexes.removed.map(name => {
|
|
308
|
+
const prev_indxs = previous_schema?.indexes || [];
|
|
309
|
+
const idx = prev_indxs.find(i => i.name === name);
|
|
310
|
+
return idx ? `
|
|
237
311
|
await queryInterface.addIndex(
|
|
238
|
-
table_name,
|
|
239
|
-
${JSON.stringify(idx.fields)},
|
|
240
|
-
|
|
241
|
-
|
|
312
|
+
table_name,
|
|
313
|
+
${JSON.stringify(idx.fields)},
|
|
314
|
+
{
|
|
315
|
+
name: "${idx.name}",
|
|
316
|
+
${serializeIndexDefinition(idx).replace(/^{|}$/g, "")}
|
|
317
|
+
}
|
|
318
|
+
);
|
|
319
|
+
` : "";
|
|
320
|
+
}).join("")}
|
|
242
321
|
|
|
243
|
-
|
|
244
|
-
|
|
322
|
+
// ----------- REVERT COLUMNS -----------
|
|
323
|
+
${diff.columns.modified.map(col => `
|
|
245
324
|
await queryInterface.changeColumn(
|
|
246
|
-
table_name,
|
|
247
|
-
"${
|
|
248
|
-
${
|
|
249
|
-
)
|
|
325
|
+
table_name,
|
|
326
|
+
"${col}",
|
|
327
|
+
${serializeSequelizeColumnDefinition(previous_schema.columns[col])}
|
|
328
|
+
);
|
|
329
|
+
`).join("")}
|
|
250
330
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
`).join("")}
|
|
331
|
+
${diff.columns.added.map(col => `
|
|
332
|
+
await queryInterface.removeColumn(table_name, "${col}");
|
|
333
|
+
`).join("")}
|
|
255
334
|
|
|
256
|
-
|
|
335
|
+
${diff.columns.removed.map(col => `
|
|
257
336
|
await queryInterface.addColumn(
|
|
258
|
-
table_name,
|
|
259
|
-
"${
|
|
260
|
-
${
|
|
337
|
+
table_name,
|
|
338
|
+
"${col}",
|
|
339
|
+
${serializeSequelizeColumnDefinition(previous_schema.columns[col])}
|
|
261
340
|
);
|
|
262
|
-
|
|
341
|
+
`).join("")}
|
|
263
342
|
|
|
264
|
-
|
|
343
|
+
${diff.table_renamed ? `
|
|
265
344
|
await queryInterface.renameTable(
|
|
266
345
|
"${diff.table_renamed.to}",
|
|
267
346
|
"${diff.table_renamed.from}"
|
|
268
|
-
)
|
|
347
|
+
);
|
|
348
|
+
` : ""}
|
|
269
349
|
|
|
270
|
-
this.logger.success(\`🗑️ Rollback
|
|
350
|
+
this.logger.success(\`🗑️ Rollback completed successfully.\`);
|
|
271
351
|
}
|
|
272
352
|
catch (error: any) {
|
|
273
353
|
this.logger.error(\`🚫 Rollback migration failed: "\${error.message}"\`, { error });
|
|
274
|
-
throw
|
|
354
|
+
throw error;
|
|
275
355
|
}
|
|
276
356
|
}
|
|
277
357
|
}
|
|
278
358
|
|
|
279
|
-
export default Update${
|
|
359
|
+
export default Update${model_name}TableMigration;
|
|
280
360
|
`;
|
|
361
|
+
};
|
|
281
362
|
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) =>
|
|
363
|
+
const SEQUELIZE_SEEDER_TEMPLATE = (class_name, table_name) => {
|
|
364
|
+
return `
|
|
283
365
|
'use strict';
|
|
284
366
|
|
|
285
367
|
import { QueryInterface } from 'sequelize';
|
|
@@ -324,6 +406,7 @@ class ${class_name}Seeder {
|
|
|
324
406
|
|
|
325
407
|
export default ${class_name}Seeder;
|
|
326
408
|
`;
|
|
409
|
+
};
|
|
327
410
|
exports.SEQUELIZE_SEEDER_TEMPLATE = SEQUELIZE_SEEDER_TEMPLATE;
|
|
328
411
|
const SEQUELIZE_MODEL_CODE_TEMPLATE = (schema_model_name, schema_file_name, schema_columns) => {
|
|
329
412
|
const attributes = Object.keys(schema_columns)
|
|
@@ -349,7 +432,7 @@ ${attributes}
|
|
|
349
432
|
${schema_model_name}Schema.columns,
|
|
350
433
|
{
|
|
351
434
|
sequelize,
|
|
352
|
-
|
|
435
|
+
table_name: ${schema_model_name}Schema.table_name,
|
|
353
436
|
modelName: ${schema_model_name}Schema.model_name,
|
|
354
437
|
timestamps: ${schema_model_name}Schema.timestamps,
|
|
355
438
|
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.63",
|
|
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",
|