strapi-plugin-populate-all 1.0.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.
package/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # Strapi Plugin Populate All
2
+
3
+ A lightweight Strapi plugin that enables you to **recursively populate** all nested components, dynamic zones and relations in your REST API responses using a simple query parameter: `?populate=all`.
4
+
5
+ ## Features
6
+
7
+ - Use `?populate=all` in your API requests to automatically and deeply populate all nested relations, components, and dynamic zones for any content type.
8
+ - Fine-tune which relations are populated using plugin configuration.
9
+ - The generated populate queries are cached, so repeated REST requests for the same model are faster.
10
+
11
+ ## Usage
12
+
13
+ Just add `?populate=all` to your REST API request, for example: `GET /api/articles?populate=all`
14
+ This will return the article with all nested components, dynamic zones and relations fully populated, regardless of depth.
15
+
16
+ ## Configuration
17
+
18
+ You can configure which relations should be populated by default.
19
+ Add the following to your Strapi project's `config/plugins.js` (or `config/plugins.ts`):
20
+
21
+ ```js
22
+ module.exports = {
23
+ 'populate-all': {
24
+ enabled: true,
25
+ config: {
26
+ // Populate all relations recursively (default)
27
+ relations: true,
28
+
29
+ // OR: Do not populate any relations
30
+ relations: false,
31
+
32
+ // OR: Only populate specific collection types by UID
33
+ relations: ['api::article.article', 'api::category.category'],
34
+ },
35
+ },
36
+ };
37
+ ```
38
+
39
+ ## How it works
40
+
41
+ - The plugin provides a global middleware that intercepts requests with `?populate=all` and rewrites the query to trigger recursive population.
42
+ - 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).
43
+ - You can control which relations are included using the relations config option.
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ const config = {
3
+ default: {
4
+ relations: true
5
+ },
6
+ validator(config2) {
7
+ const isBoolean = typeof config2?.relations === "boolean";
8
+ const isArrayOfStrings = Array.isArray(config2?.relations) && config2?.relations?.every((relation) => typeof relation === "string");
9
+ if (!(isBoolean || isArrayOfStrings)) {
10
+ throw new Error(
11
+ `[populate-all] config "relations" of type ${typeof config2?.relation} is not valid. Supported are boolean or Array of strings.`
12
+ );
13
+ }
14
+ }
15
+ };
16
+ const middlewares = {
17
+ /**
18
+ * This is a global middleware to add support for the custom query param `?populate=all`.
19
+ * Since Strapi's validator does not allow custom values for the populate param, we intercept the request here.
20
+ * If `?populate=all` is detected, we omit the value and set `?recursive=true` instead.
21
+ * The bootstrap script later picks up `?recursive=true` to apply the desired populate logic.
22
+ */
23
+ populateAll: async (ctx, next) => {
24
+ if (ctx.query.populate === "all") {
25
+ ctx.query.populate = void 0;
26
+ ctx.query.recursive = "true";
27
+ }
28
+ await next();
29
+ }
30
+ };
31
+ const register = ({ strapi: strapi2 }) => {
32
+ strapi2.server.use(middlewares.populateAll);
33
+ };
34
+ const queryCache = {};
35
+ const getPopulateQuery = (modelUid) => {
36
+ try {
37
+ if (queryCache[modelUid]) {
38
+ strapi.log.debug(`[populate-all] query cache hit: ${modelUid}`);
39
+ return queryCache[modelUid];
40
+ }
41
+ const query = { populate: {} };
42
+ const model = strapi.getModel(modelUid);
43
+ for (const fieldName in model.attributes) {
44
+ const attribute = model.attributes[fieldName];
45
+ if (fieldName === "localizations") {
46
+ continue;
47
+ }
48
+ if (attribute.type === "dynamiczone") {
49
+ const components = Object.fromEntries(
50
+ attribute.components.map((component) => [component, getPopulateQuery(component)])
51
+ );
52
+ query.populate[fieldName] = { on: components };
53
+ continue;
54
+ }
55
+ if (attribute.type === "component") {
56
+ query.populate[fieldName] = getPopulateQuery(attribute.component);
57
+ continue;
58
+ }
59
+ if (attribute.type === "relation") {
60
+ const relations = strapi.plugin("populate-all").config("relations");
61
+ strapi.log.debug(`[populate-all] relations to populate ${JSON.stringify(relations)}`);
62
+ if (relations === true) {
63
+ query.populate[fieldName] = getPopulateQuery(attribute.target);
64
+ continue;
65
+ }
66
+ if (Array.isArray(relations) && relations.includes(attribute.target)) {
67
+ query.populate[fieldName] = getPopulateQuery(attribute.target);
68
+ continue;
69
+ }
70
+ }
71
+ if (attribute.type === "media") {
72
+ query.populate[fieldName] = { populate: true };
73
+ continue;
74
+ }
75
+ }
76
+ if (Object.keys(query.populate).length === 0) {
77
+ query.populate = true;
78
+ }
79
+ strapi.log.debug(`[populate-all] new query cached: ${modelUid}`);
80
+ queryCache[modelUid] = query;
81
+ return query;
82
+ } catch (error) {
83
+ strapi.log.error(`[populate-all] getPopulateQuery(${modelUid}) failed: ${error}`);
84
+ return void 0;
85
+ }
86
+ };
87
+ const bootstrap = ({ strapi: strapi2 }) => {
88
+ strapi2.db.lifecycles.subscribe((event) => {
89
+ if (event.params?.recursive === "true") {
90
+ if (event.action === "beforeFindMany" || event.action === "beforeFindOne") {
91
+ strapi2.log.debug(`[populate-all] recursively populate ${event.model.uid}`);
92
+ const populateQuery = getPopulateQuery(event.model.uid);
93
+ if (populateQuery?.populate) {
94
+ event.params.populate = populateQuery.populate;
95
+ }
96
+ }
97
+ }
98
+ });
99
+ };
100
+ const index = {
101
+ config,
102
+ register,
103
+ bootstrap
104
+ };
105
+ module.exports = index;
@@ -0,0 +1,106 @@
1
+ const config = {
2
+ default: {
3
+ relations: true
4
+ },
5
+ validator(config2) {
6
+ const isBoolean = typeof config2?.relations === "boolean";
7
+ const isArrayOfStrings = Array.isArray(config2?.relations) && config2?.relations?.every((relation) => typeof relation === "string");
8
+ if (!(isBoolean || isArrayOfStrings)) {
9
+ throw new Error(
10
+ `[populate-all] config "relations" of type ${typeof config2?.relation} is not valid. Supported are boolean or Array of strings.`
11
+ );
12
+ }
13
+ }
14
+ };
15
+ const middlewares = {
16
+ /**
17
+ * This is a global middleware to add support for the custom query param `?populate=all`.
18
+ * Since Strapi's validator does not allow custom values for the populate param, we intercept the request here.
19
+ * If `?populate=all` is detected, we omit the value and set `?recursive=true` instead.
20
+ * The bootstrap script later picks up `?recursive=true` to apply the desired populate logic.
21
+ */
22
+ populateAll: async (ctx, next) => {
23
+ if (ctx.query.populate === "all") {
24
+ ctx.query.populate = void 0;
25
+ ctx.query.recursive = "true";
26
+ }
27
+ await next();
28
+ }
29
+ };
30
+ const register = ({ strapi: strapi2 }) => {
31
+ strapi2.server.use(middlewares.populateAll);
32
+ };
33
+ const queryCache = {};
34
+ const getPopulateQuery = (modelUid) => {
35
+ try {
36
+ if (queryCache[modelUid]) {
37
+ strapi.log.debug(`[populate-all] query cache hit: ${modelUid}`);
38
+ return queryCache[modelUid];
39
+ }
40
+ const query = { populate: {} };
41
+ const model = strapi.getModel(modelUid);
42
+ for (const fieldName in model.attributes) {
43
+ const attribute = model.attributes[fieldName];
44
+ if (fieldName === "localizations") {
45
+ continue;
46
+ }
47
+ if (attribute.type === "dynamiczone") {
48
+ const components = Object.fromEntries(
49
+ attribute.components.map((component) => [component, getPopulateQuery(component)])
50
+ );
51
+ query.populate[fieldName] = { on: components };
52
+ continue;
53
+ }
54
+ if (attribute.type === "component") {
55
+ query.populate[fieldName] = getPopulateQuery(attribute.component);
56
+ continue;
57
+ }
58
+ if (attribute.type === "relation") {
59
+ const relations = strapi.plugin("populate-all").config("relations");
60
+ strapi.log.debug(`[populate-all] relations to populate ${JSON.stringify(relations)}`);
61
+ if (relations === true) {
62
+ query.populate[fieldName] = getPopulateQuery(attribute.target);
63
+ continue;
64
+ }
65
+ if (Array.isArray(relations) && relations.includes(attribute.target)) {
66
+ query.populate[fieldName] = getPopulateQuery(attribute.target);
67
+ continue;
68
+ }
69
+ }
70
+ if (attribute.type === "media") {
71
+ query.populate[fieldName] = { populate: true };
72
+ continue;
73
+ }
74
+ }
75
+ if (Object.keys(query.populate).length === 0) {
76
+ query.populate = true;
77
+ }
78
+ strapi.log.debug(`[populate-all] new query cached: ${modelUid}`);
79
+ queryCache[modelUid] = query;
80
+ return query;
81
+ } catch (error) {
82
+ strapi.log.error(`[populate-all] getPopulateQuery(${modelUid}) failed: ${error}`);
83
+ return void 0;
84
+ }
85
+ };
86
+ const bootstrap = ({ strapi: strapi2 }) => {
87
+ strapi2.db.lifecycles.subscribe((event) => {
88
+ if (event.params?.recursive === "true") {
89
+ if (event.action === "beforeFindMany" || event.action === "beforeFindOne") {
90
+ strapi2.log.debug(`[populate-all] recursively populate ${event.model.uid}`);
91
+ const populateQuery = getPopulateQuery(event.model.uid);
92
+ if (populateQuery?.populate) {
93
+ event.params.populate = populateQuery.populate;
94
+ }
95
+ }
96
+ }
97
+ });
98
+ };
99
+ const index = {
100
+ config,
101
+ register,
102
+ bootstrap
103
+ };
104
+ export {
105
+ index as default
106
+ };
@@ -0,0 +1,5 @@
1
+ import type { Core } from '@strapi/strapi';
2
+ declare const bootstrap: ({ strapi }: {
3
+ strapi: Core.Strapi;
4
+ }) => void;
5
+ export default bootstrap;
@@ -0,0 +1,7 @@
1
+ declare const _default: {
2
+ default: {
3
+ relations: boolean;
4
+ };
5
+ validator(config: any): void;
6
+ };
7
+ export default _default;
@@ -0,0 +1,15 @@
1
+ declare const _default: {
2
+ config: {
3
+ default: {
4
+ relations: boolean;
5
+ };
6
+ validator(config: any): 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;
@@ -0,0 +1,5 @@
1
+ import type { Core } from '@strapi/strapi';
2
+ declare const register: ({ strapi }: {
3
+ strapi: Core.Strapi;
4
+ }) => void;
5
+ export default register;
@@ -0,0 +1,4 @@
1
+ import type { UID } from '@strapi/strapi';
2
+ export declare const getPopulateQuery: (modelUid: UID.Schema) => {
3
+ populate: object | true;
4
+ } | undefined;
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "version": "1.0.0",
3
+ "name": "strapi-plugin-populate-all",
4
+ "description": "A lightweight plugin to recursively populate nested data in RESTful API requests",
5
+ "keywords": [],
6
+ "license": "MIT",
7
+ "author": "Jan Fässler <strapi-plugin-populate-all@faessler.be>",
8
+ "type": "commonjs",
9
+ "homepage": "https://github.com/faessler/strapi-plugin-populate-all#readme",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/faessler/strapi-plugin-populate-all.git"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/faessler/strapi-plugin-populate-all/issues"
16
+ },
17
+ "strapi": {
18
+ "kind": "plugin",
19
+ "name": "populate-all",
20
+ "displayName": "Strapi Plugin Populate All",
21
+ "description": "A lightweight plugin to recursively populate nested data in RESTful API requests"
22
+ },
23
+ "exports": {
24
+ "./package.json": "./package.json",
25
+ "./strapi-server": {
26
+ "types": "./dist/server/src/index.d.ts",
27
+ "source": "./server/src/index.ts",
28
+ "import": "./dist/server/index.mjs",
29
+ "require": "./dist/server/index.js",
30
+ "default": "./dist/server/index.js"
31
+ }
32
+ },
33
+ "files": [
34
+ "dist"
35
+ ],
36
+ "scripts": {
37
+ "build": "strapi-plugin build",
38
+ "watch": "strapi-plugin watch",
39
+ "watch:link": "strapi-plugin watch:link",
40
+ "verify": "strapi-plugin verify",
41
+ "test:ts:back": "run -T tsc -p server/tsconfig.json"
42
+ },
43
+ "dependencies": {},
44
+ "devDependencies": {
45
+ "@strapi/strapi": "^5.13.1",
46
+ "@strapi/sdk-plugin": "^5.3.2",
47
+ "prettier": "^3.5.3",
48
+ "@strapi/typescript-utils": "^5.13.1",
49
+ "typescript": "^5.8.3"
50
+ },
51
+ "peerDependencies": {
52
+ "@strapi/strapi": "^5.13.1",
53
+ "@strapi/sdk-plugin": "^5.3.2"
54
+ }
55
+ }