@strapi/utils 4.0.0-next.6 → 4.0.0

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.
@@ -60,7 +60,7 @@ const getAssociationFromFieldKey = ({ model, field }) => {
60
60
  const castInput = ({ type, value, operator }) => {
61
61
  return Array.isArray(value)
62
62
  ? value.map(val => castValue({ type, operator, value: val }))
63
- : castValue({ type, operator, value: value });
63
+ : castValue({ type, operator, value });
64
64
  };
65
65
 
66
66
  /**
package/lib/config.js CHANGED
@@ -3,7 +3,10 @@
3
3
  const _ = require('lodash');
4
4
  const { getCommonPath } = require('./string-formatting');
5
5
 
6
- const getConfigUrls = (serverConfig, forAdminBuild = false) => {
6
+ const getConfigUrls = (config, forAdminBuild = false) => {
7
+ const serverConfig = config.get('server');
8
+ const adminConfig = config.get('admin');
9
+
7
10
  // Defines serverUrl value
8
11
  let serverUrl = _.get(serverConfig, 'url', '');
9
12
  serverUrl = _.trim(serverUrl, '/ ');
@@ -23,7 +26,7 @@ const getConfigUrls = (serverConfig, forAdminBuild = false) => {
23
26
  }
24
27
 
25
28
  // Defines adminUrl value
26
- let adminUrl = _.get(serverConfig, 'admin.url', '/admin');
29
+ let adminUrl = _.get(adminConfig, 'url', '/admin');
27
30
  adminUrl = _.trim(adminUrl, '/ ');
28
31
  if (typeof adminUrl !== 'string') {
29
32
  throw new Error('Invalid admin url config. Make sure the url is a non-empty string.');
@@ -60,7 +63,7 @@ const getConfigUrls = (serverConfig, forAdminBuild = false) => {
60
63
  };
61
64
 
62
65
  const getAbsoluteUrl = adminOrServer => (config, forAdminBuild = false) => {
63
- const { serverUrl, adminUrl } = getConfigUrls(config.server, forAdminBuild);
66
+ const { serverUrl, adminUrl } = getConfigUrls(config, forAdminBuild);
64
67
  let url = adminOrServer === 'server' ? serverUrl : adminUrl;
65
68
 
66
69
  if (url.startsWith('http')) {
@@ -68,11 +71,12 @@ const getAbsoluteUrl = adminOrServer => (config, forAdminBuild = false) => {
68
71
  }
69
72
 
70
73
  let hostname =
71
- config.environment === 'development' && ['127.0.0.1', '0.0.0.0'].includes(config.server.host)
74
+ config.get('environment') === 'development' &&
75
+ ['127.0.0.1', '0.0.0.0'].includes(config.get('server.host'))
72
76
  ? 'localhost'
73
- : config.server.host;
77
+ : config.get('server.host');
74
78
 
75
- return `http://${hostname}:${config.server.port}${url}`;
79
+ return `http://${hostname}:${config.get('server.port')}${url}`;
76
80
  };
77
81
 
78
82
  module.exports = {
@@ -1,18 +1,18 @@
1
1
  'use strict';
2
2
 
3
3
  const _ = require('lodash');
4
- const pluralize = require('pluralize');
4
+ const { has } = require('lodash/fp');
5
5
 
6
6
  const SINGLE_TYPE = 'singleType';
7
7
  const COLLECTION_TYPE = 'collectionType';
8
8
 
9
9
  const ID_ATTRIBUTE = 'id';
10
- const PUBLISHED_AT_ATTRIBUTE = 'published_at';
11
- const CREATED_BY_ATTRIBUTE = 'created_by';
12
- const UPDATED_BY_ATTRIBUTE = 'updated_by';
10
+ const PUBLISHED_AT_ATTRIBUTE = 'publishedAt';
11
+ const CREATED_BY_ATTRIBUTE = 'createdBy';
12
+ const UPDATED_BY_ATTRIBUTE = 'updatedBy';
13
13
 
14
- const CREATED_AT_ATTRIBUTE = 'created_at';
15
- const UPDATED_AT_ATTRIBUTE = 'updated_at';
14
+ const CREATED_AT_ATTRIBUTE = 'createdAt';
15
+ const UPDATED_AT_ATTRIBUTE = 'updatedAt';
16
16
 
17
17
  const DP_PUB_STATE_LIVE = 'live';
18
18
  const DP_PUB_STATE_PREVIEW = 'preview';
@@ -32,8 +32,18 @@ const constants = {
32
32
  COLLECTION_TYPE,
33
33
  };
34
34
 
35
- const getTimestamps = () => {
36
- return [CREATED_AT_ATTRIBUTE, UPDATED_AT_ATTRIBUTE];
35
+ const getTimestamps = model => {
36
+ const attributes = [];
37
+
38
+ if (has(CREATED_AT_ATTRIBUTE, model.attributes)) {
39
+ attributes.push(CREATED_AT_ATTRIBUTE);
40
+ }
41
+
42
+ if (has(UPDATED_AT_ATTRIBUTE, model.attributes)) {
43
+ attributes.push(UPDATED_AT_ATTRIBUTE);
44
+ }
45
+
46
+ return attributes;
37
47
  };
38
48
 
39
49
  const getNonWritableAttributes = (model = {}) => {
@@ -43,7 +53,7 @@ const getNonWritableAttributes = (model = {}) => {
43
53
  []
44
54
  );
45
55
 
46
- return _.uniq([ID_ATTRIBUTE, ...getTimestamps(), ...nonWritableAttributes]);
56
+ return _.uniq([ID_ATTRIBUTE, ...getTimestamps(model), ...nonWritableAttributes]);
47
57
  };
48
58
 
49
59
  const getWritableAttributes = (model = {}) => {
@@ -61,7 +71,7 @@ const getNonVisibleAttributes = model => {
61
71
  []
62
72
  );
63
73
 
64
- return _.uniq([ID_ATTRIBUTE, ...getTimestamps(), ...nonVisibleAttributes]);
74
+ return _.uniq([ID_ATTRIBUTE, ...getTimestamps(model), ...nonVisibleAttributes]);
65
75
  };
66
76
 
67
77
  const getVisibleAttributes = model => {
@@ -101,108 +111,6 @@ const isMediaAttribute = attr => {
101
111
  return attr.type === 'media';
102
112
  };
103
113
 
104
- const getKind = obj => obj.kind || 'collectionType';
105
-
106
- const pickSchema = model => {
107
- const schema = _.cloneDeep(
108
- _.pick(model, ['collectionName', 'info', 'options', 'pluginOptions', 'attributes'])
109
- );
110
-
111
- schema.kind = getKind(model);
112
- return schema;
113
- };
114
-
115
- const createContentType = (model, { modelName }, { apiName, pluginName } = {}) => {
116
- if (apiName) {
117
- Object.assign(model, {
118
- uid: `application::${apiName}.${modelName}`,
119
- apiName,
120
- collectionName: model.collectionName || modelName.toLocaleLowerCase(),
121
- globalId: getGlobalId(model, modelName),
122
- });
123
- } else if (pluginName) {
124
- Object.assign(model, {
125
- uid: `plugins::${pluginName}.${modelName}`,
126
- plugin: pluginName,
127
- collectionName: model.collectionName || `${pluginName}_${modelName}`.toLowerCase(),
128
- globalId: getGlobalId(model, modelName, pluginName),
129
- });
130
- } else {
131
- Object.assign(model, {
132
- uid: `strapi::${modelName}`,
133
- plugin: 'admin',
134
- globalId: getGlobalId(model, modelName, 'admin'),
135
- });
136
- }
137
-
138
- Object.assign(model, {
139
- __schema__: pickSchema(model),
140
- kind: getKind(model),
141
- modelType: 'contentType',
142
- modelName,
143
- });
144
-
145
- Object.defineProperty(model, 'privateAttributes', {
146
- get() {
147
- // FIXME: to fix
148
- // return strapi.getModel(model.uid).privateAttributes;
149
- return [];
150
- },
151
- configurable: true,
152
- });
153
-
154
- Object.assign(model.attributes, {
155
- [CREATED_AT_ATTRIBUTE]: {
156
- type: 'datetime',
157
- default: () => new Date(),
158
- },
159
- // TODO: handle on edit set to new date
160
- [UPDATED_AT_ATTRIBUTE]: {
161
- type: 'datetime',
162
- default: () => new Date(),
163
- },
164
- });
165
-
166
- if (hasDraftAndPublish(model)) {
167
- model.attributes[PUBLISHED_AT_ATTRIBUTE] = {
168
- type: 'datetime',
169
- configurable: false,
170
- writable: true,
171
- visible: false,
172
- };
173
- }
174
-
175
- const isPrivate = !_.get(model, 'options.populateCreatorFields', false);
176
-
177
- model.attributes[CREATED_BY_ATTRIBUTE] = {
178
- type: 'relation',
179
- relation: 'oneToOne',
180
- target: 'strapi::user',
181
- configurable: false,
182
- writable: false,
183
- visible: false,
184
- useJoinTable: false,
185
- private: isPrivate,
186
- };
187
-
188
- model.attributes[UPDATED_BY_ATTRIBUTE] = {
189
- type: 'relation',
190
- relation: 'oneToOne',
191
- target: 'strapi::user',
192
- configurable: false,
193
- writable: false,
194
- visible: false,
195
- useJoinTable: false,
196
- private: isPrivate,
197
- };
198
- };
199
-
200
- const getGlobalId = (model, modelName, prefix) => {
201
- let globalId = prefix ? `${prefix}-${modelName}` : modelName;
202
-
203
- return model.globalId || _.upperFirst(_.camelCase(globalId));
204
- };
205
-
206
114
  const isRelationalAttribute = attribute => attribute.type === 'relation';
207
115
  const isComponentAttribute = attribute => ['component', 'dynamiczone'].includes(attribute.type);
208
116
 
@@ -222,8 +130,8 @@ const isTypedAttribute = (attribute, type) => {
222
130
  */
223
131
  const getContentTypeRoutePrefix = contentType => {
224
132
  return isSingleType(contentType)
225
- ? _.kebabCase(contentType.modelName)
226
- : _.kebabCase(pluralize(contentType.modelName));
133
+ ? _.kebabCase(contentType.info.singularName)
134
+ : _.kebabCase(contentType.info.pluralName);
227
135
  };
228
136
 
229
137
  module.exports = {
@@ -240,13 +148,12 @@ module.exports = {
240
148
  isWritableAttribute,
241
149
  getNonVisibleAttributes,
242
150
  getVisibleAttributes,
151
+ getTimestamps,
243
152
  isVisibleAttribute,
244
153
  hasDraftAndPublish,
245
154
  isDraft,
246
155
  isSingleType,
247
156
  isCollectionType,
248
157
  isKind,
249
- createContentType,
250
- getGlobalId,
251
158
  getContentTypeRoutePrefix,
252
159
  };
@@ -0,0 +1,252 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Converts the standard Strapi REST query params to a more usable format for querying
5
+ * You can read more here: https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/rest-api.html#filters
6
+ */
7
+ const { has } = require('lodash/fp');
8
+ const _ = require('lodash');
9
+ const parseType = require('./parse-type');
10
+ const contentTypesUtils = require('./content-types');
11
+
12
+ const { PUBLISHED_AT_ATTRIBUTE } = contentTypesUtils.constants;
13
+
14
+ class InvalidOrderError extends Error {
15
+ constructor() {
16
+ super();
17
+ this.message = 'Invalid order. order can only be one of asc|desc|ASC|DESC';
18
+ }
19
+ }
20
+ class InvalidSortError extends Error {
21
+ constructor() {
22
+ super();
23
+ this.message =
24
+ 'Invalid sort parameter. Expected a string, an array of strings, a sort object or an array of sort objects';
25
+ }
26
+ }
27
+
28
+ const validateOrder = order => {
29
+ if (!['asc', 'desc'].includes(order.toLocaleLowerCase())) {
30
+ throw new InvalidOrderError();
31
+ }
32
+ };
33
+
34
+ const convertCountQueryParams = countQuery => {
35
+ return parseType({ type: 'boolean', value: countQuery });
36
+ };
37
+
38
+ /**
39
+ * Sort query parser
40
+ * @param {string} sortQuery - ex: id:asc,price:desc
41
+ */
42
+ const convertSortQueryParams = sortQuery => {
43
+ if (typeof sortQuery === 'string') {
44
+ return sortQuery.split(',').map(value => convertSingleSortQueryParam(value));
45
+ }
46
+
47
+ if (Array.isArray(sortQuery)) {
48
+ return sortQuery.flatMap(sortValue => convertSortQueryParams(sortValue));
49
+ }
50
+
51
+ if (_.isPlainObject(sortQuery)) {
52
+ return convertNestedSortQueryParam(sortQuery);
53
+ }
54
+
55
+ throw new InvalidSortError();
56
+ };
57
+
58
+ const convertSingleSortQueryParam = sortQuery => {
59
+ // split field and order param with default order to ascending
60
+ const [field, order = 'asc'] = sortQuery.split(':');
61
+
62
+ if (field.length === 0) {
63
+ throw new Error('Field cannot be empty');
64
+ }
65
+
66
+ validateOrder(order);
67
+
68
+ return _.set({}, field, order);
69
+ };
70
+
71
+ const convertNestedSortQueryParam = sortQuery => {
72
+ const transformedSort = {};
73
+ for (const field in sortQuery) {
74
+ const order = sortQuery[field];
75
+
76
+ // this is a deep sort
77
+ if (_.isPlainObject(order)) {
78
+ transformedSort[field] = convertNestedSortQueryParam(order);
79
+ } else {
80
+ validateOrder(order);
81
+ transformedSort[field] = order;
82
+ }
83
+ }
84
+
85
+ return transformedSort;
86
+ };
87
+
88
+ /**
89
+ * Start query parser
90
+ * @param {string} startQuery
91
+ */
92
+ const convertStartQueryParams = startQuery => {
93
+ const startAsANumber = _.toNumber(startQuery);
94
+
95
+ if (!_.isInteger(startAsANumber) || startAsANumber < 0) {
96
+ throw new Error(`convertStartQueryParams expected a positive integer got ${startAsANumber}`);
97
+ }
98
+
99
+ return startAsANumber;
100
+ };
101
+
102
+ /**
103
+ * Limit query parser
104
+ * @param {string} limitQuery
105
+ */
106
+ const convertLimitQueryParams = limitQuery => {
107
+ const limitAsANumber = _.toNumber(limitQuery);
108
+
109
+ if (!_.isInteger(limitAsANumber) || (limitAsANumber !== -1 && limitAsANumber < 0)) {
110
+ throw new Error(`convertLimitQueryParams expected a positive integer got ${limitAsANumber}`);
111
+ }
112
+
113
+ return limitAsANumber;
114
+ };
115
+
116
+ class InvalidPopulateError extends Error {
117
+ constructor() {
118
+ super();
119
+ this.message =
120
+ 'Invalid populate parameter. Expected a string, an array of strings, a populate object';
121
+ }
122
+ }
123
+
124
+ // NOTE: we could support foo.* or foo.bar.* etc later on
125
+ const convertPopulateQueryParams = (populate, depth = 0) => {
126
+ if (depth === 0 && populate === '*') {
127
+ return true;
128
+ }
129
+
130
+ if (typeof populate === 'string') {
131
+ return populate.split(',').map(value => _.trim(value));
132
+ }
133
+
134
+ if (Array.isArray(populate)) {
135
+ // map convert
136
+ return _.uniq(
137
+ populate.flatMap(value => {
138
+ if (typeof value !== 'string') {
139
+ throw new InvalidPopulateError();
140
+ }
141
+
142
+ return value.split(',').map(value => _.trim(value));
143
+ })
144
+ );
145
+ }
146
+
147
+ if (_.isPlainObject(populate)) {
148
+ const transformedPopulate = {};
149
+ for (const key in populate) {
150
+ transformedPopulate[key] = convertNestedPopulate(populate[key]);
151
+ }
152
+
153
+ return transformedPopulate;
154
+ }
155
+
156
+ throw new InvalidPopulateError();
157
+ };
158
+
159
+ const convertNestedPopulate = subPopulate => {
160
+ if (subPopulate === '*') {
161
+ return true;
162
+ }
163
+
164
+ if (_.isBoolean(subPopulate)) {
165
+ return subPopulate;
166
+ }
167
+
168
+ if (!_.isPlainObject(subPopulate)) {
169
+ throw new Error(`Invalid nested populate. Expected '*' or an object`);
170
+ }
171
+
172
+ // TODO: We will need to consider a way to add limitation / pagination
173
+ const { sort, filters, fields, populate, count } = subPopulate;
174
+
175
+ const query = {};
176
+
177
+ if (sort) {
178
+ query.orderBy = convertSortQueryParams(sort);
179
+ }
180
+
181
+ if (filters) {
182
+ query.where = convertFiltersQueryParams(filters);
183
+ }
184
+
185
+ if (fields) {
186
+ query.select = convertFieldsQueryParams(fields);
187
+ }
188
+
189
+ if (populate) {
190
+ query.populate = convertPopulateQueryParams(populate);
191
+ }
192
+
193
+ if (count) {
194
+ query.count = convertCountQueryParams(count);
195
+ }
196
+
197
+ return query;
198
+ };
199
+
200
+ const convertFieldsQueryParams = (fields, depth = 0) => {
201
+ if (depth === 0 && fields === '*') {
202
+ return undefined;
203
+ }
204
+
205
+ if (typeof fields === 'string') {
206
+ const fieldsValues = fields.split(',').map(value => _.trim(value));
207
+ return _.uniq(['id', ...fieldsValues]);
208
+ }
209
+
210
+ if (Array.isArray(fields)) {
211
+ // map convert
212
+ const fieldsValues = fields.flatMap(value => convertFieldsQueryParams(value, depth + 1));
213
+ return _.uniq(['id', ...fieldsValues]);
214
+ }
215
+
216
+ throw new Error('Invalid fields parameter. Expected a string or an array of strings');
217
+ };
218
+
219
+ const convertFiltersQueryParams = filters => filters;
220
+
221
+ const convertPublicationStateParams = (type, params = {}, query = {}) => {
222
+ if (!type) {
223
+ return;
224
+ }
225
+
226
+ const { publicationState } = params;
227
+
228
+ if (!_.isNil(publicationState)) {
229
+ if (!contentTypesUtils.constants.DP_PUB_STATES.includes(publicationState)) {
230
+ throw new Error(
231
+ `Invalid publicationState. Expected one of 'preview','live' received: ${publicationState}.`
232
+ );
233
+ }
234
+
235
+ // NOTE: this is the query layer filters not the entity service filters
236
+ query.filters = ({ meta }) => {
237
+ if (publicationState === 'live' && has(PUBLISHED_AT_ATTRIBUTE, meta.attributes)) {
238
+ return { [PUBLISHED_AT_ATTRIBUTE]: { $notNull: true } };
239
+ }
240
+ };
241
+ }
242
+ };
243
+
244
+ module.exports = {
245
+ convertSortQueryParams,
246
+ convertStartQueryParams,
247
+ convertLimitQueryParams,
248
+ convertPopulateQueryParams,
249
+ convertFiltersQueryParams,
250
+ convertFieldsQueryParams,
251
+ convertPublicationStateParams,
252
+ };
package/lib/errors.js ADDED
@@ -0,0 +1,82 @@
1
+ 'use strict';
2
+
3
+ const { HttpError } = require('http-errors');
4
+ const { formatYupErrors } = require('./format-yup-error');
5
+
6
+ /* ApplicationError */
7
+ class ApplicationError extends Error {
8
+ constructor(message, details = {}) {
9
+ super();
10
+ this.name = 'ApplicationError';
11
+ this.message = message || 'An application error occured';
12
+ this.details = details;
13
+ }
14
+ }
15
+
16
+ class ValidationError extends ApplicationError {
17
+ constructor(message, details) {
18
+ super(message, details);
19
+ this.name = 'ValidationError';
20
+ }
21
+ }
22
+
23
+ class YupValidationError extends ValidationError {
24
+ constructor(yupError, message) {
25
+ super();
26
+ const { errors, message: yupMessage } = formatYupErrors(yupError);
27
+ this.message = message || yupMessage;
28
+ this.details = { errors };
29
+ }
30
+ }
31
+
32
+ class PaginationError extends ApplicationError {
33
+ constructor(message, details) {
34
+ super(message, details);
35
+ this.name = 'PaginationError';
36
+ this.message = message || 'Invalid pagination';
37
+ }
38
+ }
39
+
40
+ class NotFoundError extends ApplicationError {
41
+ constructor(message, details) {
42
+ super(message, details);
43
+ this.name = 'NotFoundError';
44
+ this.message = message || 'Entity not found';
45
+ }
46
+ }
47
+
48
+ class ForbiddenError extends ApplicationError {
49
+ constructor(message, details) {
50
+ super(message, details);
51
+ this.name = 'ForbiddenError';
52
+ this.message = message || 'Forbidden access';
53
+ }
54
+ }
55
+
56
+ class PayloadTooLargeError extends ApplicationError {
57
+ constructor(message, details) {
58
+ super(message, details);
59
+ this.name = 'PayloadTooLargeError';
60
+ this.message = message || 'Entity too large';
61
+ }
62
+ }
63
+
64
+ class UnauthorizedError extends ApplicationError {
65
+ constructor(message, details) {
66
+ super(message, details);
67
+ this.name = 'UnauthorizedError';
68
+ this.message = message || 'Unauthorized';
69
+ }
70
+ }
71
+
72
+ module.exports = {
73
+ HttpError,
74
+ ApplicationError,
75
+ ValidationError,
76
+ YupValidationError,
77
+ PaginationError,
78
+ NotFoundError,
79
+ ForbiddenError,
80
+ PayloadTooLargeError,
81
+ UnauthorizedError,
82
+ };
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+
3
+ const { isEmpty, toPath } = require('lodash/fp');
4
+
5
+ const formatYupInnerError = yupError => ({
6
+ path: toPath(yupError.path),
7
+ message: yupError.message,
8
+ name: yupError.name,
9
+ });
10
+
11
+ const formatYupErrors = yupError => ({
12
+ errors: isEmpty(yupError.inner)
13
+ ? [formatYupInnerError(yupError)]
14
+ : yupError.inner.map(formatYupInnerError),
15
+ message: yupError.message,
16
+ });
17
+
18
+ module.exports = {
19
+ formatYupErrors,
20
+ };
package/lib/hooks.js CHANGED
@@ -24,12 +24,14 @@ const createHook = () => {
24
24
  return state.handlers;
25
25
  },
26
26
 
27
- register: handler => {
27
+ register(handler) {
28
28
  state.handlers.push(handler);
29
+ return this;
29
30
  },
30
31
 
31
- delete: handler => {
32
+ delete(handler) {
32
33
  state.handlers = remove(eq(handler), state.handlers);
34
+ return this;
33
35
  },
34
36
 
35
37
  call() {
@@ -80,7 +82,7 @@ const createAsyncSeriesWaterfallHook = () => ({
80
82
  const createAsyncParallelHook = () => ({
81
83
  ...createHook(),
82
84
 
83
- call(context) {
85
+ async call(context) {
84
86
  const promises = this.handlers.map(handler => handler(cloneDeep(context)));
85
87
 
86
88
  return Promise.all(promises);
package/lib/index.js CHANGED
@@ -4,18 +4,12 @@
4
4
  * Export shared utilities
5
5
  */
6
6
  const { buildQuery, hasDeepFilters } = require('./build-query');
7
- const {
8
- convertRestQueryParams,
9
- VALID_REST_OPERATORS,
10
- QUERY_OPERATORS,
11
- } = require('./convert-rest-query-params');
12
7
  const parseMultipartData = require('./parse-multipart');
13
- const sanitizeEntity = require('./sanitize-entity');
14
8
  const parseType = require('./parse-type');
15
- const finder = require('./finder');
16
9
  const policy = require('./policy');
17
10
  const templateConfiguration = require('./template-configuration');
18
- const { yup, formatYupErrors } = require('./validators');
11
+ const { yup, handleYupError, validateYupSchema, validateYupSchemaSync } = require('./validators');
12
+ const errors = require('./errors');
19
13
  const {
20
14
  nameToSlug,
21
15
  nameToCollectionName,
@@ -23,6 +17,8 @@ const {
23
17
  escapeQuery,
24
18
  stringIncludes,
25
19
  stringEquals,
20
+ isKebabCase,
21
+ isCamelCase,
26
22
  } = require('./string-formatting');
27
23
  const { removeUndefined } = require('./object-formatting');
28
24
  const { getConfigUrls, getAbsoluteAdminUrl, getAbsoluteServerUrl } = require('./config');
@@ -34,20 +30,21 @@ const relations = require('./relations');
34
30
  const setCreatorFields = require('./set-creator-fields');
35
31
  const hooks = require('./hooks');
36
32
  const providerFactory = require('./provider-factory');
33
+ const pagination = require('./pagination');
34
+ const sanitize = require('./sanitize');
35
+ const traverseEntity = require('./traverse-entity');
36
+ const pipeAsync = require('./pipe-async');
37
37
 
38
38
  module.exports = {
39
39
  yup,
40
- formatYupErrors,
41
- finder,
40
+ handleYupError,
42
41
  policy,
43
42
  templateConfiguration,
44
- convertRestQueryParams,
45
- VALID_REST_OPERATORS,
46
- QUERY_OPERATORS,
47
43
  buildQuery,
48
44
  hasDeepFilters,
49
45
  parseMultipartData,
50
- sanitizeEntity,
46
+ sanitize,
47
+ traverseEntity,
51
48
  parseType,
52
49
  nameToSlug,
53
50
  nameToCollectionName,
@@ -60,6 +57,8 @@ module.exports = {
60
57
  generateTimestampCode,
61
58
  stringIncludes,
62
59
  stringEquals,
60
+ isKebabCase,
61
+ isCamelCase,
63
62
  contentTypes,
64
63
  webhook,
65
64
  env,
@@ -67,4 +66,9 @@ module.exports = {
67
66
  setCreatorFields,
68
67
  hooks,
69
68
  providerFactory,
69
+ pagination,
70
+ pipeAsync,
71
+ errors,
72
+ validateYupSchema,
73
+ validateYupSchemaSync,
70
74
  };