miqro 1.5.5 → 1.6.1

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 (89) hide show
  1. package/README.md +42 -2
  2. package/dist/cli.js +8 -9
  3. package/dist/cli.js.map +1 -0
  4. package/dist/cmds/config-bash.js +1 -0
  5. package/dist/cmds/config-bash.js.map +1 -0
  6. package/dist/cmds/config-env.js +1 -0
  7. package/dist/cmds/config-env.js.map +1 -0
  8. package/dist/cmds/config-init.js +2 -0
  9. package/dist/cmds/config-init.js.map +1 -0
  10. package/dist/cmds/config.js +1 -0
  11. package/dist/cmds/config.js.map +1 -0
  12. package/dist/cmds/db-console.js +2 -0
  13. package/dist/cmds/db-console.js.map +1 -0
  14. package/dist/cmds/db-createmodel.js +4 -1
  15. package/dist/cmds/db-createmodel.js.map +1 -0
  16. package/dist/cmds/db-dump-data.js +2 -0
  17. package/dist/cmds/db-dump-data.js.map +1 -0
  18. package/dist/cmds/db-init.js +2 -0
  19. package/dist/cmds/db-init.js.map +1 -0
  20. package/dist/cmds/db-makemigrations.js +3 -0
  21. package/dist/cmds/db-makemigrations.js.map +1 -0
  22. package/dist/cmds/db-push-data.js +30 -4
  23. package/dist/cmds/db-push-data.js.map +1 -0
  24. package/dist/cmds/db-sync-makemigrations.js +3 -0
  25. package/dist/cmds/db-sync-makemigrations.js.map +1 -0
  26. package/dist/cmds/doc-json.js +1 -0
  27. package/dist/cmds/doc-json.js.map +1 -0
  28. package/dist/cmds/doc-md.js +53 -16
  29. package/dist/cmds/doc-md.js.map +1 -0
  30. package/dist/cmds/handler-apiroute-new.js +2 -36
  31. package/dist/cmds/handler-apiroute-new.js.map +1 -0
  32. package/dist/cmds/handler-main-new.d.ts +1 -2
  33. package/dist/cmds/handler-main-new.js +15 -22
  34. package/dist/cmds/handler-main-new.js.map +1 -0
  35. package/dist/cmds/new.d.ts +1 -3
  36. package/dist/cmds/new.js +8 -18
  37. package/dist/cmds/new.js.map +1 -0
  38. package/dist/cmds/start.js +4 -0
  39. package/dist/cmds/start.js.map +1 -0
  40. package/dist/index.d.ts +2 -0
  41. package/dist/index.js +15 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/utils/db/automigrations/deep-diff/index.d.ts +24 -0
  44. package/dist/utils/db/automigrations/deep-diff/index.js +240 -0
  45. package/dist/utils/db/automigrations/deep-diff/index.js.map +1 -0
  46. package/dist/utils/db/automigrations/index.js +17 -0
  47. package/dist/utils/db/automigrations/index.js.map +1 -0
  48. package/dist/utils/db/automigrations/migrate.js +136 -17
  49. package/dist/utils/db/automigrations/migrate.js.map +1 -0
  50. package/dist/utils/db/index.d.ts +3 -3
  51. package/dist/utils/db/index.js +11 -1
  52. package/dist/utils/db/index.js.map +1 -0
  53. package/dist/utils/doc/index.d.ts +10 -8
  54. package/dist/utils/doc/index.js +4 -1
  55. package/dist/utils/doc/index.js.map +1 -0
  56. package/dist/utils/index.d.ts +2 -1
  57. package/dist/utils/index.js +2 -1
  58. package/dist/utils/index.js.map +1 -0
  59. package/dist/utils/templates.d.ts +2 -2
  60. package/dist/utils/templates.js +6 -0
  61. package/dist/utils/templates.js.map +1 -0
  62. package/package.json +9 -15
  63. package/src/cli.ts +62 -0
  64. package/src/cmds/config-bash.ts +18 -0
  65. package/src/cmds/config-env.ts +18 -0
  66. package/src/cmds/config-init.ts +35 -0
  67. package/src/cmds/config.ts +16 -0
  68. package/src/cmds/db-console.ts +35 -0
  69. package/src/cmds/db-createmodel.ts +44 -0
  70. package/src/cmds/db-dump-data.ts +44 -0
  71. package/src/cmds/db-init.ts +42 -0
  72. package/src/cmds/db-makemigrations.ts +12 -0
  73. package/src/cmds/db-push-data.ts +77 -0
  74. package/src/cmds/db-sync-makemigrations.ts +12 -0
  75. package/src/cmds/doc-json.ts +16 -0
  76. package/src/cmds/doc-md.ts +233 -0
  77. package/src/cmds/handler-apiroute-new.ts +67 -0
  78. package/src/cmds/handler-main-new.ts +85 -0
  79. package/src/cmds/new.ts +128 -0
  80. package/src/cmds/start.ts +16 -0
  81. package/src/index.ts +2 -0
  82. package/src/utils/db/automigrations/deep-diff/index.ts +264 -0
  83. package/src/utils/db/automigrations/index.ts +151 -0
  84. package/src/utils/db/automigrations/migrate.ts +888 -0
  85. package/src/utils/db/index.ts +128 -0
  86. package/src/utils/doc/index.ts +31 -0
  87. package/src/utils/index.ts +9 -0
  88. package/src/utils/templates.ts +189 -0
  89. package/tsconfig.json +32 -0
@@ -0,0 +1,888 @@
1
+ import fs from "fs";
2
+
3
+ import path from "path";
4
+
5
+ import { DEEPDIFF } from "./deep-diff";
6
+
7
+ interface TableMap {
8
+ [tableName: string]: {
9
+ tableName: string;
10
+ schema: any;
11
+ indexes: any;
12
+ model?: any;
13
+ options?: any;
14
+ fields?: any[];
15
+ }
16
+ }
17
+
18
+ export interface MigrationState {
19
+ tables: TableMap;
20
+ revision: number;
21
+ }
22
+
23
+ /* tslint:disable */
24
+ const reverseSequelizeColType = (DataTypes: any, col: any, prefix = "Sequelize."): string => {
25
+ const attrName = col.type.key;
26
+ const attrObj = col.type;
27
+ const options = (col.type.options) ? col.type.options : {};
28
+
29
+ // noinspection SpellCheckingInspection,DuplicateCaseLabelJS
30
+ switch (attrName) {
31
+ // CHAR(length, binary)
32
+ case DataTypes.CHAR.key:
33
+ if (options.binary) {
34
+ return prefix + "CHAR.BINARY";
35
+ }
36
+ return prefix + "CHAR(" + options.length + ")";
37
+
38
+ // STRING(length, binary).BINARY
39
+ case DataTypes.STRING.key:
40
+ return prefix + "STRING" + ((options.length) ? "(" + options.length + ")" : "") +
41
+ ((options.binary) ? ".BINARY" : "");
42
+
43
+ // TEXT(length)
44
+ case DataTypes.TEXT.key:
45
+ if (!options.length) {
46
+ return prefix + "TEXT";
47
+ }
48
+ return prefix + "TEXT(" + options.length.toLowerCase() + ")";
49
+
50
+ // NUMBER(length, decimals).UNSIGNED.ZEROFILL
51
+ case DataTypes.NUMBER.key:
52
+ case DataTypes.TINYINT.key:
53
+ case DataTypes.SMALLINT.key:
54
+ case DataTypes.MEDIUMINT.key:
55
+ case DataTypes.BIGINT.key:
56
+ case DataTypes.FLOAT.key:
57
+ case DataTypes.REAL.key:
58
+ case DataTypes.DOUBLE.key:
59
+ case DataTypes.DECIMAL.key:
60
+ case DataTypes.INTEGER.key: {
61
+ let ret = attrName;
62
+ if (options.length) {
63
+ ret += "(" + options.length;
64
+ if (options.decimals) {
65
+ ret += ", " + options.decimals;
66
+ }
67
+ ret += ")";
68
+ }
69
+
70
+ if (options.precision) {
71
+ ret += "(" + options.precision;
72
+ if (options.scale) {
73
+ ret += ", " + options.scale;
74
+ }
75
+ ret += ")";
76
+ }
77
+
78
+ ret = [ret];
79
+
80
+ if (options.zerofill) {
81
+ ret.push("ZEROFILL");
82
+ }
83
+
84
+ if (options.unsigned) {
85
+ ret.push("UNSIGNED");
86
+ }
87
+
88
+ return prefix + ret.join(".");
89
+ }
90
+
91
+ case DataTypes.ENUM.key:
92
+ return prefix + "ENUM('" + options.values.join("', '") + "')";
93
+
94
+ case DataTypes.BLOB.key:
95
+ if (!options.length) {
96
+ return prefix + "BLOB";
97
+ }
98
+ return prefix + "BLOB(" + options.length.toLowerCase() + ")";
99
+
100
+ case DataTypes.GEOMETRY.key:
101
+ if (options.type) {
102
+ if (options.srid) {
103
+ return prefix + "GEOMETRY('" + options.type + "', " + options.srid + ")";
104
+ } else {
105
+ return prefix + "GEOMETRY('" + options.type + "')";
106
+ }
107
+ }
108
+ return prefix + "GEOMETRY";
109
+
110
+ case DataTypes.GEOGRAPHY.key:
111
+ return prefix + "GEOGRAPHY";
112
+
113
+ case DataTypes.ARRAY.key:
114
+ const _type = attrObj.toString();
115
+ let arrayType;
116
+ if (_type === "INTEGER[]" || _type === "STRING[]") {
117
+ arrayType = prefix + _type.replace("[]", "");
118
+ } else {
119
+ arrayType = (col.seqType === "Sequelize.ARRAY(Sequelize.INTEGER)") ? prefix + "INTEGER" : prefix + "STRING";
120
+ }
121
+ return prefix + `ARRAY(${arrayType})`;
122
+
123
+ case DataTypes.RANGE.key:
124
+ console.warn(attrName + " type not supported, you should make it by");
125
+ return prefix + attrObj.toSql();
126
+
127
+ // BOOLEAN
128
+ // TIME
129
+ // DATE
130
+ // DATEONLY
131
+ // HSTORE
132
+ // JSONB
133
+ // UUID
134
+ // UUIDV1
135
+ // UUIDV4
136
+ // VIRTUAL
137
+ // INET
138
+ // MACADDR
139
+ default:
140
+ return prefix + attrName;
141
+ }
142
+ };
143
+
144
+ const reverseSequelizeDefValueType = (defaultValue: any, prefix = "Sequelize."): any => {
145
+ if (typeof defaultValue === "object") {
146
+ if (defaultValue.constructor && defaultValue.constructor.name) {
147
+ return { internal: true, value: prefix + defaultValue.constructor.name };
148
+ }
149
+ }
150
+
151
+ if (typeof defaultValue === "function") {
152
+ return { notSupported: true, value: "" };
153
+ }
154
+
155
+ return { value: defaultValue };
156
+ };
157
+
158
+ const parseIndex = (idx: any): any => {
159
+ delete idx.parser;
160
+ if (idx.type == "") {
161
+ delete idx.type;
162
+ }
163
+
164
+ const options: any = {};
165
+
166
+ if (idx.name) {
167
+ options.name = options.indexName = idx.name;
168
+ } // The name of the index. Default is __
169
+
170
+ // @todo: UNIQUE|FULLTEXT|SPATIAL
171
+ if (idx.unique) {
172
+ options.type = options.indicesType = "UNIQUE";
173
+ }
174
+
175
+ if (idx.method) {
176
+ options.indexType = idx.type;
177
+ } // Set a type for the index, e.g. BTREE. See the documentation of the used dialect
178
+
179
+ if (idx.parser && idx.parser != "") {
180
+ options.parser = idx.parser;
181
+ } // For FULLTEXT columns set your parser
182
+
183
+ idx.options = options;
184
+
185
+ idx.hash = JSON.stringify(idx);
186
+
187
+ // log ('PI:', JSON.stringify(idx, null, 4));
188
+ return idx;
189
+ };
190
+
191
+ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
192
+ export const reverseModels = (sequelizeModule: any, sequelize: any, realModels: { [model: string]: any }, logger: any): TableMap => {
193
+
194
+ const models: {
195
+ [key: string]: {
196
+ tableName: string;
197
+ options: any;
198
+ rawAttributes: { [attribute: string]: any };
199
+ };
200
+ } = {};
201
+ for (const model in realModels) {
202
+ if (model !== "default") {
203
+ models[model] = {
204
+ tableName: realModels[model].tableName,
205
+ options: {
206
+ ...realModels[model].options
207
+ },
208
+ rawAttributes: {
209
+ ...realModels[model].rawAttributes
210
+ }
211
+ }
212
+ }
213
+ }
214
+ const tables: TableMap = {};
215
+
216
+ delete models.default;
217
+
218
+ for (const model in models) {
219
+ // noinspection JSUnfilteredForInLoop
220
+ const attributes = models[model].rawAttributes;
221
+
222
+ for (const column in attributes) {
223
+ // noinspection JSUnfilteredForInLoop
224
+ delete (attributes[column] as any).Model;
225
+ // noinspection JSUnfilteredForInLoop
226
+ delete (attributes[column] as any).fieldName;
227
+ // delete attributes[column].field;
228
+
229
+ // noinspection JSUnfilteredForInLoop
230
+ for (const property in attributes[column]) {
231
+ // noinspection JSUnfilteredForInLoop
232
+ if (property.startsWith("_")) {
233
+ // noinspection JSUnfilteredForInLoop
234
+ delete attributes[column][property];
235
+ continue;
236
+ }
237
+
238
+ if (property === "defaultValue") {
239
+ // noinspection JSUnfilteredForInLoop
240
+ const _val = reverseSequelizeDefValueType(attributes[column][property]);
241
+ if (_val.notSupported) {
242
+ // noinspection JSUnfilteredForInLoop
243
+ logger.info(`[Not supported] Skip defaultValue column of attribute ${model}:${column}`);
244
+ // noinspection JSUnfilteredForInLoop
245
+ delete attributes[column][property];
246
+ continue;
247
+ }
248
+ // noinspection JSUnfilteredForInLoop
249
+ attributes[column][property] = _val;
250
+ }
251
+
252
+ // remove getters, setters...
253
+ // noinspection JSUnfilteredForInLoop
254
+ if (typeof attributes[column][property] == "function") {
255
+ // noinspection JSUnfilteredForInLoop
256
+ delete attributes[column][property];
257
+ }
258
+ }
259
+ // noinspection JSUnfilteredForInLoop
260
+ if (typeof attributes[column].type === "undefined") {
261
+ // noinspection JSUnfilteredForInLoop
262
+ if (!(attributes[column] as any).seqType) {
263
+ // noinspection JSUnfilteredForInLoop
264
+ logger.info(`[Not supported] Skip column with undefined type ${model}:${column}`);
265
+ // noinspection JSUnfilteredForInLoop
266
+ delete attributes[column];
267
+ continue;
268
+ } else {
269
+ // noinspection JSUnfilteredForInLoop
270
+ if (!["Sequelize.ARRAY(Sequelize.INTEGER)", "Sequelize.ARRAY(Sequelize.STRING)"].includes((attributes[column] as any).seqType)) {
271
+ // noinspection JSUnfilteredForInLoop
272
+ delete attributes[column];
273
+ continue;
274
+ }
275
+ // noinspection JSUnfilteredForInLoop
276
+ (attributes[column] as any).type = {
277
+ key: sequelizeModule.ARRAY.key
278
+ };
279
+ }
280
+ }
281
+ // noinspection JSUnfilteredForInLoop
282
+ let seqType = reverseSequelizeColType(sequelizeModule.DataTypes, attributes[column]);
283
+
284
+ // NO virtual types in migration
285
+ if (seqType === "Sequelize.VIRTUAL") {
286
+ // noinspection JSUnfilteredForInLoop
287
+ logger.info(`[SKIP] Skip Sequelize.VIRTUAL column "${column}"", defined in model "${model}"`);
288
+ // noinspection JSUnfilteredForInLoop
289
+ delete attributes[column];
290
+ continue;
291
+ }
292
+
293
+ if (!seqType) {
294
+ // noinspection JSUnfilteredForInLoop
295
+ if (typeof (attributes[column] as any).type.options !== "undefined" && typeof (attributes[column] as any).type.options.toString === "function") {
296
+ // noinspection JSUnfilteredForInLoop
297
+ seqType = (attributes[column] as any).type.options.toString(sequelize);
298
+ }
299
+ // noinspection JSUnfilteredForInLoop
300
+ if (typeof attributes[column].type.toString === "function") {
301
+ // noinspection JSUnfilteredForInLoop
302
+ seqType = attributes[column].type.toString(sequelize);
303
+ }
304
+ }
305
+ // noinspection JSUnfilteredForInLoop
306
+ (attributes[column] as any).seqType = seqType;
307
+ // noinspection JSUnfilteredForInLoop
308
+ delete attributes[column].type;
309
+ // noinspection JSUnfilteredForInLoop
310
+ delete attributes[column].values; // ENUM
311
+ }
312
+ // noinspection JSUnfilteredForInLoop
313
+ (tables as any)[models[model].tableName] = {
314
+ // noinspection JSUnfilteredForInLoop
315
+ tableName: models[model].tableName,
316
+ schema: attributes
317
+ };
318
+ // noinspection JSUnfilteredForInLoop
319
+ if (models[model].options.indexes.length > 0) {
320
+ const idxOut = {};
321
+ // noinspection JSUnfilteredForInLoop
322
+ for (const _i in models[model].options.indexes) {
323
+ // noinspection JSUnfilteredForInLoop
324
+ const index = parseIndex(models[model].options.indexes[_i]);
325
+ (idxOut as any)[index.hash + ""] = index;
326
+ delete index.hash;
327
+
328
+ // make it immutable
329
+ Object.freeze(index);
330
+ }
331
+ // noinspection JSUnfilteredForInLoop
332
+ // models[model].options.indexes = idxOut;
333
+ tables[models[model].tableName].indexes = idxOut;
334
+ }
335
+ // noinspection JSUnfilteredForInLoop
336
+ if (typeof models[model].options.charset !== "undefined") {
337
+ // noinspection JSUnfilteredForInLoop
338
+ (tables as any)[models[model].tableName].charset = models[model].options.charset;
339
+ }
340
+ // noinspection JSUnfilteredForInLoop
341
+ (tables as any)[models[model].tableName].indexes = models[model].options.indexes;
342
+ }
343
+
344
+ return tables;
345
+ };
346
+
347
+ interface DiffAction {
348
+ actionType: string;
349
+ columnName?: string;
350
+ attributeName?: any;
351
+ fields?: string[];
352
+ tableName?: any;
353
+ attributes?: any;
354
+ options?: any;
355
+ depends?: any;
356
+ }
357
+
358
+ export const parseDifference = (previousState: TableMap, currentState: TableMap, logger: any): DiffAction[] => {
359
+ // log(JSON.stringify(currentState, null, 4));
360
+ const actions: DiffAction[] = [];
361
+ const difference = DEEPDIFF.diff(previousState, currentState);
362
+ if (difference) {
363
+ for (const df of difference) {
364
+ // log (JSON.stringify(df, null, 4));
365
+ switch (df.kind) {
366
+ // add new
367
+ case "N": {
368
+ // new table created
369
+ if (df.path && df.path.length === 1) {
370
+ const depends: any[] = [];
371
+ const tableName = df.rhs.tableName;
372
+ if (df.rhs.schema) {
373
+ Object.keys(df.rhs.schema).forEach((v: any) => {
374
+ if (v.references) {
375
+ depends.push(v.references.model);
376
+ }
377
+ });
378
+ }
379
+
380
+ const options: any = {};
381
+ if (typeof df.rhs.charset !== "undefined") {
382
+ options.charset = df.rhs.charset;
383
+ }
384
+
385
+ actions.push({
386
+ actionType: "createTable",
387
+ tableName,
388
+ attributes: df.rhs.schema,
389
+ options,
390
+ depends
391
+ });
392
+
393
+ // create indexes
394
+ if (df.rhs.indexes) {
395
+ for (const _i in df.rhs.indexes) {
396
+ // noinspection JSUnfilteredForInLoop
397
+ actions.push({
398
+ ...{
399
+ actionType: "addIndex",
400
+ tableName,
401
+ depends: [tableName]
402
+ }, ...{ ...df.rhs.indexes[_i] }
403
+ });
404
+ }
405
+ }
406
+ break;
407
+ }
408
+
409
+ const tableName = df.path && df.path[0];
410
+ const depends = [tableName];
411
+
412
+ if (df.path && df.path[1] === "schema") {
413
+ // if (df.path.length === 3) - new field
414
+ if (df.path.length === 3) {
415
+ // new field
416
+ if (df.rhs && df.rhs.references) {
417
+ depends.push(df.rhs.references.model);
418
+ }
419
+
420
+ actions.push({
421
+ actionType: "addColumn",
422
+ tableName,
423
+ attributeName: df.path[2],
424
+ options: df.rhs,
425
+ depends
426
+ });
427
+ break;
428
+ }
429
+
430
+ // if (df.path.length > 3) - add new attribute to column (change col)
431
+ if (df.path && df.path.length > 3) {
432
+ if (df.path[1] === "schema") {
433
+ // new field attributes
434
+ const options = currentState[tableName as string].schema[df.path[2]];
435
+ if (options.references) {
436
+ depends.push(options.references.nodel);
437
+ }
438
+
439
+ actions.push({
440
+ actionType: "changeColumn",
441
+ tableName,
442
+ attributeName: df.path[2],
443
+ options,
444
+ depends
445
+ });
446
+ break;
447
+ }
448
+ }
449
+ }
450
+
451
+ // new index
452
+ if (df.path && df.path[1] === "indexes") {
453
+ const tableName = df.path[0];
454
+ const index: DiffAction = {
455
+ ...df.rhs,
456
+ actionType: "addIndex",
457
+ tableName: tableName,
458
+ depends: "addIndex",
459
+ };
460
+
461
+ index.depends = [tableName];
462
+ actions.push(index);
463
+ break;
464
+ }
465
+ break;
466
+ }
467
+ // drop
468
+ case "D": {
469
+ const tableName = df.path && df.path[0];
470
+ const depends = [tableName];
471
+
472
+ if (df.path && df.path.length === 1) {
473
+ // drop table
474
+ actions.push({
475
+ actionType: "dropTable",
476
+ tableName,
477
+ depends: []
478
+ });
479
+ break;
480
+ }
481
+
482
+ if (df.path && df.path[1] === "schema") {
483
+ // if (df.path.length === 3) - drop field
484
+ if (df.path.length === 3) {
485
+ // drop column
486
+ actions.push({
487
+ actionType: "removeColumn",
488
+ tableName,
489
+ columnName: df.path[2],
490
+ depends: [tableName],
491
+ options: df.lhs
492
+ });
493
+ break;
494
+ }
495
+
496
+ // if (df.path.length > 3) - drop attribute from column (change col)
497
+ if (df.path.length > 3) {
498
+ // new field attributes
499
+ const options = currentState[tableName as string].schema[df.path[2]];
500
+ if (options.references) {
501
+ depends.push(options.references.nodel);
502
+ }
503
+
504
+ actions.push({
505
+ actionType: "changeColumn",
506
+ tableName,
507
+ attributeName: df.path[2],
508
+ options,
509
+ depends
510
+ });
511
+ break;
512
+ }
513
+ }
514
+
515
+ if (df.path && df.path[1] === "indexes") {
516
+ // log(df)
517
+ actions.push({
518
+ actionType: "removeIndex",
519
+ tableName,
520
+ fields: df.lhs.fields as any,
521
+ options: df.lhs.options,
522
+ depends: [tableName]
523
+ });
524
+ break;
525
+ }
526
+ break;
527
+ }
528
+ // edit
529
+ case "E": {
530
+ const tableName = df.path && df.path[0];
531
+ const depends = [tableName];
532
+
533
+ if (df.path && df.path[1] === "schema") {
534
+ // new field attributes
535
+ const options = currentState[tableName as string].schema[df.path[2]];
536
+ if (options.references) {
537
+ depends.push(options.references.nodel);
538
+ }
539
+
540
+ actions.push({
541
+ actionType: "changeColumn",
542
+ tableName,
543
+ attributeName: df.path[2],
544
+ options,
545
+ depends
546
+ });
547
+ }
548
+
549
+ // updated index
550
+ // only support updating and dropping indexes
551
+ if (df.path && df.path[1] === "indexes") {
552
+ const tableName = df.path && df.path[0];
553
+ let keys = Object.keys(df.rhs);
554
+
555
+ // noinspection LoopStatementThatDoesntLoopJS
556
+ for (const k in keys) {
557
+ const key = keys[k];
558
+ // noinspection JSUnusedLocalSymbols
559
+ actions.push({
560
+ actionType: "addIndex",
561
+ tableName,
562
+ fields: df.rhs[key].fields,
563
+ options: df.rhs[key].options,
564
+ depends: [tableName]
565
+ });
566
+ break;
567
+ }
568
+
569
+ keys = Object.keys(df.lhs);
570
+ // noinspection LoopStatementThatDoesntLoopJS
571
+ for (const k in keys) {
572
+ const key = keys[k];
573
+ // noinspection JSUnusedLocalSymbols
574
+ actions.push({
575
+ actionType: "removeIndex",
576
+ tableName,
577
+ fields: df.lhs[key].fields,
578
+ options: df.lhs[key].options,
579
+ depends: [tableName]
580
+ });
581
+ break;
582
+ }
583
+ }
584
+ break;
585
+ }
586
+ // array change indexes
587
+ case "A": {
588
+ logger.info("[Not supported] Array model changes! Problems are possible. Please, check result more carefully!");
589
+ logger.info("[Not supported] Difference: ");
590
+ logger.info(JSON.stringify(df, null, 4));
591
+ break;
592
+ }
593
+ default:
594
+ // code
595
+ break;
596
+ }
597
+ }
598
+ }
599
+ return actions;
600
+ };
601
+
602
+ export const sortActions = (actions: DiffAction[]): void => {
603
+ const orderedActionTypes = [
604
+ "removeIndex",
605
+ "removeColumn",
606
+ "dropTable",
607
+ "createTable",
608
+ "addColumn",
609
+ "changeColumn",
610
+ "addIndex"
611
+ ];
612
+
613
+ // test
614
+ // actions = shuffleArray(actions);
615
+
616
+ actions.sort((a, b) => {
617
+ if (orderedActionTypes.indexOf(a.actionType) < orderedActionTypes.indexOf(b.actionType)) {
618
+ return -1;
619
+ }
620
+ if (orderedActionTypes.indexOf(a.actionType) > orderedActionTypes.indexOf(b.actionType)) {
621
+ return 1;
622
+ }
623
+
624
+ if (a.depends.length === 0 && b.depends.length > 0) {
625
+ return -1;
626
+ } // a < b
627
+ if (b.depends.length === 0 && a.depends.length > 0) {
628
+ return 1;
629
+ } // b < a
630
+
631
+ return 0;
632
+ });
633
+
634
+ for (let k = 0; k <= actions.length; k++) {
635
+ for (let i = 0; i < actions.length; i++) {
636
+ if (!actions[i].depends) {
637
+ continue;
638
+ }
639
+ if (actions[i].depends.length === 0) {
640
+ continue;
641
+ }
642
+
643
+ const a = actions[i];
644
+
645
+ for (let j = 0; j < actions.length; j++) {
646
+ if (!actions[j].depends) {
647
+ continue;
648
+ }
649
+ if (actions[j].depends.length === 0) {
650
+ continue;
651
+ }
652
+
653
+ const b = actions[j];
654
+
655
+ if (a.actionType != b.actionType) {
656
+ continue;
657
+ }
658
+
659
+ if (b.depends.indexOf(a.tableName) !== -1 && i > j) {
660
+ const c = actions[i];
661
+ actions[i] = actions[j];
662
+ actions[j] = c;
663
+ }
664
+
665
+ }
666
+ }
667
+ }
668
+ };
669
+
670
+ interface Migration {
671
+ commandsUp: string[];
672
+ consoleOut: string[];
673
+ }
674
+
675
+ // noinspection SpellCheckingInspection
676
+ export const getMigration = (actions: DiffAction[]): Migration => {
677
+ const propertyToStr = (obj: any): any => {
678
+ // noinspection SpellCheckingInspection
679
+ const vals = [];
680
+ for (const k in obj) {
681
+ if (k === "seqType") {
682
+ // noinspection JSUnfilteredForInLoop
683
+ vals.push('"type": ' + obj[k]);
684
+ continue;
685
+ }
686
+
687
+ if (k === "defaultValue") {
688
+ // noinspection JSUnfilteredForInLoop
689
+ if (obj[k].internal) {
690
+ // noinspection JSUnfilteredForInLoop
691
+ vals.push('"defaultValue": ' + obj[k].value);
692
+ continue;
693
+ }
694
+ // noinspection JSUnfilteredForInLoop
695
+ if (obj[k].notSupported) {
696
+ continue;
697
+ }
698
+
699
+ const x = {};
700
+ // noinspection JSUnfilteredForInLoop
701
+ (x as any)[k] = obj[k].value;
702
+ vals.push(JSON.stringify(x).slice(1, -1));
703
+ continue;
704
+ }
705
+
706
+ const x = {};
707
+ // noinspection JSUnfilteredForInLoop
708
+ (x as any)[k] = obj[k];
709
+ vals.push(JSON.stringify(x).slice(1, -1));
710
+ }
711
+
712
+ return "{ " + vals.reverse().join(", ") + " }";
713
+ };
714
+
715
+ const getAttributes = (attrs: any[]): any => {
716
+ const ret = [];
717
+ for (const attrName in attrs) {
718
+ // noinspection JSUnfilteredForInLoop
719
+ ret.push(` "${attrName}": ${propertyToStr(attrs[attrName])}`);
720
+ }
721
+ return " { \n" + ret.join(", \n") + "\n }";
722
+ };
723
+
724
+ const commandsUp = [];
725
+ const consoleOut = [];
726
+
727
+ for (const _i in actions) {
728
+ // noinspection JSUnfilteredForInLoop
729
+ const action = actions[_i];
730
+ // noinspection FallThroughInSwitchStatementJS,FallThroughInSwitchStatementJS
731
+ switch (action.actionType) {
732
+ case "createTable": {
733
+ const resUp = `{ fn: "createTable", params: [
734
+ "${action.tableName}",
735
+ ${getAttributes(action.attributes)},
736
+ ${JSON.stringify(action.options)}
737
+ ] }`;
738
+ commandsUp.push(resUp);
739
+
740
+ consoleOut.push(`createTable "${action.tableName}", deps: [${action.depends.join(", ")}]`);
741
+ }
742
+ break;
743
+
744
+ case "dropTable": {
745
+ const res = `{ fn: "dropTable", params: ["${action.tableName}"] }`;
746
+ commandsUp.push(res);
747
+
748
+ consoleOut.push(`dropTable "${action.tableName}"`);
749
+ }
750
+ break;
751
+
752
+ case "addColumn": {
753
+ const resUp = `{ fn: "addColumn", params: [
754
+ "${action.tableName}",
755
+ "${(action.options && action.options.field) ? action.options.field : action.attributeName}",
756
+ ${propertyToStr(action.options)}
757
+ ] }`;
758
+
759
+ commandsUp.push(resUp);
760
+
761
+ consoleOut.push(`addColumn "${action.attributeName}" to table "${action.tableName}"`);
762
+ }
763
+ break;
764
+
765
+ case "removeColumn": {
766
+ const res = `{ fn: "removeColumn", params: ["${action.tableName}", "${(action.options && action.options.field) ? action.options.field : action.columnName}"] }`;
767
+ commandsUp.push(res);
768
+
769
+ consoleOut.push(`removeColumn "${(action.options && action.options.field) ? action.options.field : action.columnName}" from table "${action.tableName}"`);
770
+ }
771
+ break;
772
+
773
+ case "changeColumn": {
774
+ const res = `{ fn: "changeColumn", params: [
775
+ "${action.tableName}",
776
+ "${(action.options && action.options.field) ? action.options.field : action.attributeName}",
777
+ ${propertyToStr(action.options)}
778
+ ] }`;
779
+ commandsUp.push(res);
780
+
781
+ consoleOut.push(`changeColumn "${action.attributeName}" on table "${action.tableName}"`);
782
+ }
783
+ break;
784
+
785
+ case "addIndex": {
786
+ const res = `{ fn: "addIndex", params: [
787
+ "${action.tableName}",
788
+ ${JSON.stringify(action.fields)},
789
+ ${JSON.stringify(action.options)}
790
+ ] }`;
791
+ commandsUp.push(res);
792
+
793
+ const nameOrAttrs = (action.options && action.options.indexName && action.options.indexName != "") ? `"${action.options.indexName}"` : JSON.stringify(action.fields);
794
+ consoleOut.push(`addIndex ${nameOrAttrs} to table "${action.tableName}"`);
795
+ }
796
+ break;
797
+
798
+ case "removeIndex": {
799
+ // log(action)
800
+ const nameOrAttrs = (action.options && action.options.indexName && action.options.indexName != "") ? `"${action.options.indexName}"` : JSON.stringify(action.fields);
801
+
802
+ const res = `{ fn: "removeIndex", params: [
803
+ "${action.tableName}",
804
+ ${nameOrAttrs}
805
+ ] }`;
806
+ commandsUp.push(res);
807
+
808
+ consoleOut.push(`removeIndex ${nameOrAttrs} from table "${action.tableName}"`);
809
+ }
810
+
811
+ default:
812
+ // code
813
+ }
814
+ }
815
+
816
+ return { commandsUp, consoleOut };
817
+ };
818
+
819
+ interface WritenMigration {
820
+ filename: string;
821
+ info: {
822
+ revision: number;
823
+ name: string;
824
+ created: Date;
825
+ comment: string;
826
+ }
827
+ }
828
+
829
+ export const writeMigration = (revision: number, migration: Migration, migrationsDir: string, name = "", comment = ""): WritenMigration => {
830
+ const _commands = "var migrationCommands = [ \n" + migration.commandsUp.join(", \n") + " \n];\n";
831
+ const _actions = " * " + migration.consoleOut.join("\n * ");
832
+
833
+ //_commands = js_beautify(_commands);
834
+ const info = {
835
+ revision,
836
+ name,
837
+ created: new Date(),
838
+ comment
839
+ };
840
+
841
+ const template = `'use strict';
842
+
843
+ const Sequelize = require('sequelize');
844
+
845
+ /**
846
+ * Actions summary:
847
+ *
848
+ ${_actions}
849
+ *
850
+ **/
851
+
852
+ const info = ${JSON.stringify(info, null, 4)};
853
+
854
+ ${_commands}
855
+
856
+ module.exports = {
857
+ pos: 0,
858
+ up: function(queryInterface, Sequelize)
859
+ {
860
+ var index = this.pos;
861
+ return new Promise(function(resolve, reject) {
862
+ function next() {
863
+ if (index < migrationCommands.length)
864
+ {
865
+ let command = migrationCommands[index];
866
+ console.log("[#"+index+"] execute: " + command.fn);
867
+ index++;
868
+ queryInterface[command.fn].apply(queryInterface, command.params).then(next, reject);
869
+ }
870
+ else
871
+ resolve();
872
+ }
873
+ next();
874
+ });
875
+ },
876
+ info: info
877
+ };
878
+ `;
879
+
880
+ name = name.replace(" ", "_");
881
+ const now = new Date();
882
+ const timestamp = `${now.getFullYear()}${now.getMonth() + 1 < 10 ? "0" : ""}${now.getMonth() + 1}${now.getDate() < 10 ? "0" : ""}${now.getDate()}${now.getHours() < 10 ? "0" : ""}${now.getHours()}${now.getMinutes() < 10 ? "0" : ""}${now.getMinutes()}${now.getSeconds() < 10 ? "0" : ""}${now.getSeconds()}-${revision}`;
883
+ const filename = path.join(migrationsDir, timestamp + ((name != "") ? `-${name}` : "") + ".js");
884
+
885
+ fs.writeFileSync(filename, template);
886
+
887
+ return { filename, info };
888
+ };