Skip to content

Commit 3ae2d85

Browse files
author
Dane Pilcher
authored
Merge pull request #829 from aws-amplify/main
Release references codegen for native
2 parents d2f08ba + a909ff3 commit 3ae2d85

13 files changed

+382
-1459
lines changed

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

Lines changed: 85 additions & 515 deletions
Large diffs are not rendered by default.

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

Lines changed: 97 additions & 609 deletions
Large diffs are not rendered by default.

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

Lines changed: 32 additions & 236 deletions
Large diffs are not rendered by default.

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

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,7 @@ describe('custom references', () => {
523523
}
524524
`;
525525

526-
const visitor: AppSyncModelIntrospectionVisitor = getVisitor(schema);
526+
const visitor: AppSyncModelIntrospectionVisitor = getVisitor(schema, { respectPrimaryKeyAttributesOnConnectionField: true });
527527
expect(visitor.generate()).toMatchSnapshot();
528528
});
529529

@@ -543,7 +543,7 @@ describe('custom references', () => {
543543
}
544544
`;
545545

546-
const visitor: AppSyncModelIntrospectionVisitor = getVisitor(schema);
546+
const visitor: AppSyncModelIntrospectionVisitor = getVisitor(schema, { respectPrimaryKeyAttributesOnConnectionField: true });
547547
expect(visitor.generate()).toMatchSnapshot();
548548
});
549549

@@ -567,7 +567,7 @@ describe('custom references', () => {
567567
primary: Primary @belongsTo(references: ["primaryId"])
568568
}
569569
`;
570-
const visitor: AppSyncModelIntrospectionVisitor = getVisitor(schema);
570+
const visitor: AppSyncModelIntrospectionVisitor = getVisitor(schema, { respectPrimaryKeyAttributesOnConnectionField: true });
571571
expect(visitor.generate()).toMatchSnapshot();
572572
});
573573

@@ -588,7 +588,7 @@ describe('custom references', () => {
588588
}
589589
`;
590590

591-
const visitor: AppSyncModelIntrospectionVisitor = getVisitor(schema);
591+
const visitor: AppSyncModelIntrospectionVisitor = getVisitor(schema, { respectPrimaryKeyAttributesOnConnectionField: true });
592592
expect(visitor.generate()).toMatchSnapshot();
593593
});
594594

@@ -611,7 +611,7 @@ describe('custom references', () => {
611611
}
612612
`;
613613

614-
const visitor: AppSyncModelIntrospectionVisitor = getVisitor(schema);
614+
const visitor: AppSyncModelIntrospectionVisitor = getVisitor(schema, { respectPrimaryKeyAttributesOnConnectionField: true });
615615
expect(visitor.generate()).toMatchSnapshot();
616616
});
617617

@@ -633,7 +633,7 @@ describe('custom references', () => {
633633
primary: Primary @belongsTo(references: ["primaryTenantId", "primaryInstanceId", "primaryRecordId"])
634634
}
635635
`;
636-
const visitor: AppSyncModelIntrospectionVisitor = getVisitor(schema);
636+
const visitor: AppSyncModelIntrospectionVisitor = getVisitor(schema, { respectPrimaryKeyAttributesOnConnectionField: true });
637637
expect(visitor.generate()).toMatchSnapshot();
638638
});
639639

@@ -653,7 +653,7 @@ describe('custom references', () => {
653653
}
654654
`;
655655

656-
const visitor: AppSyncModelIntrospectionVisitor = getVisitor(schema);
656+
const visitor: AppSyncModelIntrospectionVisitor = getVisitor(schema, { respectPrimaryKeyAttributesOnConnectionField: true });
657657
expect(() => visitor.generate())
658658
.toThrowError(`'fields' and 'references' cannot be used together.`);
659659
});
@@ -674,7 +674,7 @@ describe('custom references', () => {
674674
}
675675
`;
676676

677-
const visitor: AppSyncModelIntrospectionVisitor = getVisitor(schema);
677+
const visitor: AppSyncModelIntrospectionVisitor = getVisitor(schema, { respectPrimaryKeyAttributesOnConnectionField: true });
678678
expect(() => visitor.generate())
679679
.toThrowError(`'fields' and 'references' cannot be used together.`);
680680
});
@@ -695,7 +695,7 @@ describe('custom references', () => {
695695
}
696696
`;
697697

698-
const visitor: AppSyncModelIntrospectionVisitor = getVisitor(schema);
698+
const visitor: AppSyncModelIntrospectionVisitor = getVisitor(schema, { respectPrimaryKeyAttributesOnConnectionField: true });
699699
expect(() => visitor.generate())
700700
.toThrowError(`'fields' and 'references' cannot be used together.`);
701701
});
@@ -716,7 +716,7 @@ describe('custom references', () => {
716716
}
717717
`;
718718

719-
const visitor: AppSyncModelIntrospectionVisitor = getVisitor(schema);
719+
const visitor: AppSyncModelIntrospectionVisitor = getVisitor(schema, { respectPrimaryKeyAttributesOnConnectionField: true });
720720
expect(() => visitor.generate())
721721
.toThrowError(`Error processing @hasOne directive on SqlPrimary.related. @belongsTo directive with references ["primaryId"] was not found in connected model SqlRelated`);
722722
});
@@ -737,11 +737,33 @@ describe('custom references', () => {
737737
}
738738
`;
739739

740-
const visitor: AppSyncModelIntrospectionVisitor = getVisitor(schema);
740+
const visitor: AppSyncModelIntrospectionVisitor = getVisitor(schema, { respectPrimaryKeyAttributesOnConnectionField: true });
741741
expect(() => visitor.generate())
742742
.toThrowError(`Error processing @belongsTo directive on SqlRelated.primary. @hasOne or @hasMany directive with references ["primaryId"] was not found in connected model SqlPrimary`);
743743
});
744744

745+
test('throws error when missing references on hasMany related model when custom pk is disabled', () => {
746+
const schema = /* GraphQL */ `
747+
type SqlPrimary @refersTo(name: "sql_primary") @model {
748+
id: Int! @primaryKey
749+
content: String
750+
related: [SqlRelated] @hasMany(references: ["primaryId"])
751+
}
752+
753+
type SqlRelated @refersTo(name: "sql_related") @model {
754+
id: Int! @primaryKey
755+
content: String
756+
primaryId: Int! @refersTo(name: "primary_id") @index(name: "primary_id")
757+
primary: SqlPrimary @belongsTo
758+
}
759+
`;
760+
761+
const visitor: AppSyncModelIntrospectionVisitor = getVisitor(schema, { respectPrimaryKeyAttributesOnConnectionField: false });
762+
expect(() => visitor.generate())
763+
.toThrowError(`Error processing @hasMany directive on SqlPrimary.related. @belongsTo directive with references ["primaryId"] was not found in connected model SqlRelated`);
764+
});
765+
766+
745767
test('throws error when missing references on hasMany related model', () => {
746768
const schema = /* GraphQL */ `
747769
type SqlPrimary @refersTo(name: "sql_primary") @model {
@@ -758,7 +780,7 @@ describe('custom references', () => {
758780
}
759781
`;
760782

761-
const visitor: AppSyncModelIntrospectionVisitor = getVisitor(schema);
783+
const visitor: AppSyncModelIntrospectionVisitor = getVisitor(schema, { respectPrimaryKeyAttributesOnConnectionField: true });
762784
expect(() => visitor.generate())
763785
.toThrowError(`Error processing @hasMany directive on SqlPrimary.related. @belongsTo directive with references ["primaryId"] was not found in connected model SqlRelated`);
764786
});
@@ -779,7 +801,7 @@ describe('custom references', () => {
779801
}
780802
`;
781803

782-
const visitor: AppSyncModelIntrospectionVisitor = getVisitor(schema);
804+
const visitor: AppSyncModelIntrospectionVisitor = getVisitor(schema, { respectPrimaryKeyAttributesOnConnectionField: true });
783805
expect(() => visitor.generate())
784806
.toThrowError(`Error processing @belongsTo directive on SqlRelated.primary. @hasOne or @hasMany directive with references ["primaryId"] was not found in connected model SqlPrimary`);
785807
});

packages/appsync-modelgen-plugin/src/utils/process-belongs-to.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
flattenFieldDirectives,
77
makeConnectionAttributeName,
88
} from './process-connections';
9-
import { getConnectedFieldV2, fieldsAndReferencesErrorMessage } from './process-connections-v2';
9+
import { getConnectedFieldV2, getConnectedFieldForReferences } from './process-connections-v2';
1010

1111

1212
export function processBelongsToConnection(
@@ -15,7 +15,6 @@ export function processBelongsToConnection(
1515
modelMap: CodeGenModelMap,
1616
connectionDirective: CodeGenDirective,
1717
isCustomPKEnabled: boolean = false,
18-
respectReferences: boolean = false, // remove when enabled references for all targets
1918
): CodeGenFieldConnection | undefined {
2019
if (field.isList) {
2120
throw new Error(
@@ -29,22 +28,23 @@ export function processBelongsToConnection(
2928
`A 'belongsTo' field should match to a corresponding 'hasMany' or 'hasOne' field`
3029
);
3130
}
32-
const otherSideField = isCustomPKEnabled ? otherSideConnectedFields[0] : getConnectedFieldV2(field, model, otherSide, connectionDirective.name, false, respectReferences);
33-
const connectionFields = connectionDirective.arguments.fields || [];
34-
3531
const references = connectionDirective.arguments.references || [];
36-
37-
if (connectionFields.length > 0 && references.length > 0) {
38-
throw new Error(fieldsAndReferencesErrorMessage);
32+
const isUsingReferences = references.length > 0;
33+
if (isUsingReferences) {
34+
// ensure there is a matching hasOne/hasMany field with references
35+
getConnectedFieldForReferences(field, model, otherSide, connectionDirective.name)
3936
}
37+
38+
const otherSideField = isCustomPKEnabled ? otherSideConnectedFields[0] : getConnectedFieldV2(field, model, otherSide, connectionDirective.name);
39+
const connectionFields = connectionDirective.arguments.fields || [];
40+
4041
// if a type is connected using name, then amplify-graphql-relational-transformer adds a field to
4142
// track the connection and that field is not part of the selection set
4243
// but if the field are connected using fields argument in connection directive
4344
// we are reusing the field and it should be preserved in selection set
4445
const otherSideHasMany = otherSideField.isList;
45-
const isUsingReferences = respectReferences && references.length > 0;
4646
// New metada type introduced by custom PK v2 support
47-
let targetNames = isUsingReferences ? [ ...connectionFields, ...references ] : [ ...connectionFields ];
47+
let targetNames: string[] = [ ...connectionFields, ...references ];
4848
if (targetNames.length === 0) {
4949
if (otherSideHasMany) {
5050
targetNames = isCustomPKEnabled

packages/appsync-modelgen-plugin/src/utils/process-connections-v2.ts

Lines changed: 56 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -13,30 +13,14 @@ export function getConnectedFieldV2(
1313
model: CodeGenModel,
1414
connectedModel: CodeGenModel,
1515
directiveName: string,
16-
shouldUseModelNameFieldInHasManyAndBelongsTo: boolean = false,
17-
respectReferences: boolean = false,
16+
shouldUseModelNameFieldInHasManyAndBelongsTo: boolean = false
1817
): CodeGenField {
1918
const connectionInfo = getDirective(field)(directiveName);
2019
if (!connectionInfo) {
2120
throw new Error(`The ${field.name} on model ${model.name} is not connected`);
2221
}
23-
24-
const references = connectionInfo.arguments.references;
2522
if (connectionInfo.name === 'belongsTo') {
2623
let connectedFieldsBelongsTo = getBelongsToConnectedFields(model, connectedModel);
27-
if (respectReferences && references) {
28-
const connectedField = connectedFieldsBelongsTo.find((field) => {
29-
return field.directives.some((dir) => {
30-
return (dir.name === 'hasOne' || dir.name === 'hasMany')
31-
&& dir.arguments.references
32-
&& JSON.stringify(dir.arguments.references) === JSON.stringify(connectionInfo.arguments.references);
33-
});
34-
});
35-
if (!connectedField) {
36-
throw new Error(`Error processing @belongsTo directive on ${model.name}.${field.name}. @hasOne or @hasMany directive with references ${JSON.stringify(connectionInfo.arguments?.references)} was not found in connected model ${connectedModel.name}`);
37-
}
38-
return connectedField;
39-
}
4024

4125
if (connectedFieldsBelongsTo.length === 1) {
4226
return connectedFieldsBelongsTo[0];
@@ -45,25 +29,10 @@ export function getConnectedFieldV2(
4529

4630
const indexName = connectionInfo.arguments.indexName;
4731
const connectionFields = connectionInfo.arguments.fields;
48-
if (connectionFields && references) {
49-
throw new Error(fieldsAndReferencesErrorMessage);
50-
}
51-
if (references || connectionFields || directiveName === 'hasOne') {
32+
if (connectionFields || directiveName === 'hasOne') {
5233
let connectionDirective;
53-
if (respectReferences && references) {
54-
if (connectionInfo) {
55-
connectionDirective = flattenFieldDirectives(connectedModel).find((dir) => {
56-
return dir.arguments.references
57-
&& JSON.stringify(dir.arguments.references) === JSON.stringify(connectionInfo.arguments.references);
58-
});
59-
if (!connectionDirective) {
60-
throw new Error(`Error processing @${connectionInfo.name} directive on ${model.name}.${field.name}. @belongsTo directive with references ${JSON.stringify(connectionInfo.arguments?.references)} was not found in connected model ${connectedModel.name}`);
61-
}
62-
}
63-
}
64-
6534
// Find gsi on other side if index is defined
66-
else if (indexName) {
35+
if (indexName) {
6736
connectionDirective = flattenFieldDirectives(connectedModel).find(dir => {
6837
return dir.name === 'index' && dir.arguments.name === indexName;
6938
});
@@ -155,29 +124,76 @@ export function getConnectedFieldV2(
155124
};
156125
}
157126

127+
export function getConnectedFieldForReferences(
128+
field: CodeGenField,
129+
model: CodeGenModel,
130+
connectedModel: CodeGenModel,
131+
directiveName: string,
132+
): CodeGenField {
133+
const connectionInfo = getDirective(field)(directiveName);
134+
if (!connectionInfo) {
135+
throw new Error(`The ${field.name} on model ${model.name} is not connected`);
136+
}
137+
const references = connectionInfo.arguments.references;
138+
if (!references) {
139+
throw new Error(`The ${field.name} on model ${model.name} does not have references.`);
140+
}
141+
const connectionFields = connectionInfo.arguments.fields;
142+
if (connectionFields && references) {
143+
throw new Error(`'fields' and 'references' cannot be used together.`);
144+
}
145+
146+
if (connectionInfo.name === 'belongsTo') {
147+
let connectedFieldsBelongsTo = getBelongsToConnectedFields(model, connectedModel);
148+
const connectedField = connectedFieldsBelongsTo.find((field) => {
149+
return field.directives.some((dir) => {
150+
return (dir.name === 'hasOne' || dir.name === 'hasMany')
151+
&& dir.arguments.references
152+
&& JSON.stringify(dir.arguments.references) === JSON.stringify(connectionInfo.arguments.references);
153+
});
154+
});
155+
if (!connectedField) {
156+
throw new Error(`Error processing @belongsTo directive on ${model.name}.${field.name}. @hasOne or @hasMany directive with references ${JSON.stringify(connectionInfo.arguments?.references)} was not found in connected model ${connectedModel.name}`);
157+
}
158+
return connectedField;
159+
}
160+
161+
// hasOne and hasMany
162+
const connectionDirective = flattenFieldDirectives(connectedModel).find((dir) => {
163+
return dir.arguments.references
164+
&& JSON.stringify(dir.arguments.references) === JSON.stringify(connectionInfo.arguments.references);
165+
});
166+
if (!connectionDirective) {
167+
throw new Error(`Error processing @${connectionInfo.name} directive on ${model.name}.${field.name}. @belongsTo directive with references ${JSON.stringify(connectionInfo.arguments?.references)} was not found in connected model ${connectedModel.name}`);
168+
}
169+
const connectedFieldName = ((fieldDir: CodeGenFieldDirective) => {
170+
return fieldDir.fieldName;
171+
})(connectionDirective as CodeGenFieldDirective)
172+
173+
const connectedField = connectedModel.fields.find(f => f.name === connectedFieldName);
174+
return connectedField!;
175+
}
176+
158177
export function processConnectionsV2(
159178
field: CodeGenField,
160179
model: CodeGenModel,
161180
modelMap: CodeGenModelMap,
162181
shouldUseModelNameFieldInHasManyAndBelongsTo: boolean = false,
163182
isCustomPKEnabled: boolean = false,
164183
shouldUseFieldsInAssociatedWithInHasOne: boolean = false,
165-
respectReferences: boolean = false, // remove when enabled references for all targets
166184
): CodeGenFieldConnection | undefined {
167185
const connectionDirective = field.directives.find(d => d.name === 'hasOne' || d.name === 'hasMany' || d.name === 'belongsTo');
168186

169187
if (connectionDirective) {
170188
switch (connectionDirective.name) {
171189
case 'hasOne':
172-
return processHasOneConnection(field, model, modelMap, connectionDirective, isCustomPKEnabled, shouldUseFieldsInAssociatedWithInHasOne, respectReferences);
190+
return processHasOneConnection(field, model, modelMap, connectionDirective, isCustomPKEnabled, shouldUseFieldsInAssociatedWithInHasOne);
173191
case 'belongsTo':
174-
return processBelongsToConnection(field, model, modelMap, connectionDirective, isCustomPKEnabled, respectReferences);
192+
return processBelongsToConnection(field, model, modelMap, connectionDirective, isCustomPKEnabled);
175193
case 'hasMany':
176-
return processHasManyConnection(field, model, modelMap, connectionDirective, shouldUseModelNameFieldInHasManyAndBelongsTo, isCustomPKEnabled, respectReferences);
194+
return processHasManyConnection(field, model, modelMap, connectionDirective, shouldUseModelNameFieldInHasManyAndBelongsTo, isCustomPKEnabled);
177195
default:
178196
break;
179197
}
180198
}
181199
}
182-
183-
export const fieldsAndReferencesErrorMessage = `'fields' and 'references' cannot be used together.`;

packages/appsync-modelgen-plugin/src/utils/process-connections.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export type CodeGenFieldConnectionBelongsTo = CodeGenConnectionTypeBase & {
2222
export type CodeGenFieldConnectionHasOne = CodeGenConnectionTypeBase & {
2323
kind: CodeGenConnectionType.HAS_ONE;
2424
associatedWith: CodeGenField;// Legacy field remained for backward compatability
25+
associatedWithNativeReferences?: CodeGenField; // native uses the connected field instead of associatedWithFields
2526
associatedWithFields: CodeGenField[]; // New attribute for v2 custom pk support
2627
targetName?: string; // Legacy field remained for backward compatability
2728
targetNames?: string[]; // New attribute for v2 custom pk support
@@ -30,6 +31,7 @@ export type CodeGenFieldConnectionHasOne = CodeGenConnectionTypeBase & {
3031
export type CodeGenFieldConnectionHasMany = CodeGenConnectionTypeBase & {
3132
kind: CodeGenConnectionType.HAS_MANY;
3233
associatedWith: CodeGenField;// Legacy field remained for backward compatability
34+
associatedWithNativeReferences?: CodeGenField; // native uses the connected field instead of associatedWithFields
3335
associatedWithFields: CodeGenField[]; // New attribute for v2 custom pk support
3436
};
3537

0 commit comments

Comments
 (0)