@strapi/utils 0.0.0-fd8e4c6bfa → 0.0.0-ffd0a215143bcafd0354d791533a57a084e53ffb

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/lib/async.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ export type MapAsync<T = any, R = any> = lodash.CurriedFunction3<
2
+ T[],
3
+ (element: T, index: number) => R | Promise<R>,
4
+ { concurrency?: number },
5
+ Promise<R[]>
6
+ >;
7
+
8
+ export type ForEachAsync<T = any, R = any> = (
9
+ array: T[],
10
+ func: (element: T, index: number) => R | Promise<R>,
11
+ options?: { concurrency?: number }
12
+ ) => Promise<R[]>;
package/lib/async.js ADDED
@@ -0,0 +1,34 @@
1
+ 'use strict';
2
+
3
+ const pMap = require('p-map');
4
+ const { curry } = require('lodash/fp');
5
+
6
+ function pipeAsync(...methods) {
7
+ return async (data) => {
8
+ let res = data;
9
+
10
+ for (const method of methods) {
11
+ res = await method(res);
12
+ }
13
+
14
+ return res;
15
+ };
16
+ }
17
+
18
+ /**
19
+ * @type { import('./async').MapAsync }
20
+ */
21
+ const mapAsync = curry(pMap);
22
+
23
+ /**
24
+ * @type { import('./async').ForEachAsync }
25
+ */
26
+ const forEachAsync = curry(async (array, func, options) => {
27
+ await mapAsync(array, func, options);
28
+ });
29
+
30
+ module.exports = {
31
+ mapAsync,
32
+ forEachAsync,
33
+ pipeAsync,
34
+ };
@@ -21,7 +21,7 @@ const getAssociationFromFieldKey = ({ model, field }) => {
21
21
  let association;
22
22
  let attribute;
23
23
 
24
- for (let i = 0; i < fieldParts.length; i++) {
24
+ for (let i = 0; i < fieldParts.length; i += 1) {
25
25
  const part = fieldParts[i];
26
26
  attribute = part;
27
27
 
@@ -104,7 +104,26 @@ const isPrivateAttribute = (model = {}, attributeName) => {
104
104
  };
105
105
 
106
106
  const isScalarAttribute = (attribute) => {
107
- return !['media', 'component', 'relation', 'dynamiczone'].includes(attribute.type);
107
+ return !['media', 'component', 'relation', 'dynamiczone'].includes(attribute?.type);
108
+ };
109
+ const isMediaAttribute = (attribute) => attribute?.type === 'media';
110
+ const isRelationalAttribute = (attribute) => attribute?.type === 'relation';
111
+ const isComponentAttribute = (attribute) => ['component', 'dynamiczone'].includes(attribute?.type);
112
+
113
+ const isDynamicZoneAttribute = (attribute) => attribute?.type === 'dynamiczone';
114
+ const isMorphToRelationalAttribute = (attribute) => {
115
+ return isRelationalAttribute(attribute) && attribute?.relation?.startsWith?.('morphTo');
116
+ };
117
+
118
+ const getComponentAttributes = (schema) => {
119
+ return _.reduce(
120
+ schema.attributes,
121
+ (acc, attr, attrName) => {
122
+ if (isComponentAttribute(attr)) acc.push(attrName);
123
+ return acc;
124
+ },
125
+ []
126
+ );
108
127
  };
109
128
 
110
129
  const getScalarAttributes = (schema) => {
@@ -118,10 +137,6 @@ const getScalarAttributes = (schema) => {
118
137
  );
119
138
  };
120
139
 
121
- const isMediaAttribute = (attribute) => attribute.type === 'media';
122
- const isRelationalAttribute = (attribute) => attribute.type === 'relation';
123
- const isComponentAttribute = (attribute) => ['component', 'dynamiczone'].includes(attribute.type);
124
-
125
140
  /**
126
141
  * Checks if an attribute is of type `type`
127
142
  * @param {object} attribute
@@ -147,11 +162,14 @@ module.exports = {
147
162
  isMediaAttribute,
148
163
  isRelationalAttribute,
149
164
  isComponentAttribute,
165
+ isDynamicZoneAttribute,
166
+ isMorphToRelationalAttribute,
150
167
  isTypedAttribute,
151
168
  getPrivateAttributes,
152
169
  isPrivateAttribute,
153
170
  constants,
154
171
  getNonWritableAttributes,
172
+ getComponentAttributes,
155
173
  getScalarAttributes,
156
174
  getWritableAttributes,
157
175
  isWritableAttribute,
@@ -6,10 +6,28 @@
6
6
  * Converts the standard Strapi REST query params to a more usable format for querying
7
7
  * You can read more here: https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/rest-api.html#filters
8
8
  */
9
- const { has, isEmpty, isObject, isPlainObject, cloneDeep, get, mergeAll } = require('lodash/fp');
9
+
10
+ const {
11
+ isNil,
12
+ toNumber,
13
+ isInteger,
14
+ has,
15
+ isEmpty,
16
+ isObject,
17
+ isPlainObject,
18
+ cloneDeep,
19
+ get,
20
+ mergeAll,
21
+ } = require('lodash/fp');
10
22
  const _ = require('lodash');
11
23
  const parseType = require('./parse-type');
12
24
  const contentTypesUtils = require('./content-types');
25
+ const { PaginationError } = require('./errors');
26
+ const {
27
+ isMediaAttribute,
28
+ isDynamicZoneAttribute,
29
+ isMorphToRelationalAttribute,
30
+ } = require('./content-types');
13
31
 
14
32
  const { PUBLISHED_AT_ATTRIBUTE } = contentTypesUtils.constants;
15
33
 
@@ -37,6 +55,10 @@ const convertCountQueryParams = (countQuery) => {
37
55
  return parseType({ type: 'boolean', value: countQuery });
38
56
  };
39
57
 
58
+ const convertOrderingQueryParams = (ordering) => {
59
+ return ordering;
60
+ };
61
+
40
62
  /**
41
63
  * Sort query parser
42
64
  * @param {string} sortQuery - ex: id:asc,price:desc
@@ -117,6 +139,41 @@ const convertLimitQueryParams = (limitQuery) => {
117
139
  return limitAsANumber;
118
140
  };
119
141
 
142
+ const convertPageQueryParams = (page) => {
143
+ const pageVal = toNumber(page);
144
+
145
+ if (!isInteger(pageVal) || pageVal <= 0) {
146
+ throw new PaginationError(
147
+ `Invalid 'page' parameter. Expected an integer > 0, received: ${page}`
148
+ );
149
+ }
150
+
151
+ return pageVal;
152
+ };
153
+
154
+ const convertPageSizeQueryParams = (pageSize, page) => {
155
+ const pageSizeVal = toNumber(pageSize);
156
+
157
+ if (!isInteger(pageSizeVal) || pageSizeVal <= 0) {
158
+ throw new PaginationError(
159
+ `Invalid 'pageSize' parameter. Expected an integer > 0, received: ${page}`
160
+ );
161
+ }
162
+
163
+ return pageSizeVal;
164
+ };
165
+
166
+ const validatePaginationParams = (page, pageSize, start, limit) => {
167
+ const isPagePagination = !isNil(page) || !isNil(pageSize);
168
+ const isOffsetPagination = !isNil(start) || !isNil(limit);
169
+
170
+ if (isPagePagination && isOffsetPagination) {
171
+ throw new PaginationError(
172
+ 'Invalid pagination attributes. You cannot use page and offset pagination in the same query'
173
+ );
174
+ }
175
+ };
176
+
120
177
  class InvalidPopulateError extends Error {
121
178
  constructor() {
122
179
  super();
@@ -169,12 +226,41 @@ const convertPopulateObject = (populate, schema) => {
169
226
  return acc;
170
227
  }
171
228
 
172
- // FIXME: This is a temporary solution for dynamic zones that should be
173
- // fixed when we'll implement a more accurate way to query them
229
+ // Allow adding an 'on' strategy to populate queries for polymorphic relations, media and dynamic zones
230
+ const isAllowedAttributeForFragmentPopulate =
231
+ isDynamicZoneAttribute(attribute) ||
232
+ isMediaAttribute(attribute) ||
233
+ isMorphToRelationalAttribute(attribute);
234
+
235
+ const hasFragmentPopulateDefined = typeof subPopulate === 'object' && 'on' in subPopulate;
236
+
237
+ if (isAllowedAttributeForFragmentPopulate && hasFragmentPopulateDefined) {
238
+ return {
239
+ ...acc,
240
+ [key]: {
241
+ on: Object.entries(subPopulate.on).reduce(
242
+ (acc, [type, typeSubPopulate]) => ({
243
+ ...acc,
244
+ [type]: convertNestedPopulate(typeSubPopulate, strapi.getModel(type)),
245
+ }),
246
+ {}
247
+ ),
248
+ },
249
+ };
250
+ }
251
+
252
+ // TODO: This is a query's populate fallback for DynamicZone and is kept for legacy purpose.
253
+ // Removing it could break existing user queries but it should be removed in V5.
174
254
  if (attribute.type === 'dynamiczone') {
175
255
  const populates = attribute.components
176
256
  .map((uid) => strapi.getModel(uid))
177
- .map((schema) => convertNestedPopulate(subPopulate, schema));
257
+ .map((schema) => convertNestedPopulate(subPopulate, schema))
258
+ .map((populate) => (populate === true ? {} : populate)) // cast boolean to empty object to avoid merging issues
259
+ .filter((populate) => populate !== false);
260
+
261
+ if (isEmpty(populates)) {
262
+ return acc;
263
+ }
178
264
 
179
265
  return {
180
266
  ...acc,
@@ -203,16 +289,22 @@ const convertPopulateObject = (populate, schema) => {
203
289
  return acc;
204
290
  }
205
291
 
292
+ const populateObject = convertNestedPopulate(subPopulate, targetSchema);
293
+
294
+ if (!populateObject) {
295
+ return acc;
296
+ }
297
+
206
298
  return {
207
299
  ...acc,
208
- [key]: convertNestedPopulate(subPopulate, targetSchema),
300
+ [key]: populateObject,
209
301
  };
210
302
  }, {});
211
303
  };
212
304
 
213
305
  const convertNestedPopulate = (subPopulate, schema) => {
214
- if (subPopulate === '*') {
215
- return true;
306
+ if (_.isString(subPopulate)) {
307
+ return parseType({ type: 'boolean', value: subPopulate, forceCast: true });
216
308
  }
217
309
 
218
310
  if (_.isBoolean(subPopulate)) {
@@ -223,8 +315,8 @@ const convertNestedPopulate = (subPopulate, schema) => {
223
315
  throw new Error(`Invalid nested populate. Expected '*' or an object`);
224
316
  }
225
317
 
226
- // TODO: We will need to consider a way to add limitation / pagination
227
- const { sort, filters, fields, populate, count } = subPopulate;
318
+ const { sort, filters, fields, populate, count, ordering, page, pageSize, start, limit } =
319
+ subPopulate;
228
320
 
229
321
  const query = {};
230
322
 
@@ -248,6 +340,30 @@ const convertNestedPopulate = (subPopulate, schema) => {
248
340
  query.count = convertCountQueryParams(count);
249
341
  }
250
342
 
343
+ if (ordering) {
344
+ query.ordering = convertOrderingQueryParams(ordering);
345
+ }
346
+
347
+ validatePaginationParams(page, pageSize, start, limit);
348
+
349
+ if (!isNil(page)) {
350
+ query.page = convertPageQueryParams(page);
351
+ }
352
+
353
+ if (!isNil(pageSize)) {
354
+ query.pageSize = convertPageSizeQueryParams(pageSize, page);
355
+ }
356
+
357
+ if (!isNil(start)) {
358
+ query.offset = convertStartQueryParams(start);
359
+ }
360
+
361
+ if (!isNil(limit)) {
362
+ query.limit = convertLimitQueryParams(limit);
363
+ }
364
+
365
+ convertPublicationStateParams(schema, subPopulate, query);
366
+
251
367
  return query;
252
368
  };
253
369
 
@@ -377,6 +493,57 @@ const convertPublicationStateParams = (type, params = {}, query = {}) => {
377
493
  }
378
494
  };
379
495
 
496
+ const transformParamsToQuery = (uid, params) => {
497
+ // NOTE: can be a CT, a Compo or nothing in the case of polymorphism (DZ & morph relations)
498
+ const schema = strapi.getModel(uid);
499
+
500
+ const query = {};
501
+
502
+ const { _q, sort, filters, fields, populate, page, pageSize, start, limit } = params;
503
+
504
+ if (!isNil(_q)) {
505
+ query._q = _q;
506
+ }
507
+
508
+ if (!isNil(sort)) {
509
+ query.orderBy = convertSortQueryParams(sort);
510
+ }
511
+
512
+ if (!isNil(filters)) {
513
+ query.where = convertFiltersQueryParams(filters, schema);
514
+ }
515
+
516
+ if (!isNil(fields)) {
517
+ query.select = convertFieldsQueryParams(fields);
518
+ }
519
+
520
+ if (!isNil(populate)) {
521
+ query.populate = convertPopulateQueryParams(populate, schema);
522
+ }
523
+
524
+ validatePaginationParams(page, pageSize, start, limit);
525
+
526
+ if (!isNil(page)) {
527
+ query.page = convertPageQueryParams(page);
528
+ }
529
+
530
+ if (!isNil(pageSize)) {
531
+ query.pageSize = convertPageSizeQueryParams(pageSize, page);
532
+ }
533
+
534
+ if (!isNil(start)) {
535
+ query.offset = convertStartQueryParams(start);
536
+ }
537
+
538
+ if (!isNil(limit)) {
539
+ query.limit = convertLimitQueryParams(limit);
540
+ }
541
+
542
+ convertPublicationStateParams(schema, params, query);
543
+
544
+ return query;
545
+ };
546
+
380
547
  module.exports = {
381
548
  convertSortQueryParams,
382
549
  convertStartQueryParams,
@@ -385,4 +552,5 @@ module.exports = {
385
552
  convertFiltersQueryParams,
386
553
  convertFieldsQueryParams,
387
554
  convertPublicationStateParams,
555
+ transformParamsToQuery,
388
556
  };
package/lib/errors.js CHANGED
@@ -55,19 +55,28 @@ class ForbiddenError extends ApplicationError {
55
55
  }
56
56
  }
57
57
 
58
- class PayloadTooLargeError extends ApplicationError {
58
+ class UnauthorizedError extends ApplicationError {
59
59
  constructor(message, details) {
60
60
  super(message, details);
61
- this.name = 'PayloadTooLargeError';
62
- this.message = message || 'Entity too large';
61
+ this.name = 'UnauthorizedError';
62
+ this.message = message || 'Unauthorized';
63
63
  }
64
64
  }
65
65
 
66
- class UnauthorizedError extends ApplicationError {
66
+ class RateLimitError extends ApplicationError {
67
67
  constructor(message, details) {
68
68
  super(message, details);
69
- this.name = 'UnauthorizedError';
70
- this.message = message || 'Unauthorized';
69
+ this.name = 'RateLimitError';
70
+ this.message = message || 'Too many requests, please try again later.';
71
+ this.details = details || {};
72
+ }
73
+ }
74
+
75
+ class PayloadTooLargeError extends ApplicationError {
76
+ constructor(message, details) {
77
+ super(message, details);
78
+ this.name = 'PayloadTooLargeError';
79
+ this.message = message || 'Entity too large';
71
80
  }
72
81
  }
73
82
 
@@ -88,7 +97,8 @@ module.exports = {
88
97
  PaginationError,
89
98
  NotFoundError,
90
99
  ForbiddenError,
91
- PayloadTooLargeError,
92
100
  UnauthorizedError,
101
+ RateLimitError,
102
+ PayloadTooLargeError,
93
103
  PolicyError,
94
104
  };
package/lib/file.js ADDED
@@ -0,0 +1,60 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Utils file containing file treatment utils
5
+ */
6
+ const { Writable } = require('stream');
7
+
8
+ const kbytesToBytes = (kbytes) => kbytes * 1000;
9
+ const bytesToKbytes = (bytes) => Math.round((bytes / 1000) * 100) / 100;
10
+ const bytesToHumanReadable = (bytes) => {
11
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
12
+ if (bytes === 0) return '0 Bytes';
13
+ const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1000)), 10);
14
+ return `${Math.round(bytes / 1000 ** i, 2)} ${sizes[i]}`;
15
+ };
16
+
17
+ const streamToBuffer = (stream) =>
18
+ new Promise((resolve, reject) => {
19
+ const chunks = [];
20
+ stream.on('data', (chunk) => {
21
+ chunks.push(chunk);
22
+ });
23
+ stream.on('end', () => {
24
+ resolve(Buffer.concat(chunks));
25
+ });
26
+ stream.on('error', reject);
27
+ });
28
+
29
+ const getStreamSize = (stream) =>
30
+ new Promise((resolve, reject) => {
31
+ let size = 0;
32
+ stream.on('data', (chunk) => {
33
+ size += Buffer.byteLength(chunk);
34
+ });
35
+ stream.on('close', () => resolve(size));
36
+ stream.on('error', reject);
37
+ stream.resume();
38
+ });
39
+
40
+ /**
41
+ * Create a writeable Node.js stream that discards received data.
42
+ * Useful for testing, draining a stream of data, etc.
43
+ */
44
+ function writableDiscardStream(options) {
45
+ return new Writable({
46
+ ...options,
47
+ write(chunk, encding, callback) {
48
+ setImmediate(callback);
49
+ },
50
+ });
51
+ }
52
+
53
+ module.exports = {
54
+ streamToBuffer,
55
+ bytesToHumanReadable,
56
+ bytesToKbytes,
57
+ kbytesToBytes,
58
+ getStreamSize,
59
+ writableDiscardStream,
60
+ };
package/lib/hooks.js CHANGED
@@ -2,17 +2,8 @@
2
2
 
3
3
  const { eq, remove, cloneDeep } = require('lodash/fp');
4
4
 
5
- /**
6
- * @typedef Hook
7
- * @property {Array<Function>} _handlers - A registry of handler used by the hook
8
- * @property {function(Function):Hook} register - Register a new handler into the hook's registry
9
- * @property {function(Function):Hook} delete- Delete the given handler from the hook's registry
10
- * @property {Function} call - Not implemented by default, can be replaced by any implementation.
11
- */
12
-
13
5
  /**
14
6
  * Create a default Strapi hook
15
- * @return {Hook}
16
7
  */
17
8
  const createHook = () => {
18
9
  const state = {
@@ -45,7 +36,6 @@ const createHook = () => {
45
36
  /**
46
37
  * Create an async series hook.
47
38
  * Upon execution, it will execute every handler in order with the same context
48
- * @return {Hook}
49
39
  */
50
40
  const createAsyncSeriesHook = () => ({
51
41
  ...createHook(),
@@ -60,7 +50,6 @@ const createAsyncSeriesHook = () => ({
60
50
  /**
61
51
  * Create an async series waterfall hook.
62
52
  * Upon execution, it will execute every handler in order and pass the return value of the last handler to the next one
63
- * @return {Hook}
64
53
  */
65
54
  const createAsyncSeriesWaterfallHook = () => ({
66
55
  ...createHook(),
@@ -79,7 +68,6 @@ const createAsyncSeriesWaterfallHook = () => ({
79
68
  /**
80
69
  * Create an async parallel hook.
81
70
  * Upon execution, it will execute every registered handler in band.
82
- * @return {Hook}
83
71
  */
84
72
  const createAsyncParallelHook = () => ({
85
73
  ...createHook(),
@@ -91,6 +79,24 @@ const createAsyncParallelHook = () => ({
91
79
  },
92
80
  });
93
81
 
82
+ /**
83
+ * Create an async parallel hook.
84
+ * Upon execution, it will execute every registered handler in serie and return the first result found.
85
+ */
86
+ const createAsyncBailHook = () => ({
87
+ ...createHook(),
88
+
89
+ async call(context) {
90
+ for (const handler of this.getHandlers()) {
91
+ const result = await handler(context);
92
+
93
+ if (result !== undefined) {
94
+ return result;
95
+ }
96
+ }
97
+ },
98
+ });
99
+
94
100
  module.exports = {
95
101
  // Internal utils
96
102
  internals: {
@@ -100,4 +106,5 @@ module.exports = {
100
106
  createAsyncSeriesHook,
101
107
  createAsyncSeriesWaterfallHook,
102
108
  createAsyncParallelHook,
109
+ createAsyncBailHook,
103
110
  };
@@ -0,0 +1,10 @@
1
+ 'use strict';
2
+
3
+ const importDefault =
4
+ (this && this.importDefault) ||
5
+ function (modName) {
6
+ const mod = require(modName);
7
+ return mod && mod.__esModule ? mod.default : mod;
8
+ };
9
+
10
+ module.exports = importDefault;
package/lib/index.js CHANGED
@@ -24,7 +24,7 @@ const {
24
24
  joinBy,
25
25
  toKebabCase,
26
26
  } = require('./string-formatting');
27
- const { removeUndefined } = require('./object-formatting');
27
+ const { removeUndefined, keysDeep } = require('./object-formatting');
28
28
  const { getConfigUrls, getAbsoluteAdminUrl, getAbsoluteServerUrl } = require('./config');
29
29
  const { generateTimestampCode } = require('./code-generator');
30
30
  const contentTypes = require('./content-types');
@@ -37,7 +37,11 @@ const providerFactory = require('./provider-factory');
37
37
  const pagination = require('./pagination');
38
38
  const sanitize = require('./sanitize');
39
39
  const traverseEntity = require('./traverse-entity');
40
- const pipeAsync = require('./pipe-async');
40
+ const { pipeAsync, mapAsync, forEachAsync } = require('./async');
41
+ const convertQueryParams = require('./convert-query-params');
42
+ const importDefault = require('./import-default');
43
+ const template = require('./template');
44
+ const file = require('./file');
41
45
 
42
46
  module.exports = {
43
47
  yup,
@@ -59,11 +63,13 @@ module.exports = {
59
63
  getConfigUrls,
60
64
  escapeQuery,
61
65
  removeUndefined,
66
+ keysDeep,
62
67
  getAbsoluteAdminUrl,
63
68
  getAbsoluteServerUrl,
64
69
  generateTimestampCode,
65
70
  stringIncludes,
66
71
  stringEquals,
72
+ template,
67
73
  isKebabCase,
68
74
  isCamelCase,
69
75
  toKebabCase,
@@ -76,7 +82,12 @@ module.exports = {
76
82
  providerFactory,
77
83
  pagination,
78
84
  pipeAsync,
85
+ mapAsync,
86
+ forEachAsync,
79
87
  errors,
80
88
  validateYupSchema,
81
89
  validateYupSchemaSync,
90
+ convertQueryParams,
91
+ importDefault,
92
+ file,
82
93
  };
@@ -4,6 +4,12 @@ const _ = require('lodash');
4
4
 
5
5
  const removeUndefined = (obj) => _.pickBy(obj, (value) => typeof value !== 'undefined');
6
6
 
7
+ const keysDeep = (obj, path = []) =>
8
+ !_.isObject(obj)
9
+ ? path.join('.')
10
+ : _.reduce(obj, (acc, next, key) => _.concat(acc, keysDeep(next, [...path, key])), []);
11
+
7
12
  module.exports = {
8
13
  removeUndefined,
14
+ keysDeep,
9
15
  };
@@ -11,6 +11,7 @@ const symbolToString = typeof Symbol !== 'undefined' ? Symbol.prototype.toString
11
11
  const SYMBOL_REGEXP = /^Symbol\((.*)\)(.*)$/;
12
12
 
13
13
  function printNumber(val) {
14
+ // eslint-disable-next-line eqeqeq
14
15
  if (val != +val) return 'NaN';
15
16
  const isNegativeZero = val === 0 && 1 / val < 0;
16
17
  return isNegativeZero ? '-0' : `${val}`;
@@ -25,25 +25,9 @@ const createProviderHooksMap = () => ({
25
25
  });
26
26
 
27
27
  /**
28
- * A customizable item provider enhanced with register/delete hooks
29
- * @typedef {Object} Provider
30
- * @property hooks
31
- * @property register
32
- * @property delete
33
- * @property get
34
- * @property getWhere
35
- * @property values
36
- * @property keys
37
- * @property has
38
- * @property size
39
- * @property clear
40
- */
41
-
42
- /**
43
- * A {@link Provider} factory
28
+ * A Provider factory
44
29
  * @param {Object} [options] - The factory options
45
30
  * @param {boolean = true} options.throwOnDuplicates - Specify the wanted behaviour when encountering a duplicate key on register
46
- * @return {Provider}
47
31
  */
48
32
  const providerFactory = (options = {}) => {
49
33
  const { throwOnDuplicates = true } = options;
package/lib/relations.js CHANGED
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const { isRelationalAttribute } = require('./content-types');
4
+
3
5
  const MANY_RELATIONS = ['oneToMany', 'manyToMany'];
4
6
 
5
7
  const getRelationalFields = (contentType) => {
@@ -8,8 +10,21 @@ const getRelationalFields = (contentType) => {
8
10
  });
9
11
  };
10
12
 
13
+ const isOneToAny = (attribute) =>
14
+ isRelationalAttribute(attribute) && ['oneToOne', 'oneToMany'].includes(attribute.relation);
15
+ const isManyToAny = (attribute) =>
16
+ isRelationalAttribute(attribute) && ['manyToMany', 'manyToOne'].includes(attribute.relation);
17
+ const isAnyToOne = (attribute) =>
18
+ isRelationalAttribute(attribute) && ['oneToOne', 'manyToOne'].includes(attribute.relation);
19
+ const isAnyToMany = (attribute) =>
20
+ isRelationalAttribute(attribute) && ['oneToMany', 'manyToMany'].includes(attribute.relation);
21
+
11
22
  module.exports = {
12
23
  getRelationalFields,
24
+ isOneToAny,
25
+ isManyToAny,
26
+ isAnyToOne,
27
+ isAnyToMany,
13
28
  constants: {
14
29
  MANY_RELATIONS,
15
30
  },
@@ -4,7 +4,7 @@ const { isArray } = require('lodash/fp');
4
4
 
5
5
  const traverseEntity = require('../traverse-entity');
6
6
  const { getNonWritableAttributes } = require('../content-types');
7
- const pipeAsync = require('../pipe-async');
7
+ const { pipeAsync } = require('../async');
8
8
 
9
9
  const visitors = require('./visitors');
10
10
  const sanitizers = require('./sanitizers');
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { curry } = require('lodash/fp');
4
4
 
5
- const pipeAsync = require('../pipe-async');
5
+ const { pipeAsync } = require('../async');
6
6
  const traverseEntity = require('../traverse-entity');
7
7
 
8
8
  const { removePassword, removePrivate } = require('./visitors');
@@ -0,0 +1,28 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Create a strict interpolation RegExp based on the given variables' name
5
+ *
6
+ * @param {string[]} allowedVariableNames - The list of allowed variables
7
+ * @param {string} [flags] - The RegExp flags
8
+ */
9
+ const createStrictInterpolationRegExp = (allowedVariableNames, flags) => {
10
+ const oneOfVariables = allowedVariableNames.join('|');
11
+
12
+ // 1. We need to match the delimiters: <%= ... %>
13
+ // 2. We accept any number of whitespaces characters before and/or after the variable name: \s* ... \s*
14
+ // 3. We only accept values from the variable list as interpolation variables' name: : (${oneOfVariables})
15
+ return new RegExp(`<%=\\s*(${oneOfVariables})\\s*%>`, flags);
16
+ };
17
+
18
+ /**
19
+ * Create a loose interpolation RegExp to match as many groups as possible
20
+ *
21
+ * @param {string} [flags] - The RegExp flags
22
+ */
23
+ const createLooseInterpolationRegExp = (flags) => new RegExp(/<%=([\s\S]+?)%>/, flags);
24
+
25
+ module.exports = {
26
+ createStrictInterpolationRegExp,
27
+ createLooseInterpolationRegExp,
28
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strapi/utils",
3
- "version": "0.0.0-fd8e4c6bfa",
3
+ "version": "0.0.0-ffd0a215143bcafd0354d791533a57a084e53ffb",
4
4
  "description": "Shared utilities for the Strapi packages",
5
5
  "keywords": [
6
6
  "strapi",
@@ -36,14 +36,15 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "@sindresorhus/slugify": "1.1.0",
39
- "date-fns": "2.29.2",
39
+ "date-fns": "2.29.3",
40
40
  "http-errors": "1.8.1",
41
41
  "lodash": "4.17.21",
42
+ "p-map": "4.0.0",
42
43
  "yup": "0.32.9"
43
44
  },
44
45
  "engines": {
45
- "node": ">=14.19.1 <=16.x.x",
46
+ "node": ">=14.19.1 <=18.x.x",
46
47
  "npm": ">=6.0.0"
47
48
  },
48
- "gitHead": "fd8e4c6bfa2fd687a3c62f8428e00f3b320c32c2"
49
+ "gitHead": "ffd0a215143bcafd0354d791533a57a084e53ffb"
49
50
  }
package/lib/pipe-async.js DELETED
@@ -1,13 +0,0 @@
1
- 'use strict';
2
-
3
- module.exports =
4
- (...methods) =>
5
- async (data) => {
6
- let res = data;
7
-
8
- for (const method of methods) {
9
- res = await method(res);
10
- }
11
-
12
- return res;
13
- };