Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions examples/demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"type": "module",
"dependencies": {
"@apollo/client": "^3.12.4",
"@apollo/client": "^4.1.6",
"@mui/icons-material": "^7.0.0",
Comment thread
erwanMarmelab marked this conversation as resolved.
"@mui/material": "^7.0.0",
"@types/recharts": "^1.8.10",
Expand All @@ -14,7 +14,7 @@
"date-fns": "^3.6.0",
"echarts": "^5.6.0",
"fakerest": "^4.2.0",
"graphql": "^15.6.0",
"graphql": "^16.13.2",
"graphql-tag": "^2.12.6",
"inflection": "^3.0.0",
"json-graphql-server": "^3.0.1",
Expand All @@ -30,7 +30,8 @@
"react-admin": "^5.0.0",
"react-dom": "^19.0.0",
"react-router": "^7.1.1",
"react-router-dom": "^7.1.1"
"react-router-dom": "^7.1.1",
"rxjs": "^7.8.2"
},
"scripts": {
"dev": "vite",
Expand Down
5 changes: 2 additions & 3 deletions examples/demo/src/dataProvider/graphql.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { ApolloQueryResult } from '@apollo/client';
import buildApolloClient, {
buildQuery as buildQueryFactory,
} from 'ra-data-graphql-simple';
Expand Down Expand Up @@ -44,7 +43,7 @@ const customBuildQuery: BuildQueryFactory = introspectionResults => {
}
}`,
variables: { id: params.id },
parseResponse: ({ data }: ApolloQueryResult<any>) => {
parseResponse: ({ data }) => {
if (data[`remove${resource}`]) {
return { data: { id: params.id } };
}
Expand Down Expand Up @@ -98,7 +97,7 @@ const customBuildQuery: BuildQueryFactory = introspectionResults => {
}
`,
variables: params.data,
parseResponse: ({ data }: ApolloQueryResult<any>) => {
parseResponse: ({ data }) => {
if (data.createCustomer) {
return { data: { id: data.createCustomer.id } };
}
Expand Down
7 changes: 5 additions & 2 deletions packages/ra-data-graphql-simple/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,18 @@ built with [Apollo](https://www.apollodata.com/) and tailored to target a simple
Install with:

```sh
npm install --save graphql ra-data-graphql-simple
npm install --save graphql rxjs ra-data-graphql-simple
```

or

```sh
yarn add graphql ra-data-graphql-simple
yarn add graphql rxjs ra-data-graphql-simple
```

This package is built on Apollo Client 4, which requires `graphql` 16 or later and
`rxjs` 7.3 or later. Both are peer dependencies, so your application provides them.

## Usage

The `ra-data-graphql-simple` package exposes a single function, which is a constructor for a `dataProvider` based on a GraphQL endpoint. When executed, this function calls the GraphQL endpoint, running an [introspection](https://graphql.org/learn/introspection/) query. It uses the result of this query (the GraphQL schema) to automatically configure the `dataProvider` accordingly.
Expand Down
10 changes: 6 additions & 4 deletions packages/ra-data-graphql-simple/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,20 @@
"build": "zshy --silent"
},
"dependencies": {
"@apollo/client": "^3.3.19",
"@apollo/client": "^4.1.6",
"graphql-ast-types-browser": "~1.0.2",
"lodash": "~4.18.1",
"pluralize": "~7.0.0",
"ra-data-graphql": "^5.14.7"
},
"peerDependencies": {
"graphql": "^15.6.0",
"ra-core": "^5.0.0"
"graphql": "^16.0.0 || ^17.0.0",
"ra-core": "^5.0.0",
"rxjs": "^7.3.0"
},
"devDependencies": {
"graphql": "^15.6.0",
"graphql": "^16.13.2",
"rxjs": "^7.8.2",
"typescript": "^5.1.3",
"zshy": "^0.5.0"
},
Expand Down
6 changes: 2 additions & 4 deletions packages/ra-data-graphql-simple/src/buildGqlQuery.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -995,8 +995,7 @@ describe('buildGqlQuery', () => {
data: deleteCommands(ids: $ids) {
ids
}
}
`
}`
);
});

Expand All @@ -1018,8 +1017,7 @@ describe('buildGqlQuery', () => {
data: updateCommands(ids: $ids, data: $data) {
ids
}
}
`
}`
);
});
});
80 changes: 79 additions & 1 deletion packages/ra-data-graphql-simple/src/getGqlType.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { TypeKind, print } from 'graphql';
import {
buildSchema,
getIntrospectionQuery,
graphqlSync,
print,
TypeKind,
} from 'graphql';
import { getGqlType } from './getGqlType';

describe('getGqlType', () => {
Expand Down Expand Up @@ -106,4 +112,76 @@ describe('getGqlType', () => {
)
).toEqual('[[ID!]]!');
});

it('returns the arg type for a named type carrying a null ofType', () => {
expect(
print(
getGqlType({
kind: TypeKind.SCALAR,
name: 'foo',
ofType: null,
})
)
).toEqual('foo');
});

it('returns the arg type for wrapped types carrying a null ofType', () => {
expect(
print(
getGqlType({
kind: TypeKind.NON_NULL,
name: null,
ofType: {
kind: TypeKind.LIST,
name: null,
ofType: {
kind: TypeKind.NON_NULL,
name: null,
ofType: {
kind: TypeKind.SCALAR,
name: 'ID',
ofType: null,
},
},
},
})
)
).toEqual('[ID!]!');
});

it('prints every argument type of a real introspection result', () => {
const schema = buildSchema(`
input CommandInput { id: ID }
type Command { id: ID! }
type Query {
command(
id: ID!
tags: [String]
requiredTags: [String!]!
nested: [[Int!]]!
input: CommandInput
): Command
}
`);
const introspection = graphqlSync({
schema,
source: getIntrospectionQuery(),
});
const types = (introspection.data as any).__schema.types;
const args = types
.find(type => type.name === 'Query')
.fields.find(field => field.name === 'command').args;

expect(
Object.fromEntries(
args.map(arg => [arg.name, print(getGqlType(arg.type))])
)
).toEqual({
id: 'ID!',
tags: '[String]',
requiredTags: '[String!]!',
nested: '[[Int!]]!',
input: 'CommandInput',
});
});
});
23 changes: 12 additions & 11 deletions packages/ra-data-graphql-simple/src/getGqlType.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import {
IntrospectionListTypeRef,
IntrospectionType,
IntrospectionTypeRef,
TypeKind,
TypeNode,
} from 'graphql';
import * as gqlTypes from 'graphql-ast-types-browser';

const isWrappingTypeRef = (
type: IntrospectionType | IntrospectionTypeRef
): type is Extract<IntrospectionTypeRef, { ofType: unknown }> =>
type.kind === TypeKind.LIST || type.kind === TypeKind.NON_NULL;

export const getGqlType = (
type: IntrospectionType | IntrospectionListTypeRef | IntrospectionTypeRef
type: IntrospectionType | IntrospectionTypeRef
): TypeNode => {
switch (type.kind) {
case TypeKind.LIST:
return gqlTypes.listType(getGqlType(type.ofType));

case TypeKind.NON_NULL:
return gqlTypes.nonNullType(getGqlType(type.ofType));

default:
return gqlTypes.namedType(gqlTypes.name(type.name));
if (isWrappingTypeRef(type)) {
return type.kind === TypeKind.LIST
? gqlTypes.listType(getGqlType(type.ofType))
: gqlTypes.nonNullType(getGqlType(type.ofType));
}

return gqlTypes.namedType(gqlTypes.name(type.name));
};
4 changes: 2 additions & 2 deletions packages/ra-data-graphql-simple/src/getResponseParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ import {
} from 'ra-core';
import { IntrospectionResult, IntrospectedResource } from 'ra-data-graphql';
import { IntrospectionField } from 'graphql';
import { ApolloQueryResult } from '@apollo/client';
import { ApolloClient } from '@apollo/client';

export default (_introspectionResults: IntrospectionResult) =>
(
raFetchMethod: string,
_resource: IntrospectedResource,
_queryType: IntrospectionField
) =>
(response: ApolloQueryResult<any>) => {
(response: ApolloClient.QueryResult<any>) => {
const data = response.data;

if (
Expand Down
7 changes: 5 additions & 2 deletions packages/ra-data-graphql/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,18 @@ Below is a rough graph summarizing how the data flows:
Install with:

```sh
npm install --save graphql ra-data-graphql
npm install --save graphql rxjs ra-data-graphql
```

or

```sh
yarn add graphql ra-data-graphql
yarn add graphql rxjs ra-data-graphql
```

This package is built on Apollo Client 4, which requires `graphql` 16 or later and
`rxjs` 7.3 or later. Both are peer dependencies, so your application provides them.

## Usage

Build the data provider on mount, and pass it to the `<Admin>` component when ready:
Expand Down
10 changes: 6 additions & 4 deletions packages/ra-data-graphql/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,19 @@
"build": "zshy --silent"
},
"dependencies": {
"@apollo/client": "^3.3.19",
"@apollo/client": "^4.1.6",
"graphql-tag": "^2.12.6",
"lodash": "~4.18.1",
"pluralize": "~7.0.0"
},
"peerDependencies": {
"graphql": "^15.6.0 || ^16",
"ra-core": "^5.0.0"
"graphql": "^16.0.0 || ^17.0.0",
"ra-core": "^5.0.0",
"rxjs": "^7.3.0"
},
"devDependencies": {
"graphql": "^15.6.0",
"graphql": "^16.13.2",
"rxjs": "^7.8.2",
"typescript": "^5.1.3",
"zshy": "^0.5.0"
},
Expand Down
76 changes: 76 additions & 0 deletions packages/ra-data-graphql/src/buildApolloClient.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { HttpLink } from '@apollo/client';
import gql from 'graphql-tag';

import buildApolloClient from './buildApolloClient';

const okResponse = () =>
Promise.resolve(
new Response(JSON.stringify({ data: { foo: 'bar' } }), {
status: 200,
headers: { 'content-type': 'application/json' },
})
);

describe('buildApolloClient', () => {
let fetchSpy;

beforeEach(() => {
fetchSpy = jest
.spyOn(globalThis, 'fetch')
.mockImplementation(okResponse as any);
});

afterEach(() => {
fetchSpy.mockRestore();
});

const runQuery = async client => {
await client.query({
query: gql`
query {
foo
}
`,
fetchPolicy: 'no-cache',
});
return fetchSpy.mock.calls[0];
};

it('sends the query to the uri passed in options', async () => {
const client = buildApolloClient({
uri: 'http://example.com/graphql',
});

const [url] = await runQuery(client);

expect(String(url)).toBe('http://example.com/graphql');
});

it('forwards credentials and headers to the HttpLink', async () => {
const client = buildApolloClient({
uri: 'http://example.com/graphql',
credentials: 'include',
headers: { 'X-Custom': 'yes' },
});

const [, init] = await runQuery(client);

expect(init.credentials).toBe('include');
expect(new Headers(init.headers).get('X-Custom')).toBe('yes');
});

it('lets an explicit link take precedence over uri', async () => {
const client = buildApolloClient({
uri: 'http://ignored.example.com/graphql',
link: new HttpLink({ uri: 'http://explicit.example.com/graphql' }),
});

const [url] = await runQuery(client);

expect(String(url)).toBe('http://explicit.example.com/graphql');
});

it('builds a client without any options', () => {
expect(buildApolloClient().link).toBeDefined();
});
});
Loading
Loading