Skip to content

Commit 9932c30

Browse files
author
Dane Pilcher
committed
fix: allow required uni-directional hasOne with fields
1 parent f268f8f commit 9932c30

File tree

3 files changed

+79
-13
lines changed

3 files changed

+79
-13
lines changed

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

Lines changed: 75 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ describe('process connection', () => {
5656

5757
it('should return HAS_MANY for Post.comments field connection info', () => {
5858
const commentsField = modelMap.Post.fields[0];
59-
const connectionInfo = (processConnections(commentsField, modelMap.Post, modelMap) as any) as CodeGenFieldConnectionHasMany;
59+
const connectionInfo = (processConnections(commentsField, modelMap.Post, modelMap, true) as any) as CodeGenFieldConnectionHasMany;
6060
expect(connectionInfo).toBeDefined();
6161

6262
expect(connectionInfo.kind).toEqual(CodeGenConnectionType.HAS_MANY);
@@ -66,7 +66,7 @@ describe('process connection', () => {
6666

6767
it('should return BELONGS_TO for Comment.post field connection info', () => {
6868
const postField = modelMap.Comment.fields[0];
69-
const connectionInfo = (processConnections(postField, modelMap.Comment, modelMap) as any) as CodeGenFieldConnectionBelongsTo;
69+
const connectionInfo = (processConnections(postField, modelMap.Comment, modelMap, true) as any) as CodeGenFieldConnectionBelongsTo;
7070
expect(connectionInfo).toBeDefined();
7171

7272
expect(connectionInfo.kind).toEqual(CodeGenConnectionType.BELONGS_TO);
@@ -120,7 +120,7 @@ describe('process connection', () => {
120120

121121
it('should return HAS_ONE Person.license field', () => {
122122
const licenseField = modelMap.Person.fields[0];
123-
const connectionInfo = (processConnections(licenseField, modelMap.Person, modelMap) as any) as CodeGenFieldConnectionHasOne;
123+
const connectionInfo = (processConnections(licenseField, modelMap.Person, modelMap, true) as any) as CodeGenFieldConnectionHasOne;
124124
expect(connectionInfo).toBeDefined();
125125
expect(connectionInfo.kind).toEqual(CodeGenConnectionType.HAS_ONE);
126126
expect(connectionInfo.associatedWith).toEqual(modelMap.License.fields[0]);
@@ -130,7 +130,7 @@ describe('process connection', () => {
130130

131131
it('should return BELONGS_TO License.person field', () => {
132132
const personField = modelMap.License.fields[0];
133-
const connectionInfo = (processConnections(personField, modelMap.License, modelMap) as any) as CodeGenFieldConnectionBelongsTo;
133+
const connectionInfo = (processConnections(personField, modelMap.License, modelMap, true) as any) as CodeGenFieldConnectionBelongsTo;
134134
expect(connectionInfo).toBeDefined();
135135
expect(connectionInfo.kind).toEqual(CodeGenConnectionType.BELONGS_TO);
136136
expect(connectionInfo.isConnectingFieldAutoCreated).toEqual(true);
@@ -141,9 +141,74 @@ describe('process connection', () => {
141141
// Make person field optional
142142
personField.isNullable = true;
143143
expect(() => {
144-
processConnections(personField, modelMap.License, modelMap);
144+
processConnections(personField, modelMap.License, modelMap, true);
145145
}).toThrowError('DataStore does not support 1 to 1 connection with both sides of connection as optional field');
146146
});
147+
148+
it('uni-directional One:One connection with required field and datastore is not enabled', () => {
149+
const schema = /* GraphQL */ `
150+
type User @model {
151+
id: ID!
152+
}
153+
type Session @model {
154+
id: ID!
155+
sessionUserId: ID!
156+
user: User! @connection(fields: ["sessionUserId"])
157+
}
158+
`;
159+
160+
const modelMap: CodeGenModelMap = {
161+
User: {
162+
name: 'User',
163+
type: 'model',
164+
directives: [],
165+
fields: [
166+
{
167+
type: 'ID',
168+
isNullable: false,
169+
isList: false,
170+
name: 'id',
171+
directives: [],
172+
},
173+
],
174+
},
175+
Session: {
176+
name: 'Session',
177+
type: 'model',
178+
directives: [],
179+
fields: [
180+
{
181+
type: 'ID',
182+
isNullable: false,
183+
isList: false,
184+
name: 'id',
185+
directives: [],
186+
},
187+
{
188+
type: 'ID',
189+
isNullable: false,
190+
isList: false,
191+
name: 'sessionUserId',
192+
directives: [],
193+
},
194+
{
195+
type: 'User',
196+
isNullable: false,
197+
isList: false,
198+
name: 'user',
199+
directives: [{ name: 'connection', arguments: { fields: ['sessionUserId'] } }],
200+
},
201+
],
202+
},
203+
};
204+
205+
modelMap.Session.fields[2]
206+
207+
const connectionInfo = (processConnections(modelMap.Session.fields[2], modelMap.User, modelMap, false) as any) as CodeGenFieldConnectionBelongsTo;
208+
expect(connectionInfo).toBeDefined();
209+
expect(connectionInfo.kind).toEqual(CodeGenConnectionType.HAS_ONE);
210+
expect(connectionInfo.isConnectingFieldAutoCreated).toEqual(false);
211+
});
147212
});
148213
});
149214
describe('Uni-directional connection (unnamed connection)', () => {
@@ -192,7 +257,7 @@ describe('process connection', () => {
192257

193258
it('should return HAS_MANY for Post.comments', () => {
194259
const commentsField = modelMap.Post.fields[0];
195-
const connectionInfo = (processConnections(commentsField, modelMap.Post, modelMap) as any) as CodeGenFieldConnectionHasMany;
260+
const connectionInfo = (processConnections(commentsField, modelMap.Post, modelMap, true) as any) as CodeGenFieldConnectionHasMany;
196261
expect(connectionInfo).toBeDefined();
197262

198263
expect(connectionInfo.kind).toEqual(CodeGenConnectionType.HAS_MANY);
@@ -208,7 +273,7 @@ describe('process connection', () => {
208273

209274
it('should return BELONGS_TO for Comment.post', () => {
210275
const commentsField = modelMap.Comment.fields[0];
211-
const connectionInfo = (processConnections(commentsField, modelMap.Comment, modelMap) as any) as CodeGenFieldConnectionBelongsTo;
276+
const connectionInfo = (processConnections(commentsField, modelMap.Comment, modelMap, true) as any) as CodeGenFieldConnectionBelongsTo;
212277
expect(connectionInfo).toBeDefined();
213278

214279
expect(connectionInfo.kind).toEqual(CodeGenConnectionType.BELONGS_TO);
@@ -280,12 +345,12 @@ describe('process connection', () => {
280345

281346
it('should not throw error if connection directive has keyName', () => {
282347
const commentsField = modelMap.Post.fields[0];
283-
expect(() => processConnections(commentsField, modelMap.Post, modelMap)).not.toThrowError();
348+
expect(() => processConnections(commentsField, modelMap.Post, modelMap, true)).not.toThrowError();
284349
});
285350

286351
it('should support connection with @key on BELONGS_TO side', () => {
287352
const postField = modelMap.Comment.fields[2];
288-
const connectionInfo = (processConnections(postField, modelMap.Post, modelMap) as any) as CodeGenFieldConnectionBelongsTo;
353+
const connectionInfo = (processConnections(postField, modelMap.Post, modelMap, true) as any) as CodeGenFieldConnectionBelongsTo;
289354
expect(connectionInfo).toBeDefined();
290355
expect(connectionInfo.kind).toEqual(CodeGenConnectionType.BELONGS_TO);
291356
expect(connectionInfo.targetName).toEqual(modelMap.Comment.fields[0].name);
@@ -462,4 +527,4 @@ describe('process connection', () => {
462527
expect(getConnectedField(subordinateField, employeeModel, employeeModel)).toEqual(supervisorField);
463528
});
464529
});
465-
});
530+
});

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ export function processConnections(
128128
field: CodeGenField,
129129
model: CodeGenModel,
130130
modelMap: CodeGenModelMap,
131+
isDataStoreEnabled: boolean,
131132
): CodeGenFieldConnection | undefined {
132133
const connectionDirective = field.directives.find(d => d.name === 'connection');
133134
if (connectionDirective) {
@@ -189,7 +190,7 @@ export function processConnections(
189190
targetName: connectionFields[0] || makeConnectionAttributeName(model.name, field.name),
190191
targetNames: [] // New attribute for v2 custom pk support. Not used in v1 so use empty array.
191192
};
192-
} else if (field.isNullable && !otherSideField.isNullable) {
193+
} else if ((field.isNullable && !otherSideField.isNullable) || !isDataStoreEnabled) {
193194
/*
194195
# model
195196
type License { # belongsTo

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ export interface ParsedAppSyncModelConfig extends ParsedConfig {
169169
selectedType?: string;
170170
generate?: CodeGenGenerateEnum;
171171
target?: string;
172-
isDataStoreEnabled?: string;
172+
isDataStoreEnabled?: boolean;
173173
isTimestampFieldsAdded?: boolean;
174174
handleListNullabilityTransparently?: boolean;
175175
usePipelinedTransformer?: boolean;
@@ -818,7 +818,7 @@ export class AppSyncModelVisitor<
818818
protected processConnectionDirective(): void {
819819
Object.values(this.modelMap).forEach(model => {
820820
model.fields.forEach(field => {
821-
const connectionInfo = processConnections(field, model, this.modelMap);
821+
const connectionInfo = processConnections(field, model, this.modelMap, !!this.config.isDataStoreEnabled);
822822
if (connectionInfo) {
823823
if (connectionInfo.kind === CodeGenConnectionType.HAS_MANY || connectionInfo.kind === CodeGenConnectionType.HAS_ONE) {
824824
// Need to update the other side of the connection even if there is no connection directive

0 commit comments

Comments
 (0)