@adobe/spacecat-shared-data-access 3.61.0 → 3.63.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,21 @@
1
+ ## [@adobe/spacecat-shared-data-access-v3.63.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v3.62.0...@adobe/spacecat-shared-data-access-v3.63.0) (2026-05-14)
2
+
3
+ ### Features
4
+
5
+ * add rumConfig to Site config for deterministic RUM availability signal ([#1596](https://github.com/adobe/spacecat-shared/issues/1596)) ([3b16cdf](https://github.com/adobe/spacecat-shared/commit/3b16cdf7d66ddddc4b4b62aae4428feba3ef7f04))
6
+ * **data-access:** add preOnboarded flag to PLG onboarding steps schema ([#1601](https://github.com/adobe/spacecat-shared/issues/1601)) ([6f18f71](https://github.com/adobe/spacecat-shared/commit/6f18f71d112d5f409a545a3720ca8b7d9e3a13d7))
7
+
8
+ ### Bug Fixes
9
+
10
+ * adds a function to get latest token by createdAt ([#1597](https://github.com/adobe/spacecat-shared/issues/1597)) ([fb4d5f9](https://github.com/adobe/spacecat-shared/commit/fb4d5f91dc7bf674cff4f9855d3f630aa4807b5a))
11
+ * **deps:** update external fixes ([#1533](https://github.com/adobe/spacecat-shared/issues/1533)) ([0a3e2ab](https://github.com/adobe/spacecat-shared/commit/0a3e2abbbc5f58b5320518f7d596d4cef6271fa0))
12
+
13
+ ## [@adobe/spacecat-shared-data-access-v3.62.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v3.61.0...@adobe/spacecat-shared-data-access-v3.62.0) (2026-05-12)
14
+
15
+ ### Features
16
+
17
+ * **data-access:** add semrushWorkspaceId attribute to Organization ([#1602](https://github.com/adobe/spacecat-shared/issues/1602)) ([79a5d82](https://github.com/adobe/spacecat-shared/commit/79a5d825c2a3cb436d894f5bea90a0a4e230167b))
18
+
1
19
  ## [@adobe/spacecat-shared-data-access-v3.61.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v3.60.0...@adobe/spacecat-shared-data-access-v3.61.0) (2026-05-12)
2
20
 
3
21
  ### Features
package/README.md CHANGED
@@ -172,6 +172,25 @@ The `deliveryConfig` object on a Site stores delivery infrastructure details. It
172
172
  | `preferContentApi` | boolean | Whether to prefer the Content API for content retrieval |
173
173
  | `contentSourcePath` | string | AEM content root path for a site. Used to disambiguate multiple sites that share the same Cloud Manager program and environment. Corresponds to `/content/<site-name>` in the AEM repository. |
174
174
 
175
+ ## Site rumConfig
176
+
177
+ The `rumConfig` object on a Site tracks Real User Monitoring (RUM) domain key availability. It is set automatically on site creation and refreshed weekly by the `rum-config-refresh` audit handler.
178
+
179
+ | Property | Type | Description |
180
+ |----------|------|-------------|
181
+ | `hasDomainKey` | boolean | Whether the site's domain has an active RUM domain key registered |
182
+ | `lastCheckedAt` | string (ISO 8601) | Timestamp of the most recent RUM domain key check |
183
+
184
+ ### Config model methods
185
+
186
+ | Method | Returns | Description |
187
+ |--------|---------|-------------|
188
+ | `getRumConfig()` | `{ hasDomainKey, lastCheckedAt } \| undefined` | Returns the current rumConfig, or `undefined` if not yet set |
189
+ | `hasRumDomainKey()` | `boolean` | Returns `true` if a RUM domain key is active, `false` otherwise |
190
+ | `updateRumConfig(hasDomainKey)` | `void` | Sets `hasDomainKey` and updates `lastCheckedAt` to the current time |
191
+
192
+ Sites created before the `rum-config-refresh` handler ran will have no `rumConfig` key — callers must treat `undefined` as "unknown" rather than "not set up".
193
+
175
194
  ## Architecture
176
195
 
177
196
  ```
@@ -22,7 +22,7 @@ services:
22
22
  retries: 10
23
23
 
24
24
  postgrest:
25
- image: postgrest/postgrest:v14.8
25
+ image: postgrest/postgrest:v14.11
26
26
  container_name: spacecat-test-postgrest
27
27
  depends_on:
28
28
  db:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-data-access",
3
- "version": "3.61.0",
3
+ "version": "3.63.0",
4
4
  "description": "Shared modules of the Spacecat Services - Data Access",
5
5
  "type": "module",
6
6
  "engines": {
@@ -42,18 +42,18 @@
42
42
  "dependencies": {
43
43
  "@adobe/fetch": "^4.2.3",
44
44
  "@adobe/spacecat-shared-utils": "1.105.0",
45
- "@supabase/postgrest-js": "2.101.1",
45
+ "@supabase/postgrest-js": "2.105.4",
46
46
  "@aws-sdk/client-s3": "^3.940.0",
47
47
  "@types/joi": "17.2.3",
48
48
  "aws-xray-sdk": "3.12.0",
49
- "joi": "18.1.2",
49
+ "joi": "18.2.1",
50
50
  "pluralize": "8.0.0"
51
51
  },
52
52
  "devDependencies": {
53
53
  "chai": "6.2.2",
54
54
  "chai-as-promised": "8.0.2",
55
- "nock": "14.0.11",
56
- "sinon": "21.0.3",
55
+ "nock": "14.0.15",
56
+ "sinon": "21.1.2",
57
57
  "sinon-chai": "4.0.1"
58
58
  }
59
59
  }
@@ -19,6 +19,7 @@ export interface Organization extends BaseModel {
19
19
  getFulfillableItems(): object;
20
20
  getImsOrgId(): string;
21
21
  getName(): string;
22
+ getSemrushWorkspaceId(): string;
22
23
  getSites(): Promise<Site[]>;
23
24
  getProjects(): Promise<Project[]>;
24
25
  getEntitlements(): Promise<Entitlement[]>;
@@ -28,9 +29,12 @@ export interface Organization extends BaseModel {
28
29
  setFulfillableItems(fulfillableItems: object): Organization;
29
30
  setImsOrgId(imsOrgId: string): Organization;
30
31
  setName(name: string): Organization;
32
+ setSemrushWorkspaceId(semrushWorkspaceId: string): Organization;
31
33
  }
32
34
 
33
35
  export interface OrganizationCollection extends BaseCollection<Organization> {
34
36
  allByImsOrgId(imsOrgId: string): Promise<Organization[]>;
37
+ allBySemrushWorkspaceId(semrushWorkspaceId: string): Promise<Organization[]>;
35
38
  findByImsOrgId(imsOrgId: string): Promise<Organization | null>;
39
+ findBySemrushWorkspaceId(semrushWorkspaceId: string): Promise<Organization | null>;
36
40
  }
@@ -12,7 +12,7 @@
12
12
 
13
13
  /* c8 ignore start */
14
14
 
15
- import { isNonEmptyObject } from '@adobe/spacecat-shared-utils';
15
+ import { hasText, isNonEmptyObject } from '@adobe/spacecat-shared-utils';
16
16
 
17
17
  import { Config, DEFAULT_CONFIG, validateConfiguration } from '../site/config.js';
18
18
  import SchemaBuilder from '../base/schema.builder.js';
@@ -48,6 +48,19 @@ const schema = new SchemaBuilder(Organization, OrganizationCollection)
48
48
  type: 'any',
49
49
  validate: (value) => !value || isNonEmptyObject(value),
50
50
  })
51
- .addAllIndex(['imsOrgId']);
51
+ .addAttribute('semrushWorkspaceId', {
52
+ type: 'string',
53
+ // Minimum guard: reject empty / whitespace-only strings. Full format
54
+ // validation deferred until Semrush confirms the workspace-ID format.
55
+ // Use value == null (loose) so undefined and null short-circuit, but
56
+ // empty string ('') falls through to hasText() which rejects it.
57
+ validate: (value) => value == null || hasText(value),
58
+ })
59
+ .addAllIndex(['imsOrgId'])
60
+ // Uniqueness is enforced at the DB level via the UNIQUE constraint on
61
+ // organizations.semrush_workspace_id (mysticat-data-service migration
62
+ // 20260525000000), so findBySemrushWorkspaceId is semantically guaranteed
63
+ // to return at most one row.
64
+ .addAllIndex(['semrushWorkspaceId']);
52
65
 
53
66
  export default schema.build();
@@ -68,6 +68,7 @@ const schema = new SchemaBuilder(PlgOnboarding, PlgOnboardingCollection)
68
68
  entitlementCreated: { type: 'boolean' },
69
69
  entitlementFailed: { type: 'boolean' },
70
70
  orgResolutionFailed: { type: 'boolean' },
71
+ preOnboarded: { type: 'boolean' },
71
72
  },
72
73
  })
73
74
  .addAttribute('error', {
@@ -407,6 +407,10 @@ export const configSchema = Joi.object({
407
407
  startTime: Joi.number().optional(),
408
408
  })).optional(),
409
409
  }).optional(),
410
+ rumConfig: Joi.object({
411
+ hasDomainKey: Joi.boolean().required(),
412
+ lastCheckedAt: Joi.string().isoDate().required(),
413
+ }).optional(),
410
414
  commerceLlmoConfig: Joi.object().pattern(
411
415
  Joi.string(),
412
416
  Joi.object({
@@ -542,6 +546,18 @@ export const Config = (data = {}) => {
542
546
  self.getEdgeOptimizeConfig = () => state?.edgeOptimizeConfig;
543
547
  self.getOnboardConfig = () => state?.onboardConfig;
544
548
  self.getCommerceLlmoConfig = () => state?.commerceLlmoConfig;
549
+ /**
550
+ * Returns the RUM configuration for the site, or undefined if not set.
551
+ * Returns a shallow copy to prevent callers from mutating internal state.
552
+ * @returns {{ hasDomainKey: boolean, lastCheckedAt: string } | undefined}
553
+ */
554
+ self.getRumConfig = () => (state?.rumConfig ? { ...state.rumConfig } : undefined);
555
+
556
+ /**
557
+ * Returns true if RUM data collection is confirmed active for this site.
558
+ * @returns {boolean}
559
+ */
560
+ self.hasRumDomainKey = () => state?.rumConfig?.hasDomainKey === true;
545
561
  const AUDIT_TARGET_SOURCES = ['manual', 'moneyPages'];
546
562
  const auditTargetEntrySchema = Joi.object({
547
563
  url: Joi.string().uri().required(),
@@ -955,6 +971,22 @@ export const Config = (data = {}) => {
955
971
  state.commerceLlmoConfig = commerceLlmoConfig;
956
972
  };
957
973
 
974
+ /**
975
+ * Records the outcome of a RUM domain-key check and updates the timestamp.
976
+ * @param {boolean} hasDomainKey - Whether the site has an active RUM domain key.
977
+ * @param {Date} [now=new Date()] - Timestamp for lastCheckedAt; injectable for tests.
978
+ * @throws {Error} if hasDomainKey is not a boolean.
979
+ */
980
+ self.updateRumConfig = (hasDomainKey, now = new Date()) => {
981
+ if (typeof hasDomainKey !== 'boolean') {
982
+ throw new TypeError(`updateRumConfig: hasDomainKey must be a boolean, got ${typeof hasDomainKey}`);
983
+ }
984
+ state.rumConfig = {
985
+ hasDomainKey,
986
+ lastCheckedAt: now.toISOString(),
987
+ };
988
+ };
989
+
958
990
  return Object.freeze(self);
959
991
  };
960
992
 
@@ -974,6 +1006,7 @@ Config.toDynamoItem = (config) => ({
974
1006
  edgeOptimizeConfig: config.getEdgeOptimizeConfig(),
975
1007
  onboardConfig: config.getOnboardConfig?.(),
976
1008
  commerceLlmoConfig: config.getCommerceLlmoConfig?.(),
1009
+ rumConfig: config.getRumConfig?.(),
977
1010
  enableMoneyPageUrls: config.isMoneyPageUrlsEnabled?.() === false ? false : undefined,
978
1011
  auditTargetURLs: config.getAuditTargetURLsConfig?.(),
979
1012
  });
@@ -42,6 +42,21 @@ class TokenCollection extends BaseCollection {
42
42
  * @returns {Promise<import('./token.model.js').default|null>} Token instance
43
43
  * (existing or newly created), or null when none exists and createIfNotFound is false.
44
44
  */
45
+ /**
46
+ * Finds the most recently created Token for a given siteId and tokenType,
47
+ * regardless of cycle. Returns null if no token exists.
48
+ *
49
+ * @param {string} siteId - Site ID (UUID).
50
+ * @param {string} tokenType - Token type (e.g. grant_cwv, grant_broken_backlinks).
51
+ * @returns {Promise<import('./token.model.js').default|null>} The last created Token, or null.
52
+ */
53
+ async findLastCreatedBySiteIdAndTokenType(siteId, tokenType) {
54
+ if (!hasText(siteId) || !hasText(tokenType)) {
55
+ throw new DataAccessError('TokenCollection.findLastCreatedBySiteIdAndTokenType: siteId and tokenType are required');
56
+ }
57
+ return this.findByIndexKeys({ siteId, tokenType }, { order: 'desc' });
58
+ }
59
+
45
60
  async findBySiteIdAndTokenType(siteId, tokenType, options = {}) {
46
61
  if (!hasText(siteId) || !hasText(tokenType)) {
47
62
  throw new DataAccessError('TokenCollection.findBySiteIdAndTokenType: siteId and tokenType are required');