Skip to content

Commit 5716d10

Browse files
authored
feat(appsync-modelgen-plugin): add sort key field in manyToMany models (#401)
* test: disable flow modern tests * feat(appsync-modelgen-plugin): add sort key field in manyToMany models * fix: rm only test * fix: remove old sort key impl
1 parent 8f00f73 commit 5716d10

File tree

2 files changed

+100
-19
lines changed

2 files changed

+100
-19
lines changed

packages/appsync-modelgen-plugin/src/__tests__/visitors/appsync-visitor.test.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -913,6 +913,41 @@ describe('AppSyncModelVisitor', () => {
913913
});
914914
});
915915

916+
describe('manyToMany with sort key testing', () => {
917+
const schema = /* GraphQL */ `
918+
type ModelA @model {
919+
id: ID! @primaryKey(sortKeyFields: ["sortId"])
920+
sortId: ID!
921+
models: [ModelB] @manyToMany(relationName: "ModelAModelB")
922+
}
923+
type ModelB @model {
924+
id: ID! @primaryKey(sortKeyFields: ["sortId"])
925+
sortId: ID!
926+
models: [ModelA] @manyToMany(relationName: "ModelAModelB")
927+
}
928+
`;
929+
const { models } = createAndGeneratePipelinedTransformerVisitor(schema);
930+
const { ModelAModelB } = models;
931+
it('should generate correct fields and secondary keys for intermediate type', () => {
932+
expect(ModelAModelB).toBeDefined();
933+
expect(ModelAModelB.fields.length).toEqual(7);
934+
const modelASortKeyField = ModelAModelB.fields.find(f => f.name === 'modelAsortId');
935+
expect(modelASortKeyField).toBeDefined();
936+
expect(modelASortKeyField.type).toEqual('ID');
937+
expect(modelASortKeyField.isNullable).toBe(false);
938+
const modelBSortKeyField = ModelAModelB.fields.find(f => f.name === 'modelBsortId');
939+
expect(modelBSortKeyField).toBeDefined();
940+
expect(modelBSortKeyField.type).toEqual('ID');
941+
expect(modelBSortKeyField.isNullable).toBe(false);
942+
const modelAIndexDirective = ModelAModelB.directives.find(d => d.name === 'key' && d.arguments.name === 'byModelA');
943+
expect(modelAIndexDirective).toBeDefined();
944+
expect(modelAIndexDirective.arguments.fields).toEqual(['modelAID', 'modelAsortId']);
945+
const modelBIndexDirective = ModelAModelB.directives.find(d => d.name === 'key' && d.arguments.name === 'byModelB');
946+
expect(modelBIndexDirective).toBeDefined();
947+
expect(modelBIndexDirective.arguments.fields).toEqual(['modelBID', 'modelBsortId']);
948+
});
949+
});
950+
916951
describe('Graphql V2 fix tests for multiple has many relations of only one model type', () => {
917952
const schema = /* GraphQL*/ `
918953
type Registration @model {
@@ -936,5 +971,5 @@ describe('AppSyncModelVisitor', () => {
936971
it(`should not throw error when processing models`, () => {
937972
expect(() => createAndGeneratePipelinedTransformerVisitor(schema)).not.toThrow();
938973
});
939-
})
974+
});
940975
});

packages/appsync-modelgen-plugin/src/visitors/appsync-visitor.ts

Lines changed: 64 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ export class AppSyncModelVisitor<
296296
}
297297
processDirectives(
298298
// TODO: Remove us when we have a fix to roll-forward.
299-
shouldUseModelNameFieldInHasManyAndBelongsTo: boolean
299+
shouldUseModelNameFieldInHasManyAndBelongsTo: boolean,
300300
) {
301301
if (this.config.usePipelinedTransformer || this.config.transformerVersion === 2) {
302302
this.processV2KeyDirectives();
@@ -551,15 +551,12 @@ export class AppSyncModelVisitor<
551551
});
552552
}
553553

554-
protected generateIntermediateModel(
555-
firstModel: CodeGenModel,
556-
secondModel: CodeGenModel,
557-
firstField: CodeGenField,
558-
secondField: CodeGenField,
559-
relationName: string,
560-
) {
554+
protected generateIntermediateModel(firstModel: CodeGenModel, secondModel: CodeGenModel, relationName: string) {
561555
const firstModelKeyFieldName = `${camelCase(firstModel.name)}ID`;
556+
const firstModelSortKeyFields: CodeGenField[] = this.getSortKeyFields(firstModel);
562557
const secondModelKeyFieldName = `${camelCase(secondModel.name)}ID`;
558+
const secondModelSortKeyFields: CodeGenField[] = this.getSortKeyFields(secondModel);
559+
563560
let intermediateModel: CodeGenModel = {
564561
name: relationName,
565562
type: 'model',
@@ -577,15 +574,49 @@ export class AppSyncModelVisitor<
577574
isNullable: false,
578575
isList: false,
579576
name: firstModelKeyFieldName,
580-
directives: [{ name: 'index', arguments: { name: 'by' + firstModel.name, sortKeyFields: [secondModelKeyFieldName] } }],
577+
directives: [
578+
{
579+
name: 'index',
580+
arguments: {
581+
name: 'by' + firstModel.name,
582+
sortKeyFields: firstModelSortKeyFields.map(f => this.generateIntermediateModelSortKeyFieldName(firstModel, f)),
583+
},
584+
},
585+
],
581586
},
587+
...firstModelSortKeyFields.map(field => {
588+
return {
589+
type: field.type,
590+
isNullable: false,
591+
isList: field.isList,
592+
name: this.generateIntermediateModelSortKeyFieldName(firstModel, field),
593+
directives: [],
594+
};
595+
}),
582596
{
583597
type: 'ID',
584598
isNullable: false,
585599
isList: false,
586600
name: secondModelKeyFieldName,
587-
directives: [{ name: 'index', arguments: { name: 'by' + secondModel.name, sortKeyFields: [firstModelKeyFieldName] } }],
601+
directives: [
602+
{
603+
name: 'index',
604+
arguments: {
605+
name: 'by' + secondModel.name,
606+
sortKeyFields: secondModelSortKeyFields.map(f => this.generateIntermediateModelSortKeyFieldName(secondModel, f)),
607+
},
608+
},
609+
],
588610
},
611+
...secondModelSortKeyFields.map(field => {
612+
return {
613+
type: field.type,
614+
isNullable: false,
615+
isList: field.isList,
616+
name: this.generateIntermediateModelSortKeyFieldName(secondModel, field),
617+
directives: [],
618+
};
619+
}),
589620
{
590621
type: firstModel.name,
591622
isNullable: false,
@@ -606,6 +637,18 @@ export class AppSyncModelVisitor<
606637
return intermediateModel;
607638
}
608639

640+
protected generateIntermediateModelSortKeyFieldName(model: CodeGenModel, sortKeyField: CodeGenField): string {
641+
const modelName = model.name.charAt(0).toLocaleLowerCase() + model.name.slice(1);
642+
return `${modelName}${sortKeyField.name}`;
643+
}
644+
645+
protected getSortKeyFields(model: CodeGenModel): CodeGenField[] {
646+
const keyDirective = model.directives.find(d => d.name === 'key' && !d.arguments.name);
647+
return keyDirective
648+
? (keyDirective.arguments.fields as string[]).slice(1).map(fieldName => model.fields.find(f => f.name === fieldName)!)
649+
: [];
650+
}
651+
609652
protected determinePrimaryKeyFieldname(model: CodeGenModel): string {
610653
let primaryKeyFieldName = 'id';
611654
model.fields.forEach(field => {
@@ -664,12 +707,13 @@ export class AppSyncModelVisitor<
664707
let intermediateModel = this.generateIntermediateModel(
665708
value[0].model,
666709
value[1].model,
667-
value[0].field,
668-
value[1].field,
669710
graphqlName(toUpper(value[0].directive.arguments.relationName)),
670711
);
671712
const modelDirective = intermediateModel.directives.find(directive => directive.name === 'model');
672713
if (modelDirective) {
714+
// Maps @primaryKey and @index of intermediate model to old @key
715+
processPrimaryKey(intermediateModel);
716+
processIndex(intermediateModel);
673717
this.ensureIdField(intermediateModel);
674718
this.addTimestampFields(intermediateModel, modelDirective);
675719
this.sortFields(intermediateModel);
@@ -682,7 +726,7 @@ export class AppSyncModelVisitor<
682726

683727
protected processConnectionDirectivesV2(
684728
// TODO: Remove us when we have a fix to roll-forward.
685-
shouldUseModelNameFieldInHasManyAndBelongsTo: boolean
729+
shouldUseModelNameFieldInHasManyAndBelongsTo: boolean,
686730
): void {
687731
this.processManyToManyDirectives();
688732

@@ -726,15 +770,17 @@ export class AppSyncModelVisitor<
726770
Object.values(this.modelMap).forEach(model => {
727771
model.fields.forEach(field => {
728772
const connectionInfo = field.connectionInfo;
729-
if (connectionInfo
730-
&& connectionInfo.kind !== CodeGenConnectionType.HAS_MANY
731-
&& connectionInfo.kind !== CodeGenConnectionType.HAS_ONE
732-
&& connectionInfo.targetName !== 'id') {
773+
if (
774+
connectionInfo &&
775+
connectionInfo.kind !== CodeGenConnectionType.HAS_MANY &&
776+
connectionInfo.kind !== CodeGenConnectionType.HAS_ONE &&
777+
connectionInfo.targetName !== 'id'
778+
) {
733779
// Need to remove the field that is targetName
734780
removeFieldFromModel(model, connectionInfo.targetName);
735781
}
736782
});
737-
})
783+
});
738784
}
739785

740786
protected processV2KeyDirectives(): void {

0 commit comments

Comments
 (0)