strapi-plugin-populate-all 1.2.1 → 1.2.3
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 +1 -0
- package/dist/server/index.js +151 -0
- package/dist/server/index.mjs +152 -0
- package/dist/server/src/bootstrap.d.ts +5 -0
- package/dist/server/src/config/index.d.ts +7 -0
- package/dist/server/src/index.d.ts +15 -0
- package/dist/server/src/middlewares/index.d.ts +10 -0
- package/dist/server/src/register.d.ts +5 -0
- package/dist/server/src/utils/getPopulateQuery.d.ts +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,3 +49,4 @@ module.exports = {
|
|
|
49
49
|
- The plugin provides a global middleware that intercepts requests with `?populate=all` and rewrites the query to trigger recursive population.
|
|
50
50
|
- 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).
|
|
51
51
|
- You can control which relations are included using the relations config option.
|
|
52
|
+
- Inside the document API, you can set `populate: '*'` and `recursive: true` to make it work
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const queryCache = {};
|
|
3
|
+
const getPopulateQuery = (modelUid, parentsModelUids = []) => {
|
|
4
|
+
try {
|
|
5
|
+
if (queryCache[modelUid]) {
|
|
6
|
+
strapi.log.debug(`[populate-all] query cache hit: ${modelUid}`);
|
|
7
|
+
return queryCache[modelUid];
|
|
8
|
+
}
|
|
9
|
+
if (parentsModelUids.includes(modelUid)) {
|
|
10
|
+
strapi.log.debug(
|
|
11
|
+
`[populate-all] loop detected skipping population: ${modelUid}`
|
|
12
|
+
);
|
|
13
|
+
return { populate: {} };
|
|
14
|
+
} else {
|
|
15
|
+
parentsModelUids.push(modelUid);
|
|
16
|
+
}
|
|
17
|
+
const query = { populate: {} };
|
|
18
|
+
const model = strapi.getModel(modelUid);
|
|
19
|
+
for (const [fieldName, attribute] of Object.entries(
|
|
20
|
+
model.attributes || {}
|
|
21
|
+
)) {
|
|
22
|
+
if (fieldName === "localizations") {
|
|
23
|
+
query.populate[fieldName] = true;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (attribute.type === "dynamiczone") {
|
|
27
|
+
const components = Object.fromEntries(
|
|
28
|
+
attribute.components.map((component) => [
|
|
29
|
+
component,
|
|
30
|
+
getPopulateQuery(component, parentsModelUids)
|
|
31
|
+
])
|
|
32
|
+
);
|
|
33
|
+
query.populate[fieldName] = { on: components };
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (attribute.type === "component") {
|
|
37
|
+
query.populate[fieldName] = getPopulateQuery(
|
|
38
|
+
attribute.component,
|
|
39
|
+
parentsModelUids
|
|
40
|
+
);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (attribute.type === "relation") {
|
|
44
|
+
if (attribute.private === true) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const relations = strapi.plugin("populate-all").config("relations");
|
|
48
|
+
if (relations === true) {
|
|
49
|
+
query.populate[fieldName] = getPopulateQuery(
|
|
50
|
+
// @ts-expect-error target actually exists on attribute
|
|
51
|
+
attribute.target,
|
|
52
|
+
parentsModelUids
|
|
53
|
+
);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (Array.isArray(relations) && relations.includes(attribute.target)) {
|
|
57
|
+
query.populate[fieldName] = getPopulateQuery(
|
|
58
|
+
// @ts-expect-error target actually exists on attribute
|
|
59
|
+
attribute.target,
|
|
60
|
+
parentsModelUids
|
|
61
|
+
);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (attribute.type === "media") {
|
|
66
|
+
query.populate[fieldName] = true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (Object.keys(query.populate).length === 0) {
|
|
70
|
+
query.populate = true;
|
|
71
|
+
}
|
|
72
|
+
strapi.log.debug(`[populate-all] new query cached: ${modelUid}`);
|
|
73
|
+
queryCache[modelUid] = query;
|
|
74
|
+
return query;
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error(
|
|
77
|
+
`[populate-all] getPopulateQuery(${modelUid}) failed: ${error}`
|
|
78
|
+
);
|
|
79
|
+
return void 0;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const bootstrap = ({ strapi: strapi2 }) => {
|
|
83
|
+
strapi2.db.lifecycles.subscribe((event) => {
|
|
84
|
+
try {
|
|
85
|
+
if (event.action === "beforeFindMany" || event.action === "beforeFindOne") {
|
|
86
|
+
if (event.params?.populateAll) {
|
|
87
|
+
strapi2.log.debug(
|
|
88
|
+
`[populate-all] recursively populate ${event.model.uid}`
|
|
89
|
+
);
|
|
90
|
+
const populateQuery = getPopulateQuery(event.model.uid);
|
|
91
|
+
if (populateQuery?.populate) {
|
|
92
|
+
event.params.populate = populateQuery.populate;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error(
|
|
98
|
+
`[populate-all] failed to apply populate db query: ${error}`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
const config = {
|
|
104
|
+
default: {
|
|
105
|
+
relations: true
|
|
106
|
+
},
|
|
107
|
+
validator(config2 = {}) {
|
|
108
|
+
for (const [key, value] of Object.entries(config2)) {
|
|
109
|
+
switch (key) {
|
|
110
|
+
case "relations": {
|
|
111
|
+
const isBoolean = typeof value === "boolean";
|
|
112
|
+
const isArrayOfStrings = Array.isArray(value) && value?.every((relation) => typeof relation === "string");
|
|
113
|
+
if (!(isBoolean || isArrayOfStrings)) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
`[populate-all] config "${key}" of type ${typeof value} is not valid. Supported are boolean or Array of strings.`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
default:
|
|
121
|
+
strapi.log.warn(
|
|
122
|
+
`[populate-all] unknown config "${key}" provided. This config will be ignored.`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const middlewares = {
|
|
129
|
+
/**
|
|
130
|
+
* This is a global middleware to add support for the custom query param `?populate=all`.
|
|
131
|
+
* Since Strapi's validator does not allow custom values for the populate param, we intercept the request here.
|
|
132
|
+
* If `?populate=all` is detected, we omit the value and set `?recursive=true` instead.
|
|
133
|
+
* The bootstrap script later picks up `?recursive=true` to apply the desired populate logic.
|
|
134
|
+
*/
|
|
135
|
+
populateAll: async (ctx, next) => {
|
|
136
|
+
if (ctx.query.populate === "all") {
|
|
137
|
+
ctx.query.populate = void 0;
|
|
138
|
+
ctx.query.populateAll = true;
|
|
139
|
+
}
|
|
140
|
+
await next();
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
const register = ({ strapi: strapi2 }) => {
|
|
144
|
+
strapi2.server.use(middlewares.populateAll);
|
|
145
|
+
};
|
|
146
|
+
const index = {
|
|
147
|
+
config,
|
|
148
|
+
register,
|
|
149
|
+
bootstrap
|
|
150
|
+
};
|
|
151
|
+
module.exports = index;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
const queryCache = {};
|
|
2
|
+
const getPopulateQuery = (modelUid, parentsModelUids = []) => {
|
|
3
|
+
try {
|
|
4
|
+
if (queryCache[modelUid]) {
|
|
5
|
+
strapi.log.debug(`[populate-all] query cache hit: ${modelUid}`);
|
|
6
|
+
return queryCache[modelUid];
|
|
7
|
+
}
|
|
8
|
+
if (parentsModelUids.includes(modelUid)) {
|
|
9
|
+
strapi.log.debug(
|
|
10
|
+
`[populate-all] loop detected skipping population: ${modelUid}`
|
|
11
|
+
);
|
|
12
|
+
return { populate: {} };
|
|
13
|
+
} else {
|
|
14
|
+
parentsModelUids.push(modelUid);
|
|
15
|
+
}
|
|
16
|
+
const query = { populate: {} };
|
|
17
|
+
const model = strapi.getModel(modelUid);
|
|
18
|
+
for (const [fieldName, attribute] of Object.entries(
|
|
19
|
+
model.attributes || {}
|
|
20
|
+
)) {
|
|
21
|
+
if (fieldName === "localizations") {
|
|
22
|
+
query.populate[fieldName] = true;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (attribute.type === "dynamiczone") {
|
|
26
|
+
const components = Object.fromEntries(
|
|
27
|
+
attribute.components.map((component) => [
|
|
28
|
+
component,
|
|
29
|
+
getPopulateQuery(component, parentsModelUids)
|
|
30
|
+
])
|
|
31
|
+
);
|
|
32
|
+
query.populate[fieldName] = { on: components };
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (attribute.type === "component") {
|
|
36
|
+
query.populate[fieldName] = getPopulateQuery(
|
|
37
|
+
attribute.component,
|
|
38
|
+
parentsModelUids
|
|
39
|
+
);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (attribute.type === "relation") {
|
|
43
|
+
if (attribute.private === true) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const relations = strapi.plugin("populate-all").config("relations");
|
|
47
|
+
if (relations === true) {
|
|
48
|
+
query.populate[fieldName] = getPopulateQuery(
|
|
49
|
+
// @ts-expect-error target actually exists on attribute
|
|
50
|
+
attribute.target,
|
|
51
|
+
parentsModelUids
|
|
52
|
+
);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (Array.isArray(relations) && relations.includes(attribute.target)) {
|
|
56
|
+
query.populate[fieldName] = getPopulateQuery(
|
|
57
|
+
// @ts-expect-error target actually exists on attribute
|
|
58
|
+
attribute.target,
|
|
59
|
+
parentsModelUids
|
|
60
|
+
);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (attribute.type === "media") {
|
|
65
|
+
query.populate[fieldName] = true;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (Object.keys(query.populate).length === 0) {
|
|
69
|
+
query.populate = true;
|
|
70
|
+
}
|
|
71
|
+
strapi.log.debug(`[populate-all] new query cached: ${modelUid}`);
|
|
72
|
+
queryCache[modelUid] = query;
|
|
73
|
+
return query;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error(
|
|
76
|
+
`[populate-all] getPopulateQuery(${modelUid}) failed: ${error}`
|
|
77
|
+
);
|
|
78
|
+
return void 0;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const bootstrap = ({ strapi: strapi2 }) => {
|
|
82
|
+
strapi2.db.lifecycles.subscribe((event) => {
|
|
83
|
+
try {
|
|
84
|
+
if (event.action === "beforeFindMany" || event.action === "beforeFindOne") {
|
|
85
|
+
if (event.params?.populateAll) {
|
|
86
|
+
strapi2.log.debug(
|
|
87
|
+
`[populate-all] recursively populate ${event.model.uid}`
|
|
88
|
+
);
|
|
89
|
+
const populateQuery = getPopulateQuery(event.model.uid);
|
|
90
|
+
if (populateQuery?.populate) {
|
|
91
|
+
event.params.populate = populateQuery.populate;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.error(
|
|
97
|
+
`[populate-all] failed to apply populate db query: ${error}`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
const config = {
|
|
103
|
+
default: {
|
|
104
|
+
relations: true
|
|
105
|
+
},
|
|
106
|
+
validator(config2 = {}) {
|
|
107
|
+
for (const [key, value] of Object.entries(config2)) {
|
|
108
|
+
switch (key) {
|
|
109
|
+
case "relations": {
|
|
110
|
+
const isBoolean = typeof value === "boolean";
|
|
111
|
+
const isArrayOfStrings = Array.isArray(value) && value?.every((relation) => typeof relation === "string");
|
|
112
|
+
if (!(isBoolean || isArrayOfStrings)) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`[populate-all] config "${key}" of type ${typeof value} is not valid. Supported are boolean or Array of strings.`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
default:
|
|
120
|
+
strapi.log.warn(
|
|
121
|
+
`[populate-all] unknown config "${key}" provided. This config will be ignored.`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
const middlewares = {
|
|
128
|
+
/**
|
|
129
|
+
* This is a global middleware to add support for the custom query param `?populate=all`.
|
|
130
|
+
* Since Strapi's validator does not allow custom values for the populate param, we intercept the request here.
|
|
131
|
+
* If `?populate=all` is detected, we omit the value and set `?recursive=true` instead.
|
|
132
|
+
* The bootstrap script later picks up `?recursive=true` to apply the desired populate logic.
|
|
133
|
+
*/
|
|
134
|
+
populateAll: async (ctx, next) => {
|
|
135
|
+
if (ctx.query.populate === "all") {
|
|
136
|
+
ctx.query.populate = void 0;
|
|
137
|
+
ctx.query.populateAll = true;
|
|
138
|
+
}
|
|
139
|
+
await next();
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
const register = ({ strapi: strapi2 }) => {
|
|
143
|
+
strapi2.server.use(middlewares.populateAll);
|
|
144
|
+
};
|
|
145
|
+
const index = {
|
|
146
|
+
config,
|
|
147
|
+
register,
|
|
148
|
+
bootstrap
|
|
149
|
+
};
|
|
150
|
+
export {
|
|
151
|
+
index as default
|
|
152
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
config: {
|
|
3
|
+
default: {
|
|
4
|
+
relations: boolean;
|
|
5
|
+
};
|
|
6
|
+
validator(config?: Record<string, unknown>): void;
|
|
7
|
+
};
|
|
8
|
+
register: ({ strapi }: {
|
|
9
|
+
strapi: import("@strapi/types/dist/core").Strapi;
|
|
10
|
+
}) => void;
|
|
11
|
+
bootstrap: ({ strapi }: {
|
|
12
|
+
strapi: import("@strapi/types/dist/core").Strapi;
|
|
13
|
+
}) => void;
|
|
14
|
+
};
|
|
15
|
+
export default _default;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
/**
|
|
3
|
+
* This is a global middleware to add support for the custom query param `?populate=all`.
|
|
4
|
+
* Since Strapi's validator does not allow custom values for the populate param, we intercept the request here.
|
|
5
|
+
* If `?populate=all` is detected, we omit the value and set `?recursive=true` instead.
|
|
6
|
+
* The bootstrap script later picks up `?recursive=true` to apply the desired populate logic.
|
|
7
|
+
*/
|
|
8
|
+
populateAll: (ctx: any, next: any) => Promise<void>;
|
|
9
|
+
};
|
|
10
|
+
export default _default;
|
package/package.json
CHANGED