@strapi/utils 4.9.0-exp.90df253ba90fd6879eb56a720a1f80d04ff745b8 → 4.9.1

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/.eslintignore ADDED
@@ -0,0 +1,2 @@
1
+ node_modules/
2
+ .eslintrc.js
package/.eslintrc.js ADDED
@@ -0,0 +1,4 @@
1
+ module.exports = {
2
+ root: true,
3
+ extends: ['custom/back'],
4
+ };
package/lib/async.js CHANGED
@@ -6,9 +6,8 @@ const { curry, curryN } = require('lodash/fp');
6
6
  function pipeAsync(...methods) {
7
7
  return async (data) => {
8
8
  let res = data;
9
-
10
- for (const method of methods) {
11
- res = await method(res);
9
+ for (let i = 0; i < methods.length; i += 1) {
10
+ res = await methods[i](res);
12
11
  }
13
12
 
14
13
  return res;
package/lib/errors.js CHANGED
@@ -89,6 +89,14 @@ class PolicyError extends ForbiddenError {
89
89
  }
90
90
  }
91
91
 
92
+ class NotImplementedError extends ApplicationError {
93
+ constructor(message, details) {
94
+ super(message, details);
95
+ this.name = 'NotImplementedError';
96
+ this.message = message || 'This feature is not implemented yet';
97
+ }
98
+ }
99
+
92
100
  module.exports = {
93
101
  HttpError,
94
102
  ApplicationError,
@@ -101,4 +109,5 @@ module.exports = {
101
109
  RateLimitError,
102
110
  PayloadTooLargeError,
103
111
  PolicyError,
112
+ NotImplementedError,
104
113
  };
@@ -37,12 +37,16 @@ const createContentAPISanitizers = () => {
37
37
  return pipeAsync(...transforms)(data);
38
38
  };
39
39
 
40
- const sanitizeOuput = (data, schema, { auth } = {}) => {
40
+ const sanitizeOutput = async (data, schema, { auth } = {}) => {
41
41
  if (isArray(data)) {
42
- return Promise.all(data.map((entry) => sanitizeOuput(entry, schema, { auth })));
42
+ const res = new Array(data.length);
43
+ for (let i = 0; i < data.length; i += 1) {
44
+ res[i] = await sanitizeOutput(data[i], schema, { auth });
45
+ }
46
+ return res;
43
47
  }
44
48
 
45
- const transforms = [sanitizers.defaultSanitizeOutput(schema)];
49
+ const transforms = [(data) => sanitizers.defaultSanitizeOutput(schema, data)];
46
50
 
47
51
  if (auth) {
48
52
  transforms.push(traverseEntity(visitors.removeRestrictedRelations(auth), { schema }));
@@ -122,7 +126,7 @@ const createContentAPISanitizers = () => {
122
126
 
123
127
  return {
124
128
  input: sanitizeInput,
125
- output: sanitizeOuput,
129
+ output: sanitizeOutput,
126
130
  query: sanitizeQuery,
127
131
  filters: sanitizeFilters,
128
132
  sort: sanitizeSort,
@@ -20,17 +20,20 @@ const {
20
20
  removeMorphToRelations,
21
21
  } = require('./visitors');
22
22
 
23
- const sanitizePasswords = curry((schema, entity) => {
23
+ const sanitizePasswords = (schema) => async (entity) => {
24
24
  return traverseEntity(removePassword, { schema }, entity);
25
- });
26
-
27
- const sanitizePrivates = curry((schema, entity) => {
28
- return traverseEntity(removePrivate, { schema }, entity);
29
- });
25
+ };
30
26
 
31
- const defaultSanitizeOutput = curry((schema, entity) => {
32
- return pipeAsync(sanitizePrivates(schema), sanitizePasswords(schema))(entity);
33
- });
27
+ const defaultSanitizeOutput = async (schema, entity) => {
28
+ return traverseEntity(
29
+ (...args) => {
30
+ removePassword(...args);
31
+ removePrivate(...args);
32
+ },
33
+ { schema },
34
+ entity
35
+ );
36
+ };
34
37
 
35
38
  const defaultSanitizeFilters = curry((schema, filters) => {
36
39
  return pipeAsync(
@@ -59,6 +62,12 @@ const defaultSanitizeSort = curry((schema, sort) => {
59
62
  // Remove non attribute keys
60
63
  traverseQuerySort(
61
64
  ({ key, attribute }, { remove }) => {
65
+ // ID is not an attribute per se, so we need to make
66
+ // an extra check to ensure we're not removing it
67
+ if (key === 'id') {
68
+ return;
69
+ }
70
+
62
71
  if (!attribute) {
63
72
  remove(key);
64
73
  }
@@ -134,7 +143,6 @@ const defaultSanitizePopulate = curry((schema, populate) => {
134
143
 
135
144
  module.exports = {
136
145
  sanitizePasswords,
137
- sanitizePrivates,
138
146
  defaultSanitizeOutput,
139
147
  defaultSanitizeFilters,
140
148
  defaultSanitizeSort,
@@ -39,7 +39,8 @@ module.exports = () => {
39
39
  for (const key of keys) {
40
40
  const attribute =
41
41
  schema?.attributes?.[key] ??
42
- // look for the attribute when key is in snake_case
42
+ // FIX: Needed to not break existing behavior on the API.
43
+ // It looks for the attribute in the DB metadata when the key is in snake_case
43
44
  schema?.attributes?.[strapi.db.metadata.get(schema?.uid).columnToAttribute[key]];
44
45
 
45
46
  const newPath = { ...path };
@@ -160,6 +160,8 @@ const populate = traverseFactory()
160
160
  const { components } = attribute;
161
161
  const { on, ...properties } = value;
162
162
 
163
+ const newValue = {};
164
+
163
165
  // Handle legacy DZ params
164
166
  let newProperties = properties;
165
167
 
@@ -168,11 +170,15 @@ const populate = traverseFactory()
168
170
  newProperties = await recurse(visitor, { schema: componentSchema, path }, newProperties);
169
171
  }
170
172
 
173
+ Object.assign(newValue, newProperties);
174
+
171
175
  // Handle new morph fragment syntax
172
- const newOn = await recurse(visitor, { schema, path }, { on });
176
+ if (on) {
177
+ const newOn = await recurse(visitor, { schema, path }, { on });
173
178
 
174
- // Recompose both syntaxes
175
- const newValue = { ...newOn, ...newProperties };
179
+ // Recompose both syntaxes
180
+ Object.assign(newValue, newOn);
181
+ }
176
182
 
177
183
  set(key, newValue);
178
184
  } else {
@@ -1,6 +1,42 @@
1
1
  'use strict';
2
2
 
3
- const { cloneDeep, isObject, isArray, isNil, curry } = require('lodash/fp');
3
+ const { clone, isObject, isArray, isNil, curry } = require('lodash/fp');
4
+
5
+ const traverseMorphRelationTarget = async (visitor, path, entry) => {
6
+ const targetSchema = strapi.getModel(entry.__type);
7
+
8
+ const traverseOptions = { schema: targetSchema, path };
9
+
10
+ return traverseEntity(visitor, traverseOptions, entry);
11
+ };
12
+
13
+ const traverseRelationTarget = (schema) => async (visitor, path, entry) => {
14
+ const traverseOptions = { schema, path };
15
+
16
+ return traverseEntity(visitor, traverseOptions, entry);
17
+ };
18
+
19
+ const traverseMediaTarget = async (visitor, path, entry) => {
20
+ const targetSchemaUID = 'plugin::upload.file';
21
+ const targetSchema = strapi.getModel(targetSchemaUID);
22
+
23
+ const traverseOptions = { schema: targetSchema, path };
24
+
25
+ return traverseEntity(visitor, traverseOptions, entry);
26
+ };
27
+
28
+ const traverseComponent = async (visitor, path, schema, entry) => {
29
+ const traverseOptions = { schema, path };
30
+
31
+ return traverseEntity(visitor, traverseOptions, entry);
32
+ };
33
+
34
+ const visitDynamicZoneEntry = async (visitor, path, entry) => {
35
+ const targetSchema = strapi.getModel(entry.__component);
36
+ const traverseOptions = { schema: targetSchema, path };
37
+
38
+ return traverseEntity(visitor, traverseOptions, entry);
39
+ };
4
40
 
5
41
  const traverseEntity = async (visitor, options, entity) => {
6
42
  const { path = { raw: null, attribute: null }, schema } = options;
@@ -11,9 +47,13 @@ const traverseEntity = async (visitor, options, entity) => {
11
47
  }
12
48
 
13
49
  // Don't mutate the original entity object
14
- const copy = cloneDeep(entity);
50
+ // only clone at 1st level as the next level will get clone when traversed
51
+ const copy = clone(entity);
52
+ const visitorUtils = createVisitorUtils({ data: copy });
15
53
 
16
- for (const key of Object.keys(copy)) {
54
+ const keys = Object.keys(copy);
55
+ for (let i = 0; i < keys.length; i += 1) {
56
+ const key = keys[i];
17
57
  // Retrieve the attribute definition associated to the key from the schema
18
58
  const attribute = schema.attributes[key];
19
59
 
@@ -32,7 +72,6 @@ const traverseEntity = async (visitor, options, entity) => {
32
72
 
33
73
  // Visit the current attribute
34
74
  const visitorOptions = { data: copy, schema, key, value: copy[key], attribute, path: newPath };
35
- const visitorUtils = createVisitorUtils({ data: copy });
36
75
 
37
76
  await visitor(visitorOptions, visitorUtils);
38
77
 
@@ -52,58 +91,62 @@ const traverseEntity = async (visitor, options, entity) => {
52
91
  if (isRelation) {
53
92
  const isMorphRelation = attribute.relation.toLowerCase().startsWith('morph');
54
93
 
55
- const traverseTarget = (entry) => {
56
- // Handle polymorphic relationships
57
- const targetSchemaUID = isMorphRelation ? entry.__type : attribute.target;
58
- const targetSchema = strapi.getModel(targetSchemaUID);
59
-
60
- const traverseOptions = { schema: targetSchema, path: newPath };
94
+ const method = isMorphRelation
95
+ ? traverseMorphRelationTarget
96
+ : traverseRelationTarget(strapi.getModel(attribute.target));
61
97
 
62
- return traverseEntity(visitor, traverseOptions, entry);
63
- };
98
+ if (isArray(value)) {
99
+ const res = new Array(value.length);
100
+ for (let i = 0; i < value.length; i += 1) {
101
+ res[i] = await method(visitor, newPath, value[i]);
102
+ }
103
+ copy[key] = res;
104
+ } else {
105
+ copy[key] = await method(visitor, newPath, value);
106
+ }
64
107
 
65
- // need to update copy
66
- copy[key] = isArray(value)
67
- ? await Promise.all(value.map(traverseTarget))
68
- : await traverseTarget(value);
108
+ continue;
69
109
  }
70
110
 
71
111
  if (isMedia) {
72
- const traverseTarget = (entry) => {
73
- const targetSchemaUID = 'plugin::upload.file';
74
- const targetSchema = strapi.getModel(targetSchemaUID);
75
-
76
- const traverseOptions = { schema: targetSchema, path: newPath };
77
-
78
- return traverseEntity(visitor, traverseOptions, entry);
79
- };
80
-
81
112
  // need to update copy
82
- copy[key] = isArray(value)
83
- ? await Promise.all(value.map(traverseTarget))
84
- : await traverseTarget(value);
113
+ if (isArray(value)) {
114
+ const res = new Array(value.length);
115
+ for (let i = 0; i < value.length; i += 1) {
116
+ res[i] = await traverseMediaTarget(visitor, newPath, value[i]);
117
+ }
118
+ copy[key] = res;
119
+ } else {
120
+ copy[key] = await traverseMediaTarget(visitor, newPath, value);
121
+ }
122
+
123
+ continue;
85
124
  }
86
125
 
87
126
  if (isComponent) {
88
127
  const targetSchema = strapi.getModel(attribute.component);
89
- const traverseOptions = { schema: targetSchema, path: newPath };
90
128
 
91
- const traverseComponent = (entry) => traverseEntity(visitor, traverseOptions, entry);
129
+ if (isArray(value)) {
130
+ const res = new Array(value.length);
131
+ for (let i = 0; i < value.length; i += 1) {
132
+ res[i] = await traverseComponent(visitor, newPath, targetSchema, value[i]);
133
+ }
134
+ copy[key] = res;
135
+ } else {
136
+ copy[key] = await traverseComponent(visitor, newPath, targetSchema, value);
137
+ }
92
138
 
93
- copy[key] = isArray(value)
94
- ? await Promise.all(value.map(traverseComponent))
95
- : await traverseComponent(value);
139
+ continue;
96
140
  }
97
141
 
98
142
  if (isDynamicZone && isArray(value)) {
99
- const visitDynamicZoneEntry = (entry) => {
100
- const targetSchema = strapi.getModel(entry.__component);
101
- const traverseOptions = { schema: targetSchema, path: newPath };
102
-
103
- return traverseEntity(visitor, traverseOptions, entry);
104
- };
143
+ const res = new Array(value.length);
144
+ for (let i = 0; i < value.length; i += 1) {
145
+ res[i] = await visitDynamicZoneEntry(visitor, newPath, value[i]);
146
+ }
147
+ copy[key] = res;
105
148
 
106
- copy[key] = await Promise.all(value.map(visitDynamicZoneEntry));
149
+ continue;
107
150
  }
108
151
  }
109
152
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strapi/utils",
3
- "version": "4.9.0-exp.90df253ba90fd6879eb56a720a1f80d04ff745b8",
3
+ "version": "4.9.1",
4
4
  "description": "Shared utilities for the Strapi packages",
5
5
  "keywords": [
6
6
  "strapi",
@@ -32,7 +32,9 @@
32
32
  "lib": "./lib"
33
33
  },
34
34
  "scripts": {
35
- "test:unit": "jest --verbose"
35
+ "test:unit": "run -T jest",
36
+ "test:unit:watch": "run -T jest --watch",
37
+ "lint": "run -T eslint ."
36
38
  },
37
39
  "dependencies": {
38
40
  "@sindresorhus/slugify": "1.1.0",
@@ -46,5 +48,5 @@
46
48
  "node": ">=14.19.1 <=18.x.x",
47
49
  "npm": ">=6.0.0"
48
50
  },
49
- "gitHead": "366eb8a0d0f06935914854c6d9c4b3fe859468e0"
51
+ "gitHead": "c8f2f0543b45dda8eadde21a0782b64d156fc786"
50
52
  }