strapi-plugin-populate-all 1.4.1 → 1.5.1-beta.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
@@ -51,4 +51,4 @@ module.exports = {
51
51
  - The plugin provides a global middleware that intercepts requests with `?populate=all` and rewrites the query to trigger recursive population.
52
52
  - In the background, it builds a standard Strapi populate query as described in the [Strapi documentation](https://docs.strapi.io/cms/api/rest/populate-select).
53
53
  - You can control which relations are included using the relations config option.
54
- - Inside the document API, you can set `populate: '*'` and `populateAll: true` to make it work
54
+ - Inside the document API, you can set `populate: '*'` and `_q: "populate-all"` to make it work ("populate-all" can be anywhere inside `_q`, we detect it via .includes)
@@ -1,20 +1,61 @@
1
1
  "use strict";
2
+ const PLUGIN_QUERY_DOCUMENT_TAG = "populate-all";
3
+ const config = {
4
+ default: {
5
+ relations: true
6
+ },
7
+ validator(config2 = {}) {
8
+ for (const [key, value] of Object.entries(config2)) {
9
+ switch (key) {
10
+ case "cache": {
11
+ const isBoolean = typeof value === "boolean";
12
+ if (!isBoolean) {
13
+ throw new Error(
14
+ `[populate-all] config "${key}" of type ${typeof value} is not valid. Supported is boolean.`
15
+ );
16
+ }
17
+ break;
18
+ }
19
+ case "relations": {
20
+ const isBoolean = typeof value === "boolean";
21
+ const isArrayOfStrings = Array.isArray(value) && value?.every((relation) => typeof relation === "string");
22
+ if (!(isBoolean || isArrayOfStrings)) {
23
+ throw new Error(
24
+ `[populate-all] config "${key}" of type ${typeof value} is not valid. Supported are boolean or Array of strings.`
25
+ );
26
+ }
27
+ break;
28
+ }
29
+ default:
30
+ strapi.log.warn(
31
+ `[populate-all] unknown config "${key}" provided. This config will be ignored.`
32
+ );
33
+ }
34
+ }
35
+ }
36
+ };
2
37
  const queryCache = {};
38
+ const buildCacheKey = (modelUid, parentsModelUids) => {
39
+ if (parentsModelUids.length === 0) {
40
+ return modelUid;
41
+ }
42
+ return `${parentsModelUids.join("|")}|${modelUid}`;
43
+ };
3
44
  const getPopulateQuery = (modelUid, parentsModelUids = []) => {
4
45
  try {
5
46
  const useCache = strapi.plugin("populate-all").config("cache") ?? true;
6
- if (useCache && queryCache[modelUid]) {
7
- strapi.log.debug(`[populate-all] query cache hit: ${modelUid}`);
8
- return structuredClone(queryCache[modelUid]);
47
+ const cacheKey = buildCacheKey(modelUid, parentsModelUids);
48
+ if (useCache && queryCache[cacheKey]) {
49
+ strapi.log.debug(`[populate-all] query cache hit: ${cacheKey}`);
50
+ return structuredClone(queryCache[cacheKey]);
9
51
  }
10
52
  if (parentsModelUids.includes(modelUid)) {
11
53
  strapi.log.debug(
12
54
  `[populate-all] loop detected skipping population: ${modelUid}`
13
55
  );
14
56
  return { populate: {} };
15
- } else {
16
- parentsModelUids.push(modelUid);
17
57
  }
58
+ const nextParentsModelUids = [...parentsModelUids, modelUid];
18
59
  const query = { populate: {} };
19
60
  const model = strapi.getModel(modelUid);
20
61
  for (const [fieldName, attribute] of Object.entries(
@@ -28,7 +69,7 @@ const getPopulateQuery = (modelUid, parentsModelUids = []) => {
28
69
  const components = Object.fromEntries(
29
70
  attribute.components.map((component) => [
30
71
  component,
31
- getPopulateQuery(component, parentsModelUids)
72
+ getPopulateQuery(component, nextParentsModelUids)
32
73
  ])
33
74
  );
34
75
  query.populate[fieldName] = { on: components };
@@ -37,7 +78,7 @@ const getPopulateQuery = (modelUid, parentsModelUids = []) => {
37
78
  if (attribute.type === "component") {
38
79
  query.populate[fieldName] = getPopulateQuery(
39
80
  attribute.component,
40
- parentsModelUids
81
+ nextParentsModelUids
41
82
  );
42
83
  continue;
43
84
  }
@@ -50,7 +91,7 @@ const getPopulateQuery = (modelUid, parentsModelUids = []) => {
50
91
  query.populate[fieldName] = getPopulateQuery(
51
92
  // @ts-expect-error target actually exists on attribute
52
93
  attribute.target,
53
- parentsModelUids
94
+ nextParentsModelUids
54
95
  );
55
96
  continue;
56
97
  }
@@ -58,7 +99,7 @@ const getPopulateQuery = (modelUid, parentsModelUids = []) => {
58
99
  query.populate[fieldName] = getPopulateQuery(
59
100
  // @ts-expect-error target actually exists on attribute
60
101
  attribute.target,
61
- parentsModelUids
102
+ nextParentsModelUids
62
103
  );
63
104
  continue;
64
105
  }
@@ -71,8 +112,8 @@ const getPopulateQuery = (modelUid, parentsModelUids = []) => {
71
112
  query.populate = true;
72
113
  }
73
114
  if (useCache) {
74
- strapi.log.debug(`[populate-all] new query cached: ${modelUid}`);
75
- queryCache[modelUid] = query;
115
+ strapi.log.debug(`[populate-all] new query cached: ${cacheKey}`);
116
+ queryCache[cacheKey] = query;
76
117
  }
77
118
  return structuredClone(query);
78
119
  } catch (error) {
@@ -86,7 +127,7 @@ const bootstrap = ({ strapi: strapi2 }) => {
86
127
  strapi2.db.lifecycles.subscribe((event) => {
87
128
  try {
88
129
  if (event.action === "beforeFindMany" || event.action === "beforeFindOne") {
89
- if (strapi2.requestContext.get()?.query?.populateAll) {
130
+ if (event.params._q?.includes(PLUGIN_QUERY_DOCUMENT_TAG)) {
90
131
  strapi2.log.debug(
91
132
  `[populate-all] recursively populate ${event.model.uid}`
92
133
  );
@@ -94,6 +135,12 @@ const bootstrap = ({ strapi: strapi2 }) => {
94
135
  if (populateQuery?.populate) {
95
136
  event.params.populate = populateQuery.populate;
96
137
  }
138
+ const query = event.params._q.replace(PLUGIN_QUERY_DOCUMENT_TAG, "");
139
+ if (query.length > 0) {
140
+ event.params._q = query;
141
+ } else {
142
+ delete event.params._q;
143
+ }
97
144
  }
98
145
  }
99
146
  } catch (error) {
@@ -103,40 +150,6 @@ const bootstrap = ({ strapi: strapi2 }) => {
103
150
  }
104
151
  });
105
152
  };
106
- const config = {
107
- default: {
108
- relations: true
109
- },
110
- validator(config2 = {}) {
111
- for (const [key, value] of Object.entries(config2)) {
112
- switch (key) {
113
- case "cache": {
114
- const isBoolean = typeof value === "boolean";
115
- if (!isBoolean) {
116
- throw new Error(
117
- `[populate-all] config "${key}" of type ${typeof value} is not valid. Supported is boolean.`
118
- );
119
- }
120
- break;
121
- }
122
- case "relations": {
123
- const isBoolean = typeof value === "boolean";
124
- const isArrayOfStrings = Array.isArray(value) && value?.every((relation) => typeof relation === "string");
125
- if (!(isBoolean || isArrayOfStrings)) {
126
- throw new Error(
127
- `[populate-all] config "${key}" of type ${typeof value} is not valid. Supported are boolean or Array of strings.`
128
- );
129
- }
130
- break;
131
- }
132
- default:
133
- strapi.log.warn(
134
- `[populate-all] unknown config "${key}" provided. This config will be ignored.`
135
- );
136
- }
137
- }
138
- }
139
- };
140
153
  const middlewares = {
141
154
  /**
142
155
  * This is a global middleware to add support for the custom query param `?populate=all`.
@@ -145,9 +158,12 @@ const middlewares = {
145
158
  * The bootstrap script later picks up `?populateAll=true` to apply the desired populate logic.
146
159
  */
147
160
  populateAll: async (ctx, next) => {
161
+ if (ctx.query.populateAll && ctx.query.populateAll !== "false" || ctx.query.populateAll === null) {
162
+ ctx.query._q = [ctx.query._q || "", PLUGIN_QUERY_DOCUMENT_TAG].join("");
163
+ }
148
164
  if (ctx.query.populate === "all") {
149
165
  ctx.query.populate = void 0;
150
- ctx.query.populateAll = true;
166
+ ctx.query._q = [ctx.query._q || "", PLUGIN_QUERY_DOCUMENT_TAG].join("");
151
167
  }
152
168
  await next();
153
169
  }
@@ -1,19 +1,60 @@
1
+ const PLUGIN_QUERY_DOCUMENT_TAG = "populate-all";
2
+ const config = {
3
+ default: {
4
+ relations: true
5
+ },
6
+ validator(config2 = {}) {
7
+ for (const [key, value] of Object.entries(config2)) {
8
+ switch (key) {
9
+ case "cache": {
10
+ const isBoolean = typeof value === "boolean";
11
+ if (!isBoolean) {
12
+ throw new Error(
13
+ `[populate-all] config "${key}" of type ${typeof value} is not valid. Supported is boolean.`
14
+ );
15
+ }
16
+ break;
17
+ }
18
+ case "relations": {
19
+ const isBoolean = typeof value === "boolean";
20
+ const isArrayOfStrings = Array.isArray(value) && value?.every((relation) => typeof relation === "string");
21
+ if (!(isBoolean || isArrayOfStrings)) {
22
+ throw new Error(
23
+ `[populate-all] config "${key}" of type ${typeof value} is not valid. Supported are boolean or Array of strings.`
24
+ );
25
+ }
26
+ break;
27
+ }
28
+ default:
29
+ strapi.log.warn(
30
+ `[populate-all] unknown config "${key}" provided. This config will be ignored.`
31
+ );
32
+ }
33
+ }
34
+ }
35
+ };
1
36
  const queryCache = {};
37
+ const buildCacheKey = (modelUid, parentsModelUids) => {
38
+ if (parentsModelUids.length === 0) {
39
+ return modelUid;
40
+ }
41
+ return `${parentsModelUids.join("|")}|${modelUid}`;
42
+ };
2
43
  const getPopulateQuery = (modelUid, parentsModelUids = []) => {
3
44
  try {
4
45
  const useCache = strapi.plugin("populate-all").config("cache") ?? true;
5
- if (useCache && queryCache[modelUid]) {
6
- strapi.log.debug(`[populate-all] query cache hit: ${modelUid}`);
7
- return structuredClone(queryCache[modelUid]);
46
+ const cacheKey = buildCacheKey(modelUid, parentsModelUids);
47
+ if (useCache && queryCache[cacheKey]) {
48
+ strapi.log.debug(`[populate-all] query cache hit: ${cacheKey}`);
49
+ return structuredClone(queryCache[cacheKey]);
8
50
  }
9
51
  if (parentsModelUids.includes(modelUid)) {
10
52
  strapi.log.debug(
11
53
  `[populate-all] loop detected skipping population: ${modelUid}`
12
54
  );
13
55
  return { populate: {} };
14
- } else {
15
- parentsModelUids.push(modelUid);
16
56
  }
57
+ const nextParentsModelUids = [...parentsModelUids, modelUid];
17
58
  const query = { populate: {} };
18
59
  const model = strapi.getModel(modelUid);
19
60
  for (const [fieldName, attribute] of Object.entries(
@@ -27,7 +68,7 @@ const getPopulateQuery = (modelUid, parentsModelUids = []) => {
27
68
  const components = Object.fromEntries(
28
69
  attribute.components.map((component) => [
29
70
  component,
30
- getPopulateQuery(component, parentsModelUids)
71
+ getPopulateQuery(component, nextParentsModelUids)
31
72
  ])
32
73
  );
33
74
  query.populate[fieldName] = { on: components };
@@ -36,7 +77,7 @@ const getPopulateQuery = (modelUid, parentsModelUids = []) => {
36
77
  if (attribute.type === "component") {
37
78
  query.populate[fieldName] = getPopulateQuery(
38
79
  attribute.component,
39
- parentsModelUids
80
+ nextParentsModelUids
40
81
  );
41
82
  continue;
42
83
  }
@@ -49,7 +90,7 @@ const getPopulateQuery = (modelUid, parentsModelUids = []) => {
49
90
  query.populate[fieldName] = getPopulateQuery(
50
91
  // @ts-expect-error target actually exists on attribute
51
92
  attribute.target,
52
- parentsModelUids
93
+ nextParentsModelUids
53
94
  );
54
95
  continue;
55
96
  }
@@ -57,7 +98,7 @@ const getPopulateQuery = (modelUid, parentsModelUids = []) => {
57
98
  query.populate[fieldName] = getPopulateQuery(
58
99
  // @ts-expect-error target actually exists on attribute
59
100
  attribute.target,
60
- parentsModelUids
101
+ nextParentsModelUids
61
102
  );
62
103
  continue;
63
104
  }
@@ -70,8 +111,8 @@ const getPopulateQuery = (modelUid, parentsModelUids = []) => {
70
111
  query.populate = true;
71
112
  }
72
113
  if (useCache) {
73
- strapi.log.debug(`[populate-all] new query cached: ${modelUid}`);
74
- queryCache[modelUid] = query;
114
+ strapi.log.debug(`[populate-all] new query cached: ${cacheKey}`);
115
+ queryCache[cacheKey] = query;
75
116
  }
76
117
  return structuredClone(query);
77
118
  } catch (error) {
@@ -85,7 +126,7 @@ const bootstrap = ({ strapi: strapi2 }) => {
85
126
  strapi2.db.lifecycles.subscribe((event) => {
86
127
  try {
87
128
  if (event.action === "beforeFindMany" || event.action === "beforeFindOne") {
88
- if (strapi2.requestContext.get()?.query?.populateAll) {
129
+ if (event.params._q?.includes(PLUGIN_QUERY_DOCUMENT_TAG)) {
89
130
  strapi2.log.debug(
90
131
  `[populate-all] recursively populate ${event.model.uid}`
91
132
  );
@@ -93,6 +134,12 @@ const bootstrap = ({ strapi: strapi2 }) => {
93
134
  if (populateQuery?.populate) {
94
135
  event.params.populate = populateQuery.populate;
95
136
  }
137
+ const query = event.params._q.replace(PLUGIN_QUERY_DOCUMENT_TAG, "");
138
+ if (query.length > 0) {
139
+ event.params._q = query;
140
+ } else {
141
+ delete event.params._q;
142
+ }
96
143
  }
97
144
  }
98
145
  } catch (error) {
@@ -102,40 +149,6 @@ const bootstrap = ({ strapi: strapi2 }) => {
102
149
  }
103
150
  });
104
151
  };
105
- const config = {
106
- default: {
107
- relations: true
108
- },
109
- validator(config2 = {}) {
110
- for (const [key, value] of Object.entries(config2)) {
111
- switch (key) {
112
- case "cache": {
113
- const isBoolean = typeof value === "boolean";
114
- if (!isBoolean) {
115
- throw new Error(
116
- `[populate-all] config "${key}" of type ${typeof value} is not valid. Supported is boolean.`
117
- );
118
- }
119
- break;
120
- }
121
- case "relations": {
122
- const isBoolean = typeof value === "boolean";
123
- const isArrayOfStrings = Array.isArray(value) && value?.every((relation) => typeof relation === "string");
124
- if (!(isBoolean || isArrayOfStrings)) {
125
- throw new Error(
126
- `[populate-all] config "${key}" of type ${typeof value} is not valid. Supported are boolean or Array of strings.`
127
- );
128
- }
129
- break;
130
- }
131
- default:
132
- strapi.log.warn(
133
- `[populate-all] unknown config "${key}" provided. This config will be ignored.`
134
- );
135
- }
136
- }
137
- }
138
- };
139
152
  const middlewares = {
140
153
  /**
141
154
  * This is a global middleware to add support for the custom query param `?populate=all`.
@@ -144,9 +157,12 @@ const middlewares = {
144
157
  * The bootstrap script later picks up `?populateAll=true` to apply the desired populate logic.
145
158
  */
146
159
  populateAll: async (ctx, next) => {
160
+ if (ctx.query.populateAll && ctx.query.populateAll !== "false" || ctx.query.populateAll === null) {
161
+ ctx.query._q = [ctx.query._q || "", PLUGIN_QUERY_DOCUMENT_TAG].join("");
162
+ }
147
163
  if (ctx.query.populate === "all") {
148
164
  ctx.query.populate = void 0;
149
- ctx.query.populateAll = true;
165
+ ctx.query._q = [ctx.query._q || "", PLUGIN_QUERY_DOCUMENT_TAG].join("");
150
166
  }
151
167
  await next();
152
168
  }
@@ -1,3 +1,4 @@
1
+ export declare const PLUGIN_QUERY_DOCUMENT_TAG = "populate-all";
1
2
  declare const _default: {
2
3
  default: {
3
4
  relations: boolean;
@@ -1,5 +1,5 @@
1
1
  import type { UID } from "@strapi/strapi";
2
- export declare const queryCache: Partial<Record<UID.Schema, any>>;
2
+ export declare const queryCache: Record<string, any>;
3
3
  export declare const getPopulateQuery: (modelUid: UID.Schema, parentsModelUids?: UID.Schema[]) => {
4
4
  populate: object | true;
5
5
  } | undefined;
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.4.1",
2
+ "version": "1.5.1-beta.1",
3
3
  "name": "strapi-plugin-populate-all",
4
4
  "description": "A lightweight plugin to recursively populate nested data in RESTful API requests",
5
5
  "keywords": [],