@adobe/spacecat-shared-data-access 3.3.0 → 3.4.1

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.4.1](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v3.4.0...@adobe/spacecat-shared-data-access-v3.4.1) (2026-03-01)
2
+
3
+ ### Bug Fixes
4
+
5
+ * **data-access:** chunk batchGetByKeys to avoid 414 URI Too Large ([#1391](https://github.com/adobe/spacecat-shared/issues/1391)) ([25b0c0d](https://github.com/adobe/spacecat-shared/commit/25b0c0dab34b4e8e8b5fef9f4033cbcc698652f5)), closes [#1390](https://github.com/adobe/spacecat-shared/issues/1390)
6
+
7
+ ## [@adobe/spacecat-shared-data-access-v3.4.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v3.3.0...@adobe/spacecat-shared-data-access-v3.4.0) (2026-02-26)
8
+
9
+ ### Features
10
+
11
+ * register commerce-product-enrichments-yearly audit type ([#1381](https://github.com/adobe/spacecat-shared/issues/1381)) ([6d2990f](https://github.com/adobe/spacecat-shared/commit/6d2990f691d3c9837772d092efb72cd7069a8024))
12
+
1
13
  ## [@adobe/spacecat-shared-data-access-v3.3.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v3.2.0...@adobe/spacecat-shared-data-access-v3.3.0) (2026-02-26)
2
14
 
3
15
  ### Features
package/CLAUDE.md ADDED
@@ -0,0 +1,204 @@
1
+ # spacecat-shared-data-access
2
+
3
+ Shared data-access layer for SpaceCat services. Provides entity models/collections backed by PostgreSQL via PostgREST.
4
+
5
+ ## Architecture
6
+
7
+ ```
8
+ Lambda/ECS service
9
+ -> this package (@adobe/spacecat-shared-data-access)
10
+ -> @supabase/postgrest-js
11
+ -> mysticat-data-service (PostgREST + Aurora PostgreSQL)
12
+ ```
13
+
14
+ - **Database schema**: lives in [mysticat-data-service](https://github.com/adobe/mysticat-data-service) as dbmate SQL migrations
15
+ - **This package**: JavaScript model/collection layer mapping camelCase entities to snake_case PostgREST API
16
+ - **v2 (retired)**: ElectroDB -> DynamoDB. Published as `@adobe/spacecat-shared-data-access-v2`
17
+ - **v3 (current)**: PostgREST client -> mysticat-data-service
18
+
19
+ ## Key Files
20
+
21
+ | File | Purpose |
22
+ |------|---------|
23
+ | `src/index.js` | Default export: `dataAccessWrapper(fn)` for Helix/Lambda handlers |
24
+ | `src/service/index.js` | `createDataAccess(config, log?, client?)` factory |
25
+ | `src/models/base/schema.builder.js` | DSL for defining entity schemas (attributes, references, indexes) |
26
+ | `src/models/base/base.model.js` | Base entity class (auto-generated getters/setters, save, remove) |
27
+ | `src/models/base/base.collection.js` | Base collection class (findById, all, query, count) |
28
+ | `src/models/base/entity.registry.js` | Registers all entity collections |
29
+ | `src/util/postgrest.utils.js` | camelCase<->snake_case field mapping, query builders, cursor pagination |
30
+ | `src/models/index.js` | Barrel export of all entity models |
31
+
32
+ ## Entity Structure
33
+
34
+ Each entity lives in `src/models/<entity>/` with 4 files:
35
+
36
+ ```
37
+ src/models/site/
38
+ site.schema.js # SchemaBuilder definition (attributes, references, indexes)
39
+ site.model.js # Extends BaseModel (business logic, constants)
40
+ site.collection.js # Extends BaseCollection (custom queries)
41
+ index.js # Re-exports model, collection, schema
42
+ ```
43
+
44
+ ### Schema Definition Pattern
45
+
46
+ ```js
47
+ const schema = new SchemaBuilder(Site, SiteCollection)
48
+ .addReference('belongs_to', 'Organization') // FK -> organizations.id
49
+ .addReference('has_many', 'Audits') // One-to-many relationship
50
+ .addAttribute('baseURL', {
51
+ type: 'string',
52
+ required: true,
53
+ validate: (value) => isValidUrl(value),
54
+ })
55
+ .addAttribute('deliveryType', {
56
+ type: Object.values(Site.DELIVERY_TYPES), // Enum validation
57
+ default: Site.DEFAULT_DELIVERY_TYPE,
58
+ required: true,
59
+ })
60
+ .addAttribute('config', {
61
+ type: 'any',
62
+ default: DEFAULT_CONFIG,
63
+ get: (value) => Config(value), // Transform on read
64
+ })
65
+ .addAllIndex(['imsOrgId']) // Query index
66
+ .build();
67
+ ```
68
+
69
+ ### Attribute Options
70
+
71
+ | Option | Purpose |
72
+ |--------|---------|
73
+ | `type` | `'string'`, `'number'`, `'boolean'`, `'any'`, `'map'`, or array of enum values |
74
+ | `required` | Validation on save |
75
+ | `default` | Default value or factory function |
76
+ | `validate` | Custom validation function |
77
+ | `readOnly` | No setter generated |
78
+ | `postgrestField` | Custom DB column name (default: `camelToSnake(name)`) |
79
+ | `postgrestIgnore` | Virtual attribute, not sent to DB |
80
+ | `hidden` | Excluded from `toJSON()` |
81
+ | `watch` | Array of field names that trigger this attribute's setter |
82
+ | `set` | Custom setter `(value, allAttrs) => transformedValue` |
83
+ | `get` | Custom getter `(value) => transformedValue` |
84
+
85
+ ### Field Mapping
86
+
87
+ Models use camelCase, database uses snake_case. Mapping is automatic:
88
+
89
+ | Model field | DB column | Notes |
90
+ |-------------|-----------|-------|
91
+ | `siteId` (idName) | `id` | Primary key always maps to `id` |
92
+ | `baseURL` | `base_url` | Auto camelToSnake |
93
+ | `organizationId` | `organization_id` | FK from `belongs_to` reference |
94
+ | `isLive` | `is_live` | Auto camelToSnake |
95
+
96
+ Override with `postgrestField: 'custom_name'` on the attribute.
97
+
98
+ ## Changing Entities
99
+
100
+ Changes require **two repos**:
101
+
102
+ ### 1. Database schema — [mysticat-data-service](https://github.com/adobe/mysticat-data-service)
103
+
104
+ ```bash
105
+ make migrate-new name=add_foo_to_sites
106
+ # Edit the migration SQL (table, columns, indexes, grants, comments)
107
+ make migrate && make test
108
+ ```
109
+
110
+ Every migration must include: indexes on FKs, `GRANT` to `postgrest_anon`/`postgrest_writer`, `COMMENT ON` for OpenAPI docs. See [mysticat-data-service CLAUDE.md](https://github.com/adobe/mysticat-data-service/blob/main/CLAUDE.md).
111
+
112
+ ### 2. Model layer — this package
113
+
114
+ - Add attribute in `<entity>.schema.js` -> auto-generates getter/setter
115
+ - Add business logic in `<entity>.model.js`
116
+ - Add custom queries in `<entity>.collection.js`
117
+ - New entity: create 4 files + register in `src/models/index.js`
118
+
119
+ ### 3. Integration test
120
+
121
+ ```bash
122
+ npm run test:it # Spins up PostgREST via Docker, runs mocha suite
123
+ ```
124
+
125
+ ## Testing
126
+
127
+ ```bash
128
+ npm test # Unit tests (mocha + sinon + chai)
129
+ npm run test:debug # Unit tests with debugger
130
+ npm run test:it # Integration tests (Docker: Postgres + PostgREST)
131
+ npm run lint # ESLint
132
+ npm run lint:fix # Auto-fix lint issues
133
+ ```
134
+
135
+ ### Integration Test Setup
136
+
137
+ Integration tests pull the `mysticat-data-service` Docker image from ECR:
138
+
139
+ ```bash
140
+ # ECR login (one-time)
141
+ aws ecr get-login-password --profile spacecat-dev --region us-east-1 \
142
+ | docker login --username AWS --password-stdin 682033462621.dkr.ecr.us-east-1.amazonaws.com
143
+
144
+ # Override image tag
145
+ export MYSTICAT_DATA_SERVICE_TAG=v1.13.0
146
+ npm run test:it
147
+ ```
148
+
149
+ ### Unit Test Conventions
150
+
151
+ - Tests in `test/unit/models/<entity>/`
152
+ - PostgREST calls are stubbed via sinon
153
+ - Each entity model and collection has its own test file
154
+
155
+ ## Common Patterns
156
+
157
+ ### Collection query with WHERE clause
158
+
159
+ ```js
160
+ // In a collection method
161
+ async findByStatus(status) {
162
+ return this.all(
163
+ (attrs, op) => op.eq(attrs.status, status),
164
+ { limit: 100, order: { field: 'createdAt', direction: 'desc' } }
165
+ );
166
+ }
167
+ ```
168
+
169
+ ### Reference traversal
170
+
171
+ ```js
172
+ // belongs_to: site.getOrganization() -> fetches parent org
173
+ // has_many: organization.getSites() -> fetches child sites
174
+ const site = await dataAccess.Site.findById(id);
175
+ const org = await site.getOrganization();
176
+ const audits = await site.getAudits();
177
+ ```
178
+
179
+ ### PostgREST WHERE operators
180
+
181
+ `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `is`, `in`, `contains`, `like`, `ilike`
182
+
183
+ ```js
184
+ // Usage in collection.all()
185
+ const liveSites = await dataAccess.Site.all(
186
+ (attrs, op) => op.eq(attrs.isLive, true)
187
+ );
188
+ ```
189
+
190
+ ## Environment Variables
191
+
192
+ | Variable | Required | Purpose |
193
+ |----------|----------|---------|
194
+ | `POSTGREST_URL` | Yes | PostgREST base URL (e.g. `http://data-svc.internal`) |
195
+ | `POSTGREST_SCHEMA` | No | Schema name (default: `public`) |
196
+ | `POSTGREST_API_KEY` | No | JWT for `postgrest_writer` role (enables UPDATE/DELETE) |
197
+ | `S3_CONFIG_BUCKET` | No | Only for `Configuration` entity |
198
+ | `AWS_REGION` | No | Only for `Configuration` entity |
199
+
200
+ ## Special Entities
201
+
202
+ - **Configuration**: S3-backed (not PostgREST). Requires `S3_CONFIG_BUCKET`.
203
+ - **KeyEvent**: Deprecated in v3. All methods throw.
204
+ - **LatestAudit**: Virtual entity computed from `Audit` queries (no dedicated table).
package/README.md CHANGED
@@ -134,12 +134,88 @@ Current exported entities include:
134
134
  - `TrialUser`
135
135
  - `TrialUserActivity`
136
136
 
137
+ ## Architecture
138
+
139
+ ```
140
+ Lambda / ECS service
141
+ -> @adobe/spacecat-shared-data-access (this package)
142
+ -> @supabase/postgrest-js
143
+ -> mysticat-data-service (PostgREST + Aurora PostgreSQL)
144
+ https://github.com/adobe/mysticat-data-service
145
+ ```
146
+
147
+ **v2 (retired):** ElectroDB -> DynamoDB (direct, schema-in-code)
148
+ **v3 (current):** PostgREST client -> [mysticat-data-service](https://github.com/adobe/mysticat-data-service) (schema-in-database)
149
+
150
+ The database schema (tables, indexes, enums, grants) lives in **mysticat-data-service** as dbmate migrations.
151
+ This package provides the JavaScript model/collection layer that maps camelCase entities to the snake_case PostgREST API.
152
+
137
153
  ## V3 Behavior Notes
138
154
 
139
155
  - `Configuration` remains S3-backed in v3.
140
156
  - `KeyEvent` is deprecated in v3 and intentionally throws on access/mutation methods.
141
157
  - `LatestAudit` is virtual in v3 and derived from `Audit` queries (no dedicated table required).
142
158
 
159
+ ## Changing Entities
160
+
161
+ Adding or modifying an entity now requires changes in **two repositories**:
162
+
163
+ ### 1. Database schema — [mysticat-data-service](https://github.com/adobe/mysticat-data-service)
164
+
165
+ Create a dbmate migration for the schema change (table, columns, indexes, grants, enums):
166
+
167
+ ```bash
168
+ # In mysticat-data-service
169
+ make migrate-new name=add_foo_column_to_sites
170
+ # Edit db/migrations/YYYYMMDDHHMMSS_add_foo_column_to_sites.sql
171
+ make migrate
172
+ docker compose -f docker/docker-compose.yml restart postgrest
173
+ make test
174
+ ```
175
+
176
+ See the [mysticat-data-service CLAUDE.md](https://github.com/adobe/mysticat-data-service/blob/main/CLAUDE.md) for migration conventions (required grants, indexes, comments, etc.).
177
+
178
+ ### 2. Model/collection layer — this package
179
+
180
+ Update the entity schema, model, and/or collection in `src/models/<entity>/`:
181
+
182
+ | File | What to change |
183
+ |------|---------------|
184
+ | `<entity>.schema.js` | Add/modify attributes, references, indexes |
185
+ | `<entity>.model.js` | Add business logic methods |
186
+ | `<entity>.collection.js` | Add custom query methods |
187
+
188
+ **Adding a new attribute example:**
189
+
190
+ ```js
191
+ // In <entity>.schema.js, add to the SchemaBuilder chain:
192
+ .addAttribute('myNewField', {
193
+ type: 'string',
194
+ required: false,
195
+ // Optional: custom DB column name (default: camelToSnake)
196
+ // postgrestField: 'custom_column_name',
197
+ })
198
+ ```
199
+
200
+ This automatically generates `getMyNewField()` and `setMyNewField()` on the model.
201
+
202
+ **Adding a new entity:** Create 4 files following the pattern in any existing entity folder:
203
+ - `<entity>.schema.js` — SchemaBuilder definition
204
+ - `<entity>.model.js` — extends `BaseModel`
205
+ - `<entity>.collection.js` — extends `BaseCollection`
206
+ - `index.js` — re-exports model, collection, schema
207
+
208
+ Then register the entity in `src/models/index.js`.
209
+
210
+ ### 3. Integration test the full stack
211
+
212
+ ```bash
213
+ # In this package — runs PostgREST in Docker
214
+ npm run test:it
215
+ ```
216
+
217
+ Integration tests pull the `mysticat-data-service` Docker image from ECR, so new schema changes must be published as a new image tag first (or test against a local PostgREST).
218
+
143
219
  ## Migrating from V2
144
220
 
145
221
  If you are upgrading from DynamoDB/ElectroDB-based v2:
@@ -154,6 +230,7 @@ If you are upgrading from DynamoDB/ElectroDB-based v2:
154
230
 
155
231
  - Backing store is now Postgres via PostgREST, not DynamoDB/ElectroDB.
156
232
  - You must provide `postgrestUrl` (or `POSTGREST_URL` via wrapper env).
233
+ - Schema changes now go through [mysticat-data-service](https://github.com/adobe/mysticat-data-service) migrations, not code.
157
234
  - `Configuration` remains S3-backed (requires `s3Bucket`/`S3_CONFIG_BUCKET` when used).
158
235
  - `KeyEvent` is deprecated in v3 and now throws.
159
236
  - `LatestAudit` is no longer a dedicated table and is computed from `Audit` queries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-data-access",
3
- "version": "3.3.0",
3
+ "version": "3.4.1",
4
4
  "description": "Shared modules of the Spacecat Services - Data Access",
5
5
  "type": "module",
6
6
  "engines": {
@@ -84,6 +84,7 @@ class Audit extends BaseModel {
84
84
  TOC: 'toc',
85
85
  WIKIPEDIA_ANALYSIS: 'wikipedia-analysis',
86
86
  COMMERCE_PRODUCT_ENRICHMENTS: 'commerce-product-enrichments',
87
+ COMMERCE_PRODUCT_ENRICHMENTS_YEARLY: 'commerce-product-enrichments-yearly',
87
88
  COMMERCE_PRODUCT_PAGE_ENRICHMENT: 'commerce-product-page-enrichment',
88
89
  COMMERCE_PRODUCT_CATALOG_ENRICHMENT: 'commerce-product-catalog-enrichment',
89
90
  };
@@ -617,21 +617,37 @@ class BaseCollection {
617
617
  const dbField = this.#toDbField(bulkKeyField);
618
618
  const values = keys.map((key) => key[bulkKeyField]);
619
619
  const select = this.#buildSelect(options.attributes);
620
- const { data, error } = await this.postgrestService
621
- .from(this.tableName)
622
- .select(select)
623
- .in(dbField, values);
624
620
 
625
- if (!error) {
621
+ // Chunk values to avoid 414 URI Too Large from PostgREST GET URLs.
622
+ // Each UUID is ~36 chars; 50 × 36 ≈ 1,800 chars, well under the 8KB URL limit.
623
+ const CHUNK_SIZE = 50;
624
+ const allData = [];
625
+ let hadInvalidInput = false;
626
+
627
+ for (let i = 0; i < values.length; i += CHUNK_SIZE) {
628
+ const chunk = values.slice(i, i + CHUNK_SIZE);
629
+ // eslint-disable-next-line no-await-in-loop
630
+ const { data, error } = await this.postgrestService
631
+ .from(this.tableName)
632
+ .select(select)
633
+ .in(dbField, chunk);
634
+
635
+ if (error) {
636
+ if (!this.#isInvalidInputError(error)) {
637
+ throw error;
638
+ }
639
+ hadInvalidInput = true;
640
+ break;
641
+ }
642
+ allData.push(...(data || []));
643
+ }
644
+
645
+ if (!hadInvalidInput) {
626
646
  return {
627
- data: this.#createInstances((data || []).map((record) => this.#toModelRecord(record))),
647
+ data: this.#createInstances(allData.map((record) => this.#toModelRecord(record))),
628
648
  unprocessed: [],
629
649
  };
630
650
  }
631
-
632
- if (!this.#isInvalidInputError(error)) {
633
- throw error;
634
- }
635
651
  }
636
652
 
637
653
  const records = await Promise.all(