@adobe/spacecat-shared-data-access 3.6.2 → 3.8.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-v3.8.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v3.7.0...@adobe/spacecat-shared-data-access-v3.8.0) (2026-03-05)
2
+
3
+ ### Features
4
+
5
+ * **data-access:** add bulk entitlement query with organization embedding ([#1402](https://github.com/adobe/spacecat-shared/issues/1402)) ([588cfce](https://github.com/adobe/spacecat-shared/commit/588cfce8e1b4e81a55c3fbca4c7491368196b558))
6
+
7
+ ## [@adobe/spacecat-shared-data-access-v3.7.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v3.6.2...@adobe/spacecat-shared-data-access-v3.7.0) (2026-03-04)
8
+
9
+ ### Features
10
+
11
+ * **data-access:** add timesCited attribute to SentimentTopic schema ([#1399](https://github.com/adobe/spacecat-shared/issues/1399)) ([03cae03](https://github.com/adobe/spacecat-shared/commit/03cae0375a1c44e23d33b65f28002b5b22b975c1))
12
+
1
13
  ## [@adobe/spacecat-shared-data-access-v3.6.2](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v3.6.1...@adobe/spacecat-shared-data-access-v3.6.2) (2026-03-04)
2
14
 
3
15
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-data-access",
3
- "version": "3.6.2",
3
+ "version": "3.8.0",
4
4
  "description": "Shared modules of the Spacecat Services - Data Access",
5
5
  "type": "module",
6
6
  "engines": {
@@ -11,6 +11,8 @@
11
11
  */
12
12
 
13
13
  import BaseCollection from '../base/base.collection.js';
14
+ import DataAccessError from '../../errors/data-access.error.js';
15
+ import { DEFAULT_PAGE_SIZE } from '../../util/postgrest.utils.js';
14
16
 
15
17
  /**
16
18
  * EntitlementCollection - A collection class responsible for managing Entitlement entities.
@@ -22,7 +24,67 @@ import BaseCollection from '../base/base.collection.js';
22
24
  class EntitlementCollection extends BaseCollection {
23
25
  static COLLECTION_NAME = 'EntitlementCollection';
24
26
 
25
- // add custom methods here
27
+ /**
28
+ * Finds all entitlements for a given product code with their parent organization
29
+ * data embedded via PostgREST resource embedding (INNER JOIN). This avoids N+1
30
+ * queries when you need both entitlement and organization data.
31
+ *
32
+ * Returns plain objects, not model instances, since the result combines fields
33
+ * from two entities. Callers should access properties directly
34
+ * (e.g., `result.entitlement.tier`), not via getter methods.
35
+ *
36
+ * @param {string} productCode - Product code to filter by (e.g., 'LLMO').
37
+ * @returns {Promise<Array<{entitlement: {id: string, productCode: string, tier: string},
38
+ * organization: {id: string, name: string, imsOrgId: string}}>>}
39
+ */
40
+ async allByProductCodeWithOrganization(productCode) {
41
+ if (!productCode) {
42
+ throw new DataAccessError('productCode is required', { entityName: 'Entitlement', tableName: 'entitlements' });
43
+ }
44
+
45
+ const allResults = [];
46
+ let offset = 0;
47
+ let keepGoing = true;
48
+
49
+ while (keepGoing) {
50
+ // eslint-disable-next-line no-await-in-loop
51
+ const { data, error } = await this.postgrestService
52
+ .from('entitlements')
53
+ .select('id, product_code, tier, organizations!inner(id, name, ims_org_id)')
54
+ .eq('product_code', productCode)
55
+ .range(offset, offset + DEFAULT_PAGE_SIZE - 1);
56
+
57
+ if (error) {
58
+ this.log.error(`[Entitlement] Failed to query entitlements with organizations - ${error.message}`, error);
59
+ throw new DataAccessError(
60
+ 'Failed to query entitlements with organizations',
61
+ { entityName: 'Entitlement', tableName: 'entitlements' },
62
+ error,
63
+ );
64
+ }
65
+
66
+ if (!data || data.length === 0) {
67
+ keepGoing = false;
68
+ } else {
69
+ allResults.push(...data);
70
+ keepGoing = data.length >= DEFAULT_PAGE_SIZE;
71
+ offset += DEFAULT_PAGE_SIZE;
72
+ }
73
+ }
74
+
75
+ return allResults.map((row) => ({
76
+ entitlement: {
77
+ id: row.id,
78
+ productCode: row.product_code,
79
+ tier: row.tier,
80
+ },
81
+ organization: {
82
+ id: row.organizations.id,
83
+ name: row.organizations.name,
84
+ imsOrgId: row.organizations.ims_org_id,
85
+ },
86
+ }));
87
+ }
26
88
  }
27
89
 
28
90
  export default EntitlementCollection;
@@ -31,6 +31,19 @@ export interface Entitlement extends BaseModel {
31
31
  setQuotas(quotas: object): Entitlement;
32
32
  }
33
33
 
34
+ export interface EntitlementWithOrganization {
35
+ entitlement: {
36
+ id: string;
37
+ productCode: EntitlementProductCode;
38
+ tier: EntitlementTier;
39
+ };
40
+ organization: {
41
+ id: string;
42
+ name: string;
43
+ imsOrgId: string;
44
+ } | null;
45
+ }
46
+
34
47
  export interface EntitlementCollection extends
35
48
  BaseCollection<Entitlement> {
36
49
  allByOrganizationId(organizationId: string): Promise<Entitlement[]>;
@@ -44,4 +57,8 @@ export interface EntitlementCollection extends
44
57
  organizationId: string,
45
58
  productCode: EntitlementProductCode,
46
59
  ): Promise<Entitlement | null>;
60
+
61
+ allByProductCodeWithOrganization(
62
+ productCode: EntitlementProductCode,
63
+ ): Promise<EntitlementWithOrganization[]>;
47
64
  }
@@ -61,6 +61,20 @@ const schema = new SchemaBuilder(SentimentTopic, SentimentTopicCollection)
61
61
  required: true,
62
62
  default: [],
63
63
  })
64
+ .addAttribute('citations', {
65
+ type: 'list',
66
+ required: false,
67
+ default: [],
68
+ postgrestIgnore: true,
69
+ items: {
70
+ type: 'map',
71
+ properties: {
72
+ url: { type: 'string', required: true },
73
+ timesCited: { type: 'number', required: true },
74
+ category: { type: 'string', required: false },
75
+ },
76
+ },
77
+ })
64
78
  .addAttribute('enabled', {
65
79
  type: 'boolean',
66
80
  required: true,