@strapi/plugin-graphql 4.0.0-beta.12 → 4.0.0-beta.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -5
- package/server/bootstrap.js +24 -4
- package/server/config/default-config.js +13 -0
- package/server/config/index.js +7 -0
- package/server/services/builders/filters/content-type.js +10 -1
- package/server/services/builders/input.js +6 -3
- package/server/services/builders/resolvers/association.js +2 -6
- package/server/services/builders/resolvers/component.js +6 -2
- package/server/services/builders/type.js +3 -20
- package/server/services/builders/utils.js +5 -2
- package/server/services/content-api/index.js +7 -2
- package/server/services/content-api/wrap-resolvers.js +2 -4
- package/server/services/utils/mappers/graphql-filters-to-strapi-query.js +4 -2
- package/strapi-server.js +2 -0
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strapi/plugin-graphql",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.16",
|
|
4
4
|
"description": "Adds GraphQL endpoint with default API methods.",
|
|
5
5
|
"strapi": {
|
|
6
6
|
"displayName": "GraphQL",
|
|
7
7
|
"name": "graphql",
|
|
8
|
-
"description": "
|
|
8
|
+
"description": "Adds GraphQL endpoint with default API methods.",
|
|
9
9
|
"kind": "plugin"
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"@apollo/federation": "^0.28.0",
|
|
16
16
|
"@graphql-tools/schema": "8.1.2",
|
|
17
17
|
"@graphql-tools/utils": "^8.0.2",
|
|
18
|
-
"@strapi/utils": "4.0.0-beta.
|
|
18
|
+
"@strapi/utils": "4.0.0-beta.16",
|
|
19
19
|
"apollo-server-core": "3.1.2",
|
|
20
20
|
"apollo-server-koa": "3.1.2",
|
|
21
21
|
"glob": "^7.1.7",
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
"koa-compose": "^4.1.0",
|
|
30
30
|
"lodash": "4.17.21",
|
|
31
31
|
"nexus": "1.1.0",
|
|
32
|
-
"pluralize": "^8.0.0"
|
|
32
|
+
"pluralize": "^8.0.0",
|
|
33
|
+
"subscriptions-transport-ws": "0.9.19"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
35
36
|
"cross-env": "^7.0.3",
|
|
@@ -52,5 +53,5 @@
|
|
|
52
53
|
"npm": ">=6.0.0"
|
|
53
54
|
},
|
|
54
55
|
"license": "SEE LICENSE IN LICENSE",
|
|
55
|
-
"gitHead": "
|
|
56
|
+
"gitHead": "71bdfa34637832e8e78a6cf1b57c8c6dbadf133d"
|
|
56
57
|
}
|
package/server/bootstrap.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { isEmpty, mergeWith, isArray } = require('lodash/fp');
|
|
4
|
+
const { execute, subscribe } = require('graphql');
|
|
5
|
+
const { SubscriptionServer } = require('subscriptions-transport-ws');
|
|
4
6
|
const { ApolloServer } = require('apollo-server-koa');
|
|
5
7
|
const {
|
|
6
8
|
ApolloServerPluginLandingPageDisabled,
|
|
@@ -31,6 +33,8 @@ module.exports = async ({ strapi }) => {
|
|
|
31
33
|
|
|
32
34
|
const { config } = strapi.plugin('graphql');
|
|
33
35
|
|
|
36
|
+
const path = config('endpoint');
|
|
37
|
+
|
|
34
38
|
const defaultServerConfig = {
|
|
35
39
|
// Schema
|
|
36
40
|
schema,
|
|
@@ -59,14 +63,29 @@ module.exports = async ({ strapi }) => {
|
|
|
59
63
|
],
|
|
60
64
|
};
|
|
61
65
|
|
|
62
|
-
const serverConfig = merge(defaultServerConfig, config('apolloServer'
|
|
66
|
+
const serverConfig = merge(defaultServerConfig, config('apolloServer'));
|
|
67
|
+
|
|
68
|
+
// Handle subscriptions
|
|
69
|
+
if (config('subscriptions')) {
|
|
70
|
+
const subscriptionServer = SubscriptionServer.create(
|
|
71
|
+
{ schema, execute, subscribe },
|
|
72
|
+
{ server: strapi.server.httpServer, path }
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
serverConfig.plugins.push({
|
|
76
|
+
async serverWillStart() {
|
|
77
|
+
return {
|
|
78
|
+
async drainServer() {
|
|
79
|
+
subscriptionServer.close();
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|
|
63
85
|
|
|
64
86
|
// Create a new Apollo server
|
|
65
87
|
const server = new ApolloServer(serverConfig);
|
|
66
88
|
|
|
67
|
-
// Link the Apollo server & the Strapi app
|
|
68
|
-
const path = config('endpoint', '/graphql');
|
|
69
|
-
|
|
70
89
|
// Register the upload middleware
|
|
71
90
|
useUploadMiddleware(strapi, path);
|
|
72
91
|
|
|
@@ -77,6 +96,7 @@ module.exports = async ({ strapi }) => {
|
|
|
77
96
|
strapi.log.error('Failed to start the Apollo server', e.message);
|
|
78
97
|
}
|
|
79
98
|
|
|
99
|
+
// Link the Apollo server & the Strapi app
|
|
80
100
|
strapi.server.routes([
|
|
81
101
|
{
|
|
82
102
|
method: 'ALL',
|
|
@@ -13,7 +13,7 @@ module.exports = ({ strapi }) => {
|
|
|
13
13
|
const utils = strapi.plugin('graphql').service('utils');
|
|
14
14
|
const extension = strapi.plugin('graphql').service('extension');
|
|
15
15
|
|
|
16
|
-
const { getFiltersInputTypeName } = utils.naming;
|
|
16
|
+
const { getFiltersInputTypeName, getScalarFilterInputTypeName } = utils.naming;
|
|
17
17
|
const { isStrapiScalar, isRelation } = utils.attributes;
|
|
18
18
|
|
|
19
19
|
const { attributes } = contentType;
|
|
@@ -31,6 +31,15 @@ module.exports = ({ strapi }) => {
|
|
|
31
31
|
.hasFiltersEnabeld()
|
|
32
32
|
);
|
|
33
33
|
|
|
34
|
+
const isIDFilterEnabled = extension
|
|
35
|
+
.shadowCRUD(contentType.uid)
|
|
36
|
+
.field('id')
|
|
37
|
+
.hasFiltersEnabeld();
|
|
38
|
+
// Add an ID filter to the collection types
|
|
39
|
+
if (contentType.kind === 'collectionType' && isIDFilterEnabled) {
|
|
40
|
+
t.field('id', { type: getScalarFilterInputTypeName('ID') });
|
|
41
|
+
}
|
|
42
|
+
|
|
34
43
|
// Add every defined attribute
|
|
35
44
|
for (const [attributeName, attribute] of validAttributes) {
|
|
36
45
|
// Handle scalars
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { inputObjectType, nonNull } = require('nexus');
|
|
4
|
+
const {
|
|
5
|
+
contentTypes: { isWritableAttribute },
|
|
6
|
+
} = require('@strapi/utils');
|
|
4
7
|
|
|
5
8
|
module.exports = context => {
|
|
6
9
|
const { strapi } = context;
|
|
@@ -45,9 +48,9 @@ module.exports = context => {
|
|
|
45
48
|
.hasInputEnabled();
|
|
46
49
|
};
|
|
47
50
|
|
|
48
|
-
const validAttributes = Object.entries(attributes).filter(([attributeName]) =>
|
|
49
|
-
isFieldEnabled(attributeName)
|
|
50
|
-
);
|
|
51
|
+
const validAttributes = Object.entries(attributes).filter(([attributeName]) => {
|
|
52
|
+
return isWritableAttribute(contentType, attributeName) && isFieldEnabled(attributeName);
|
|
53
|
+
});
|
|
51
54
|
|
|
52
55
|
// Add the ID for the component to enable inplace updates
|
|
53
56
|
if (modelType === 'component' && isFieldEnabled('id')) {
|
|
@@ -4,7 +4,7 @@ const { get } = require('lodash/fp');
|
|
|
4
4
|
|
|
5
5
|
const utils = require('@strapi/utils');
|
|
6
6
|
|
|
7
|
-
const { sanitize } = utils;
|
|
7
|
+
const { sanitize, pipeAsync } = utils;
|
|
8
8
|
const { ApplicationError } = utils.errors;
|
|
9
9
|
|
|
10
10
|
module.exports = ({ strapi }) => {
|
|
@@ -65,11 +65,7 @@ module.exports = ({ strapi }) => {
|
|
|
65
65
|
const unwrapData = get(attributeName);
|
|
66
66
|
|
|
67
67
|
// Sanitizer definition
|
|
68
|
-
const sanitizeMorphAttribute =
|
|
69
|
-
wrapData,
|
|
70
|
-
sanitizeData,
|
|
71
|
-
unwrapData
|
|
72
|
-
);
|
|
68
|
+
const sanitizeMorphAttribute = pipeAsync(wrapData, sanitizeData, unwrapData);
|
|
73
69
|
|
|
74
70
|
return sanitizeMorphAttribute(data);
|
|
75
71
|
}
|
|
@@ -5,8 +5,12 @@ module.exports = ({ strapi }) => ({
|
|
|
5
5
|
const { transformArgs } = strapi.plugin('graphql').service('builders').utils;
|
|
6
6
|
|
|
7
7
|
return async (parent, args = {}) => {
|
|
8
|
-
const contentType = strapi.
|
|
9
|
-
|
|
8
|
+
const contentType = strapi.getModel(contentTypeUID);
|
|
9
|
+
|
|
10
|
+
const { component: componentName } = contentType.attributes[attributeName];
|
|
11
|
+
const component = strapi.getModel(componentName);
|
|
12
|
+
|
|
13
|
+
const transformedArgs = transformArgs(args, { contentType: component, usePagination: true });
|
|
10
14
|
|
|
11
15
|
return strapi.entityService.load(contentTypeUID, parent, attributeName, transformedArgs);
|
|
12
16
|
};
|
|
@@ -66,7 +66,7 @@ module.exports = context => {
|
|
|
66
66
|
strapi,
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
-
const args = getContentTypeArgs(targetComponent);
|
|
69
|
+
const args = getContentTypeArgs(targetComponent, { multiple: !!attribute.repeatable });
|
|
70
70
|
|
|
71
71
|
builder.field(attributeName, { type, resolve, args });
|
|
72
72
|
};
|
|
@@ -150,12 +150,6 @@ module.exports = context => {
|
|
|
150
150
|
const type = attribute.multiple
|
|
151
151
|
? naming.getRelationResponseCollectionName(fileContentType)
|
|
152
152
|
: naming.getEntityResponseName(fileContentType);
|
|
153
|
-
const resolverPath = `${naming.getTypeName(contentType)}.${attributeName}`;
|
|
154
|
-
const resolverAuthScope = `${fileUID}.find`;
|
|
155
|
-
|
|
156
|
-
extension.use({
|
|
157
|
-
resolversConfig: { [resolverPath]: { auth: { scope: [resolverAuthScope] } } },
|
|
158
|
-
});
|
|
159
153
|
|
|
160
154
|
builder.field(attributeName, { type, resolve, args });
|
|
161
155
|
};
|
|
@@ -275,10 +269,9 @@ module.exports = context => {
|
|
|
275
269
|
isRelation,
|
|
276
270
|
} = utils.attributes;
|
|
277
271
|
|
|
278
|
-
const { attributes, modelType
|
|
272
|
+
const { attributes, modelType } = contentType;
|
|
279
273
|
|
|
280
274
|
const attributesKey = Object.keys(attributes);
|
|
281
|
-
const hasTimestamps = isArray(options.timestamps);
|
|
282
275
|
|
|
283
276
|
const name = (modelType === 'component' ? getComponentName : getTypeName).call(
|
|
284
277
|
null,
|
|
@@ -293,17 +286,7 @@ module.exports = context => {
|
|
|
293
286
|
t.nonNull.id('id');
|
|
294
287
|
}
|
|
295
288
|
|
|
296
|
-
|
|
297
|
-
// If the content type has timestamps enabled
|
|
298
|
-
// then we should add the corresponding attributes in the definition
|
|
299
|
-
if (hasTimestamps) {
|
|
300
|
-
const [createdAtKey, updatedAtKey] = contentType.options.timestamps;
|
|
301
|
-
|
|
302
|
-
t.nonNull.dateTime(createdAtKey);
|
|
303
|
-
t.nonNull.dateTime(updatedAtKey);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
/** 2. Attributes
|
|
289
|
+
/** Attributes
|
|
307
290
|
*
|
|
308
291
|
* Attributes can be of 7 different kind:
|
|
309
292
|
* - Scalar
|
|
@@ -25,6 +25,8 @@ module.exports = ({ strapi }) => {
|
|
|
25
25
|
|
|
26
26
|
// Components
|
|
27
27
|
if (modelType === 'component') {
|
|
28
|
+
if (!multiple) return {};
|
|
29
|
+
|
|
28
30
|
return {
|
|
29
31
|
filters: naming.getFiltersInputTypeName(contentType),
|
|
30
32
|
pagination: args.PaginationArg,
|
|
@@ -96,6 +98,7 @@ module.exports = ({ strapi }) => {
|
|
|
96
98
|
*/
|
|
97
99
|
transformArgs(args, { contentType, usePagination = false } = {}) {
|
|
98
100
|
const { mappers } = getService('utils');
|
|
101
|
+
const { config } = strapi.plugin('graphql');
|
|
99
102
|
const { pagination = {}, filters = {} } = args;
|
|
100
103
|
|
|
101
104
|
// Init
|
|
@@ -103,8 +106,8 @@ module.exports = ({ strapi }) => {
|
|
|
103
106
|
|
|
104
107
|
// Pagination
|
|
105
108
|
if (usePagination) {
|
|
106
|
-
const defaultLimit =
|
|
107
|
-
const maxLimit =
|
|
109
|
+
const defaultLimit = config('defaultLimit');
|
|
110
|
+
const maxLimit = config('maxLimit');
|
|
108
111
|
|
|
109
112
|
Object.assign(
|
|
110
113
|
newArgs,
|
|
@@ -5,6 +5,7 @@ const {
|
|
|
5
5
|
makeExecutableSchema,
|
|
6
6
|
addResolversToSchema,
|
|
7
7
|
} = require('@graphql-tools/schema');
|
|
8
|
+
const { pruneSchema } = require('@graphql-tools/utils');
|
|
8
9
|
const { makeSchema } = require('nexus');
|
|
9
10
|
const { prop, startsWith } = require('lodash/fp');
|
|
10
11
|
|
|
@@ -37,7 +38,7 @@ module.exports = ({ strapi }) => {
|
|
|
37
38
|
let builders;
|
|
38
39
|
|
|
39
40
|
const buildSchema = () => {
|
|
40
|
-
const isShadowCRUDEnabled = !!config('shadowCRUD'
|
|
41
|
+
const isShadowCRUDEnabled = !!config('shadowCRUD');
|
|
41
42
|
|
|
42
43
|
// Create a new empty type registry
|
|
43
44
|
registry = getGraphQLService('type-registry').new();
|
|
@@ -71,7 +72,11 @@ module.exports = ({ strapi }) => {
|
|
|
71
72
|
// Wrap resolvers if needed (auth, middlewares, policies...) as configured in the extension
|
|
72
73
|
const wrappedSchema = wrapResolvers({ schema, strapi, extension });
|
|
73
74
|
|
|
74
|
-
|
|
75
|
+
// Prune schema, remove unused types
|
|
76
|
+
// eg: removes registered subscriptions if they're disabled in the config)
|
|
77
|
+
const prunedSchema = pruneSchema(wrappedSchema);
|
|
78
|
+
|
|
79
|
+
return prunedSchema;
|
|
75
80
|
};
|
|
76
81
|
|
|
77
82
|
const buildSchemas = ({ registry }) => {
|
|
@@ -33,8 +33,6 @@ const wrapResolvers = ({ schema, strapi, extension = {} }) => {
|
|
|
33
33
|
|
|
34
34
|
const typeMap = schema.getTypeMap();
|
|
35
35
|
|
|
36
|
-
// Iterate over every field from every type within the
|
|
37
|
-
// schema's type map and wrap its resolve attribute if needed
|
|
38
36
|
Object.entries(typeMap).forEach(([type, definition]) => {
|
|
39
37
|
const isGraphQLObjectType = definition instanceof GraphQLObjectType;
|
|
40
38
|
const isIgnoredType = introspectionQueries.includes(type);
|
|
@@ -82,12 +80,12 @@ const wrapResolvers = ({ schema, strapi, extension = {} }) => {
|
|
|
82
80
|
const authConfig = get('auth', resolverConfig);
|
|
83
81
|
const authContext = get('state.auth', context);
|
|
84
82
|
|
|
85
|
-
const
|
|
83
|
+
const isValidType = ['Mutation', 'Query', 'Subscription'].includes(type);
|
|
86
84
|
const hasConfig = !isNil(authConfig);
|
|
87
85
|
|
|
88
86
|
const isAuthDisabled = authConfig === false;
|
|
89
87
|
|
|
90
|
-
if ((
|
|
88
|
+
if ((isValidType || hasConfig) && !isAuthDisabled) {
|
|
91
89
|
try {
|
|
92
90
|
await strapi.auth.verify(authContext, authConfig);
|
|
93
91
|
} catch (error) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { has, propEq, isNil } = require('lodash/fp');
|
|
3
|
+
const { has, propEq, isNil, isDate, isObject } = require('lodash/fp');
|
|
4
4
|
|
|
5
5
|
// todo[v4]: Find a way to get that dynamically
|
|
6
6
|
const virtualScalarAttributes = ['id'];
|
|
@@ -15,7 +15,9 @@ module.exports = ({ strapi }) => {
|
|
|
15
15
|
return data.map(recursivelyReplaceScalarOperators);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
// Note: We need to make an exception for date since GraphQL
|
|
19
|
+
// automatically cast date strings to date instances in args
|
|
20
|
+
if (isDate(data) || !isObject(data)) {
|
|
19
21
|
return data;
|
|
20
22
|
}
|
|
21
23
|
|
package/strapi-server.js
CHANGED