@adobe/spacecat-shared-data-access 4.21.0 → 4.22.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,15 @@
1
+ ## [@adobe/spacecat-shared-data-access-v4.22.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v4.21.1...@adobe/spacecat-shared-data-access-v4.22.0) (2026-08-14)
2
+
3
+ ### Features
4
+
5
+ * schema changes for trial users entity to allow searching by external user id ([#1871](https://github.com/adobe/spacecat-shared/issues/1871)) ([3ecf546](https://github.com/adobe/spacecat-shared/commit/3ecf5466b41dcd32cf5dd591f8762fefd714af1f))
6
+
7
+ ## [@adobe/spacecat-shared-data-access-v4.21.1](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v4.21.0...@adobe/spacecat-shared-data-access-v4.21.1) (2026-08-14)
8
+
9
+ ### Bug Fixes
10
+
11
+ * **data-access:** publish brand semrushWorkspaceId mirror removal (SITES-49202) ([#1880](https://github.com/adobe/spacecat-shared/issues/1880)) ([80a296a](https://github.com/adobe/spacecat-shared/commit/80a296af1b4f49ffa66aba80d677a5e6b42f3856)), closes [#1867](https://github.com/adobe/spacecat-shared/issues/1867) [#1867](https://github.com/adobe/spacecat-shared/issues/1867) [#1867](https://github.com/adobe/spacecat-shared/issues/1867)
12
+
1
13
  ## [@adobe/spacecat-shared-data-access-v4.21.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v4.20.0...@adobe/spacecat-shared-data-access-v4.21.0) (2026-08-13)
2
14
 
3
15
  ### Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-data-access",
3
- "version": "4.21.0",
3
+ "version": "4.22.0",
4
4
  "description": "Shared modules of the Spacecat Services - Data Access",
5
5
  "type": "module",
6
6
  "engines": {
@@ -15,8 +15,9 @@ import BaseCollection from '../base/base.collection.js';
15
15
  /**
16
16
  * BrandCollection - collection of Brand rows. `findById` (the brand UUID) is
17
17
  * the primary access path for serenity dual-mode resolution;
18
- * `findBySemrushWorkspaceId` (from the addAllIndex below) is used to repair a
19
- * pointer when a sub-workspace is found to have been deleted out-of-band.
18
+ * `findBySemrushSubWorkspaceId` (from the addAllIndex on the schema) is used to
19
+ * repair a pointer when a sub-workspace is found to have been deleted
20
+ * out-of-band.
20
21
  *
21
22
  * @class BrandCollection
22
23
  * @extends BaseCollection
@@ -16,17 +16,24 @@ import BaseModel from '../base/base.model.js';
16
16
  * Brand - an Adobe brand, stored in the `brands` table in mysticat-data-service
17
17
  * and served over PostgREST. Intentionally minimal: it surfaces only the fields
18
18
  * the serenity sub-workspace provisioning flows read/write
19
- * (`semrushWorkspaceId`, `status`, `name`). Brands are created and fully
19
+ * (`semrushSubWorkspaceId`, `status`, `name`). Brands are created and fully
20
20
  * managed elsewhere (Brandalf sync, onboarding); this entity is a read +
21
21
  * targeted-patch surface, not a create surface.
22
22
  *
23
- * `semrushWorkspaceId` is the dual-mode switch: NULL = the brand is not
23
+ * `semrushSubWorkspaceId` is the dual-mode switch: NULL = the brand is not
24
24
  * connected to a Semrush sub-workspace (resolves against the org parent
25
25
  * workspace — "flat" mode); set = the brand has its own Semrush sub-workspace.
26
26
  * Deactivation empties the sub-workspace and clears this pointer (the
27
27
  * sub-workspace itself is never deleted). See serenity-docs
28
28
  * brand-semrush-provisioning-v2-phase1-sync.md §6.
29
29
  *
30
+ * NOTE: there is no brand-level `semrushWorkspaceId` accessor. The deprecated
31
+ * read-only mirror (attribute, index, `findBySemrushWorkspaceId`,
32
+ * `allBySemrushWorkspaceId`, `setSemrushWorkspaceId`) was removed in SITES-49202;
33
+ * `semrushSubWorkspaceId` above is the write-of-record. The identically-named
34
+ * `Organization.semrushWorkspaceId` is a DISTINCT field and stays — do not
35
+ * reintroduce a brand mirror by symbol-name sweep.
36
+ *
30
37
  * @class Brand
31
38
  * @extends BaseModel
32
39
  */
@@ -39,23 +46,6 @@ class Brand extends BaseModel {
39
46
  * `pending`; customer offboard writes `deleted`.
40
47
  */
41
48
  static STATUSES = Object.freeze(['pending', 'active', 'deleted', 'ignored']);
42
-
43
- /**
44
- * Deprecated BC-compat setter. `semrushWorkspaceId` is `readOnly: true` in
45
- * the schema (mirrored by the mysticat-data-service sync trigger), so no
46
- * setter is auto-generated for it — this manual method exists purely so an
47
- * existing external caller of `setSemrushWorkspaceId` does not get a
48
- * semver-breaking runtime error on upgrade. Delegates to the real
49
- * write-of-record attribute. Remove once every direct caller has migrated
50
- * to `setSemrushSubWorkspaceId` (see brand.schema.js).
51
- *
52
- * @deprecated Use setSemrushSubWorkspaceId instead.
53
- * @param {string|null} value
54
- * @returns {Brand}
55
- */
56
- setSemrushWorkspaceId(value) {
57
- return this.setSemrushSubWorkspaceId(value);
58
- }
59
49
  }
60
50
 
61
51
  export default Brand;
@@ -28,37 +28,23 @@ const schema = new SchemaBuilder(Brand, BrandCollection)
28
28
  })
29
29
  // reference_status enum on the brands table. Not `required`: this entity
30
30
  // never creates a brand, and a targeted PATCH (e.g. setting only
31
- // semrushWorkspaceId) must not be forced to also send status. The validator
31
+ // semrushSubWorkspaceId) must not be forced to also send status. The validator
32
32
  // still rejects an out-of-enum value when status IS written
33
33
  // (activate → 'active', deactivate → 'pending').
34
34
  .addAttribute('status', {
35
35
  type: Brand.STATUSES,
36
36
  validate: (value) => value == null || Brand.STATUSES.includes(value),
37
37
  })
38
- // DEPRECATED (serenity-docs brand-semrush-mapping-maintenance.md §10
39
- // rename, write-of-record cutover): read-only mirror of
40
- // semrushSubWorkspaceId below, maintained entirely by the
41
- // mysticat-data-service brands_sync_semrush_workspace_id trigger
42
- // (migration 20260702094229). No schema-generated setter — app code must
43
- // write semrushSubWorkspaceId instead. brand.model.js still defines a
44
- // manual, deprecated setSemrushWorkspaceId() that delegates to
45
- // setSemrushSubWorkspaceId(), so an existing external caller of the old
46
- // setter is not broken (a bare readOnly flip here would be a semver-breaking
47
- // removal for any @adobe/spacecat-shared-data-access consumer). Will be
48
- // retired (attribute, column, and trigger) once every direct external
49
- // reader has migrated.
50
- .addAttribute('semrushWorkspaceId', {
51
- type: 'string',
52
- readOnly: true,
53
- })
54
38
  // Brand → Semrush sub-workspace. Nullable (NULL = no sub-workspace
55
- // connected). Same minimum guard as organizations.semrushWorkspaceId: the
56
- // shared `hasText` rejects the empty string (and non-strings) while letting
39
+ // connected). Same minimum guard the distinct `Organization` entity applies
40
+ // to its own `semrushWorkspaceId` field (the brand has no such field — its
41
+ // deprecated mirror was removed in SITES-49202): the shared `hasText` rejects
42
+ // the empty string (and non-strings) while letting
57
43
  // null/undefined short-circuit. Note hasText does NOT trim, so a
58
44
  // whitespace-only value would pass — acceptable here because this column is
59
45
  // only ever written by the activate flow with a real Semrush workspace UUID,
60
- // never user input. This is now the write-of-record (see semrushWorkspaceId
61
- // above for the deprecated BC mirror).
46
+ // never user input. This is the write-of-record for the brand → Semrush
47
+ // sub-workspace pointer.
62
48
  .addAttribute('semrushSubWorkspaceId', {
63
49
  type: 'string',
64
50
  validate: (value) => value == null || hasText(value),
@@ -72,15 +58,9 @@ const schema = new SchemaBuilder(Brand, BrandCollection)
72
58
  type: 'any',
73
59
  validate: (value) => value == null || (typeof value === 'object' && !Array.isArray(value)),
74
60
  })
75
- // Uniqueness is enforced at the DB level via the UNIQUE constraint on the
76
- // deprecated brands.semrush_workspace_id (mysticat-data-service migration
77
- // 20260615102123), so findBySemrushWorkspaceId returns at most one row.
78
- // Kept for BC lookups against the mirrored column; new code should prefer
79
- // findBySemrushSubWorkspaceId below.
80
- .addAllIndex(['semrushWorkspaceId'])
81
- // Same uniqueness guarantee on the write-of-record column
61
+ // Uniqueness guarantee on the write-of-record column
82
62
  // (brands.semrush_sub_workspace_id, mysticat-data-service migration
83
- // 20260702091920).
63
+ // 20260702091920), so findBySemrushSubWorkspaceId returns at most one row.
84
64
  .addAllIndex(['semrushSubWorkspaceId']);
85
65
 
86
66
  export default schema.build();
@@ -17,24 +17,13 @@ import type {
17
17
  export interface Brand extends BaseModel {
18
18
  getName(): string;
19
19
  getStatus(): string;
20
- // Deprecated BC mirror (brands.semrush_workspace_id), maintained by the
21
- // mysticat-data-service sync trigger. No schema-generated setter; the
22
- // deprecated setSemrushWorkspaceId below is a manual delegate defined in
23
- // brand.model.js, kept only for backward compatibility. Use
24
- // getSemrushSubWorkspaceId/setSemrushSubWorkspaceId instead. See
25
- // brand.schema.js.
26
- getSemrushWorkspaceId(): string | null;
27
20
  getSemrushSubWorkspaceId(): string | null;
28
21
  setName(value: string): Brand;
29
22
  setStatus(value: string): Brand;
30
23
  setSemrushSubWorkspaceId(value: string | null): Brand;
31
- /** @deprecated Use setSemrushSubWorkspaceId instead. */
32
- setSemrushWorkspaceId(value: string | null): Brand;
33
24
  }
34
25
 
35
26
  export interface BrandCollection extends BaseCollection<Brand> {
36
- allBySemrushWorkspaceId(semrushWorkspaceId: string): Promise<Brand[]>;
37
- findBySemrushWorkspaceId(semrushWorkspaceId: string): Promise<Brand | null>;
38
27
  allBySemrushSubWorkspaceId(semrushSubWorkspaceId: string): Promise<Brand[]>;
39
28
  findBySemrushSubWorkspaceId(semrushSubWorkspaceId: string): Promise<Brand | null>;
40
29
  }
@@ -43,9 +43,29 @@ export interface TrialUserCollection extends BaseCollection<TrialUser> {
43
43
  allByProviderAndExternalUserId(provider: ProviderType, externalId: string): Promise<TrialUser[]>;
44
44
  allByOrganizationId(organizationId: string): Promise<TrialUser[]>;
45
45
  allByEmailId(emailId: string): Promise<TrialUser[]>;
46
+ allByExternalUserId(externalUserId: string): Promise<TrialUser[]>;
47
+ allByExternalUserIdAndUpdatedAt(
48
+ externalUserId: string,
49
+ updatedAt: string
50
+ ): Promise<TrialUser[]>;
46
51
  findByProvider(provider: ProviderType): Promise<TrialUser | null>;
47
52
  findByProviderAndExternalUserId(provider: ProviderType, externalId: string):
48
53
  Promise<TrialUser | null>;
49
54
  findByOrganizationId(organizationId: string): Promise<TrialUser | null>;
50
55
  findByEmailId(emailId: string): Promise<TrialUser | null>;
56
+ /**
57
+ * externalUserId is optional and not enforced unique (no DB unique constraint), so this
58
+ * returns one arbitrary match when multiple trial users share the same externalUserId.
59
+ * Use allByExternalUserId if you need every matching record.
60
+ */
61
+ findByExternalUserId(externalUserId: string): Promise<TrialUser | null>;
62
+ /**
63
+ * externalUserId is optional and not enforced unique (no DB unique constraint), so this
64
+ * returns one arbitrary match when multiple trial users share the same externalUserId.
65
+ * Use allByExternalUserIdAndUpdatedAt if you need every matching record.
66
+ */
67
+ findByExternalUserIdAndUpdatedAt(
68
+ externalUserId: string,
69
+ updatedAt: string
70
+ ): Promise<TrialUser | null>;
51
71
  }
@@ -53,6 +53,11 @@ const schema = new SchemaBuilder(TrialUser, TrialUserCollection)
53
53
  { composite: ['organizationId'] },
54
54
  { composite: ['updatedAt'] },
55
55
  )
56
+ // externalUserId is not enforced unique (no DB unique constraint) and is optional, so
57
+ // findByExternalUserId only returns one arbitrary match. Prefer allByExternalUserId
58
+ // unless the caller can guarantee uniqueness for their use case.
59
+ // Note: this schema is now at 4 of the 5-index cap enforced by SchemaBuilder
60
+ // (#buildIndexes) - budget the next lookup index accordingly.
56
61
  .addIndex(
57
62
  { composite: ['externalUserId'] },
58
63
  { composite: ['updatedAt'] },