@fourlights/strapi-plugin-deep-populate 1.3.1 → 1.4.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/README.md CHANGED
@@ -12,6 +12,7 @@ It does not impose a limit on the level of nesting and can cache the populate ob
12
12
  - Handles circular references and edge cases
13
13
  - Includes caching for improved performance
14
14
  - Honors `populateCreatorFields` setting
15
+ - Supports optional allow/deny lists for specific relations or components during population
15
16
 
16
17
  ## Installation
17
18
 
@@ -72,7 +73,36 @@ The plugin caches populate objects to improve performance. Cache can be disabled
72
73
 
73
74
  The plugin automatically populates `createdBy` and `updatedBy` fields when `populateCreatorFields` is enabled in the content-type configuration.
74
75
 
75
- ## How It Works
76
+ ### Allow / Deny Lists
77
+
78
+ Sometimes you may want to restrict the nested population of certain relations or components. For example if you have a `Page` contentType where a deeply nested `Link` component has a relation to another `Page`.
79
+ In those situations you can use the allow or deny lists to control where the plugin should stop resolving nested relations.
80
+
81
+ ```ts
82
+ // config/plugins.js
83
+ module.exports = ({ env }) => ({
84
+ 'deep-populate': {
85
+ enabled: true,
86
+ config: {
87
+ useCache: true,
88
+ replaceWildcard: true,
89
+
90
+ contentTypes: {
91
+ 'api::page.page': {
92
+ deny: {
93
+ relations: ['api::page.page'] // prevent resolving nested pages when populating a page
94
+ // alternatively we could have denied the link component in this case
95
+ // components: ['shared.link']
96
+ }
97
+ }
98
+ }
99
+ }
100
+ }
101
+ });
102
+ ```
103
+ ---
104
+
105
+ ## How The Plugin Works
76
106
 
77
107
  The plugin recursively:
78
108
  1. Traverses the content-type schema
@@ -1,4 +1,6 @@
1
1
  "use strict";
2
+ const isEmpty$1 = require("lodash/isEmpty");
3
+ const isObject$4 = require("lodash/isObject");
2
4
  const ___default = require("lodash");
3
5
  const fp = require("lodash/fp");
4
6
  const require$$1 = require("crypto");
@@ -23,6 +25,8 @@ const get = require("lodash/get");
23
25
  const mergeWith = require("lodash/mergeWith");
24
26
  const set$2 = require("lodash/set");
25
27
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
28
+ const isEmpty__default = /* @__PURE__ */ _interopDefault(isEmpty$1);
29
+ const isObject__default = /* @__PURE__ */ _interopDefault(isObject$4);
26
30
  const ___default__default = /* @__PURE__ */ _interopDefault(___default);
27
31
  const require$$1__default = /* @__PURE__ */ _interopDefault(require$$1);
28
32
  const require$$0__default = /* @__PURE__ */ _interopDefault(require$$0$1);
@@ -45,8 +49,43 @@ const get__default = /* @__PURE__ */ _interopDefault(get);
45
49
  const mergeWith__default = /* @__PURE__ */ _interopDefault(mergeWith);
46
50
  const set__default = /* @__PURE__ */ _interopDefault(set$2);
47
51
  const config = {
48
- default: ({ env: env2 }) => ({ useCache: true, replaceWildcard: true }),
52
+ default: ({ env: env2 }) => ({ useCache: true, replaceWildcard: true, contentTypes: {} }),
49
53
  validator: (config2) => {
54
+ if (!isObject__default.default(config2.contentTypes)) {
55
+ throw new Error("plugin::deep-populate config.contentTypes must be an object");
56
+ }
57
+ if (!isEmpty__default.default(config2.contentTypes)) {
58
+ for (const [uid, contentTypeConfig] of Object.entries(config2.contentTypes)) {
59
+ if (!isObject__default.default(contentTypeConfig)) {
60
+ throw new Error(`plugin::deep-populate config.contentTypes.${uid} must be an object`);
61
+ }
62
+ if (!contentTypeConfig.allow && !contentTypeConfig.deny) {
63
+ throw new Error(`plugin::deep-populate config.contentTypes.${uid} must have an "allow" or "deny".`);
64
+ }
65
+ if (contentTypeConfig.allow && !isObject__default.default(contentTypeConfig.allow)) {
66
+ throw new Error(`plugin::deep-populate config.contentTypes.${uid}.allow must be an object`);
67
+ }
68
+ if (contentTypeConfig.deny && !isObject__default.default(contentTypeConfig.deny)) {
69
+ throw new Error(`plugin::deep-populate config.contentTypes.${uid}.deny must be an object`);
70
+ }
71
+ if (contentTypeConfig.allow) {
72
+ if (contentTypeConfig.allow.relations && !Array.isArray(contentTypeConfig.allow.relations)) {
73
+ throw new Error(`plugin::deep-populate config.contentTypes.${uid}.allow.relations must be an array`);
74
+ }
75
+ if (contentTypeConfig.allow.components && !Array.isArray(contentTypeConfig.allow.components)) {
76
+ throw new Error(`plugin::deep-populate config.contentTypes.${uid}.allow.components must be an array`);
77
+ }
78
+ }
79
+ if (contentTypeConfig.deny) {
80
+ if (contentTypeConfig.deny.relations && !Array.isArray(contentTypeConfig.deny.relations)) {
81
+ throw new Error(`plugin::deep-populate config.contentTypes.${uid}.deny.relations must be an array`);
82
+ }
83
+ if (contentTypeConfig.deny.components && !Array.isArray(contentTypeConfig.deny.components)) {
84
+ throw new Error(`plugin::deep-populate config.contentTypes.${uid}.deny.components must be an array`);
85
+ }
86
+ }
87
+ }
88
+ }
50
89
  }
51
90
  };
52
91
  const schema$1 = {
@@ -14573,7 +14612,7 @@ const register = async ({ strapi: strapi2 }) => {
14573
14612
  });
14574
14613
  };
14575
14614
  const getHash = (params) => {
14576
- return `${params.contentType}-${params.documentId}-${params.locale}-${params.status}-${params.omitEmpty ? "sparse" : "full"}`;
14615
+ return `${params.contentType}-${params.documentId}-${params.locale}-${params.status}-${params.omitEmpty ? "sparse" : "full"}-${params.localizations ? "all" : "single"}`;
14577
14616
  };
14578
14617
  const cache = ({ strapi: strapi2 }) => ({
14579
14618
  async get(params) {
@@ -14636,18 +14675,26 @@ const hasValue = (value) => {
14636
14675
  return !(value === null || value === void 0 || Array.isArray(value) && value.length === 0 || typeof value === "object" && isEmpty(value));
14637
14676
  };
14638
14677
  async function _populateComponent({
14678
+ schema: schema2,
14639
14679
  populate: populate2 = {},
14640
14680
  lookup,
14641
14681
  attrName,
14642
14682
  inDynamicZone = false,
14683
+ __allow,
14684
+ __deny,
14643
14685
  ...params
14644
14686
  }) {
14645
14687
  const componentLookup = lookup.length === 0 ? [attrName] : [...lookup, inDynamicZone ? "on" : "populate", attrName];
14646
14688
  const componentPopulate = populate2;
14647
14689
  set__default.default(componentPopulate, componentLookup, { populate: "*" });
14690
+ if (__allow?.components && !__allow.components.includes(schema2)) return { populate: "*" };
14691
+ if (__deny?.components?.includes(schema2)) return { populate: "*" };
14648
14692
  const nestedPopulate = await _populate({
14693
+ schema: schema2,
14649
14694
  populate: componentPopulate,
14650
14695
  lookup: componentLookup,
14696
+ __allow,
14697
+ __deny,
14651
14698
  ...params
14652
14699
  });
14653
14700
  return isEmpty(nestedPopulate) ? true : { populate: nestedPopulate };
@@ -14680,9 +14727,7 @@ async function _populateRelation({
14680
14727
  contentType,
14681
14728
  relation,
14682
14729
  resolvedRelations,
14683
- omitEmpty,
14684
- locale: locale2,
14685
- status: status2
14730
+ ...params
14686
14731
  }) {
14687
14732
  const isSingleRelation = !Array.isArray(relation);
14688
14733
  const relations = isSingleRelation ? [relation] : relation;
@@ -14694,9 +14739,7 @@ async function _populateRelation({
14694
14739
  documentId: relation2.documentId,
14695
14740
  schema: contentType,
14696
14741
  resolvedRelations,
14697
- omitEmpty,
14698
- locale: locale2,
14699
- status: status2
14742
+ ...params
14700
14743
  });
14701
14744
  resolvedRelations.set(relation2.documentId, relationPopulate);
14702
14745
  }
@@ -14746,6 +14789,8 @@ async function _populate({
14746
14789
  lookup = [],
14747
14790
  resolvedRelations,
14748
14791
  omitEmpty,
14792
+ __deny,
14793
+ __allow,
14749
14794
  ...params
14750
14795
  }) {
14751
14796
  const newPopulate = {};
@@ -14771,9 +14816,33 @@ async function _populate({
14771
14816
  for (const [attrName, attr] of relations) {
14772
14817
  const value = _resolveValue({ document: document2, attrName, lookup });
14773
14818
  if (!hasValue(value)) {
14774
- if (!omitEmpty) newPopulate[attrName] = true;
14819
+ if (omitEmpty !== true) newPopulate[attrName] = true;
14775
14820
  continue;
14776
14821
  }
14822
+ if (params.localizations !== true && attrName === "localizations" && hasValue(value)) {
14823
+ newPopulate[attrName] = true;
14824
+ continue;
14825
+ }
14826
+ if (contentTypes.isRelationalAttribute(attr)) {
14827
+ if (__allow?.relations && !__allow.relations.includes(attr.target)) {
14828
+ newPopulate[attrName] = true;
14829
+ continue;
14830
+ }
14831
+ if (__deny?.relations?.includes(attr.target)) {
14832
+ newPopulate[attrName] = true;
14833
+ continue;
14834
+ }
14835
+ }
14836
+ if (contentTypes.isComponentAttribute(attr) && !contentTypes.isDynamicZoneAttribute(attr)) {
14837
+ if (__allow?.components && !__allow.components.includes(attr.component)) {
14838
+ newPopulate[attrName] = true;
14839
+ continue;
14840
+ }
14841
+ if (__deny?.components?.includes(attr.component)) {
14842
+ newPopulate[attrName] = true;
14843
+ continue;
14844
+ }
14845
+ }
14777
14846
  resolveRelations.push([attrName, attr, value]);
14778
14847
  }
14779
14848
  relations = null;
@@ -14790,6 +14859,8 @@ async function _populate({
14790
14859
  attrName,
14791
14860
  resolvedRelations,
14792
14861
  omitEmpty,
14862
+ __deny,
14863
+ __allow,
14793
14864
  ...params
14794
14865
  });
14795
14866
  }
@@ -14800,7 +14871,10 @@ async function _populate({
14800
14871
  resolvedRelations,
14801
14872
  omitEmpty,
14802
14873
  locale: params.locale,
14803
- status: params.status
14874
+ status: params.status,
14875
+ localizations: params.localizations,
14876
+ __deny,
14877
+ __allow
14804
14878
  });
14805
14879
  }
14806
14880
  if (contentTypes.isComponentAttribute(attr) && !contentTypes.isDynamicZoneAttribute(attr)) {
@@ -14811,6 +14885,8 @@ async function _populate({
14811
14885
  attrName,
14812
14886
  resolvedRelations,
14813
14887
  omitEmpty,
14888
+ __deny,
14889
+ __allow,
14814
14890
  ...params
14815
14891
  });
14816
14892
  }
@@ -14821,8 +14897,20 @@ async function _populate({
14821
14897
  return newPopulate;
14822
14898
  }
14823
14899
  async function populate$1(params) {
14900
+ const { contentTypes: contentTypes2 } = strapi.config.get("plugin::deep-populate");
14901
+ const contentTypeConfig = has__default.default(contentTypes2, "*") ? get__default.default(contentTypes2, "*") : {};
14902
+ if (has__default.default(contentTypes2, params.contentType)) {
14903
+ mergeWith__default.default(contentTypeConfig, get__default.default(contentTypes2, params.contentType));
14904
+ }
14905
+ const { allow, deny } = contentTypeConfig;
14824
14906
  const resolvedRelations = /* @__PURE__ */ new Map();
14825
- const populated = await _populate({ ...params, schema: params.contentType, resolvedRelations });
14907
+ const populated = await _populate({
14908
+ ...params,
14909
+ schema: params.contentType,
14910
+ resolvedRelations,
14911
+ __deny: deny,
14912
+ __allow: allow
14913
+ });
14826
14914
  return { populate: populated, dependencies: [...resolvedRelations.keys()] };
14827
14915
  }
14828
14916
  const populate = ({ strapi: strapi2 }) => ({
@@ -1,5 +1,7 @@
1
+ import isEmpty$1 from "lodash/isEmpty";
2
+ import isObject$4 from "lodash/isObject";
1
3
  import ___default from "lodash";
2
- import { curry, isArray, isObject as isObject$4, isEmpty as isEmpty$1, cloneDeep, omit, isNil, trim as trim$1, isString, pipe as pipe$1, split as split$1, map as map$2, flatten, first, identity, constant, join, eq, clone as clone$3, get, pick, has as has$1, union, getOr, toPath, isBoolean as isBoolean$1 } from "lodash/fp";
4
+ import { curry, isArray, isObject as isObject$5, isEmpty as isEmpty$2, cloneDeep, omit, isNil, trim as trim$1, isString, pipe as pipe$1, split as split$1, map as map$2, flatten, first, identity, constant, join, eq, clone as clone$3, get, pick, has as has$1, union, getOr, toPath, isBoolean as isBoolean$1 } from "lodash/fp";
3
5
  import require$$1 from "crypto";
4
6
  import require$$0$1 from "child_process";
5
7
  import has from "lodash/has";
@@ -22,8 +24,43 @@ import get$1 from "lodash/get";
22
24
  import mergeWith from "lodash/mergeWith";
23
25
  import set$2 from "lodash/set";
24
26
  const config = {
25
- default: ({ env: env2 }) => ({ useCache: true, replaceWildcard: true }),
27
+ default: ({ env: env2 }) => ({ useCache: true, replaceWildcard: true, contentTypes: {} }),
26
28
  validator: (config2) => {
29
+ if (!isObject$4(config2.contentTypes)) {
30
+ throw new Error("plugin::deep-populate config.contentTypes must be an object");
31
+ }
32
+ if (!isEmpty$1(config2.contentTypes)) {
33
+ for (const [uid, contentTypeConfig] of Object.entries(config2.contentTypes)) {
34
+ if (!isObject$4(contentTypeConfig)) {
35
+ throw new Error(`plugin::deep-populate config.contentTypes.${uid} must be an object`);
36
+ }
37
+ if (!contentTypeConfig.allow && !contentTypeConfig.deny) {
38
+ throw new Error(`plugin::deep-populate config.contentTypes.${uid} must have an "allow" or "deny".`);
39
+ }
40
+ if (contentTypeConfig.allow && !isObject$4(contentTypeConfig.allow)) {
41
+ throw new Error(`plugin::deep-populate config.contentTypes.${uid}.allow must be an object`);
42
+ }
43
+ if (contentTypeConfig.deny && !isObject$4(contentTypeConfig.deny)) {
44
+ throw new Error(`plugin::deep-populate config.contentTypes.${uid}.deny must be an object`);
45
+ }
46
+ if (contentTypeConfig.allow) {
47
+ if (contentTypeConfig.allow.relations && !Array.isArray(contentTypeConfig.allow.relations)) {
48
+ throw new Error(`plugin::deep-populate config.contentTypes.${uid}.allow.relations must be an array`);
49
+ }
50
+ if (contentTypeConfig.allow.components && !Array.isArray(contentTypeConfig.allow.components)) {
51
+ throw new Error(`plugin::deep-populate config.contentTypes.${uid}.allow.components must be an array`);
52
+ }
53
+ }
54
+ if (contentTypeConfig.deny) {
55
+ if (contentTypeConfig.deny.relations && !Array.isArray(contentTypeConfig.deny.relations)) {
56
+ throw new Error(`plugin::deep-populate config.contentTypes.${uid}.deny.relations must be an array`);
57
+ }
58
+ if (contentTypeConfig.deny.components && !Array.isArray(contentTypeConfig.deny.components)) {
59
+ throw new Error(`plugin::deep-populate config.contentTypes.${uid}.deny.components must be an array`);
60
+ }
61
+ }
62
+ }
63
+ }
27
64
  }
28
65
  };
29
66
  const schema$1 = {
@@ -13117,7 +13154,7 @@ const traverseEntity = async (visitor2, options, entity) => {
13117
13154
  const traverseOptions = { schema: targetSchema, path: path22, getModel, parent };
13118
13155
  return traverseEntity(visitor22, traverseOptions, entry);
13119
13156
  };
13120
- if (!isObject$4(entity) || isNil(schema2)) {
13157
+ if (!isObject$5(entity) || isNil(schema2)) {
13121
13158
  return entity;
13122
13159
  }
13123
13160
  const copy = clone$3(entity);
@@ -13353,7 +13390,7 @@ const removeRestrictedRelations = (auth) => async ({ data, key, attribute, schem
13353
13390
  return allowedElements;
13354
13391
  }
13355
13392
  for (const element of elements) {
13356
- if (!isObject$4(element) || !("__type" in element)) {
13393
+ if (!isObject$5(element) || !("__type" in element)) {
13357
13394
  continue;
13358
13395
  }
13359
13396
  const scopes = ACTIONS_TO_VERIFY$1.map((action) => `${element.__type}.${action}`);
@@ -13586,7 +13623,7 @@ const traverseFactory = () => {
13586
13623
  }
13587
13624
  };
13588
13625
  };
13589
- const isObj$2 = (value) => isObject$4(value);
13626
+ const isObj$2 = (value) => isObject$5(value);
13590
13627
  const filters = traverseFactory().intercept(
13591
13628
  // Intercept filters arrays and apply the traversal to each one individually
13592
13629
  isArray,
@@ -13597,11 +13634,11 @@ const filters = traverseFactory().intercept(
13597
13634
  return recurse(visitor2, { ...options, path: newPath }, filter);
13598
13635
  })
13599
13636
  // todo: move that to the visitors
13600
- ).then((res) => res.filter((val) => !(isObject$4(val) && isEmpty$1(val))));
13637
+ ).then((res) => res.filter((val) => !(isObject$5(val) && isEmpty$2(val))));
13601
13638
  }
13602
13639
  ).intercept(
13603
13640
  // Ignore non object filters and return the value as-is
13604
- (filters2) => !isObject$4(filters2),
13641
+ (filters2) => !isObject$5(filters2),
13605
13642
  (_2, __, filters2) => {
13606
13643
  return filters2;
13607
13644
  }
@@ -13668,23 +13705,23 @@ const ORDERS = { asc: "asc", desc: "desc" };
13668
13705
  const ORDER_VALUES = Object.values(ORDERS);
13669
13706
  const isSortOrder = (value) => ORDER_VALUES.includes(value.toLowerCase());
13670
13707
  const isStringArray$2 = (value) => Array.isArray(value) && value.every(isString);
13671
- const isObjectArray = (value) => Array.isArray(value) && value.every(isObject$4);
13708
+ const isObjectArray = (value) => Array.isArray(value) && value.every(isObject$5);
13672
13709
  const isNestedSorts = (value) => isString(value) && value.split(",").length > 1;
13673
- const isObj$1 = (value) => isObject$4(value);
13710
+ const isObj$1 = (value) => isObject$5(value);
13674
13711
  const sort = traverseFactory().intercept(
13675
13712
  // String with chained sorts (foo,bar,foobar) => split, map(recurse), then recompose
13676
13713
  isNestedSorts,
13677
13714
  async (visitor2, options, sort2, { recurse }) => {
13678
13715
  return Promise.all(
13679
13716
  sort2.split(",").map(trim$1).map((nestedSort) => recurse(visitor2, options, nestedSort))
13680
- ).then((res) => res.filter((part) => !isEmpty$1(part)).join(","));
13717
+ ).then((res) => res.filter((part) => !isEmpty$2(part)).join(","));
13681
13718
  }
13682
13719
  ).intercept(
13683
13720
  // Array of strings ['foo', 'foo,bar'] => map(recurse), then filter out empty items
13684
13721
  isStringArray$2,
13685
13722
  async (visitor2, options, sort2, { recurse }) => {
13686
13723
  return Promise.all(sort2.map((nestedSort) => recurse(visitor2, options, nestedSort))).then(
13687
- (res) => res.filter((nestedSort) => !isEmpty$1(nestedSort))
13724
+ (res) => res.filter((nestedSort) => !isEmpty$2(nestedSort))
13688
13725
  );
13689
13726
  }
13690
13727
  ).intercept(
@@ -13692,7 +13729,7 @@ const sort = traverseFactory().intercept(
13692
13729
  isObjectArray,
13693
13730
  async (visitor2, options, sort2, { recurse }) => {
13694
13731
  return Promise.all(sort2.map((nestedSort) => recurse(visitor2, options, nestedSort))).then(
13695
- (res) => res.filter((nestedSort) => !isEmpty$1(nestedSort))
13732
+ (res) => res.filter((nestedSort) => !isEmpty$2(nestedSort))
13696
13733
  );
13697
13734
  }
13698
13735
  ).parse(isString, () => {
@@ -13702,7 +13739,7 @@ const sort = traverseFactory().intercept(
13702
13739
  return void 0;
13703
13740
  }
13704
13741
  return parts.reduce((acc, part) => {
13705
- if (isEmpty$1(part)) {
13742
+ if (isEmpty$2(part)) {
13706
13743
  return acc;
13707
13744
  }
13708
13745
  if (acc === "") {
@@ -13797,7 +13834,7 @@ const isPopulateString = (value) => {
13797
13834
  return isString(value) && !isWildcard(value);
13798
13835
  };
13799
13836
  const isStringArray$1 = (value) => isArray(value) && value.every(isString);
13800
- const isObj = (value) => isObject$4(value);
13837
+ const isObj = (value) => isObject$5(value);
13801
13838
  const populate$2 = traverseFactory().intercept(isPopulateString, async (visitor2, options, populate2, { recurse }) => {
13802
13839
  const populateObject = pathsToObjectPopulate([populate2]);
13803
13840
  const traversedPopulate = await recurse(visitor2, options, populateObject);
@@ -13846,7 +13883,7 @@ const populate$2 = traverseFactory().intercept(isPopulateString, async (visitor2
13846
13883
  if (root2 !== key) {
13847
13884
  return data;
13848
13885
  }
13849
- return isNil(value) || isEmpty$1(value) ? root2 : `${root2}.${value}`;
13886
+ return isNil(value) || isEmpty$2(value) ? root2 : `${root2}.${value}`;
13850
13887
  },
13851
13888
  keys(data) {
13852
13889
  const v = first(tokenize(data));
@@ -13907,7 +13944,7 @@ const populate$2 = traverseFactory().intercept(isPopulateString, async (visitor2
13907
13944
  }
13908
13945
  const parent = { key, path: path2, schema: schema2, attribute };
13909
13946
  if (isMorphToRelationalAttribute(attribute)) {
13910
- if (!isObject$4(value) || !("on" in value && isObject$4(value?.on))) {
13947
+ if (!isObject$5(value) || !("on" in value && isObject$5(value?.on))) {
13911
13948
  return;
13912
13949
  }
13913
13950
  const newValue2 = await recurse(
@@ -13956,7 +13993,7 @@ const populate$2 = traverseFactory().intercept(isPopulateString, async (visitor2
13956
13993
  }
13957
13994
  ).onDynamicZone(
13958
13995
  async ({ key, value, schema: schema2, visitor: visitor2, path: path2, attribute, getModel }, { set: set2, recurse }) => {
13959
- if (isNil(value) || !isObject$4(value)) {
13996
+ if (isNil(value) || !isObject$5(value)) {
13960
13997
  return;
13961
13998
  }
13962
13999
  const parent = { key, path: path2, schema: schema2, attribute };
@@ -14069,7 +14106,7 @@ const defaultSanitizeFilters = curry((ctx, filters2) => {
14069
14106
  traverseQueryFilters(visitor$7, ctx),
14070
14107
  // Remove empty objects
14071
14108
  traverseQueryFilters(({ key, value }, { remove: remove2 }) => {
14072
- if (isObject$4(value) && isEmpty$1(value)) {
14109
+ if (isObject$5(value) && isEmpty$2(value)) {
14073
14110
  remove2(key);
14074
14111
  }
14075
14112
  }, ctx)
@@ -14102,7 +14139,7 @@ const defaultSanitizeSort = curry((ctx, sort2) => {
14102
14139
  if ([ID_ATTRIBUTE$2, DOC_ID_ATTRIBUTE$2].includes(key)) {
14103
14140
  return;
14104
14141
  }
14105
- if (!isScalarAttribute(attribute) && isEmpty$1(value)) {
14142
+ if (!isScalarAttribute(attribute) && isEmpty$2(value)) {
14106
14143
  remove2(key);
14107
14144
  }
14108
14145
  }, ctx)
@@ -14550,7 +14587,7 @@ const register = async ({ strapi: strapi2 }) => {
14550
14587
  });
14551
14588
  };
14552
14589
  const getHash = (params) => {
14553
- return `${params.contentType}-${params.documentId}-${params.locale}-${params.status}-${params.omitEmpty ? "sparse" : "full"}`;
14590
+ return `${params.contentType}-${params.documentId}-${params.locale}-${params.status}-${params.omitEmpty ? "sparse" : "full"}-${params.localizations ? "all" : "single"}`;
14554
14591
  };
14555
14592
  const cache = ({ strapi: strapi2 }) => ({
14556
14593
  async get(params) {
@@ -14613,18 +14650,26 @@ const hasValue = (value) => {
14613
14650
  return !(value === null || value === void 0 || Array.isArray(value) && value.length === 0 || typeof value === "object" && isEmpty(value));
14614
14651
  };
14615
14652
  async function _populateComponent({
14653
+ schema: schema2,
14616
14654
  populate: populate2 = {},
14617
14655
  lookup,
14618
14656
  attrName,
14619
14657
  inDynamicZone = false,
14658
+ __allow,
14659
+ __deny,
14620
14660
  ...params
14621
14661
  }) {
14622
14662
  const componentLookup = lookup.length === 0 ? [attrName] : [...lookup, inDynamicZone ? "on" : "populate", attrName];
14623
14663
  const componentPopulate = populate2;
14624
14664
  set$2(componentPopulate, componentLookup, { populate: "*" });
14665
+ if (__allow?.components && !__allow.components.includes(schema2)) return { populate: "*" };
14666
+ if (__deny?.components?.includes(schema2)) return { populate: "*" };
14625
14667
  const nestedPopulate = await _populate({
14668
+ schema: schema2,
14626
14669
  populate: componentPopulate,
14627
14670
  lookup: componentLookup,
14671
+ __allow,
14672
+ __deny,
14628
14673
  ...params
14629
14674
  });
14630
14675
  return isEmpty(nestedPopulate) ? true : { populate: nestedPopulate };
@@ -14657,9 +14702,7 @@ async function _populateRelation({
14657
14702
  contentType,
14658
14703
  relation,
14659
14704
  resolvedRelations,
14660
- omitEmpty,
14661
- locale: locale2,
14662
- status: status2
14705
+ ...params
14663
14706
  }) {
14664
14707
  const isSingleRelation = !Array.isArray(relation);
14665
14708
  const relations = isSingleRelation ? [relation] : relation;
@@ -14671,9 +14714,7 @@ async function _populateRelation({
14671
14714
  documentId: relation2.documentId,
14672
14715
  schema: contentType,
14673
14716
  resolvedRelations,
14674
- omitEmpty,
14675
- locale: locale2,
14676
- status: status2
14717
+ ...params
14677
14718
  });
14678
14719
  resolvedRelations.set(relation2.documentId, relationPopulate);
14679
14720
  }
@@ -14723,6 +14764,8 @@ async function _populate({
14723
14764
  lookup = [],
14724
14765
  resolvedRelations,
14725
14766
  omitEmpty,
14767
+ __deny,
14768
+ __allow,
14726
14769
  ...params
14727
14770
  }) {
14728
14771
  const newPopulate = {};
@@ -14748,9 +14791,33 @@ async function _populate({
14748
14791
  for (const [attrName, attr] of relations) {
14749
14792
  const value = _resolveValue({ document: document2, attrName, lookup });
14750
14793
  if (!hasValue(value)) {
14751
- if (!omitEmpty) newPopulate[attrName] = true;
14794
+ if (omitEmpty !== true) newPopulate[attrName] = true;
14752
14795
  continue;
14753
14796
  }
14797
+ if (params.localizations !== true && attrName === "localizations" && hasValue(value)) {
14798
+ newPopulate[attrName] = true;
14799
+ continue;
14800
+ }
14801
+ if (contentTypes.isRelationalAttribute(attr)) {
14802
+ if (__allow?.relations && !__allow.relations.includes(attr.target)) {
14803
+ newPopulate[attrName] = true;
14804
+ continue;
14805
+ }
14806
+ if (__deny?.relations?.includes(attr.target)) {
14807
+ newPopulate[attrName] = true;
14808
+ continue;
14809
+ }
14810
+ }
14811
+ if (contentTypes.isComponentAttribute(attr) && !contentTypes.isDynamicZoneAttribute(attr)) {
14812
+ if (__allow?.components && !__allow.components.includes(attr.component)) {
14813
+ newPopulate[attrName] = true;
14814
+ continue;
14815
+ }
14816
+ if (__deny?.components?.includes(attr.component)) {
14817
+ newPopulate[attrName] = true;
14818
+ continue;
14819
+ }
14820
+ }
14754
14821
  resolveRelations.push([attrName, attr, value]);
14755
14822
  }
14756
14823
  relations = null;
@@ -14767,6 +14834,8 @@ async function _populate({
14767
14834
  attrName,
14768
14835
  resolvedRelations,
14769
14836
  omitEmpty,
14837
+ __deny,
14838
+ __allow,
14770
14839
  ...params
14771
14840
  });
14772
14841
  }
@@ -14777,7 +14846,10 @@ async function _populate({
14777
14846
  resolvedRelations,
14778
14847
  omitEmpty,
14779
14848
  locale: params.locale,
14780
- status: params.status
14849
+ status: params.status,
14850
+ localizations: params.localizations,
14851
+ __deny,
14852
+ __allow
14781
14853
  });
14782
14854
  }
14783
14855
  if (contentTypes.isComponentAttribute(attr) && !contentTypes.isDynamicZoneAttribute(attr)) {
@@ -14788,6 +14860,8 @@ async function _populate({
14788
14860
  attrName,
14789
14861
  resolvedRelations,
14790
14862
  omitEmpty,
14863
+ __deny,
14864
+ __allow,
14791
14865
  ...params
14792
14866
  });
14793
14867
  }
@@ -14798,8 +14872,20 @@ async function _populate({
14798
14872
  return newPopulate;
14799
14873
  }
14800
14874
  async function populate$1(params) {
14875
+ const { contentTypes: contentTypes2 } = strapi.config.get("plugin::deep-populate");
14876
+ const contentTypeConfig = has(contentTypes2, "*") ? get$1(contentTypes2, "*") : {};
14877
+ if (has(contentTypes2, params.contentType)) {
14878
+ mergeWith(contentTypeConfig, get$1(contentTypes2, params.contentType));
14879
+ }
14880
+ const { allow, deny } = contentTypeConfig;
14801
14881
  const resolvedRelations = /* @__PURE__ */ new Map();
14802
- const populated = await _populate({ ...params, schema: params.contentType, resolvedRelations });
14882
+ const populated = await _populate({
14883
+ ...params,
14884
+ schema: params.contentType,
14885
+ resolvedRelations,
14886
+ __deny: deny,
14887
+ __allow: allow
14888
+ });
14803
14889
  return { populate: populated, dependencies: [...resolvedRelations.keys()] };
14804
14890
  }
14805
14891
  const populate = ({ strapi: strapi2 }) => ({
@@ -1,10 +1,29 @@
1
+ import type { UID } from "@strapi/strapi";
2
+ export type ContentTypeConfigAllow = {
3
+ relations?: UID.ContentType[];
4
+ components?: UID.Component[];
5
+ };
6
+ export type ContentTypeConfigDeny = {
7
+ relations?: UID.ContentType[];
8
+ components?: UID.Component[];
9
+ };
10
+ type ContentTypeConfig = {
11
+ allow?: ContentTypeConfigAllow;
12
+ deny?: ContentTypeConfigDeny;
13
+ };
14
+ export type Config = {
15
+ useCache: boolean;
16
+ replaceWildcard: boolean;
17
+ contentTypes: Record<UID.ContentType | "*", ContentTypeConfig>;
18
+ };
1
19
  declare const _default: {
2
20
  default: ({ env }: {
3
21
  env: any;
4
22
  }) => {
5
23
  useCache: boolean;
6
24
  replaceWildcard: boolean;
25
+ contentTypes: {};
7
26
  };
8
- validator: (config: any) => void;
27
+ validator: (config: Config) => void;
9
28
  };
10
29
  export default _default;
@@ -5,8 +5,9 @@ declare const _default: {
5
5
  }) => {
6
6
  useCache: boolean;
7
7
  replaceWildcard: boolean;
8
+ contentTypes: {};
8
9
  };
9
- validator: (config: any) => void;
10
+ validator: (config: import("./config").Config) => void;
10
11
  };
11
12
  contentTypes: {
12
13
  cache: {
@@ -72,6 +73,7 @@ declare const _default: {
72
73
  contentType: import("@strapi/types/dist/uid").ContentType;
73
74
  documentId: string;
74
75
  omitEmpty?: boolean;
76
+ localizations?: boolean;
75
77
  } & {
76
78
  populate?: import("@strapi/types/dist/modules/documents/params/populate").Any<import("@strapi/types/dist/uid").ContentType>;
77
79
  } & {
@@ -14,6 +14,7 @@ declare const _default: {
14
14
  contentType: import("@strapi/types/dist/uid").ContentType;
15
15
  documentId: string;
16
16
  omitEmpty?: boolean;
17
+ localizations?: boolean;
17
18
  } & {
18
19
  populate?: import("@strapi/types/dist/modules/documents/params/populate").Any<import("@strapi/types/dist/uid").ContentType>;
19
20
  } & {
@@ -3,6 +3,7 @@ export type PopulateParams<TContentType extends UID.ContentType = UID.ContentTyp
3
3
  contentType: TContentType;
4
4
  documentId: string;
5
5
  omitEmpty?: boolean;
6
+ localizations?: boolean;
6
7
  };
7
8
  declare const _default: ({ strapi }: {
8
9
  strapi: Core.Strapi;
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.3.1",
2
+ "version": "1.4.1",
3
3
  "keywords": [
4
4
  "strapi",
5
5
  "strapi-plugin",