@smartive/graphql-magic 22.1.0 → 22.2.0-next.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.
- package/CHANGELOG.md +9 -5
- package/dist/bin/gqm.cjs +209 -33
- package/dist/cjs/index.cjs +194 -33
- package/dist/esm/migrations/generate.d.ts +5 -0
- package/dist/esm/migrations/generate.js +184 -35
- package/dist/esm/migrations/generate.js.map +1 -1
- package/dist/esm/models/model-definitions.d.ts +1 -0
- package/package.json +5 -5
- package/src/bin/gqm/gqm.ts +21 -0
- package/src/migrations/generate.ts +224 -35
- package/src/models/model-definitions.ts +1 -0
|
@@ -31,6 +31,7 @@ export class MigrationGenerator {
|
|
|
31
31
|
private columns: Record<string, Column[]> = {};
|
|
32
32
|
private uuidUsed?: boolean;
|
|
33
33
|
private nowUsed?: boolean;
|
|
34
|
+
public needsMigration = false;
|
|
34
35
|
|
|
35
36
|
constructor(
|
|
36
37
|
knex: Knex,
|
|
@@ -159,35 +160,27 @@ export class MigrationGenerator {
|
|
|
159
160
|
);
|
|
160
161
|
|
|
161
162
|
// Update fields
|
|
162
|
-
const
|
|
163
|
+
const rawExistingFields = model.fields.filter((field) => {
|
|
164
|
+
if (!field.generateAs) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
|
|
163
168
|
const col = this.getColumn(model.name, field.kind === 'relation' ? `${field.name}Id` : field.name);
|
|
164
169
|
if (!col) {
|
|
165
170
|
return false;
|
|
166
171
|
}
|
|
167
172
|
|
|
168
|
-
if (
|
|
173
|
+
if (col.generation_expression !== field.generateAs) {
|
|
169
174
|
return true;
|
|
170
175
|
}
|
|
171
176
|
|
|
172
|
-
|
|
173
|
-
if (field.type === 'Int') {
|
|
174
|
-
if (col.data_type !== 'integer') {
|
|
175
|
-
return true;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
if (field.type === 'Float') {
|
|
179
|
-
if (field.double) {
|
|
180
|
-
if (col.data_type !== 'double precision') {
|
|
181
|
-
return true;
|
|
182
|
-
}
|
|
183
|
-
} else if (col.data_type !== 'numeric') {
|
|
184
|
-
return true;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return false;
|
|
177
|
+
return this.hasChanged(model, field);
|
|
190
178
|
});
|
|
179
|
+
if (rawExistingFields.length) {
|
|
180
|
+
this.updateFieldsRaw(model, rawExistingFields, up, down);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const existingFields = model.fields.filter((field) => !field.generateAs && this.hasChanged(model, field));
|
|
191
184
|
this.updateFields(model, existingFields, up, down);
|
|
192
185
|
}
|
|
193
186
|
|
|
@@ -320,6 +313,9 @@ export class MigrationGenerator {
|
|
|
320
313
|
writer.writeLine(`const now = date();`).blankLine();
|
|
321
314
|
}
|
|
322
315
|
|
|
316
|
+
if (up.length || down.length) {
|
|
317
|
+
this.needsMigration = true;
|
|
318
|
+
}
|
|
323
319
|
this.migration('up', up);
|
|
324
320
|
this.migration('down', down.reverse());
|
|
325
321
|
|
|
@@ -371,6 +367,10 @@ export class MigrationGenerator {
|
|
|
371
367
|
for (const field of fields) {
|
|
372
368
|
alter.push(() => this.column(field, { setNonNull: field.defaultValue !== undefined }));
|
|
373
369
|
|
|
370
|
+
if (field.generateAs) {
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
374
|
// If the field is not nullable but has no default, write placeholder code
|
|
375
375
|
if (field.nonNull && field.defaultValue === undefined) {
|
|
376
376
|
updates.push(() => this.writer.write(`${field.name}: 'TODO',`).newLine());
|
|
@@ -401,13 +401,63 @@ export class MigrationGenerator {
|
|
|
401
401
|
|
|
402
402
|
down.push(() => {
|
|
403
403
|
this.alterTable(model.name, () => {
|
|
404
|
-
for (const { kind, name } of fields) {
|
|
404
|
+
for (const { kind, name } of fields.toReversed()) {
|
|
405
405
|
this.dropColumn(kind === 'relation' ? `${name}Id` : name);
|
|
406
406
|
}
|
|
407
407
|
});
|
|
408
408
|
});
|
|
409
409
|
}
|
|
410
410
|
|
|
411
|
+
private updateFieldsRaw(model: EntityModel, fields: EntityField[], up: Callbacks, down: Callbacks) {
|
|
412
|
+
if (!fields.length) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
up.push(() => {
|
|
417
|
+
this.alterTableRaw(model.name, () => {
|
|
418
|
+
for (const [index, field] of fields.entries()) {
|
|
419
|
+
this.columnRaw(field, { alter: true }, index);
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
down.push(() => {
|
|
425
|
+
this.alterTableRaw(model.name, () => {
|
|
426
|
+
for (const [index, field] of fields.entries()) {
|
|
427
|
+
this.columnRaw(field, { alter: true }, index);
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
if (isUpdatableModel(model)) {
|
|
433
|
+
const updatableFields = fields.filter(isUpdatableField);
|
|
434
|
+
if (!updatableFields.length) {
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
up.push(() => {
|
|
439
|
+
this.alterTable(`${model.name}Revision`, () => {
|
|
440
|
+
for (const [index, field] of updatableFields.entries()) {
|
|
441
|
+
this.columnRaw(field, { alter: true }, index);
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
down.push(() => {
|
|
447
|
+
this.alterTable(`${model.name}Revision`, () => {
|
|
448
|
+
for (const [index, field] of updatableFields.entries()) {
|
|
449
|
+
this.columnRaw(
|
|
450
|
+
field,
|
|
451
|
+
{ alter: true },
|
|
452
|
+
index,
|
|
453
|
+
summonByName(this.columns[model.name], field.kind === 'relation' ? `${field.name}Id` : field.name),
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
411
461
|
private updateFields(model: EntityModel, fields: EntityField[], up: Callbacks, down: Callbacks) {
|
|
412
462
|
if (!fields.length) {
|
|
413
463
|
return;
|
|
@@ -568,6 +618,12 @@ export class MigrationGenerator {
|
|
|
568
618
|
.blankLine();
|
|
569
619
|
}
|
|
570
620
|
|
|
621
|
+
private alterTableRaw(table: string, block: () => void) {
|
|
622
|
+
this.writer.write(`await knex.raw('ALTER TABLE "${table}"`);
|
|
623
|
+
block();
|
|
624
|
+
this.writer.write(`');`).newLine().blankLine();
|
|
625
|
+
}
|
|
626
|
+
|
|
571
627
|
private alterTable(table: string, block: () => void) {
|
|
572
628
|
return this.writer
|
|
573
629
|
.write(`await knex.schema.alterTable('${table}', (table) => `)
|
|
@@ -601,29 +657,125 @@ export class MigrationGenerator {
|
|
|
601
657
|
return value;
|
|
602
658
|
}
|
|
603
659
|
|
|
660
|
+
private columnRaw(
|
|
661
|
+
{ name, ...field }: EntityField,
|
|
662
|
+
{ setNonNull = true, alter = false } = {},
|
|
663
|
+
index: number,
|
|
664
|
+
toColumn?: Column,
|
|
665
|
+
) {
|
|
666
|
+
const nonNull = () => {
|
|
667
|
+
if (setNonNull) {
|
|
668
|
+
if (toColumn) {
|
|
669
|
+
if (toColumn.is_nullable) {
|
|
670
|
+
return false;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
return true;
|
|
674
|
+
}
|
|
675
|
+
if (field.nonNull) {
|
|
676
|
+
return true;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
return false;
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
const kind = field.kind;
|
|
683
|
+
if (field.generateAs) {
|
|
684
|
+
let type = '';
|
|
685
|
+
switch (kind) {
|
|
686
|
+
case undefined:
|
|
687
|
+
case 'primitive':
|
|
688
|
+
switch (field.type) {
|
|
689
|
+
case 'Float':
|
|
690
|
+
type = `decimal(${field.precision ?? 'undefined'}, ${field.scale ?? 'undefined'})`;
|
|
691
|
+
break;
|
|
692
|
+
default:
|
|
693
|
+
throw new Error(`Generated columns of kind ${kind} and type ${field.type} are not supported yet.`);
|
|
694
|
+
}
|
|
695
|
+
break;
|
|
696
|
+
default:
|
|
697
|
+
throw new Error(`Generated columns of kind ${kind} are not supported yet.`);
|
|
698
|
+
}
|
|
699
|
+
if (index) {
|
|
700
|
+
this.writer.write(`,`);
|
|
701
|
+
}
|
|
702
|
+
if (alter) {
|
|
703
|
+
this.writer.write(` ALTER COLUMN "${name}" TYPE ${type}`);
|
|
704
|
+
if (setNonNull) {
|
|
705
|
+
if (nonNull()) {
|
|
706
|
+
this.writer.write(`, ALTER COLUMN "${name}" SET NOT NULL`);
|
|
707
|
+
} else {
|
|
708
|
+
this.writer.write(`, ALTER COLUMN "${name}" DROP NOT NULL`);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
this.writer.write(`, ALTER COLUMN "${name}" SET EXPRESSION AS (${field.generateAs})`);
|
|
712
|
+
} else {
|
|
713
|
+
this.writer.write(
|
|
714
|
+
`${alter ? 'ALTER' : 'ADD'} COLUMN "${name}" ${type}${nonNull() ? ' not null' : ''} GENERATED ALWAYS AS (${field.generateAs}) STORED`,
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
throw new Error(`Only generated columns can be created with columnRaw`);
|
|
722
|
+
}
|
|
723
|
+
|
|
604
724
|
private column(
|
|
605
725
|
{ name, primary, list, ...field }: EntityField,
|
|
606
726
|
{ setUnique = true, setNonNull = true, alter = false, foreign = true, setDefault = true } = {},
|
|
607
727
|
toColumn?: Column,
|
|
608
728
|
) {
|
|
609
|
-
const
|
|
610
|
-
if (what) {
|
|
611
|
-
this.writer.write(what);
|
|
612
|
-
}
|
|
729
|
+
const nonNull = () => {
|
|
613
730
|
if (setNonNull) {
|
|
614
731
|
if (toColumn) {
|
|
615
732
|
if (toColumn.is_nullable) {
|
|
616
|
-
|
|
617
|
-
} else {
|
|
618
|
-
this.writer.write('.notNullable()');
|
|
619
|
-
}
|
|
620
|
-
} else {
|
|
621
|
-
if (field.nonNull) {
|
|
622
|
-
this.writer.write(`.notNullable()`);
|
|
623
|
-
} else {
|
|
624
|
-
this.writer.write('.nullable()');
|
|
733
|
+
return false;
|
|
625
734
|
}
|
|
735
|
+
|
|
736
|
+
return true;
|
|
626
737
|
}
|
|
738
|
+
if (field.nonNull) {
|
|
739
|
+
return true;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
return false;
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
const kind = field.kind;
|
|
746
|
+
if (field.generateAs) {
|
|
747
|
+
let type = '';
|
|
748
|
+
switch (kind) {
|
|
749
|
+
case undefined:
|
|
750
|
+
case 'primitive':
|
|
751
|
+
switch (field.type) {
|
|
752
|
+
case 'Float':
|
|
753
|
+
type = `decimal(${field.precision ?? 'undefined'}, ${field.scale ?? 'undefined'})`;
|
|
754
|
+
break;
|
|
755
|
+
default:
|
|
756
|
+
throw new Error(`Generated columns of kind ${kind} and type ${field.type} are not supported yet.`);
|
|
757
|
+
}
|
|
758
|
+
break;
|
|
759
|
+
default:
|
|
760
|
+
throw new Error(`Generated columns of kind ${kind} are not supported yet.`);
|
|
761
|
+
}
|
|
762
|
+
this.writer.write(
|
|
763
|
+
`table.specificType('${name}', '${type}${nonNull() ? ' not null' : ''} GENERATED ALWAYS AS (${field.generateAs}) STORED')`,
|
|
764
|
+
);
|
|
765
|
+
if (alter) {
|
|
766
|
+
this.writer.write('.alter()');
|
|
767
|
+
}
|
|
768
|
+
this.writer.write(';').newLine();
|
|
769
|
+
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
const col = (what?: string) => {
|
|
774
|
+
if (what) {
|
|
775
|
+
this.writer.write(what);
|
|
776
|
+
}
|
|
777
|
+
if (setNonNull) {
|
|
778
|
+
this.writer.write(nonNull() ? '.notNullable()' : '.nullable()');
|
|
627
779
|
}
|
|
628
780
|
if (setDefault && field.defaultValue !== undefined) {
|
|
629
781
|
this.writer.write(`.defaultTo(${this.value(field.defaultValue)})`);
|
|
@@ -638,7 +790,6 @@ export class MigrationGenerator {
|
|
|
638
790
|
}
|
|
639
791
|
this.writer.write(';').newLine();
|
|
640
792
|
};
|
|
641
|
-
const kind = field.kind;
|
|
642
793
|
switch (kind) {
|
|
643
794
|
case undefined:
|
|
644
795
|
case 'primitive':
|
|
@@ -712,6 +863,44 @@ export class MigrationGenerator {
|
|
|
712
863
|
private getColumn(tableName: string, columnName: string) {
|
|
713
864
|
return this.columns[tableName].find((col) => col.name === columnName);
|
|
714
865
|
}
|
|
866
|
+
|
|
867
|
+
private hasChanged(model: EntityModel, field: EntityField) {
|
|
868
|
+
const col = this.getColumn(model.name, field.kind === 'relation' ? `${field.name}Id` : field.name);
|
|
869
|
+
if (!col) {
|
|
870
|
+
return false;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
if (field.generateAs) {
|
|
874
|
+
if (col.generation_expression !== field.generateAs) {
|
|
875
|
+
throw new Error(
|
|
876
|
+
`Column ${col.name} has specific type ${col.generation_expression} but expected ${field.generateAs}`,
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
if ((!field.nonNull && !col.is_nullable) || (field.nonNull && col.is_nullable)) {
|
|
882
|
+
return true;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
if (!field.kind || field.kind === 'primitive') {
|
|
886
|
+
if (field.type === 'Int') {
|
|
887
|
+
if (col.data_type !== 'integer') {
|
|
888
|
+
return true;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
if (field.type === 'Float') {
|
|
892
|
+
if (field.double) {
|
|
893
|
+
if (col.data_type !== 'double precision') {
|
|
894
|
+
return true;
|
|
895
|
+
}
|
|
896
|
+
} else if (col.data_type !== 'numeric') {
|
|
897
|
+
return true;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
return false;
|
|
903
|
+
}
|
|
715
904
|
}
|
|
716
905
|
|
|
717
906
|
export const getMigrationDate = () => {
|