Repository: strapi/strapi
Pull request #23487: feat: add rules engine for json logic schema validation and tests
Author: login: butcherZ
Labels: name: source: core:utils
description: Source is core/utils or utils packages
name: pr: enhancement
description: This PR adds or updates some part of the codebase or features
Created: 2025-05-07T14:56:10Z | Merged: 2025-05-09T12:40:41Z



### What does it do?

create rules engine for generate validate schema based on json logic

### How to test it?

test with rules-engine.test.ts

### Related issue(s)/PR(s)

dx-1989



Diff:
diff --git a/packages/core/utils/package.json b/packages/core/utils/package.json
index 7c027337e469..859336992799 100644
--- a/packages/core/utils/package.json
+++ b/packages/core/utils/package.json
@@ -50,6 +50,7 @@
     "date-fns": "2.30.0",
     "execa": "5.1.1",
     "http-errors": "2.0.0",
+    "json-logic-js": "2.0.5",
     "lodash": "4.17.21",
     "node-machine-id": "1.1.12",
     "p-map": "4.0.0",
@@ -59,6 +60,7 @@
   },
   "devDependencies": {
     "@types/http-errors": "2.0.4",
+    "@types/json-logic-js": "2.0.8",
     "@types/koa": "2.13.4",
     "@types/node": "18.19.24",
     "eslint-config-custom": "5.13.0",
diff --git a/packages/core/utils/src/__tests__/rules-engine.test.ts b/packages/core/utils/src/__tests__/rules-engine.test.ts
new file mode 100644
index 000000000000..8ab5b4aa1264
--- /dev/null
+++ b/packages/core/utils/src/__tests__/rules-engine.test.ts
@@ -0,0 +1,79 @@
+import { createRulesEngine, Condition } from '../rules-engine';
+
+describe('RulesEngine with is & isNot operator', () => {
+  const engine = createRulesEngine();
+
+  describe('validate()', () => {
+    it('should validate a correct condition', () => {
+      const condition: Condition = { dependsOn: 'type', operator: 'is', value: 'foo' };
+      expect(() => engine.validate(condition)).not.toThrow();
+    });
+
+    it('should throw on invalid condition', () => {
+      const invalidCondition = { dependsOn: '', operator: 'is', value: 'bar' };
+      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+      // @ts-expect-error
+      expect(() => engine.validate(invalidCondition)).toThrowError();
+    });
+  });
+
+  describe('generate() and evaluate()', () => {
+    const cases: { condition: Condition; data: any; expected: boolean }[] = [
+      {
+        condition: { dependsOn: 'type', operator: 'is', value: 'international' },
+        data: { type: 'international' },
+        expected: true,
+      },
+      {
+        condition: { dependsOn: 'type', operator: 'is', value: 'international' },
+        data: { type: 'local' },
+        expected: false,
+      },
+      {
+        condition: { dependsOn: 'type', operator: 'isNot', value: 'draft' },
+        data: { type: 'published' },
+        expected: true,
+      },
+      {
+        condition: { dependsOn: 'type', operator: 'isNot', value: 'published' },
+        data: { type: 'published' },
+        expected: false,
+      },
+      {
+        condition: { dependsOn: 'age', operator: 'is', value: 18 },
+        data: { age: 18 },
+        expected: true,
+      },
+      {
+        condition: { dependsOn: 'age', operator: 'isNot', value: 18 },
+        data: { age: 18 },
+        expected: false,
+      },
+      {
+        condition: { dependsOn: 'isActive', operator: 'is', value: true },
+        data: { isActive: true },
+        expected: true,
+      },
+      {
+        condition: { dependsOn: 'isActive', operator: 'isNot', value: false },
+        data: { isActive: true },
+        expected: true,
+      },
+    ];
+
+    cases.forEach(({ condition, data, expected }, idx) => {
+      it(`should correctly evaluate case #${idx + 1}`, () => {
+        const logic = engine.generate(condition);
+        expect(engine.evaluate(logic, data)).toBe(expected);
+      });
+    });
+  });
+
+  describe('evaluate() error handling', () => {
+    it('should throw on invalid logic input', () => {
+      expect(() => engine.evaluate({ foo: 'bar' } as any, {})).toThrow(
+        'Invalid condition: Unrecognized operation foo'
+      );
+    });
+  });
+});
diff --git a/packages/core/utils/src/index.ts b/packages/core/utils/src/index.ts
index 01280d920c93..7c3449819fae 100644
--- a/packages/core/utils/src/index.ts
+++ b/packages/core/utils/src/index.ts
@@ -24,6 +24,7 @@ export * as errors from './errors';
 export * as contentTypes from './content-types';
 export * as relations from './relations';
 export * as hooks from './hooks';
+export * as rulesEngine from './rules-engine';
 export * from './zod';
 
 export * from './primitives';
diff --git a/packages/core/utils/src/rules-engine.ts b/packages/core/utils/src/rules-engine.ts
new file mode 100644
index 000000000000..cfc05f3991e1
--- /dev/null
+++ b/packages/core/utils/src/rules-engine.ts
@@ -0,0 +1,73 @@
+import jsonLogic from 'json-logic-js';
+import { z } from 'zod';
+
+export const ConditionSchema = z.object({
+  dependsOn: z.string().min(1),
+  operator: z.enum(['is', 'isNot']),
+  value: z.union([z.string(), z.number(), z.boolean()]),
+});
+
+export type Condition = z.infer<typeof ConditionSchema>;
+export type JsonLogicCondition = jsonLogic.RulesLogic<jsonLogic.AdditionalOperation>;
+export type RulesEngine = {
+  generate: (condition: Condition) => JsonLogicCondition;
+  validate: (condition: Condition) => void;
+  evaluate: (condition: JsonLogicCondition, data: unknown) => boolean;
+};
+
+export function createRulesEngine(): RulesEngine {
+  /**
+   * Transforms a high-level `Condition` object into a JSON Logic-compatible condition.
+   *
+   * Converts operators like 'is' and 'isNot' into their JSON Logic equivalents ('==' and '!=').
+   * Throws an error if the operator is not supported.
+   *
+   * @param condition - The condition object to convert.
+   * @returns A JSON Logic AST representing the condition.
+   * @throws {Error} If the operator is not recognized.
+   */
+  const generate = (condition: Condition): JsonLogicCondition => {
+    const { dependsOn, operator, value } = condition;
+    const operatorsMap = {
+      is: '==',
+      isNot: '!=',
+    };
+    if (!operatorsMap[operator]) {
+      throw new Error(`Invalid operator: ${operator}`);
+    }
+    return { [operatorsMap[operator]]: [{ var: dependsOn }, value] };
+  };
+
+  /**
+   * Validates a condition object against the `ConditionSchema`.
+   *
+   * Ensures that the condition adheres to the expected structure and types.
+   *
+   * @param condition - The condition object to validate.
+   * @throws {ZodError} If the condition is invalid.
+   */
+  const validate = (condition: Condition) => {
+    ConditionSchema.parse(condition);
+  };
+
+  /**
+   * Evaluates a JSON Logic condition against provided data.
+   * @throws {Error} If the condition is invalid.
+   */
+  const evaluate = (
+    condition: jsonLogic.RulesLogic<jsonLogic.AdditionalOperation>,
+    data: unknown
+  ): boolean => {
+    try {
+      return jsonLogic.apply(condition, data);
+    } catch (err: any) {
+      throw new Error(`Invalid condition: ${err.message}`);
+    }
+  };
+
+  return {
+    generate,
+    validate,
+    evaluate,
+  };
+}
diff --git a/yarn.lock b/yarn.lock
index bf4ccd3f8994..3d8a7a310a12 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9855,12 +9855,14 @@ __metadata:
   dependencies:
     "@sindresorhus/slugify": "npm:1.1.0"
     "@types/http-errors": "npm:2.0.4"
+    "@types/json-logic-js": "npm:2.0.8"
     "@types/koa": "npm:2.13.4"
     "@types/node": "npm:18.19.24"
     date-fns: "npm:2.30.0"
     eslint-config-custom: "npm:5.13.0"
     execa: "npm:5.1.1"
     http-errors: "npm:2.0.0"
+    json-logic-js: "npm:2.0.5"
     koa: "npm:2.15.4"
     koa-body: "npm:6.0.1"
     lodash: "npm:4.17.21"
@@ -10888,6 +10890,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@types/json-logic-js@npm:2.0.8":
+  version: 2.0.8
+  resolution: "@types/json-logic-js@npm:2.0.8"
+  checksum: 10c0/b1fc0b39242b8f1f939c86f47bfda213e1eb57686ee4dfbe11057df88fad0dbe0abc3582b72429975e8fb07b0267959d98341e5079ba945345931973538d2f43
+  languageName: node
+  linkType: hard
+
 "@types/json-schema@npm:*, @types/json-schema@npm:^7.0.12, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9":
   version: 7.0.13
   resolution: "@types/json-schema@npm:7.0.13"
@@ -21802,6 +21811,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"json-logic-js@npm:2.0.5":
+  version: 2.0.5
+  resolution: "json-logic-js@npm:2.0.5"
+  checksum: 10c0/c80d96a9f704dac2f4488c7119d9c94cbf67adbd93cb47eaa5b7eadc4947f18ed6d62fd5555f245cc2f87525f9e8d7bd5f70bd9e7fba2c70366e63bd68010f23
+  languageName: node
+  linkType: hard
+
 "json-parse-better-errors@npm:^1.0.1":
   version: 1.0.2
   resolution: "json-parse-better-errors@npm:1.0.2"


Reviews:
user: login: innerdvations
body: LGTM! Just one comment
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T10:56:40Z
user: login: Bassel17
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-09T11:04:09Z

Review comments:
diff_hunk: @@ -0,0 +1,73 @@
+import jsonLogic from 'json-logic-js';
+import { z } from 'zod';
+
+export const ConditionSchema = z.object({
+  dependsOn: z.string().min(1),
+  operator: z.enum(['is', 'isNot']),
+  value: z.union([z.string(), z.number(), z.boolean()]),
+});
+
+export type Condition = z.infer<typeof ConditionSchema>;
+export type JsonLogicCondition = jsonLogic.RulesLogic<jsonLogic.AdditionalOperation>;
+export type RulesEngine = {
+  generate: (condition: Condition) => JsonLogicCondition;
+  validate: (condition: Condition) => void;
+  evaluate: (condition: JsonLogicCondition, data: unknown) => boolean;
+};
+
+export function createRulesEngine(): RulesEngine {
+  /**
+   * Transforms a high-level `Condition` object into a JSON Logic-compatible condition.
+   *
+   * Converts operators like 'is' and 'isNot' into their JSON Logic equivalents ('==' and '!=').
path: packages/core/utils/src/rules-engine.ts
user: login: innerdvations
body: Do we actually want to do this? If we keep it the same format as JSON logic we can state that we use their format, and point to their docs -- even if it's not exposed to users yet/ever, it would still be nice internally to not have to think about what we've changed.
created_at: 2025-05-09T10:46:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,73 @@
+import jsonLogic from 'json-logic-js';
+import { z } from 'zod';
+
+export const ConditionSchema = z.object({
+  dependsOn: z.string().min(1),
+  operator: z.enum(['is', 'isNot']),
+  value: z.union([z.string(), z.number(), z.boolean()]),
+});
+
+export type Condition = z.infer<typeof ConditionSchema>;
+export type JsonLogicCondition = jsonLogic.RulesLogic<jsonLogic.AdditionalOperation>;
+export type RulesEngine = {
+  generate: (condition: Condition) => JsonLogicCondition;
+  validate: (condition: Condition) => void;
+  evaluate: (condition: JsonLogicCondition, data: unknown) => boolean;
+};
+
+export function createRulesEngine(): RulesEngine {
+  /**
+   * Transforms a high-level `Condition` object into a JSON Logic-compatible condition.
+   *
+   * Converts operators like 'is' and 'isNot' into their JSON Logic equivalents ('==' and '!=').
path: packages/core/utils/src/rules-engine.ts
user: login: Bassel17
body: yeah good question, not a 100% sure what we'll end up with in the end, but it serves the purpose of simplifying a bit instead of having to create the structure multiple times in different places, might be something we move to the CTB internally only, will all be clearer once we use it 
created_at: 2025-05-09T11:04:09Z
author_association: MEMBER

PR comments:
user: login: vercel[bot]
created_at: 2025-05-07T14:56:16Z
body: [vc]: #zRyob1vCScfFgZ54cuxfDRmsO+UBt4Hl480+/r3oRG4=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJjb250cmlidXRvci1kb2NzIiwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL3N0cmFwaWpzL2NvbnRyaWJ1dG9yLWRvY3MvQmVwcnM1Q3F0eUVSWkNOejRzWDR2dXU1cTVGWSIsInByZXZpZXdVcmwiOiJjb250cmlidXRvci1kb2NzLWdpdC1mZWF0LWNmLXNjaGVtYS12YWxpZGF0aW9uLXN0cmFwaWpzLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiY29udHJpYnV0b3ItZG9jcy1naXQtZmVhdC1jZi1zY2hlbWEtdmFsaWRhdGlvbi1zdHJhcGlqcy52ZXJjZWwuYXBwIn0sInJvb3REaXJlY3RvcnkiOm51bGx9XX0=
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **contributor-docs** | ✅ Ready ([Inspect](https://vercel.com/strapijs/contributor-docs/Beprs5CqtyERZCNz4sX4vuu5q5FY)) | [Visit Preview](https://contributor-docs-git-feat-cf-schema-validation-strapijs.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/contributor-docs-git-feat-cf-schema-validation-strapijs.vercel.app?via=pr-comment-feedback-link) | May 8, 2025 1:26pm |


author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-07T15:05:45Z
body: **Size Change:** +3.42 kB (+0.03%) 

**Total Size:** 11.6 MB

| Filename | Size | Change |  |
| :--- | :---: | :---: | :---: |
| `examples/getstarted/build/App-***********` | 8.39 kB | +3.04 kB (+56.98%) | 🆘 |
| `examples/getstarted/build/CreatePage-***********` | 251 B | -1.65 kB (-86.77%) | 🏆 |
| `examples/getstarted/build/CreateView-***********` | 305 B | -58 B (-15.98%) | 👏 |
| `examples/getstarted/build/HomePage-***********` | 1.97 kB | +1.74 kB (+754.11%) | 🆘 |
| `examples/getstarted/build/index-***********` | 1.92 kB | -2.43 kB (-55.86%) | 🏆 |
| `examples/getstarted/build/ListPage-***********` | 257 B | -1.66 kB (-86.58%) | 🏆 |
| `examples/getstarted/build/ListView-***********` | 5.6 kB | +4.24 kB (+312.38%) | 🆘 |
| `examples/getstarted/build/Settings-***********` | 1.82 kB | +209 B (+13%) | ⚠️ |
| `examples/getstarted/build/strapi-***********` | 1.74 MB | +15 B (0%) |  |

<details><summary>ℹ️ <strong>View Unchanged</strong></summary>

| Filename | Size | Change |
| :--- | :---: | :---: |
| `examples/getstarted/build/_arrayIncludesWith-***********` | 123 B | 0 B |
| `examples/getstarted/build/_baseEach-***********` | 221 B | -2 B (-0.9%) |
| `examples/getstarted/build/_baseMap-***********` | 192 B | +2 B (+1.05%) |
| `examples/getstarted/build/AdminSeatInfo-***********` | 832 B | -3 B (-0.36%) |
| `examples/getstarted/build/apiTokens-***********` | 442 B | -2 B (-0.45%) |
| `examples/getstarted/build/ApplicationInfoPage-***********` | 4.29 kB | -1 B (-0.02%) |
| `examples/getstarted/build/ar.json-***********` | 16.4 kB | 0 B |
| `examples/getstarted/build/AuthenticatedLayout-***********` | 15.6 kB | -3 B (-0.02%) |
| `examples/getstarted/build/AuthResponse-***********` | 419 B | -2 B (-0.48%) |
| `examples/getstarted/build/ca.json-***********` | 3.76 kB | 0 B |
| `examples/getstarted/build/CardDragPreview-***********` | 567 B | -1 B (-0.18%) |
| `examples/getstarted/build/ColorPickerInput-***********` | 4.23 kB | -1 B (-0.02%) |
| `examples/getstarted/build/ComponentConfigurationPage-***********` | 1.5 kB | -2 B (-0.13%) |
| `examples/getstarted/build/ConfigureTheView-***********` | 1.53 kB | -1 B (-0.07%) |
| `examples/getstarted/build/constants-***********` | 72 B | 0 B |
| `examples/getstarted/build/CreateActionEE-***********` | 503 B | 0 B |
| `examples/getstarted/build/cs.json-***********` | 338 B | 0 B |
| `examples/getstarted/build/de.json-***********` | 1.49 kB | 0 B |
| `examples/getstarted/build/dk.json-***********` | 7.61 kB | 0 B |
| `examples/getstarted/build/dummy-preview-***********` | 947 B | -1 B (-0.11%) |
| `examples/getstarted/build/EditConfigurationPage-***********` | 1.06 kB | -1 B (-0.09%) |
| `examples/getstarted/build/EditPage-***********` | 4.68 kB | +2 B (+0.04%) |
| `examples/getstarted/build/EditView-***********` | 2.04 kB | +1 B (+0.05%) |
| `examples/getstarted/build/EditViewPage-***********` | 5.15 kB | +2 B (+0.04%) |
| `examples/getstarted/build/en-***********` | 74 B | 0 B |
| `examples/getstarted/build/en-GB-***********` | 12.9 kB | 0 B |
| `examples/getstarted/build/en.json-***********` | 381 B | 0 B |
| `examples/getstarted/build/es-***********` | 74 B | 0 B |
| `examples/getstarted/build/es.json-***********` | 3.61 kB | 0 B |
| `examples/getstarted/build/eu.json-***********` | 3.7 kB | 0 B |
| `examples/getstarted/build/EventsTable-***********` | 389 B | 0 B |
| `examples/getstarted/build/FieldTypeIcon-***********` | 454 B | 0 B |
| `examples/getstarted/build/Form-***********` | 4.53 kB | -1 B (-0.02%) |
| `examples/getstarted/build/fr.json-***********` | 794 B | 0 B |
| `examples/getstarted/build/getTrad-***********` | 381 B | -2 B (-0.52%) |
| `examples/getstarted/build/groupBy-***********` | 373 B | +3 B (+0.81%) |
| `examples/getstarted/build/gu.json-***********` | 9.57 kB | 0 B |
| `examples/getstarted/build/he.json-***********` | 1.63 kB | 0 B |
| `examples/getstarted/build/hi.json-***********` | 11.6 kB | 0 B |
| `examples/getstarted/build/History-***********` | 6.12 kB | 0 B |
| `examples/getstarted/build/hooks-***********` | 82 B | 0 B |
| `examples/getstarted/build/hu.json-***********` | 3.98 kB | 0 B |
| `examples/getstarted/build/id-***********` | 7.5 kB | -1 B (-0.01%) |
| `examples/getstarted/build/id.json-***********` | 3.21 kB | 0 B |
| `examples/getstarted/build/index.html` | 592 B | -2 B (-0.34%) |
| `examples/getstarted/build/InstalledPlugins-***********` | 775 B | -1 B (-0.13%) |
| `examples/getstarted/build/isNil-***********` | 122 B | -1 B (-0.81%) |
| `examples/getstarted/build/it.json-***********` | 5.51 kB | 0 B |
| `examples/getstarted/build/ja.json-***********` | 1.41 kB | 0 B |
| `examples/getstarted/build/ko.json-***********` | 2.1 kB | 0 B |
| `examples/getstarted/build/layout-***********` | 3.25 kB | +2 B (+0.06%) |
| `examples/getstarted/build/Layout-***********` | 1.66 kB | 0 B |
| `examples/getstarted/build/ListConfigurationPage-***********` | 4.22 kB | +1 B (+0.02%) |
| `examples/getstarted/build/ListViewPage-***********` | 5.96 kB | 0 B |
| `examples/getstarted/build/Login-***********` | 458 B | -2 B (-0.43%) |
| `examples/getstarted/build/lt-***********` | 2.82 kB | -2 B (-0.07%) |
| `examples/getstarted/build/MagicLinkEE-***********` | 388 B | 0 B |
| `examples/getstarted/build/MarketplacePage-***********` | 13.8 kB | 0 B |
| `examples/getstarted/build/ml.json-***********` | 11.9 kB | 0 B |
| `examples/getstarted/build/ModalForm-***********` | 243 B | 0 B |
| `examples/getstarted/build/ms.json-***********` | 1.18 kB | 0 B |
| `examples/getstarted/build/nl.json-***********` | 1.23 kB | 0 B |
| `examples/getstarted/build/no.json-***********` | 5.47 kB | 0 B |
| `examples/getstarted/build/NoContentTypePage-***********` | 454 B | -1 B (-0.22%) |
| `examples/getstarted/build/NoPermissionsPage-***********` | 231 B | -2 B (-0.86%) |
| `examples/getstarted/build/Ornaments-***********` | 1.5 kB | 0 B |
| `examples/getstarted/build/Permissions-***********` | 9.33 kB | 0 B |
| `examples/getstarted/build/pl.json-***********` | 2.09 kB | 0 B |
| `examples/getstarted/build/PrivateRoute-***********` | 248 B | -2 B (-0.8%) |
| `examples/getstarted/build/ProfilePage-***********` | 2.24 kB | -1 B (-0.04%) |
| `examples/getstarted/build/pt-BR.json-***********` | 1.51 kB | 0 B |
| `examples/getstarted/build/pt.json-***********` | 1.03 kB | 0 B |
| `examples/getstarted/build/purchase-review-workflows-***********` | 252 kB | +3 B (0%) |
| `examples/getstarted/build/PurchaseAuditLogs-***********` | 250 kB | +1 B (0%) |
| `examples/getstarted/build/PurchaseContentHistory-***********` | 8.62 MB | -1 B (0%) |
| `examples/getstarted/build/PurchaseContentReleases-***********` | 138 kB | +2 B (0%) |
| `examples/getstarted/build/PurchaseSingleSignOn-***********` | 155 kB | +1 B (0%) |
| `examples/getstarted/build/ReleasesSettingsPage-***********` | 1.35 kB | +1 B (+0.07%) |
| `examples/getstarted/build/router-***********` | 398 B | +2 B (+0.51%) |
| `examples/getstarted/build/ru-***********` | 91 B | 0 B |
| `examples/getstarted/build/ru.json-***********` | 241 B | 0 B |
| `examples/getstarted/build/sa.json-***********` | 11.7 kB | 0 B |
| `examples/getstarted/build/schemas-***********` | 3.16 kB | -1 B (-0.03%) |
| `examples/getstarted/build/SearchInput-***********` | 526 B | -4 B (-0.75%) |
| `examples/getstarted/build/selectors-***********` | 111 B | 0 B |
| `examples/getstarted/build/SelectRoles-***********` | 1.03 kB | +1 B (+0.1%) |
| `examples/getstarted/build/SettingsPage-***********` | 1.54 kB | +2 B (+0.13%) |
| `examples/getstarted/build/SingleSignOnPage-***********` | 1.75 kB | 0 B |
| `examples/getstarted/build/sk.json-***********` | 748 B | 0 B |
| `examples/getstarted/build/sortBy-***********` | 695 B | -2 B (-0.29%) |
| `examples/getstarted/build/SSOProviders-***********` | 722 B | -1 B (-0.14%) |
| `examples/getstarted/build/strapi-************` | 2 kB | 0 B |
| `examples/getstarted/build/sv.json-***********` | 4.04 kB | 0 B |
| `examples/getstarted/build/Table-***********` | 1.44 kB | 0 B |
| `examples/getstarted/build/tail-***********` | 245 B | -2 B (-0.81%) |
| `examples/getstarted/build/th.json-***********` | 4.07 kB | 0 B |
| `examples/getstarted/build/TokenTypeSelect-***********` | 121 kB | +1 B (0%) |
| `examples/getstarted/build/tr-***********` | 83 B | 0 B |
| `examples/getstarted/build/tr.json-***********` | 1.02 kB | 0 B |
| `examples/getstarted/build/transferTokens-***********` | 501 B | -3 B (-0.6%) |
| `examples/getstarted/build/uk-***********` | 91 B | 0 B |
| `examples/getstarted/build/uk.json-***********` | 3.32 kB | 0 B |
| `examples/getstarted/build/useAdminRoles-***********` | 245 B | -1 B (-0.41%) |
| `examples/getstarted/build/UseCasePage-***********` | 1.24 kB | -2 B (-0.16%) |
| `examples/getstarted/build/useDragLayer-***********` | 179 B | -2 B (-1.1%) |
| `examples/getstarted/build/useLicenseLimitNotification-***********` | 712 B | +1 B (+0.14%) |
| `examples/getstarted/build/useOnce-***********` | 102 B | 0 B |
| `examples/getstarted/build/useReviewWorkflows-***********` | 1.34 kB | -1 B (-0.07%) |
| `examples/getstarted/build/users-***********` | 288 B | 0 B |
| `examples/getstarted/build/useWebhooks-***********` | 621 B | -3 B (-0.48%) |
| `examples/getstarted/build/validation-***********` | 520 B | -1 B (-0.19%) |
| `examples/getstarted/build/vi.json-***********` | 1.41 kB | 0 B |
| `examples/getstarted/build/WidgetHelpers-***********` | 521 B | -1 B (-0.19%) |
| `examples/getstarted/build/Widgets-***********` | 1.16 kB | +1 B (+0.09%) |
| `examples/getstarted/build/zh-Hans.json-***********` | 2.92 kB | 0 B |
| `examples/getstarted/build/zh.json-***********` | 1.09 kB | 0 B |

</details>



<a href="https://github.com/preactjs/compressed-size-action"><sub>compressed-size-action</sub></a>
author_association: CONTRIBUTOR
user: login: trunk-io[bot]
created_at: 2025-05-07T16:47:15Z
body: <!-- Trunk Test Analytics -->


[![Static Badge](https://raster.shields.io/badge/1008_tests-3b6fff62-purple)](https://app.trunk.io/strapi/flaky-tests/pr/23487?repo=strapi/strapi&commitHash=3b6fff62b435a8e80f5ceb6fedbaa8ef455629a3&utm_source=gh-comment) &ensp; [![Static Badge](https://raster.shields.io/badge/0-failed-crimson)](https://app.trunk.io/strapi/flaky-tests/pr/23487?repo=strapi/strapi&commitHash=3b6fff62b435a8e80f5ceb6fedbaa8ef455629a3&utm_source=gh-comment&runConclusion=FAILURE) &ensp; [![Static Badge](https://raster.shields.io/badge/0-flaky-red)](https://app.trunk.io/strapi/flaky-tests/pr/23487?repo=strapi/strapi&commitHash=3b6fff62b435a8e80f5ceb6fedbaa8ef455629a3&utm_source=gh-comment&runConclusion=FAILURE) &ensp; [![Static Badge](https://raster.shields.io/badge/0-quarantined-yellow)](https://app.trunk.io/strapi/flaky-tests/pr/23487?repo=strapi/strapi&commitHash=3b6fff62b435a8e80f5ceb6fedbaa8ef455629a3&utm_source=gh-comment&runConclusion=QUARANTINED)


<sub>

[View Full Report ↗︎](https://app.trunk.io/strapi/flaky-tests/pr/23487?repo=strapi/strapi&commitHash=3b6fff62b435a8e80f5ceb6fedbaa8ef455629a3&utm_source=gh-comment) ⋅ [Docs](https://docs.trunk.io/flaky-tests?utm_source=gh-comment)

</sub>
author_association: NONE