react-admin
react-admin copied to clipboard
[ra-data-graphql] Circular reference of entity nested in object/array provokes "Maximum call stack size exceeded" upon introspection
type User {
_id: String!
bookmark: Bookmark # Works just fine
bookmarks: [Bookmark!]! # Provoke a "Maximum call stack size exceeded" Error
stuffs: [Stuff!]
}
type Bookmark {
book: Book! # ResolveField
bookId: String!
}
type Book {
_id: String!
author: User! # ResolveField
authorId: String!
}
type Stuff {
bookmark: Bookmark! # Provoke a "Maximum call stack size exceeded" Error
bookmarks: [Bookmark!]! # Provoke a "Maximum call stack size exceeded" Error
value: String!
}
There is a loop User => Bookmark => Book => User.
This loop is not a problem as long as Bookmark is a standalone field of User (see User.bookmark).
So this gives me the assumption that reference cycles are properly handled. Afterall GraphQL is designed to achieve graphs of entities (hence the name).
What you were expecting:
It should work fine.
What happened instead:
Once Bookmark is nested, either inside an array (see User.bookmarks), or an object (see User.stuff.bookmark), the introspection that runs in ra-data-graphql fails with
RangeError: Maximum call stack size exceeded
at Object.validate (index.js:91:12)
at validate (index.js:177:9)
at Object.builder (index.js:145:7)
at buildGqlQuery.ts:132:53
at Array.reduce (<anonymous>)
at buildGqlQuery.ts:109:12
at buildGqlQuery.ts:155:40
at Array.reduce (<anonymous>)
at buildGqlQuery.ts:109:12
at buildGqlQuery.ts:155:40
Related code:
This issue is the same https://github.com/marmelab/react-admin/issues/2938, but it was maybe poorly explained / investigated, and it did not provide any resolution.
Environment
- React-admin version: 4.8.3
- Last version that did not exhibit the issue (if applicable):
- React version: 18.2.0
- Browser: Chrome
- Stack trace (in case of a JS error):
RangeError: Maximum call stack size exceeded
at Object.validate (index.js:91:12)
at validate (index.js:177:9)
at Object.builder (index.js:145:7)
at buildGqlQuery.ts:132:53
at Array.reduce (<anonymous>)
at buildGqlQuery.ts:109:12
at buildGqlQuery.ts:155:40
at Array.reduce (<anonymous>)
at buildGqlQuery.ts:109:12
at buildGqlQuery.ts:155:40
Workaround for now
import { IntrospectionSchema } from 'graphql';
type TypeConfig = {
// Workaround for https://github.com/marmelab/react-admin/issues/8734
// We simply omit the resolveFields that provoke the cycles.
excludedFields?: string[];
};
const typeConfigs: Record<string, TypeConfig> = {
Bookmark: {
excludedFields: ['book'],
},
};
export const schemaMiddleware = (schema: IntrospectionSchema): IntrospectionSchema => {
return {
...schema,
types: schema.types.map((t) => {
const config = typeConfigs[t.name];
if (!config) {
return t;
}
if (t.kind !== 'OBJECT') {
throw Error(`Unsupported kind "${t.kind}" for Type "${t.name}". Expected "OBJECT"`);
}
const { excludedFields } = config;
return {
...t,
fields: excludedFields
? t.fields.filter((f) => !excludedFields.includes(f.name))
: t.fields,
};
}),
};
};
const graphQlDataProvider = await buildGraphQLProvider({
// ...
introspection: {
schema: schemaMiddleware(graphqlSchema),
},
});
Thanks for the detailed issue report and the workaround. Since we did not run into this issue ourselves, and there exists a workaround, we cannot give a high priority to fixing this bug. However, if you are willing to work further on this issue, we would gladly accept a PR. In any case, thanks for the report and the investigation work you've done so far!
How is this adapter supposed to determine whether requesting a given field or not? Afaik it has no way to know whether a field is a proper field (subdoc part of the model), or a resolved field (value generated on-the-fly) potentially leading to cycles. What is the current policy? Does it resolve N degrees arbitrarily ?
PROPOSAL: It may be desirable that the default behavior is that the RA GQL adapter should only request all scalar fields, rather than all fields. This would omit relations in most cases (good for query and DB). In a given scenario, FE could override the query def to explicit request relations on an as-needed basis.
Seems like a good idea. Could you open a PR?