@adobe/spacecat-shared-utils 1.54.0 → 1.56.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,17 @@
1
+ # [@adobe/spacecat-shared-utils-v1.56.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.55.0...@adobe/spacecat-shared-utils-v1.56.0) (2025-10-07)
2
+
3
+
4
+ ### Features
5
+
6
+ * deletedEntities and prompts ([#1005](https://github.com/adobe/spacecat-shared/issues/1005)) ([c7c0c8f](https://github.com/adobe/spacecat-shared/commit/c7c0c8f635fcde250936579bb42d9942d7b6a1ab))
7
+
8
+ # [@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)
9
+
10
+
11
+ ### Features
12
+
13
+ * 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))
14
+
1
15
  # [@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
16
 
3
17
 
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.56.0",
4
4
  "description": "Shared modules of the Spacecat Services - utils",
5
5
  "type": "module",
6
6
  "engines": {
@@ -41,12 +41,17 @@ 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
  },
47
49
  competitors: {
48
50
  competitors: [],
49
51
  },
52
+ deleted: {
53
+ prompts: {},
54
+ },
50
55
  };
51
56
  }
52
57
 
package/src/schemas.js CHANGED
@@ -38,14 +38,39 @@ 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
+ });
63
+
64
+ const deletedPrompt = prompt.extend({
65
+ topic: nonEmptyString,
66
+ category: nonEmptyString,
67
+ regions: z.array(region).min(1),
68
+ });
46
69
 
47
70
  export const llmoConfig = z.object({
48
71
  entities: z.record(z.uuid(), entity),
72
+ categories: z.record(z.uuid(), category),
73
+ topics: z.record(z.uuid(), topic),
49
74
  brands: z.object({
50
75
  aliases: z.array(
51
76
  z.object({
@@ -66,60 +91,72 @@ export const llmoConfig = z.object({
66
91
  }),
67
92
  ),
68
93
  }),
94
+ deleted: z.object({
95
+ prompts: z.record(z.uuid(), deletedPrompt).optional(),
96
+ }).optional(),
69
97
  }).superRefine((value, ctx) => {
70
- const { entities, brands, competitors } = value;
98
+ const {
99
+ categories, topics, brands, competitors,
100
+ } = value;
71
101
 
72
102
  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');
103
+ ensureCategoryExists(categories, ctx, alias.category, ['brands', 'aliases', index, 'category']);
104
+ ensureRegionCompatibility(categories, ctx, alias.category, alias.region, ['brands', 'aliases', index, 'region'], 'brand alias');
75
105
  });
76
106
 
77
107
  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');
108
+ ensureCategoryExists(categories, ctx, competitor.category, ['competitors', 'competitors', index, 'category']);
109
+ ensureRegionCompatibility(categories, ctx, competitor.category, competitor.region, ['competitors', 'competitors', index, 'region'], 'competitor');
110
+ });
111
+
112
+ // Validate topic prompts regions against their category
113
+ Object.entries(topics).forEach(([topicId, topicEntity]) => {
114
+ if (topicEntity.prompts && topicEntity.category) {
115
+ // If category is a UUID, validate against the referenced category entity
116
+ 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)) {
117
+ topicEntity.prompts.forEach((promptItem, promptIndex) => {
118
+ ensureRegionCompatibility(
119
+ categories,
120
+ ctx,
121
+ topicEntity.category,
122
+ promptItem.regions,
123
+ ['topics', topicId, 'prompts', promptIndex, 'regions'],
124
+ 'topic prompt',
125
+ );
126
+ });
127
+ }
128
+ }
80
129
  });
81
130
  });
82
131
 
83
132
  /**
84
- * @param {LLMOConfig['entities']} entities
133
+ * @param {LLMOConfig['categories']} categories
85
134
  * @param {z.RefinementCtx} ctx
86
135
  * @param {string} id
87
- * @param {string} expectedType
88
136
  * @param {Array<number | string>} path
89
- * @param {string} refLabel
90
137
  */
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) {
138
+ function ensureCategoryExists(categories, ctx, id, path) {
139
+ if (!categories[id]) {
103
140
  ctx.addIssue({
104
141
  code: 'custom',
105
142
  path,
106
- message: `Entity ${id} referenced as ${refLabel} must have type "${expectedType}" but was "${entityValue.type}"`,
143
+ message: `Category ${id} does not exist`,
107
144
  });
108
145
  }
109
146
  }
110
147
 
111
148
  /**
112
- * @param {LLMOConfig['entities']} entities
149
+ * @param {LLMOConfig['categories']} categories
113
150
  * @param {z.RefinementCtx} ctx
114
151
  * @param {string} categoryId
115
152
  * @param {string | string[]} itemRegion
116
153
  * @param {Array<number | string>} path
117
154
  * @param {string} itemLabel
118
155
  */
119
- function ensureRegionCompatibility(entities, ctx, categoryId, itemRegion, path, itemLabel) {
120
- const categoryEntity = entities[categoryId];
156
+ function ensureRegionCompatibility(categories, ctx, categoryId, itemRegion, path, itemLabel) {
157
+ const categoryEntity = categories[categoryId];
121
158
  if (!categoryEntity) {
122
- // Category validation is handled by ensureEntityType
159
+ // Category validation is handled by ensureCategoryExists
123
160
  return;
124
161
  }
125
162