@adobe/spacecat-shared-utils 1.91.0 → 1.92.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.92.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.91.0...@adobe/spacecat-shared-utils-v1.92.0) (2026-02-10)
2
+
3
+
4
+ ### Features
5
+
6
+ * schema for oppty workspace data ([#1307](https://github.com/adobe/spacecat-shared/issues/1307)) ([49decad](https://github.com/adobe/spacecat-shared/commit/49decada5a0e598628563c8856b77715de549fce))
7
+
1
8
  # [@adobe/spacecat-shared-utils-v1.91.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.90.3...@adobe/spacecat-shared-utils-v1.91.0) (2026-02-08)
2
9
 
3
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-utils",
3
- "version": "1.91.0",
3
+ "version": "1.92.0",
4
4
  "description": "Shared modules of the Spacecat Services - utils",
5
5
  "type": "module",
6
6
  "exports": {
@@ -11,9 +11,13 @@
11
11
  */
12
12
 
13
13
  import { GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
14
+ import { strategyWorkspaceData } from './strategy-schema.js';
15
+
16
+ export { strategyWorkspaceData };
14
17
 
15
18
  /**
16
19
  * @import { S3Client } from "@aws-sdk/client-s3"
20
+ * @import { StrategyWorkspaceData } from "./strategy-schema.js"
17
21
  */
18
22
 
19
23
  /**
@@ -34,8 +38,8 @@ export function strategyPath(siteId) {
34
38
  * @param {string} [options.version] Optional version ID of the strategy to read.
35
39
  * Defaults to the latest version.
36
40
  * @param {string} [options.s3Bucket] Optional S3 bucket name.
37
- * @returns {Promise<{data: object | null, exists: boolean, version?: string}>} The strategy data,
38
- * a flag indicating if it existed, and the version ID if it exists.
41
+ * @returns {Promise<{data: StrategyWorkspaceData | null, exists: boolean, version?: string}>}
42
+ * The strategy data, a flag indicating if it existed, and the version ID if it exists.
39
43
  * @throws {Error} If reading the strategy fails for reasons other than it not existing.
40
44
  */
41
45
  export async function readStrategy(siteId, s3Client, options) {
@@ -64,14 +68,14 @@ export async function readStrategy(siteId, s3Client, options) {
64
68
  throw new Error('Strategy body is empty');
65
69
  }
66
70
  const text = await body.transformToString();
67
- const data = JSON.parse(text);
71
+ const data = strategyWorkspaceData.parse(JSON.parse(text));
68
72
  return { data, exists: true, version: res.VersionId || undefined };
69
73
  }
70
74
 
71
75
  /**
72
76
  * Writes the strategy JSON for a given site.
73
77
  * @param {string} siteId The ID of the site.
74
- * @param {object} data The data object to write (any valid JSON).
78
+ * @param {StrategyWorkspaceData} data The strategy data object to write.
75
79
  * @param {S3Client} s3Client The S3 client to use for writing the strategy.
76
80
  * @param {object} [options]
77
81
  * @param {string} [options.s3Bucket] Optional S3 bucket name.
@@ -0,0 +1,129 @@
1
+ /*
2
+ * Copyright 2025 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+ import * as z from 'zod';
14
+
15
+ // ===== SCHEMA DEFINITIONS ====================================================
16
+ // Schemas defined here must be forward- and backward-compatible when making changes.
17
+ // See llmoConfig schema for detailed guidelines.
18
+ // ============================================================================
19
+
20
+ /**
21
+ * @typedef {z.output<typeof strategyWorkspaceData>} StrategyWorkspaceData
22
+ */
23
+
24
+ const nonEmptyString = z.string().min(1);
25
+
26
+ // Shared status union for forward compatibility
27
+ const workflowStatus = z.union([
28
+ z.literal('new'),
29
+ z.literal('planning'),
30
+ z.literal('in_progress'),
31
+ z.literal('completed'),
32
+ z.literal('on_hold'),
33
+ z.string(), // Catchall for future statuses
34
+ ]);
35
+
36
+ const strategyGoalType = z.union([
37
+ z.literal('visibility-score'),
38
+ z.string(), // Catchall for future goal types
39
+ ]);
40
+
41
+ /**
42
+ * Library opportunity - user-created reusable opportunity template
43
+ */
44
+ const opportunity = z.object({
45
+ id: nonEmptyString,
46
+ name: nonEmptyString,
47
+ description: z.string(),
48
+ category: nonEmptyString,
49
+ });
50
+
51
+ /**
52
+ * Reference to an opportunity within a strategy
53
+ * Two types:
54
+ * 1. Library opportunity: opportunityId references Opportunity.id, no link field
55
+ * 2. System opportunity: has link field, opportunityId is system ID
56
+ */
57
+ const strategyOpportunity = z.object({
58
+ opportunityId: nonEmptyString,
59
+ name: z.string().optional(), // For system opportunities (name stored directly)
60
+ link: z.string().optional(), // URL path to opportunity page (system-generated)
61
+ status: workflowStatus,
62
+ assignee: z.string(),
63
+ completedAt: z.string().optional(), // ISO 8601 date string
64
+ });
65
+
66
+ /**
67
+ * Strategy containing references to opportunities
68
+ */
69
+ const strategy = z.object({
70
+ id: nonEmptyString,
71
+ name: nonEmptyString,
72
+ status: workflowStatus,
73
+ url: z.string(),
74
+ description: z.string(),
75
+ topic: z.string(),
76
+ platform: nonEmptyString,
77
+ opportunities: z.array(strategyOpportunity),
78
+ createdAt: z.string(), // ISO 8601 date string
79
+ completedAt: z.string().optional(), // ISO 8601 date string
80
+ goalType: strategyGoalType.optional(),
81
+ });
82
+
83
+ /**
84
+ * Root schema for strategy workspace data
85
+ */
86
+ export const strategyWorkspaceData = z.object({
87
+ opportunities: z.array(opportunity),
88
+ strategies: z.array(strategy),
89
+ }).superRefine((value, ctx) => {
90
+ const { opportunities, strategies } = value;
91
+
92
+ // Build a set of library opportunity IDs for reference validation
93
+ const libraryOpportunityIds = new Set(opportunities.map((opp) => opp.id));
94
+
95
+ // Validate that library opportunity references exist
96
+ strategies.forEach((strat, strategyIndex) => {
97
+ strat.opportunities.forEach((stratOpp, oppIndex) => {
98
+ // Only validate library opportunities (those without a link)
99
+ if (!stratOpp.link && !libraryOpportunityIds.has(stratOpp.opportunityId)) {
100
+ ctx.addIssue({
101
+ code: 'custom',
102
+ path: ['strategies', strategyIndex, 'opportunities', oppIndex, 'opportunityId'],
103
+ message: `Library opportunity ${stratOpp.opportunityId} does not exist`,
104
+ });
105
+ }
106
+ });
107
+ });
108
+
109
+ // Validate completedAt consistency with status
110
+ strategies.forEach((strat, strategyIndex) => {
111
+ if (strat.status === 'completed' && !strat.completedAt) {
112
+ ctx.addIssue({
113
+ code: 'custom',
114
+ path: ['strategies', strategyIndex, 'completedAt'],
115
+ message: 'completedAt is required when status is completed',
116
+ });
117
+ }
118
+
119
+ strat.opportunities.forEach((stratOpp, oppIndex) => {
120
+ if (stratOpp.status === 'completed' && !stratOpp.completedAt) {
121
+ ctx.addIssue({
122
+ code: 'custom',
123
+ path: ['strategies', strategyIndex, 'opportunities', oppIndex, 'completedAt'],
124
+ message: 'completedAt is required when status is completed',
125
+ });
126
+ }
127
+ });
128
+ });
129
+ });