@adobe/spacecat-shared-utils 1.52.0 → 1.53.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/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ # [@adobe/spacecat-shared-utils-v1.53.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.52.0...@adobe/spacecat-shared-utils-v1.53.0) (2025-10-02)
2
+
3
+
4
+ ### Features
5
+
6
+ * Add regions to categories and related validation on dependent entities ([#996](https://github.com/adobe/spacecat-shared/issues/996)) ([60c52e2](https://github.com/adobe/spacecat-shared/commit/60c52e2d9c7e79a67132ae2b26e40e617e8af358))
7
+
1
8
  # [@adobe/spacecat-shared-utils-v1.52.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.51.1...@adobe/spacecat-shared-utils-v1.52.0) (2025-09-29)
2
9
 
3
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-utils",
3
- "version": "1.52.0",
3
+ "version": "1.53.0",
4
4
  "description": "Shared modules of the Spacecat Services - utils",
5
5
  "type": "module",
6
6
  "engines": {
package/src/schemas.js CHANGED
@@ -36,14 +36,14 @@ import * as z from 'zod';
36
36
 
37
37
  const nonEmptyString = z.string().min(1);
38
38
 
39
+ const region = z.string().length(2).regex(/^[a-z][a-z]$/i);
40
+
39
41
  const entity = z.union([
40
- z.object({ type: z.literal('category'), name: nonEmptyString }),
42
+ z.object({ type: z.literal('category'), name: nonEmptyString, region: z.union([region, z.array(region)]).optional() }),
41
43
  z.object({ type: z.literal('topic'), name: nonEmptyString }),
42
44
  z.object({ type: nonEmptyString }),
43
45
  ]);
44
46
 
45
- const region = z.string().length(2).regex(/^[a-z][a-z]$/i);
46
-
47
47
  export const llmoConfig = z.object({
48
48
  entities: z.record(z.uuid(), entity),
49
49
  brands: z.object({
@@ -73,10 +73,12 @@ export const llmoConfig = z.object({
73
73
  brands.aliases.forEach((alias, index) => {
74
74
  ensureEntityType(entities, ctx, alias.category, 'category', ['brands', 'aliases', index, 'category'], 'category');
75
75
  ensureEntityType(entities, ctx, alias.topic, 'topic', ['brands', 'aliases', index, 'topic'], 'topic');
76
+ ensureRegionCompatibility(entities, ctx, alias.category, alias.region, ['brands', 'aliases', index, 'region'], 'brand alias');
76
77
  });
77
78
 
78
79
  competitors.competitors.forEach((competitor, index) => {
79
80
  ensureEntityType(entities, ctx, competitor.category, 'category', ['competitors', 'competitors', index, 'category'], 'category');
81
+ ensureRegionCompatibility(entities, ctx, competitor.category, competitor.region, ['competitors', 'competitors', index, 'region'], 'competitor');
80
82
  });
81
83
  });
82
84
 
@@ -107,3 +109,47 @@ function ensureEntityType(entities, ctx, id, expectedType, path, refLabel) {
107
109
  });
108
110
  }
109
111
  }
112
+
113
+ /**
114
+ * @param {LLMOConfig['entities']} entities
115
+ * @param {z.RefinementCtx} ctx
116
+ * @param {string} categoryId
117
+ * @param {string | string[]} itemRegion
118
+ * @param {Array<number | string>} path
119
+ * @param {string} itemLabel
120
+ */
121
+ function ensureRegionCompatibility(entities, ctx, categoryId, itemRegion, path, itemLabel) {
122
+ const categoryEntity = entities[categoryId];
123
+ if (!categoryEntity) {
124
+ // Category validation is handled by ensureEntityType
125
+ return;
126
+ }
127
+
128
+ const categoryRegions = categoryEntity.region;
129
+
130
+ // If category has no regions defined, item should not have regions
131
+ if (!categoryRegions) {
132
+ ctx.addIssue({
133
+ code: 'custom',
134
+ path,
135
+ message: `${itemLabel} cannot have regions when the referenced category has no regions defined`,
136
+ });
137
+ return;
138
+ }
139
+
140
+ // Normalize regions to arrays for comparison
141
+ const categoryRegionArray = Array.isArray(categoryRegions) ? categoryRegions : [categoryRegions];
142
+ const itemRegionArray = Array.isArray(itemRegion) ? itemRegion : [itemRegion];
143
+
144
+ // Check if all item regions are contained in category regions
145
+ const invalidRegions = itemRegionArray.filter(
146
+ (regionItem) => !categoryRegionArray.includes(regionItem),
147
+ );
148
+ if (invalidRegions.length > 0) {
149
+ ctx.addIssue({
150
+ code: 'custom',
151
+ path,
152
+ message: `${itemLabel} regions [${invalidRegions.join(', ')}] are not allowed. Category only supports regions: [${categoryRegionArray.join(', ')}]`,
153
+ });
154
+ }
155
+ }