@strapi/utils 4.10.0-beta.0 → 4.10.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.
Files changed (41) hide show
  1. package/.eslintignore +3 -0
  2. package/.eslintrc.js +4 -0
  3. package/coverage/clover.xml +638 -0
  4. package/coverage/coverage-final.json +24 -0
  5. package/coverage/lcov-report/base.css +224 -0
  6. package/coverage/lcov-report/block-navigation.js +87 -0
  7. package/coverage/lcov-report/favicon.png +0 -0
  8. package/coverage/lcov-report/index.html +146 -0
  9. package/coverage/lcov-report/lib/async.js.html +223 -0
  10. package/coverage/lcov-report/lib/content-types.js.html +643 -0
  11. package/coverage/lcov-report/lib/env-helper.js.html +319 -0
  12. package/coverage/lcov-report/lib/errors.js.html +397 -0
  13. package/coverage/lcov-report/lib/format-yup-error.js.html +145 -0
  14. package/coverage/lcov-report/lib/hooks.js.html +415 -0
  15. package/coverage/lcov-report/lib/import-default.js.html +115 -0
  16. package/coverage/lcov-report/lib/index.html +326 -0
  17. package/coverage/lcov-report/lib/pagination.js.html +382 -0
  18. package/coverage/lcov-report/lib/parse-type.js.html +385 -0
  19. package/coverage/lcov-report/lib/policy.js.html +472 -0
  20. package/coverage/lcov-report/lib/print-value.js.html +241 -0
  21. package/coverage/lcov-report/lib/provider-factory.js.html +433 -0
  22. package/coverage/lcov-report/lib/relations.js.html +178 -0
  23. package/coverage/lcov-report/lib/sanitize/visitors/allowed-fields.js.html +367 -0
  24. package/coverage/lcov-report/lib/sanitize/visitors/index.html +191 -0
  25. package/coverage/lcov-report/lib/sanitize/visitors/index.js.html +112 -0
  26. package/coverage/lcov-report/lib/sanitize/visitors/remove-password.js.html +106 -0
  27. package/coverage/lcov-report/lib/sanitize/visitors/remove-private.js.html +118 -0
  28. package/coverage/lcov-report/lib/sanitize/visitors/remove-restricted-relations.js.html +316 -0
  29. package/coverage/lcov-report/lib/sanitize/visitors/restricted-fields.js.html +181 -0
  30. package/coverage/lcov-report/lib/string-formatting.js.html +322 -0
  31. package/coverage/lcov-report/lib/validators.js.html +445 -0
  32. package/coverage/lcov-report/prettify.css +1 -0
  33. package/coverage/lcov-report/prettify.js +2 -0
  34. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  35. package/coverage/lcov-report/sorter.js +196 -0
  36. package/index.d.ts +3 -0
  37. package/lib/async.js +2 -3
  38. package/lib/sanitize/index.js +8 -4
  39. package/lib/sanitize/sanitizers.js +12 -10
  40. package/lib/traverse-entity.js +83 -40
  41. package/package.json +6 -3
@@ -0,0 +1,196 @@
1
+ /* eslint-disable */
2
+ var addSorting = (function() {
3
+ 'use strict';
4
+ var cols,
5
+ currentSort = {
6
+ index: 0,
7
+ desc: false
8
+ };
9
+
10
+ // returns the summary table element
11
+ function getTable() {
12
+ return document.querySelector('.coverage-summary');
13
+ }
14
+ // returns the thead element of the summary table
15
+ function getTableHeader() {
16
+ return getTable().querySelector('thead tr');
17
+ }
18
+ // returns the tbody element of the summary table
19
+ function getTableBody() {
20
+ return getTable().querySelector('tbody');
21
+ }
22
+ // returns the th element for nth column
23
+ function getNthColumn(n) {
24
+ return getTableHeader().querySelectorAll('th')[n];
25
+ }
26
+
27
+ function onFilterInput() {
28
+ const searchValue = document.getElementById('fileSearch').value;
29
+ const rows = document.getElementsByTagName('tbody')[0].children;
30
+ for (let i = 0; i < rows.length; i++) {
31
+ const row = rows[i];
32
+ if (
33
+ row.textContent
34
+ .toLowerCase()
35
+ .includes(searchValue.toLowerCase())
36
+ ) {
37
+ row.style.display = '';
38
+ } else {
39
+ row.style.display = 'none';
40
+ }
41
+ }
42
+ }
43
+
44
+ // loads the search box
45
+ function addSearchBox() {
46
+ var template = document.getElementById('filterTemplate');
47
+ var templateClone = template.content.cloneNode(true);
48
+ templateClone.getElementById('fileSearch').oninput = onFilterInput;
49
+ template.parentElement.appendChild(templateClone);
50
+ }
51
+
52
+ // loads all columns
53
+ function loadColumns() {
54
+ var colNodes = getTableHeader().querySelectorAll('th'),
55
+ colNode,
56
+ cols = [],
57
+ col,
58
+ i;
59
+
60
+ for (i = 0; i < colNodes.length; i += 1) {
61
+ colNode = colNodes[i];
62
+ col = {
63
+ key: colNode.getAttribute('data-col'),
64
+ sortable: !colNode.getAttribute('data-nosort'),
65
+ type: colNode.getAttribute('data-type') || 'string'
66
+ };
67
+ cols.push(col);
68
+ if (col.sortable) {
69
+ col.defaultDescSort = col.type === 'number';
70
+ colNode.innerHTML =
71
+ colNode.innerHTML + '<span class="sorter"></span>';
72
+ }
73
+ }
74
+ return cols;
75
+ }
76
+ // attaches a data attribute to every tr element with an object
77
+ // of data values keyed by column name
78
+ function loadRowData(tableRow) {
79
+ var tableCols = tableRow.querySelectorAll('td'),
80
+ colNode,
81
+ col,
82
+ data = {},
83
+ i,
84
+ val;
85
+ for (i = 0; i < tableCols.length; i += 1) {
86
+ colNode = tableCols[i];
87
+ col = cols[i];
88
+ val = colNode.getAttribute('data-value');
89
+ if (col.type === 'number') {
90
+ val = Number(val);
91
+ }
92
+ data[col.key] = val;
93
+ }
94
+ return data;
95
+ }
96
+ // loads all row data
97
+ function loadData() {
98
+ var rows = getTableBody().querySelectorAll('tr'),
99
+ i;
100
+
101
+ for (i = 0; i < rows.length; i += 1) {
102
+ rows[i].data = loadRowData(rows[i]);
103
+ }
104
+ }
105
+ // sorts the table using the data for the ith column
106
+ function sortByIndex(index, desc) {
107
+ var key = cols[index].key,
108
+ sorter = function(a, b) {
109
+ a = a.data[key];
110
+ b = b.data[key];
111
+ return a < b ? -1 : a > b ? 1 : 0;
112
+ },
113
+ finalSorter = sorter,
114
+ tableBody = document.querySelector('.coverage-summary tbody'),
115
+ rowNodes = tableBody.querySelectorAll('tr'),
116
+ rows = [],
117
+ i;
118
+
119
+ if (desc) {
120
+ finalSorter = function(a, b) {
121
+ return -1 * sorter(a, b);
122
+ };
123
+ }
124
+
125
+ for (i = 0; i < rowNodes.length; i += 1) {
126
+ rows.push(rowNodes[i]);
127
+ tableBody.removeChild(rowNodes[i]);
128
+ }
129
+
130
+ rows.sort(finalSorter);
131
+
132
+ for (i = 0; i < rows.length; i += 1) {
133
+ tableBody.appendChild(rows[i]);
134
+ }
135
+ }
136
+ // removes sort indicators for current column being sorted
137
+ function removeSortIndicators() {
138
+ var col = getNthColumn(currentSort.index),
139
+ cls = col.className;
140
+
141
+ cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, '');
142
+ col.className = cls;
143
+ }
144
+ // adds sort indicators for current column being sorted
145
+ function addSortIndicators() {
146
+ getNthColumn(currentSort.index).className += currentSort.desc
147
+ ? ' sorted-desc'
148
+ : ' sorted';
149
+ }
150
+ // adds event listeners for all sorter widgets
151
+ function enableUI() {
152
+ var i,
153
+ el,
154
+ ithSorter = function ithSorter(i) {
155
+ var col = cols[i];
156
+
157
+ return function() {
158
+ var desc = col.defaultDescSort;
159
+
160
+ if (currentSort.index === i) {
161
+ desc = !currentSort.desc;
162
+ }
163
+ sortByIndex(i, desc);
164
+ removeSortIndicators();
165
+ currentSort.index = i;
166
+ currentSort.desc = desc;
167
+ addSortIndicators();
168
+ };
169
+ };
170
+ for (i = 0; i < cols.length; i += 1) {
171
+ if (cols[i].sortable) {
172
+ // add the click event handler on the th so users
173
+ // dont have to click on those tiny arrows
174
+ el = getNthColumn(i).querySelector('.sorter').parentElement;
175
+ if (el.addEventListener) {
176
+ el.addEventListener('click', ithSorter(i));
177
+ } else {
178
+ el.attachEvent('onclick', ithSorter(i));
179
+ }
180
+ }
181
+ }
182
+ }
183
+ // adds sorting functionality to the UI
184
+ return function() {
185
+ if (!getTable()) {
186
+ return;
187
+ }
188
+ cols = loadColumns();
189
+ loadData();
190
+ addSearchBox();
191
+ addSortIndicators();
192
+ enableUI();
193
+ };
194
+ })();
195
+
196
+ window.addEventListener('load', addSorting);
package/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ const utils: any;
2
+
3
+ export default utils;
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;
@@ -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(
@@ -140,7 +143,6 @@ const defaultSanitizePopulate = curry((schema, populate) => {
140
143
 
141
144
  module.exports = {
142
145
  sanitizePasswords,
143
- sanitizePrivates,
144
146
  defaultSanitizeOutput,
145
147
  defaultSanitizeFilters,
146
148
  defaultSanitizeSort,
@@ -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.10.0-beta.0",
3
+ "version": "4.10.0",
4
4
  "description": "Shared utilities for the Strapi packages",
5
5
  "keywords": [
6
6
  "strapi",
@@ -28,11 +28,14 @@
28
28
  }
29
29
  ],
30
30
  "main": "./lib",
31
+ "types": "./index.d.ts",
31
32
  "directories": {
32
33
  "lib": "./lib"
33
34
  },
34
35
  "scripts": {
35
- "test:unit": "jest --verbose"
36
+ "test:unit": "run -T jest",
37
+ "test:unit:watch": "run -T jest --watch",
38
+ "lint": "run -T eslint ."
36
39
  },
37
40
  "dependencies": {
38
41
  "@sindresorhus/slugify": "1.1.0",
@@ -46,5 +49,5 @@
46
49
  "node": ">=14.19.1 <=18.x.x",
47
50
  "npm": ">=6.0.0"
48
51
  },
49
- "gitHead": "1519ef0e56d27b738f24fc88223797651ad47aaf"
52
+ "gitHead": "9b5519778faaedfb837879f9c6f7e28fdfd6750d"
50
53
  }