@adobe/spacecat-shared-utils 1.54.0 → 1.55.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.55.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.54.0...@adobe/spacecat-shared-utils-v1.55.0) (2025-10-03)
2
+
3
+
4
+ ### Features
5
+
6
+ * add topics with nested prompts + make categories and prompts top level entities ([#999](https://github.com/adobe/spacecat-shared/issues/999)) ([1b43970](https://github.com/adobe/spacecat-shared/commit/1b43970912478361b2eb4eaf1e7f173e77ad80e9))
7
+
1
8
  # [@adobe/spacecat-shared-utils-v1.54.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.53.0...@adobe/spacecat-shared-utils-v1.54.0) (2025-10-02)
2
9
 
3
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-utils",
3
- "version": "1.54.0",
3
+ "version": "1.55.0",
4
4
  "description": "Shared modules of the Spacecat Services - utils",
5
5
  "type": "module",
6
6
  "engines": {
@@ -41,6 +41,8 @@ export function llmoConfigPath(siteId) {
41
41
  export function defaultConfig() {
42
42
  return {
43
43
  entities: {},
44
+ categories: {},
45
+ topics: {},
44
46
  brands: {
45
47
  aliases: [],
46
48
  },
package/src/schemas.js CHANGED
@@ -38,14 +38,33 @@ const nonEmptyString = z.string().min(1);
38
38
 
39
39
  const region = z.string().length(2).regex(/^[a-z][a-z]$/i);
40
40
 
41
- const entity = z.union([
42
- z.object({ type: z.literal('category'), name: nonEmptyString, region: z.union([region, z.array(region)]) }),
43
- z.object({ type: z.literal('topic'), name: nonEmptyString }),
44
- z.object({ type: nonEmptyString }),
45
- ]);
41
+ const prompt = z.object({
42
+ prompt: nonEmptyString,
43
+ regions: z.array(region),
44
+ origin: z.union([z.literal('human'), z.literal('ai'), z.string()]),
45
+ source: z.union([z.literal('config'), z.literal('api'), z.string()]),
46
+ });
47
+
48
+ const entity = z.object({
49
+ type: nonEmptyString,
50
+ name: nonEmptyString,
51
+ });
52
+
53
+ const category = z.object({
54
+ name: nonEmptyString,
55
+ region: z.union([region, z.array(region)]),
56
+ });
57
+
58
+ const topic = z.object({
59
+ name: nonEmptyString,
60
+ prompts: z.array(prompt).min(1),
61
+ category: z.union([z.uuid(), nonEmptyString]),
62
+ });
46
63
 
47
64
  export const llmoConfig = z.object({
48
65
  entities: z.record(z.uuid(), entity),
66
+ categories: z.record(z.uuid(), category),
67
+ topics: z.record(z.uuid(), topic),
49
68
  brands: z.object({
50
69
  aliases: z.array(
51
70
  z.object({
@@ -67,59 +86,68 @@ export const llmoConfig = z.object({
67
86
  ),
68
87
  }),
69
88
  }).superRefine((value, ctx) => {
70
- const { entities, brands, competitors } = value;
89
+ const {
90
+ categories, topics, brands, competitors,
91
+ } = value;
71
92
 
72
93
  brands.aliases.forEach((alias, index) => {
73
- ensureEntityType(entities, ctx, alias.category, 'category', ['brands', 'aliases', index, 'category'], 'category');
74
- ensureRegionCompatibility(entities, ctx, alias.category, alias.region, ['brands', 'aliases', index, 'region'], 'brand alias');
94
+ ensureCategoryExists(categories, ctx, alias.category, ['brands', 'aliases', index, 'category']);
95
+ ensureRegionCompatibility(categories, ctx, alias.category, alias.region, ['brands', 'aliases', index, 'region'], 'brand alias');
75
96
  });
76
97
 
77
98
  competitors.competitors.forEach((competitor, index) => {
78
- ensureEntityType(entities, ctx, competitor.category, 'category', ['competitors', 'competitors', index, 'category'], 'category');
79
- ensureRegionCompatibility(entities, ctx, competitor.category, competitor.region, ['competitors', 'competitors', index, 'region'], 'competitor');
99
+ ensureCategoryExists(categories, ctx, competitor.category, ['competitors', 'competitors', index, 'category']);
100
+ ensureRegionCompatibility(categories, ctx, competitor.category, competitor.region, ['competitors', 'competitors', index, 'region'], 'competitor');
101
+ });
102
+
103
+ // Validate topic prompts regions against their category
104
+ Object.entries(topics).forEach(([topicId, topicEntity]) => {
105
+ if (topicEntity.prompts && topicEntity.category) {
106
+ // If category is a UUID, validate against the referenced category entity
107
+ if (topicEntity.category.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)) {
108
+ topicEntity.prompts.forEach((promptItem, promptIndex) => {
109
+ ensureRegionCompatibility(
110
+ categories,
111
+ ctx,
112
+ topicEntity.category,
113
+ promptItem.regions,
114
+ ['topics', topicId, 'prompts', promptIndex, 'regions'],
115
+ 'topic prompt',
116
+ );
117
+ });
118
+ }
119
+ }
80
120
  });
81
121
  });
82
122
 
83
123
  /**
84
- * @param {LLMOConfig['entities']} entities
124
+ * @param {LLMOConfig['categories']} categories
85
125
  * @param {z.RefinementCtx} ctx
86
126
  * @param {string} id
87
- * @param {string} expectedType
88
127
  * @param {Array<number | string>} path
89
- * @param {string} refLabel
90
128
  */
91
- function ensureEntityType(entities, ctx, id, expectedType, path, refLabel) {
92
- const entityValue = entities[id];
93
- if (!entityValue) {
94
- ctx.addIssue({
95
- code: 'custom',
96
- path,
97
- message: `Unknown ${refLabel} entity: ${id}`,
98
- });
99
- return;
100
- }
101
-
102
- if (entityValue.type !== expectedType) {
129
+ function ensureCategoryExists(categories, ctx, id, path) {
130
+ if (!categories[id]) {
103
131
  ctx.addIssue({
104
132
  code: 'custom',
105
133
  path,
106
- message: `Entity ${id} referenced as ${refLabel} must have type "${expectedType}" but was "${entityValue.type}"`,
134
+ message: `Category ${id} does not exist`,
107
135
  });
108
136
  }
109
137
  }
110
138
 
111
139
  /**
112
- * @param {LLMOConfig['entities']} entities
140
+ * @param {LLMOConfig['categories']} categories
113
141
  * @param {z.RefinementCtx} ctx
114
142
  * @param {string} categoryId
115
143
  * @param {string | string[]} itemRegion
116
144
  * @param {Array<number | string>} path
117
145
  * @param {string} itemLabel
118
146
  */
119
- function ensureRegionCompatibility(entities, ctx, categoryId, itemRegion, path, itemLabel) {
120
- const categoryEntity = entities[categoryId];
147
+ function ensureRegionCompatibility(categories, ctx, categoryId, itemRegion, path, itemLabel) {
148
+ const categoryEntity = categories[categoryId];
121
149
  if (!categoryEntity) {
122
- // Category validation is handled by ensureEntityType
150
+ // Category validation is handled by ensureCategoryExists
123
151
  return;
124
152
  }
125
153