@strapi/plugin-graphql 4.0.0-next.8 → 4.0.2
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/README.md +1 -1
- package/admin/src/index.js +0 -8
- package/package.json +43 -35
- package/server/bootstrap.js +148 -0
- package/server/config/default-config.js +13 -0
- package/server/config/index.js +7 -0
- package/server/format-graphql-error.js +50 -0
- package/server/services/builders/dynamic-zones.js +97 -0
- package/server/services/builders/entity-meta.js +7 -0
- package/server/services/builders/entity.js +43 -0
- package/server/services/builders/enums.js +24 -0
- package/server/services/builders/filters/content-type.js +93 -0
- package/server/services/builders/filters/index.js +7 -0
- package/server/services/builders/filters/operators/and.js +15 -0
- package/server/services/builders/filters/operators/between.js +15 -0
- package/server/services/builders/filters/operators/contains.js +13 -0
- package/server/services/builders/filters/operators/containsi.js +13 -0
- package/server/services/builders/filters/operators/ends-with.js +13 -0
- package/server/services/builders/filters/operators/eq.js +23 -0
- package/server/services/builders/filters/operators/gt.js +13 -0
- package/server/services/builders/filters/operators/gte.js +13 -0
- package/server/services/builders/filters/operators/in.js +15 -0
- package/server/services/builders/filters/operators/index.js +38 -0
- package/server/services/builders/filters/operators/lt.js +13 -0
- package/server/services/builders/filters/operators/lte.js +13 -0
- package/server/services/builders/filters/operators/ne.js +13 -0
- package/server/services/builders/filters/operators/not-contains.js +13 -0
- package/server/services/builders/filters/operators/not-containsi.js +13 -0
- package/server/services/builders/filters/operators/not-in.js +15 -0
- package/server/services/builders/filters/operators/not-null.js +13 -0
- package/server/services/builders/filters/operators/not.js +19 -0
- package/server/services/builders/filters/operators/null.js +13 -0
- package/server/services/builders/filters/operators/or.js +15 -0
- package/server/services/builders/filters/operators/starts-with.js +13 -0
- package/server/services/builders/generic-morph.js +41 -0
- package/server/services/builders/index.js +92 -0
- package/server/services/builders/input.js +121 -0
- package/server/services/builders/mutations/collection-type.js +191 -0
- package/server/services/builders/mutations/index.js +9 -0
- package/server/services/builders/mutations/single-type.js +141 -0
- package/server/services/builders/queries/collection-type.js +120 -0
- package/server/services/builders/queries/index.js +9 -0
- package/server/services/builders/queries/single-type.js +70 -0
- package/server/services/builders/relation-response-collection.js +35 -0
- package/server/services/builders/resolvers/association.js +85 -0
- package/server/services/builders/resolvers/component.js +18 -0
- package/server/services/builders/resolvers/dynamic-zone.js +9 -0
- package/server/services/builders/resolvers/index.js +18 -0
- package/server/services/builders/resolvers/mutation.js +33 -0
- package/server/services/builders/resolvers/query.js +19 -0
- package/server/services/builders/response-collection.js +43 -0
- package/server/services/builders/response.js +32 -0
- package/server/services/builders/type.js +364 -0
- package/server/services/builders/utils.js +134 -0
- package/server/services/constants.js +149 -0
- package/server/services/content-api/index.js +179 -0
- package/server/services/content-api/policy.js +60 -0
- package/server/services/content-api/register-functions/collection-type.js +72 -0
- package/server/services/content-api/register-functions/component.js +15 -0
- package/server/services/content-api/register-functions/content-type/dynamic-zones.js +36 -0
- package/server/services/content-api/register-functions/content-type/enums.js +33 -0
- package/server/services/content-api/register-functions/content-type/filters.js +15 -0
- package/server/services/content-api/register-functions/content-type/index.js +13 -0
- package/server/services/content-api/register-functions/content-type/inputs.js +21 -0
- package/server/services/content-api/register-functions/index.js +22 -0
- package/server/services/content-api/register-functions/internals.js +13 -0
- package/server/services/content-api/register-functions/polymorphic.js +69 -0
- package/server/services/content-api/register-functions/scalars.js +14 -0
- package/server/services/content-api/register-functions/single-type.js +72 -0
- package/server/services/content-api/wrap-resolvers.js +144 -0
- package/server/services/extension/extension.js +95 -0
- package/server/services/extension/index.js +5 -0
- package/server/services/extension/shadow-crud-manager.js +159 -0
- package/server/services/format/index.js +7 -0
- package/server/services/format/return-types.js +27 -0
- package/server/services/index.js +21 -0
- package/server/services/internals/args/index.js +11 -0
- package/server/services/internals/args/pagination.js +19 -0
- package/server/services/internals/args/publication-state.js +12 -0
- package/server/services/internals/args/sort.js +10 -0
- package/server/services/internals/helpers/get-enabled-scalars.js +15 -0
- package/server/services/internals/helpers/index.js +7 -0
- package/server/services/internals/index.js +13 -0
- package/server/services/internals/scalars/index.js +18 -0
- package/server/services/internals/scalars/time.js +36 -0
- package/server/services/internals/types/error.js +34 -0
- package/server/services/internals/types/filters.js +39 -0
- package/server/services/internals/types/index.js +29 -0
- package/server/services/internals/types/pagination.js +24 -0
- package/server/services/internals/types/publication-state.js +24 -0
- package/server/services/internals/types/response-collection-meta.js +39 -0
- package/server/services/type-registry.js +104 -0
- package/server/services/utils/attributes.js +84 -0
- package/server/services/utils/index.js +11 -0
- package/server/services/utils/mappers/entity-to-response-entity.js +12 -0
- package/server/services/utils/mappers/graphql-filters-to-strapi-query.js +109 -0
- package/server/services/utils/mappers/graphql-scalar-to-operators.js +17 -0
- package/server/services/utils/mappers/index.js +13 -0
- package/server/services/utils/mappers/strapi-scalar-to-graphql-scalar.js +25 -0
- package/server/services/utils/naming.js +287 -0
- package/strapi-admin.js +3 -0
- package/strapi-server.js +7 -9
- package/admin/src/assets/images/logo.svg +0 -38
- package/config/routes.json +0 -3
- package/config/schema.graphql +0 -1
- package/config/settings.json +0 -12
- package/controllers/GraphQL.js +0 -9
- package/hooks/graphql/defaults.json +0 -5
- package/hooks/graphql/index.js +0 -174
- package/hooks/graphql/load-config.js +0 -42
- package/services/build-aggregation.js +0 -565
- package/services/data-loaders.js +0 -55
- package/services/naming.js +0 -15
- package/services/resolvers-builder.js +0 -204
- package/services/schema-definitions.js +0 -131
- package/services/schema-generator.js +0 -178
- package/services/shadow-crud.js +0 -612
- package/services/type-builder.js +0 -311
- package/services/utils.js +0 -200
- package/types/dynamiczoneScalar.js +0 -40
- package/types/publication-state.js +0 -16
- package/types/time.js +0 -26
|
@@ -1,565 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Aggregator.js service
|
|
3
|
-
*
|
|
4
|
-
* @description: A set of functions similar to controller's actions to avoid code duplication.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
'use strict';
|
|
8
|
-
|
|
9
|
-
const _ = require('lodash');
|
|
10
|
-
const pluralize = require('pluralize');
|
|
11
|
-
const { convertRestQueryParams, buildQuery } = require('@strapi/utils');
|
|
12
|
-
|
|
13
|
-
const { buildQuery: buildQueryResolver } = require('./resolvers-builder');
|
|
14
|
-
const { convertToParams, convertToQuery, nonRequired } = require('./utils');
|
|
15
|
-
const { toSDL } = require('./schema-definitions');
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Returns all fields of type primitive
|
|
19
|
-
*
|
|
20
|
-
* @returns {Boolean}
|
|
21
|
-
*/
|
|
22
|
-
const isPrimitiveType = type => {
|
|
23
|
-
const nonRequiredType = nonRequired(type);
|
|
24
|
-
return (
|
|
25
|
-
nonRequiredType === 'Int' ||
|
|
26
|
-
nonRequiredType === 'Float' ||
|
|
27
|
-
nonRequiredType === 'String' ||
|
|
28
|
-
nonRequiredType === 'Boolean' ||
|
|
29
|
-
nonRequiredType === 'DateTime' ||
|
|
30
|
-
nonRequiredType === 'JSON'
|
|
31
|
-
);
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Checks if the field is of type enum
|
|
36
|
-
*
|
|
37
|
-
* @returns {Boolean}
|
|
38
|
-
*/
|
|
39
|
-
const isEnumType = type => {
|
|
40
|
-
return type === 'enumeration';
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Returns all fields that are not of type array
|
|
45
|
-
*
|
|
46
|
-
* @returns {Boolean}
|
|
47
|
-
*
|
|
48
|
-
* @example
|
|
49
|
-
*
|
|
50
|
-
* isNotOfTypeArray([String])
|
|
51
|
-
* // => false
|
|
52
|
-
* isNotOfTypeArray(String!)
|
|
53
|
-
* // => true
|
|
54
|
-
*/
|
|
55
|
-
const isNotOfTypeArray = type => {
|
|
56
|
-
return !/(\[\w+!?\])/.test(type);
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Returns all fields of type Integer or float
|
|
61
|
-
*/
|
|
62
|
-
const isNumberType = type => {
|
|
63
|
-
const nonRequiredType = nonRequired(type);
|
|
64
|
-
return nonRequiredType === 'Int' || nonRequiredType === 'Float';
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Returns a list of fields that have type included in fieldTypes.
|
|
69
|
-
*/
|
|
70
|
-
const getFieldsByTypes = (fields, typeCheck, returnType) => {
|
|
71
|
-
return _.reduce(
|
|
72
|
-
fields,
|
|
73
|
-
(acc, fieldType, fieldName) => {
|
|
74
|
-
if (typeCheck(fieldType)) {
|
|
75
|
-
acc[fieldName] = returnType(fieldType, fieldName);
|
|
76
|
-
}
|
|
77
|
-
return acc;
|
|
78
|
-
},
|
|
79
|
-
{}
|
|
80
|
-
);
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Use the field resolver otherwise fall through the field value
|
|
85
|
-
*
|
|
86
|
-
* @returns {function}
|
|
87
|
-
*/
|
|
88
|
-
const fieldResolver = (field, key) => {
|
|
89
|
-
return object => {
|
|
90
|
-
const resolver =
|
|
91
|
-
field.resolve ||
|
|
92
|
-
function resolver(obj) {
|
|
93
|
-
// eslint-disable-line no-unused-vars
|
|
94
|
-
return obj[key];
|
|
95
|
-
};
|
|
96
|
-
return resolver(object);
|
|
97
|
-
};
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Create fields resolvers
|
|
102
|
-
*
|
|
103
|
-
* @return {Object}
|
|
104
|
-
*/
|
|
105
|
-
const createFieldsResolver = function(fields, resolverFn, typeCheck) {
|
|
106
|
-
const resolver = Object.keys(fields).reduce((acc, fieldKey) => {
|
|
107
|
-
const field = fields[fieldKey];
|
|
108
|
-
// Check if the field is of the correct type
|
|
109
|
-
if (typeCheck(field)) {
|
|
110
|
-
return _.set(acc, fieldKey, (obj, options, context) => {
|
|
111
|
-
return resolverFn(
|
|
112
|
-
obj,
|
|
113
|
-
options,
|
|
114
|
-
context,
|
|
115
|
-
fieldResolver(field, fieldKey),
|
|
116
|
-
fieldKey,
|
|
117
|
-
obj,
|
|
118
|
-
field
|
|
119
|
-
);
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
return acc;
|
|
123
|
-
}, {});
|
|
124
|
-
|
|
125
|
-
return resolver;
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Convert non-primitive type to string (non-primitive types corresponds to a reference to an other model)
|
|
130
|
-
*
|
|
131
|
-
* @returns {String}
|
|
132
|
-
*
|
|
133
|
-
* @example
|
|
134
|
-
*
|
|
135
|
-
* extractType(String!)
|
|
136
|
-
* // => String
|
|
137
|
-
*
|
|
138
|
-
* extractType(user)
|
|
139
|
-
* // => ID
|
|
140
|
-
*
|
|
141
|
-
* extractType(ENUM_TEST_FIELD, enumeration)
|
|
142
|
-
* // => String
|
|
143
|
-
*
|
|
144
|
-
*/
|
|
145
|
-
const extractType = function(_type, attributeType) {
|
|
146
|
-
return isPrimitiveType(_type)
|
|
147
|
-
? _type.replace('!', '')
|
|
148
|
-
: isEnumType(attributeType)
|
|
149
|
-
? 'String'
|
|
150
|
-
: 'ID';
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Create the resolvers for each aggregation field
|
|
155
|
-
*
|
|
156
|
-
* @return {Object}
|
|
157
|
-
*
|
|
158
|
-
* @example
|
|
159
|
-
*
|
|
160
|
-
* const model = // Strapi model
|
|
161
|
-
*
|
|
162
|
-
* const fields = {
|
|
163
|
-
* username: String,
|
|
164
|
-
* age: Int,
|
|
165
|
-
* }
|
|
166
|
-
*
|
|
167
|
-
* const typeCheck = (type) => type === 'Int' || type === 'Float',
|
|
168
|
-
*
|
|
169
|
-
* const fieldsResoler = createAggregationFieldsResolver(model, fields, 'sum', typeCheck);
|
|
170
|
-
*
|
|
171
|
-
* // => {
|
|
172
|
-
* age: function ageResolver() { .... }
|
|
173
|
-
* }
|
|
174
|
-
*/
|
|
175
|
-
const createAggregationFieldsResolver = function(model, fields, operation, typeCheck) {
|
|
176
|
-
return createFieldsResolver(
|
|
177
|
-
fields,
|
|
178
|
-
async (obj, options, context, fieldResolver, fieldKey) => {
|
|
179
|
-
const filters = convertRestQueryParams({
|
|
180
|
-
...convertToParams(_.omit(obj, 'where')),
|
|
181
|
-
...convertToQuery(obj.where),
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
if (model.orm === 'mongoose') {
|
|
185
|
-
return buildQuery({ model, filters, aggregate: true })
|
|
186
|
-
.group({
|
|
187
|
-
_id: null,
|
|
188
|
-
[fieldKey]: { [`$${operation}`]: `$${fieldKey}` },
|
|
189
|
-
})
|
|
190
|
-
.exec()
|
|
191
|
-
.then(result => _.get(result, [0, fieldKey]));
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (model.orm === 'bookshelf') {
|
|
195
|
-
return model
|
|
196
|
-
.query(qb => {
|
|
197
|
-
// apply filters
|
|
198
|
-
buildQuery({ model, filters })(qb);
|
|
199
|
-
|
|
200
|
-
// `sum, avg, min, max` pass nicely to knex :->
|
|
201
|
-
qb[operation](`${fieldKey} as ${operation}_${fieldKey}`);
|
|
202
|
-
})
|
|
203
|
-
.fetch()
|
|
204
|
-
.then(result => result.get(`${operation}_${fieldKey}`));
|
|
205
|
-
}
|
|
206
|
-
},
|
|
207
|
-
typeCheck
|
|
208
|
-
);
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Correctly format the data returned by the group by
|
|
213
|
-
*/
|
|
214
|
-
const preProcessGroupByData = function({ result, fieldKey, filters }) {
|
|
215
|
-
const _result = _.toArray(result).filter(value => Boolean(value._id));
|
|
216
|
-
return _.map(_result, value => {
|
|
217
|
-
return {
|
|
218
|
-
key: value._id.toString(),
|
|
219
|
-
connection: () => {
|
|
220
|
-
// filter by the grouped by value in next connection
|
|
221
|
-
|
|
222
|
-
return {
|
|
223
|
-
...filters,
|
|
224
|
-
where: {
|
|
225
|
-
...(filters.where || {}),
|
|
226
|
-
[fieldKey]: value._id.toString(),
|
|
227
|
-
},
|
|
228
|
-
};
|
|
229
|
-
},
|
|
230
|
-
};
|
|
231
|
-
});
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Create the resolvers for each group by field
|
|
236
|
-
*
|
|
237
|
-
* @return {Object}
|
|
238
|
-
*
|
|
239
|
-
* @example
|
|
240
|
-
*
|
|
241
|
-
* const model = // Strapi model
|
|
242
|
-
* const fields = {
|
|
243
|
-
* username: [UserConnectionUsername],
|
|
244
|
-
* email: [UserConnectionEmail],
|
|
245
|
-
* }
|
|
246
|
-
* const fieldsResoler = createGroupByFieldsResolver(model, fields);
|
|
247
|
-
*
|
|
248
|
-
* // => {
|
|
249
|
-
* username: function usernameResolver() { .... }
|
|
250
|
-
* email: function emailResolver() { .... }
|
|
251
|
-
* }
|
|
252
|
-
*/
|
|
253
|
-
const createGroupByFieldsResolver = function(model, fields) {
|
|
254
|
-
const resolver = async (filters, options, context, fieldResolver, fieldKey) => {
|
|
255
|
-
const params = convertRestQueryParams({
|
|
256
|
-
...convertToParams(_.omit(filters, 'where')),
|
|
257
|
-
...convertToQuery(filters.where),
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
if (model.orm === 'mongoose') {
|
|
261
|
-
const result = await buildQuery({
|
|
262
|
-
model,
|
|
263
|
-
filters: params,
|
|
264
|
-
aggregate: true,
|
|
265
|
-
}).group({
|
|
266
|
-
_id: `$${fieldKey === 'id' ? model.primaryKey : fieldKey}`,
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
return preProcessGroupByData({
|
|
270
|
-
result,
|
|
271
|
-
fieldKey,
|
|
272
|
-
filters,
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (model.orm === 'bookshelf') {
|
|
277
|
-
return model
|
|
278
|
-
.query(qb => {
|
|
279
|
-
buildQuery({ model, filters: params })(qb);
|
|
280
|
-
qb.groupBy(fieldKey);
|
|
281
|
-
qb.select(fieldKey);
|
|
282
|
-
})
|
|
283
|
-
.fetchAll()
|
|
284
|
-
.then(result => {
|
|
285
|
-
let values = result.models
|
|
286
|
-
.map(m => m.get(fieldKey)) // extract aggregate field
|
|
287
|
-
.filter(v => !!v) // remove null
|
|
288
|
-
.map(v => '' + v); // convert to string
|
|
289
|
-
return values.map(v => ({
|
|
290
|
-
key: v,
|
|
291
|
-
connection: () => {
|
|
292
|
-
return {
|
|
293
|
-
..._.omit(filters, ['limit']), // we shouldn't carry limit to sub-field
|
|
294
|
-
where: {
|
|
295
|
-
...(filters.where || {}),
|
|
296
|
-
[fieldKey]: v,
|
|
297
|
-
},
|
|
298
|
-
};
|
|
299
|
-
},
|
|
300
|
-
}));
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
};
|
|
304
|
-
|
|
305
|
-
return createFieldsResolver(fields, resolver, () => true);
|
|
306
|
-
};
|
|
307
|
-
/**
|
|
308
|
-
* Generate the connection type of each non-array field of the model
|
|
309
|
-
*
|
|
310
|
-
* @return {String}
|
|
311
|
-
*/
|
|
312
|
-
const generateConnectionFieldsTypes = function(fields, model) {
|
|
313
|
-
const { globalId, attributes } = model;
|
|
314
|
-
const primitiveFields = getFieldsByTypes(fields, isNotOfTypeArray, (type, name) =>
|
|
315
|
-
extractType(type, (attributes[name] || {}).type)
|
|
316
|
-
);
|
|
317
|
-
|
|
318
|
-
const connectionFields = _.mapValues(primitiveFields, fieldType => ({
|
|
319
|
-
key: fieldType,
|
|
320
|
-
connection: `${globalId}Connection`,
|
|
321
|
-
}));
|
|
322
|
-
|
|
323
|
-
return Object.keys(primitiveFields)
|
|
324
|
-
.map(
|
|
325
|
-
fieldKey =>
|
|
326
|
-
`type ${globalId}Connection${_.upperFirst(fieldKey)} {${toSDL(connectionFields[fieldKey])}}`
|
|
327
|
-
)
|
|
328
|
-
.join('\n\n');
|
|
329
|
-
};
|
|
330
|
-
|
|
331
|
-
const formatConnectionGroupBy = function(fields, model) {
|
|
332
|
-
const { globalId } = model;
|
|
333
|
-
const groupByGlobalId = `${globalId}GroupBy`;
|
|
334
|
-
|
|
335
|
-
// Extract all primitive fields and change their types
|
|
336
|
-
const groupByFields = getFieldsByTypes(
|
|
337
|
-
fields,
|
|
338
|
-
isNotOfTypeArray,
|
|
339
|
-
(fieldType, fieldName) => `[${globalId}Connection${_.upperFirst(fieldName)}]`
|
|
340
|
-
);
|
|
341
|
-
|
|
342
|
-
// Get the generated field types
|
|
343
|
-
let groupByTypes = `type ${groupByGlobalId} {${toSDL(groupByFields)}}\n\n`;
|
|
344
|
-
groupByTypes += generateConnectionFieldsTypes(fields, model);
|
|
345
|
-
|
|
346
|
-
return {
|
|
347
|
-
globalId: groupByGlobalId,
|
|
348
|
-
type: groupByTypes,
|
|
349
|
-
resolver: {
|
|
350
|
-
[groupByGlobalId]: createGroupByFieldsResolver(model, groupByFields),
|
|
351
|
-
},
|
|
352
|
-
};
|
|
353
|
-
};
|
|
354
|
-
|
|
355
|
-
const formatConnectionAggregator = function(fields, model, modelName) {
|
|
356
|
-
const { globalId } = model;
|
|
357
|
-
|
|
358
|
-
// Extract all fields of type Integer and Float and change their type to Float
|
|
359
|
-
const numericFields = getFieldsByTypes(fields, isNumberType, () => 'Float');
|
|
360
|
-
|
|
361
|
-
// Don't create an aggregator field if the model has not number fields
|
|
362
|
-
const aggregatorGlobalId = `${globalId}Aggregator`;
|
|
363
|
-
const initialFields = {
|
|
364
|
-
count: 'Int',
|
|
365
|
-
totalCount: 'Int',
|
|
366
|
-
};
|
|
367
|
-
|
|
368
|
-
// Only add the aggregator's operations if there are some numeric fields
|
|
369
|
-
if (!_.isEmpty(numericFields)) {
|
|
370
|
-
['sum', 'avg', 'min', 'max'].forEach(agg => {
|
|
371
|
-
initialFields[agg] = `${aggregatorGlobalId}${_.startCase(agg)}`;
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
const gqlNumberFormat = toSDL(numericFields);
|
|
376
|
-
let aggregatorTypes = `type ${aggregatorGlobalId} {${toSDL(initialFields)}}\n\n`;
|
|
377
|
-
|
|
378
|
-
let resolvers = {
|
|
379
|
-
[aggregatorGlobalId]: {
|
|
380
|
-
count(obj) {
|
|
381
|
-
const opts = convertToQuery(obj.where);
|
|
382
|
-
|
|
383
|
-
if (opts._q) {
|
|
384
|
-
// allow search param
|
|
385
|
-
return strapi.query(modelName, model.plugin).countSearch(opts);
|
|
386
|
-
}
|
|
387
|
-
return strapi.query(modelName, model.plugin).count(opts);
|
|
388
|
-
},
|
|
389
|
-
totalCount() {
|
|
390
|
-
return strapi.query(modelName, model.plugin).count({});
|
|
391
|
-
},
|
|
392
|
-
},
|
|
393
|
-
};
|
|
394
|
-
|
|
395
|
-
// Only add the aggregator's operations types and resolver if there are some numeric fields
|
|
396
|
-
if (!_.isEmpty(numericFields)) {
|
|
397
|
-
// Returns the actual object and handle aggregation in the query resolvers
|
|
398
|
-
const defaultAggregatorFunc = obj => {
|
|
399
|
-
// eslint-disable-line no-unused-vars
|
|
400
|
-
return obj;
|
|
401
|
-
};
|
|
402
|
-
|
|
403
|
-
aggregatorTypes += `type ${aggregatorGlobalId}Sum {${gqlNumberFormat}}\n\n`;
|
|
404
|
-
aggregatorTypes += `type ${aggregatorGlobalId}Avg {${gqlNumberFormat}}\n\n`;
|
|
405
|
-
aggregatorTypes += `type ${aggregatorGlobalId}Min {${gqlNumberFormat}}\n\n`;
|
|
406
|
-
aggregatorTypes += `type ${aggregatorGlobalId}Max {${gqlNumberFormat}}\n\n`;
|
|
407
|
-
|
|
408
|
-
_.merge(resolvers[aggregatorGlobalId], {
|
|
409
|
-
sum: defaultAggregatorFunc,
|
|
410
|
-
avg: defaultAggregatorFunc,
|
|
411
|
-
min: defaultAggregatorFunc,
|
|
412
|
-
max: defaultAggregatorFunc,
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
resolvers = {
|
|
416
|
-
...resolvers,
|
|
417
|
-
[`${aggregatorGlobalId}Sum`]: createAggregationFieldsResolver(
|
|
418
|
-
model,
|
|
419
|
-
fields,
|
|
420
|
-
'sum',
|
|
421
|
-
isNumberType
|
|
422
|
-
),
|
|
423
|
-
[`${aggregatorGlobalId}Avg`]: createAggregationFieldsResolver(
|
|
424
|
-
model,
|
|
425
|
-
fields,
|
|
426
|
-
'avg',
|
|
427
|
-
isNumberType
|
|
428
|
-
),
|
|
429
|
-
[`${aggregatorGlobalId}Min`]: createAggregationFieldsResolver(
|
|
430
|
-
model,
|
|
431
|
-
fields,
|
|
432
|
-
'min',
|
|
433
|
-
isNumberType
|
|
434
|
-
),
|
|
435
|
-
[`${aggregatorGlobalId}Max`]: createAggregationFieldsResolver(
|
|
436
|
-
model,
|
|
437
|
-
fields,
|
|
438
|
-
'max',
|
|
439
|
-
isNumberType
|
|
440
|
-
),
|
|
441
|
-
};
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
return {
|
|
445
|
-
globalId: aggregatorGlobalId,
|
|
446
|
-
type: aggregatorTypes,
|
|
447
|
-
resolver: resolvers,
|
|
448
|
-
};
|
|
449
|
-
};
|
|
450
|
-
|
|
451
|
-
/**
|
|
452
|
-
* This method is the entry point to the GraphQL's Aggregation.
|
|
453
|
-
* It takes as param the model and its fields and it'll create the aggregation types and resolver to it
|
|
454
|
-
* Example:
|
|
455
|
-
* type User {
|
|
456
|
-
* username: String,
|
|
457
|
-
* age: Int,
|
|
458
|
-
* }
|
|
459
|
-
*
|
|
460
|
-
* It'll create
|
|
461
|
-
* type UserConnection {
|
|
462
|
-
* values: [User],
|
|
463
|
-
* groupBy: UserGroupBy,
|
|
464
|
-
* aggreate: UserAggregate
|
|
465
|
-
* }
|
|
466
|
-
*
|
|
467
|
-
* type UserAggregate {
|
|
468
|
-
* count: Int
|
|
469
|
-
* sum: UserAggregateSum
|
|
470
|
-
* avg: UserAggregateAvg
|
|
471
|
-
* }
|
|
472
|
-
*
|
|
473
|
-
* type UserAggregateSum {
|
|
474
|
-
* age: Float
|
|
475
|
-
* }
|
|
476
|
-
*
|
|
477
|
-
* type UserAggregateAvg {
|
|
478
|
-
* age: Float
|
|
479
|
-
* }
|
|
480
|
-
*
|
|
481
|
-
* type UserGroupBy {
|
|
482
|
-
* username: [UserConnectionUsername]
|
|
483
|
-
* age: [UserConnectionAge]
|
|
484
|
-
* }
|
|
485
|
-
*
|
|
486
|
-
* type UserConnectionUsername {
|
|
487
|
-
* key: String
|
|
488
|
-
* connection: UserConnection
|
|
489
|
-
* }
|
|
490
|
-
*
|
|
491
|
-
* type UserConnectionAge {
|
|
492
|
-
* key: Int
|
|
493
|
-
* connection: UserConnection
|
|
494
|
-
* }
|
|
495
|
-
*
|
|
496
|
-
*/
|
|
497
|
-
const formatModelConnectionsGQL = function({ fields, model: contentType, name, resolver }) {
|
|
498
|
-
const { globalId } = contentType;
|
|
499
|
-
const model = strapi.getModel(contentType.uid);
|
|
500
|
-
|
|
501
|
-
const connectionGlobalId = `${globalId}Connection`;
|
|
502
|
-
|
|
503
|
-
const aggregatorFormat = formatConnectionAggregator(fields, model, name);
|
|
504
|
-
const groupByFormat = formatConnectionGroupBy(fields, model);
|
|
505
|
-
const connectionFields = {
|
|
506
|
-
values: `[${globalId}]`,
|
|
507
|
-
groupBy: `${globalId}GroupBy`,
|
|
508
|
-
aggregate: `${globalId}Aggregator`,
|
|
509
|
-
};
|
|
510
|
-
const pluralName = pluralize.plural(_.camelCase(name));
|
|
511
|
-
|
|
512
|
-
let modelConnectionTypes = `type ${connectionGlobalId} {${toSDL(connectionFields)}}\n\n`;
|
|
513
|
-
if (aggregatorFormat) {
|
|
514
|
-
modelConnectionTypes += aggregatorFormat.type;
|
|
515
|
-
}
|
|
516
|
-
modelConnectionTypes += groupByFormat.type;
|
|
517
|
-
|
|
518
|
-
const connectionResolver = buildQueryResolver(`${pluralName}Connection.values`, resolver);
|
|
519
|
-
|
|
520
|
-
const connectionQueryName = `${pluralName}Connection`;
|
|
521
|
-
|
|
522
|
-
return {
|
|
523
|
-
globalId: connectionGlobalId,
|
|
524
|
-
definition: modelConnectionTypes,
|
|
525
|
-
query: {
|
|
526
|
-
[`${pluralName}Connection`]: {
|
|
527
|
-
args: {
|
|
528
|
-
sort: 'String',
|
|
529
|
-
limit: 'Int',
|
|
530
|
-
start: 'Int',
|
|
531
|
-
where: 'JSON',
|
|
532
|
-
...(resolver.args || {}),
|
|
533
|
-
},
|
|
534
|
-
type: connectionGlobalId,
|
|
535
|
-
},
|
|
536
|
-
},
|
|
537
|
-
resolvers: {
|
|
538
|
-
Query: {
|
|
539
|
-
[connectionQueryName]: buildQueryResolver(connectionQueryName, {
|
|
540
|
-
resolverOf: resolver.resolverOf || resolver.resolver,
|
|
541
|
-
resolver(obj, options) {
|
|
542
|
-
return options;
|
|
543
|
-
},
|
|
544
|
-
}),
|
|
545
|
-
},
|
|
546
|
-
[connectionGlobalId]: {
|
|
547
|
-
values(obj, options, gqlCtx) {
|
|
548
|
-
return connectionResolver(obj, obj, gqlCtx);
|
|
549
|
-
},
|
|
550
|
-
groupBy(obj) {
|
|
551
|
-
return obj;
|
|
552
|
-
},
|
|
553
|
-
aggregate(obj) {
|
|
554
|
-
return obj;
|
|
555
|
-
},
|
|
556
|
-
},
|
|
557
|
-
...aggregatorFormat.resolver,
|
|
558
|
-
...groupByFormat.resolver,
|
|
559
|
-
},
|
|
560
|
-
};
|
|
561
|
-
};
|
|
562
|
-
|
|
563
|
-
module.exports = {
|
|
564
|
-
formatModelConnectionsGQL,
|
|
565
|
-
};
|
package/services/data-loaders.js
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Loaders.js service
|
|
5
|
-
*
|
|
6
|
-
* @description: A set of functions similar to controller's actions to avoid code duplication.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const _ = require('lodash');
|
|
10
|
-
const DataLoader = require('dataloader');
|
|
11
|
-
|
|
12
|
-
module.exports = {
|
|
13
|
-
loaders: {},
|
|
14
|
-
|
|
15
|
-
initializeLoader() {
|
|
16
|
-
this.resetLoaders();
|
|
17
|
-
|
|
18
|
-
// Create loaders for each relational field (exclude core models & plugins).
|
|
19
|
-
Object.values(strapi.contentTypes).forEach(model => this.createLoader(model.uid));
|
|
20
|
-
},
|
|
21
|
-
|
|
22
|
-
resetLoaders() {
|
|
23
|
-
this.loaders = {};
|
|
24
|
-
},
|
|
25
|
-
|
|
26
|
-
createLoader(modelUID) {
|
|
27
|
-
if (this.loaders[modelUID]) {
|
|
28
|
-
return this.loaders[modelUID];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const loadFn = queries => this.batchQuery(modelUID, queries);
|
|
32
|
-
const loadOptions = {
|
|
33
|
-
cacheKeyFn: key => this.serializeKey(key),
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
this.loaders[modelUID] = new DataLoader(loadFn, loadOptions);
|
|
37
|
-
},
|
|
38
|
-
|
|
39
|
-
serializeKey(key) {
|
|
40
|
-
return _.isObjectLike(key) ? JSON.stringify(key) : key;
|
|
41
|
-
},
|
|
42
|
-
|
|
43
|
-
async batchQuery(modelUID, queries) {
|
|
44
|
-
// Extract queries from keys and merge similar queries.
|
|
45
|
-
return Promise.all(queries.map(query => this.makeQuery(modelUID, query)));
|
|
46
|
-
},
|
|
47
|
-
|
|
48
|
-
async makeQuery(modelUID, query = {}) {
|
|
49
|
-
if (query.single === true) {
|
|
50
|
-
return strapi.query(modelUID).findOne(query.filters, []);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return strapi.query(modelUID).find(query.filters, []);
|
|
54
|
-
},
|
|
55
|
-
};
|
package/services/naming.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const _ = require('lodash');
|
|
4
|
-
const pluralize = require('pluralize');
|
|
5
|
-
|
|
6
|
-
const toPlural = str => pluralize(_.camelCase(str));
|
|
7
|
-
const toSingular = str => _.camelCase(pluralize.singular(str));
|
|
8
|
-
|
|
9
|
-
const toInputName = str => `${_.upperFirst(toSingular(str))}Input`;
|
|
10
|
-
|
|
11
|
-
module.exports = {
|
|
12
|
-
toSingular,
|
|
13
|
-
toPlural,
|
|
14
|
-
toInputName,
|
|
15
|
-
};
|