GraphQL Call on Many-to-many Relationship Does Not Return Related Items
Before opening, please confirm:
- [X] I have searched for duplicate or closed issues and discussions.
- [X] I have read the guide for submitting bug reports.
- [X] I have done my best to include a minimal, self-contained set of instructions for consistently reproducing the issue.
JavaScript Framework
Angular
Amplify APIs
GraphQL API
Amplify Version
v6
Amplify Categories
api
Backend
Amplify CLI
Environment information
# Put output below this line
System:
OS: macOS 12.7.6
CPU: (8) x64 Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz
Memory: 2.49 GB / 16.00 GB
Shell: 6.21.00 - /bin/tcsh
Binaries:
Node: 20.10.0 - /usr/local/bin/node
Yarn: 1.22.22 - ~/Development/Javascript/compass/compass-frontend/node_modules/.bin/yarn
npm: 10.2.3 - /usr/local/bin/npm
pnpm: 9.11.0 - /usr/local/bin/pnpm
Browsers:
Chrome: 129.0.6668.60
Safari: 17.6
npmPackages:
@angular-devkit/build-angular: ^18.2.6 => 18.2.6
@angular-eslint/builder: ^18.2.0 => 18.3.1
@angular-eslint/eslint-plugin: ^18.2.0 => 18.3.1
@angular-eslint/eslint-plugin-template: ^18.2.0 => 18.3.1
@angular-eslint/schematics: ^18.2.0 => 18.3.1
@angular-eslint/template-parser: ^18.2.0 => 18.3.1
@angular/animations: ^18.2.6 => 18.2.6
@angular/cdk: ^18.2.6 => 18.2.6
@angular/cli: ~18.2.6 => 18.2.6
@angular/common: ^18.2.6 => 18.2.6
@angular/compiler: ^18.2.6 => 18.2.6
@angular/compiler-cli: ^18.2.6 => 18.2.6
@angular/core: ^18.2.6 => 18.2.6
@angular/forms: ^18.2.6 => 18.2.6
@angular/material: ^18.2.5 => 18.2.6
@angular/platform-browser: ^18.2.6 => 18.2.6
@angular/platform-browser-dynamic: ^18.2.6 => 18.2.6
@angular/router: ^18.2.6 => 18.2.6
@aws-amplify/api: ^6.0.28 => 6.0.51
@aws-amplify/api/internals: undefined ()
@aws-amplify/api/server: undefined ()
@aws-amplify/auth: ^6.0.27 => 6.4.2
@aws-amplify/auth/cognito: undefined ()
@aws-amplify/auth/cognito/server: undefined ()
@aws-amplify/auth/enable-oauth-listener: undefined ()
@aws-amplify/auth/server: undefined ()
@aws-amplify/core: ^6.0.27 => 6.4.2
@aws-amplify/core/internals/adapter-core: undefined ()
@aws-amplify/core/internals/aws-client-utils: undefined ()
@aws-amplify/core/internals/aws-client-utils/composers: undefined ()
@aws-amplify/core/internals/aws-clients/cognitoIdentity: undefined ()
@aws-amplify/core/internals/aws-clients/pinpoint: undefined ()
@aws-amplify/core/internals/providers/pinpoint: undefined ()
@aws-amplify/core/internals/utils: undefined ()
@aws-amplify/core/server: undefined ()
@aws-amplify/storage: ^6.0.27 => 6.6.7
@aws-amplify/storage/s3: undefined ()
@aws-amplify/storage/s3/server: undefined ()
@aws-amplify/storage/server: undefined ()
@aws-amplify/ui-angular: ^5.0.26 => 5.0.26
@types/d3: ^7.4.0 => 7.4.3
@types/jasmine: ~4.3.2 => 4.3.6
@typescript-eslint/eslint-plugin: ^8.0.0 => 8.7.0
@typescript-eslint/parser: ^8.0.0 => 8.7.0
amplify: ^0.0.11 => 0.0.11
angular: ^1.8.3 => 1.8.3
aws-amplify: ^6.0.28 => 6.6.2
aws-amplify/adapter-core: undefined ()
aws-amplify/analytics: undefined ()
aws-amplify/analytics/kinesis: undefined ()
aws-amplify/analytics/kinesis-firehose: undefined ()
aws-amplify/analytics/personalize: undefined ()
aws-amplify/analytics/pinpoint: undefined ()
aws-amplify/api: undefined ()
aws-amplify/api/server: undefined ()
aws-amplify/auth: undefined ()
aws-amplify/auth/cognito: undefined ()
aws-amplify/auth/cognito/server: undefined ()
aws-amplify/auth/enable-oauth-listener: undefined ()
aws-amplify/auth/server: undefined ()
aws-amplify/data: undefined ()
aws-amplify/data/server: undefined ()
aws-amplify/datastore: undefined ()
aws-amplify/in-app-messaging: undefined ()
aws-amplify/in-app-messaging/pinpoint: undefined ()
aws-amplify/push-notifications: undefined ()
aws-amplify/push-notifications/pinpoint: undefined ()
aws-amplify/storage: undefined ()
aws-amplify/storage/s3: undefined ()
aws-amplify/storage/s3/server: undefined ()
aws-amplify/storage/server: undefined ()
aws-amplify/utils: undefined ()
chart.js: ^4.3.0 => 4.4.4
chart.js-auto: undefined ()
chart.js-helpers: undefined ()
d3: ^7.8.5 => 7.9.0
eslint: ^9.11.0 => 9.11.1
jasmine-core: ~5.0.0 => 5.0.1 (4.6.1)
karma: ~6.4.2 => 6.4.4
karma-chrome-launcher: ~3.2.0 => 3.2.0
karma-coverage: ~2.2.0 => 2.2.1
karma-coverage-coffee-example: 1.0.0
karma-jasmine: ~5.1.0 => 5.1.0
karma-jasmine-html-reporter: ~2.1.0 => 2.1.0
ng2-charts: ^4.1.1 => 4.1.1
npm-check-updates: ^16.10.12 => 16.14.20
rxjs: ~7.8.1 => 7.8.1
rxjs/ajax: undefined ()
rxjs/fetch: undefined ()
rxjs/operators: undefined ()
rxjs/testing: undefined ()
rxjs/webSocket: undefined ()
tslib: ^2.5.3 => 2.7.0 (2.6.3)
typescript: ^5.2.0 => 5.5.4
yarn: ^1.22.0 => 1.22.22
zone.js: ~0.14.0 => 0.14.10
npmGlobalPackages:
@angular/cli: 17.3.3
@aws-amplify/cli: 11.0.2
bootstrap: 3.3.7
bower: 1.8.0
browser-sync: 2.17.5
corepack: 0.22.0
firebase-tools: 3.0.8
generator-angular: 0.15.1
generator-bootstrap: 0.2.2
generator-gulp-angular: 1.1.1
generator-karma: 2.0.0
generator-node-webkit: 2.0.0-alpha.3
generator-nwjs: 0.0.3
generator-oldmen: 0.1.13
generator-react-gulp-browserify-less: 1.0.1
grunt-cli: 1.2.0
gulp: 3.9.1
http-server: 0.9.0
istanbul: 0.4.5
jasmine-node: 1.14.5
jekyll-cli: 0.3.9
karma-cli: 1.0.1
mocha: 3.2.0
n: 5.0.1
nmp: 1.0.3
nodewebkit: 0.11.6
npm-check-updates: 16.10.12
npm: 10.2.3
protractor: 7.0.0
typescript: 5.4.4
yo: 1.8.5
Describe the bug
No items are returned by GraphQL call on many-to-many relation. Items exist in the correctly named DynamoDB join table. Specific error is "TypeError: checklistModel.actionModels.items is not iterable"
The definition of the ChecklistModel and ActionModel types are below. All models in the schema file are public (to eliminate any security related errors that may be causing this issue).
type ActionModel @model @auth(rules: [{ allow: public }]){
id: ID!
company: String!
name: String
notes: String
duration: Int
checklists: [ChecklistModel] @manyToMany(relationName: "ChecklistActions")
}
type ChecklistModel @model @auth(rules: [{ allow: public }]){
id: ID!
company: String!
name: String
notes: String
duration: Int
preCharter: Boolean
actionModels: [ActionModel] @manyToMany(relationName: "ChecklistActions")
workflows: [WorkflowModel] @manyToMany(relationName: "WorkflowChecklists")
}
Expected behavior
checklistModel.actionModels.items is an array with one item (and so therefore iterable)
Reproduction steps
- Select 'Checklist Admin'
- Select a Checklist item
- Open the context-based menu, and select 'edit'
The error occurs when the EditChecklist component is opened.
Code Snippet
// Put your code below this line.
// Code used to create a Checklist Model with Action Models
// In the add-checklist component
async onAddChecklistPressed(model: ChecklistModel, formDirective: FormGroupDirective) {
try {
this._checklistModelsService.createChecklistModel(model, this.checklist).then(() => {
this._snackBar.open('Created a new checklist', 'OK', { duration: 3000 });
this.checklistForm.reset();
formDirective.resetForm();
this.checklist = [];
this._getActions();
});
} catch (error) {
this._snackBar.open('Error creating the checklist', 'OK', { duration: 3000 });
}
}
// In the ChecklistModel service
async createChecklistModel(checklistModel: ChecklistModel, actions: ActionModel[]) {
const checklistMutationResult = await this.client.graphql({
query: createChecklistModel,
variables: {
input: {
company: 'seaforth',
name: checklistModel.name,
duration: checklistModel.duration,
notes: checklistModel.notes,
preCharter: checklistModel.preCharter,
}
}
});
const checklist = checklistMutationResult.data.createChecklistModel;
for (const action of actions) {
await this.client.graphql({
query: createChecklistActions,
variables: {
input: {
actionModelId: action.id,
checklistModelId: checklist.id
},
}
});
}
const updatedChecklist = await this.getChecklistModelFromId(checklist.id);
console.log(updatedChecklist.actionModels);
}
// Code to retrieve the ChecklistModel
// In the ChecklistModel service
async getChecklistModelFromId(id: string): Promise<ChecklistModel> {
const checklistResult = await this.client.graphql({
query: getChecklistModel,
variables: { id: id }
})
return checklistResult.data.getChecklistModel as ChecklistModel;
}
Log output
The item from the join table is shown below. The actionModelId and checklistModelId match the ids from the corresponding ActionModel and ChecklistModel tables. The value returned from the getChecklistModelFromId is also attached.
// Put your logs below this line
aws-exports.js
No response
Manual configuration
No response
Additional configuration
No response
Mobile Device
No response
Mobile Operating System
No response
Mobile Browser
No response
Mobile Browser Version
No response
Additional information and screenshots
No response
Hi @desert-digital 👋 thanks for raising this issue and providing detailed reproduction steps!
I will attempt to reproduce and report back with any findings.
hey @chrisbonifacio: any insight on this? thanks!
...or perhaps @HuiSF / @chrisbonifacio this is a good opportunity to move this to Gen 2? Does many-to-many work in Gen 2? I ask because the Data table at this link says "No" for manyToMany but the documentation at this link shows how a many-to-many relationship can be built. Thanks again!
Hi @HuiSF and @chrisbonifacio: I am kind of stuck here. Any update? Thanks! Ian.
Hi @desert-digital, I was not able to reproduce the behavior described in this issue.
I deployed this graphql schema:
type ActionModel @model @auth(rules: [{ allow: public }]) {
id: ID!
company: String!
name: String
notes: String
duration: Int
checklists: [ChecklistModel] @manyToMany(relationName: "ChecklistActions")
}
type ChecklistModel @model @auth(rules: [{ allow: public }]) {
id: ID!
company: String!
name: String
notes: String
duration: Int
preCharter: Boolean
actionModels: [ActionModel] @manyToMany(relationName: "ChecklistActions")
}
and used this code to create the checklistModel, actionModel, and the checklistAction join table record.
const getChecklistModelQuery = async (id: string) => {
const checklistModel = await client.graphql({
query: getChecklistModel,
variables: {
id,
} as GetChecklistModelQueryVariables,
});
console.log(checklistModel.data.getChecklistModel);
return checklistModel.data.getChecklistModel;
};
const createCheckListModelMutation = async () => {
const result = await client.graphql<
GraphQLQuery<CreateChecklistModelMutation>
>({
query: createChecklistModel,
variables: {
input: {
company: "testChecklist",
},
} as CreateChecklistModelMutationVariables,
});
console.log(result.data.createChecklistModel);
return result.data.createChecklistModel;
};
const createActionModelMutation = async () => {
const result = await client.graphql<
GraphQLQuery<CreateActionModelMutation>
>({
query: createActionModel,
variables: {
input: {
company: "testAction",
},
} as CreateActionModelMutationVariables,
});
console.log(result.data.createActionModel);
return result.data.createActionModel;
};
const createChecklistActionsMutation = async () => {
const checklistModel = await createCheckListModelMutation();
setId(checklistModel?.id as string);
const actionModel = await createActionModelMutation();
const result = await client.graphql<
GraphQLQuery<CreateChecklistActionsMutation>
>({
query: createChecklistActions,
variables: {
input: {
actionModelId: actionModel?.id,
checklistModelId: checklistModel?.id,
},
} as CreateChecklistActionsMutationVariables,
});
const checklistModelQueryResult = await getChecklistModelQuery(
checklistModel?.id as string
);
console.log("ChecklistAction:", result.data.createChecklistActions);
console.log("ChecklistModel:", checklistModelQueryResult);
};
This is the result:
The join record is created. The result of the getChecklistModel query doesn't return any related actionModels can be explained because the query does not include it in the selection set:
export const getChecklistModel = /* GraphQL */ `query GetChecklistModel($id: ID!) {
getChecklistModel(id: $id) {
id
company
name
notes
duration
preCharter
actionModels {
nextToken
__typename
}
createdAt
updatedAt
__typename
}
}
` as GeneratedQuery<
APITypes.GetChecklistModelQueryVariables,
APITypes.GetChecklistModelQuery
>;
if i adjust the query to include actionModel items, i get the related actionModel items:
I tried reproducing with the latest versions of the amplify packages. Can you make sure you are using the latest versions of the amplify packages?
Try upgrading and let me know if that helps resolve the issue.
Hi: thanks so much @chrisbonifacio.
Just upgrading resulted in no change, unfortunately; was certainly worth a try.
I created another Checklist, and can see a new ChecklistActions record, so the creation is (still) works. When I look at the query in queries.ts, it is the same as the query above that doesn't work
export const getChecklistModel = /* GraphQL */ `query GetChecklistModel($id: ID!) {
getChecklistModel(id: $id) {
id
company
name
notes
duration
preCharter
actionModels {
nextToken
__typename
}
workflows {
nextToken
__typename
}
createdAt
updatedAt
__typename
}
}
` as GeneratedQuery<
APITypes.GetChecklistModelQueryVariables,
APITypes.GetChecklistModelQuery
>;
Interestingly, the GraphQL query in queries.graphql is different:
query GetChecklistModel($id: ID!) {
getChecklistModel(id: $id) {
id
company
name
notes
duration
preCharter
actions {
items {
id
actionModelId
checklistModelId
createdAt
updatedAt
__typename
}
nextToken
__typename
}
workflows {
items {
id
checklistModelId
workflowModelId
createdAt
updatedAt
__typename
}
nextToken
__typename
}
createdAt
updatedAt
__typename
}
}
So my next question is: shouldn't the query in queries.ts (which is generated by codegen) include the action model items?! Do I need to generate the queries using typecript instead of angular (when I created the API)?
Thanks! Ian.
Is this the correct place to get the queries?
// Amplify
import { generateClient } from '@aws-amplify/api';
import {
listChecklistModels,
getChecklistModel,
checklistActionsByChecklistModelId
} from '../../graphql/queries';
import {
createChecklistModel,
deleteChecklistModel,
createChecklistActions,
deleteChecklistActions
} from '../../graphql/mutations';
// Local
import { ChecklistModel, ActionModel } from '../API.service';
import { ActionsService } from './actions.service';
OK: it's working. I ran
amplify codegen configure
and selected typescript, and then the defaults. The queries.ts file (in the graphql/queries folder) was updated, and I could see the correct GraphQL is there now. Thanks so much for pointing me in the right direction!