Skip to content

Commit 3c1353c

Browse files
committed
Allow graphql dataProviders to leverage the introspection results
1 parent 40bda09 commit 3c1353c

File tree

3 files changed

+192
-2
lines changed

3 files changed

+192
-2
lines changed

packages/ra-data-graphql/README.md

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,116 @@ The `./schema` file is a `schema.json` in `./src` retrieved with [get-graphql-sc
199199

200200
> Note: Importing the `schema.json` file will significantly increase the bundle size.
201201
202+
## Leveraging Introspection In Custom Methods
203+
204+
If you need to build custom methods based on the introspection, you can leverage the `getIntrospection` method of the `dataProvider`. It returns an object with the following format:
205+
206+
```js
207+
{
208+
// The original schema as returned by the Apollo client
209+
schema: {},
210+
// An array of object describing the types that are compatible with react-admin resources
211+
// and the methods they support. Note that not all methods may be supported.
212+
resources: [
213+
{
214+
type: { name: 'name-of-the-type' }, // e.g. Post
215+
GET_LIST: { name: 'name-of-the-query' }, // e.g. allPosts
216+
GET_MANY: { name: 'name-of-the-query' }, // e.g. allPosts
217+
GET_MANY_REFERENCE: { name: 'name-of-the-query' }, // e.g. allPosts
218+
GET_ONE: { name: 'name-of-the-query' }, // e.g. Post
219+
CREATE: { name: 'name-of-the-query' }, // e.g. createPost
220+
UPDATE: { name: 'name-of-the-query' }, // e.g. updatePost
221+
DELETE: { name: 'name-of-the-query' }, // e.g. deletePost
222+
},
223+
],
224+
}
225+
```
226+
227+
This is useful if you need to support custom dataProvider methods such as those needed for ['@react-admin/ra-realtime'](https://react-admin-ee.marmelab.com/documentation/ra-realtime#dataprovider-requirements):
228+
229+
```tsx
230+
import { Identifier, GET_LIST, GET_ONE } from 'ra-core';
231+
import { RealTimeDataProvider } from '@react-admin/ra-realtime';
232+
import { IntrospectedResource } from 'ra-data-graphql';
233+
234+
const subscriptions: {
235+
topic: string;
236+
subscription: any;
237+
subscriptionCallback: any;
238+
}[];
239+
240+
const baseDataProvider = buildDataProvider(/* */);
241+
242+
export const dataProvider: RealTimeDataProvider = {
243+
...baseDataProvider,
244+
subscribe: async (topic, subscriptionCallback) => {
245+
const raRealTimeTopic = topic.startsWith('resource/') ? topic.split('/') : null;
246+
if (!raRealTimeTopic) throw new Error(`Invalid ra-realtime topic ${topic}`);
247+
248+
// Two possible topic patterns
249+
// 1. resource/${resource}
250+
// 2. resource/${resource}/${id}
251+
const [, resourceName, id] = raRealTimeTopic;
252+
const introspectionResults = await baseDataProvider.getIntrospection();
253+
const resourceIntrospection = introspectionResults.resources.find(
254+
resource => resource.type.name === resourceName
255+
);
256+
if (!resourceIntrospection) throw new Error(`Invalid resource ${resourceName}`);
257+
258+
const { query, queryName, variables } = buildQuery({ id, resource, resourceIntrospection });
259+
const subscription = dataProvider.client
260+
.subscribe({ query, variables })
261+
.subscribe(data =>
262+
subscriptionCallback(data.data[queryName].event)
263+
);
264+
265+
subscriptions.push({
266+
topic,
267+
subscription,
268+
subscriptionCallback,
269+
});
270+
271+
return Promise.resolve({ data: null });
272+
},
273+
unsubscribe: async (topic: string, subscriptionCallback: any) => {
274+
subscriptions = subscriptions.filter(
275+
subscription =>
276+
subscription.topic !== topic ||
277+
subscription.subscriptionCallback !== subscriptionCallback
278+
);
279+
return Promise.resolve({ data: null });
280+
},
281+
}
282+
283+
const buildQuery = (
284+
{
285+
id,
286+
resource,
287+
resourceIntrospection
288+
}: {
289+
id: Identifier | undefined;
290+
resource: string;
291+
resourceIntrospection: IntrospectedResource
292+
}
293+
) => {
294+
if (!id) {
295+
if (!resourceIntrospection[GET_LIST]) throw new Error(`Resource ${resource} does not support the getList method`);
296+
return {
297+
queryName: resourceIntrospection[GET_LIST],
298+
query: gql`subscription ${queryName} { ${queryName}{ topic event } }`,
299+
variables: {},
300+
}
301+
}
302+
303+
if (!resourceIntrospection[GET_ONE]) throw new Error(`Resource ${resource} does not support the getOne method`);
304+
return {
305+
queryName: resourceIntrospection[GET_LIST],
306+
query: gql`subscription ${queryName}($id: ID!) { ${queryName}(id: $id){ topic event } }`,
307+
variables: { id },
308+
}
309+
}
310+
```
311+
202312
## Troubleshooting
203313

204314
## When I create or edit a resource, the list or edit page does not refresh its data

packages/ra-data-graphql/src/index.test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,66 @@ describe('GraphQL data provider', () => {
4949
});
5050
});
5151
});
52+
describe('getIntrospection', () => {
53+
it('returns introspection result', async () => {
54+
const schema = {
55+
queryType: { name: 'Query' },
56+
mutationType: { name: 'Mutation' },
57+
types: [
58+
{
59+
name: 'Query',
60+
fields: [{ name: 'allPosts' }, { name: 'Post' }],
61+
},
62+
{
63+
name: 'Mutation',
64+
fields: [
65+
{ name: 'createPost' },
66+
{ name: 'updatePost' },
67+
{ name: 'deletePost' },
68+
],
69+
},
70+
{ name: 'Post' },
71+
],
72+
};
73+
const client = {
74+
query: jest.fn(() =>
75+
Promise.resolve({
76+
data: {
77+
__schema: schema,
78+
},
79+
})
80+
),
81+
};
82+
83+
const dataProvider = buildDataProvider({
84+
client: client as unknown as ApolloClient<unknown>,
85+
buildQuery: () => () => undefined,
86+
});
87+
88+
const introspection = await dataProvider.getIntrospection();
89+
expect(introspection).toEqual({
90+
queries: [
91+
{ name: 'allPosts' },
92+
{ name: 'Post' },
93+
{ name: 'createPost' },
94+
{ name: 'updatePost' },
95+
{ name: 'deletePost' },
96+
],
97+
types: [{ name: 'Post' }],
98+
resources: [
99+
{
100+
type: { name: 'Post' },
101+
GET_LIST: { name: 'allPosts' },
102+
GET_MANY: { name: 'allPosts' },
103+
GET_MANY_REFERENCE: { name: 'allPosts' },
104+
GET_ONE: { name: 'Post' },
105+
CREATE: { name: 'createPost' },
106+
UPDATE: { name: 'updatePost' },
107+
DELETE: { name: 'deletePost' },
108+
},
109+
],
110+
schema,
111+
});
112+
});
113+
});
52114
});

packages/ra-data-graphql/src/index.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ export type Options = {
131131
watchQuery?: GetWatchQueryOptions;
132132
};
133133

134-
const buildGraphQLProvider = (options: Options): DataProvider => {
134+
const buildGraphQLProvider = (options: Options): GraphqlDataProvider => {
135135
const {
136136
client: clientObject,
137137
clientOptions,
@@ -224,7 +224,7 @@ const buildGraphQLProvider = (options: Options): DataProvider => {
224224
);
225225
};
226226

227-
const raDataProvider: DataProvider = {
227+
const raDataProvider: GraphqlDataProvider = {
228228
create: (resource, params) => callApollo(CREATE, resource, params),
229229
delete: (resource, params) => callApollo(DELETE, resource, params),
230230
deleteMany: (resource, params) =>
@@ -237,6 +237,19 @@ const buildGraphQLProvider = (options: Options): DataProvider => {
237237
update: (resource, params) => callApollo(UPDATE, resource, params),
238238
updateMany: (resource, params) =>
239239
callApollo(UPDATE_MANY, resource, params),
240+
getIntrospection: () => {
241+
if (introspection) {
242+
if (!introspectionResultsPromise) {
243+
introspectionResultsPromise = resolveIntrospection(
244+
client,
245+
introspection
246+
);
247+
}
248+
249+
return introspectionResultsPromise;
250+
}
251+
},
252+
client,
240253
};
241254

242255
return raDataProvider;
@@ -261,4 +274,9 @@ const getQueryOperation = query => {
261274
throw new Error('Unable to determine the query operation');
262275
};
263276

277+
export type GetIntrospection = () => Promise<IntrospectionResult>;
278+
export type GraphqlDataProvider = DataProvider & {
279+
getIntrospection: GetIntrospection;
280+
};
281+
264282
export default buildGraphQLProvider;

0 commit comments

Comments
 (0)