@adobe/spacecat-shared-data-access 3.6.0 → 3.6.2

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.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
+
3
+ ### Bug Fixes
4
+
5
+ * **data-access:** surface PG error details in logs ([#1401](https://github.com/adobe/spacecat-shared/issues/1401)) ([ddd3041](https://github.com/adobe/spacecat-shared/commit/ddd3041322a1468ee911f49f06c240a6697b8e66))
6
+
7
+ ## [@adobe/spacecat-shared-data-access-v3.6.1](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v3.6.0...@adobe/spacecat-shared-data-access-v3.6.1) (2026-03-03)
8
+
9
+ ### Bug Fixes
10
+
11
+ * stage changes wrt to spacecat-shared ([#1394](https://github.com/adobe/spacecat-shared/issues/1394)) ([7145fb0](https://github.com/adobe/spacecat-shared/commit/7145fb037e5b809d4552889c291b6f8688655b88))
12
+
1
13
  ## [@adobe/spacecat-shared-data-access-v3.6.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v3.5.0...@adobe/spacecat-shared-data-access-v3.6.0) (2026-03-02)
2
14
 
3
15
  ### Features
package/CLAUDE.md CHANGED
@@ -21,7 +21,8 @@ Lambda/ECS service
21
21
  | File | Purpose |
22
22
  |------|---------|
23
23
  | `src/index.js` | Default export: `dataAccessWrapper(fn)` for Helix/Lambda handlers |
24
- | `src/service/index.js` | `createDataAccess(config, log?, client?)` factory |
24
+ | `src/service/index.js` | `createDataAccess(config, log?, client?)` factory — returns entity collections + `services.postgrestClient` |
25
+ | `src/service/index.d.ts` | `DataAccess` and `DataAccessServices` type definitions |
25
26
  | `src/models/base/schema.builder.js` | DSL for defining entity schemas (attributes, references, indexes) |
26
27
  | `src/models/base/base.model.js` | Base entity class (auto-generated getters/setters, save, remove) |
27
28
  | `src/models/base/base.collection.js` | Base collection class (findById, all, query, count) |
@@ -152,6 +153,22 @@ npm run test:it
152
153
  - PostgREST calls are stubbed via sinon
153
154
  - Each entity model and collection has its own test file
154
155
 
156
+ ## Direct PostgREST Queries
157
+
158
+ `dataAccess.services.postgrestClient` exposes the raw `@supabase/postgrest-js` `PostgrestClient` for querying tables that don't have entity models (e.g. analytics views, reporting tables):
159
+
160
+ ```js
161
+ const { postgrestClient } = context.dataAccess.services;
162
+
163
+ const { data, error } = await postgrestClient
164
+ .from('brand_presence_executions')
165
+ .select('*')
166
+ .eq('site_id', siteId)
167
+ .limit(100);
168
+ ```
169
+
170
+ This is the same client instance used internally by entity collections — same URL, auth, and schema.
171
+
155
172
  ## Common Patterns
156
173
 
157
174
  ### Collection query with WHERE clause
package/README.md CHANGED
@@ -16,9 +16,10 @@ npm install @adobe/spacecat-shared-data-access
16
16
  ## What You Get
17
17
 
18
18
  The package provides:
19
- - `createDataAccess(config, log?, client?)`
19
+ - `createDataAccess(config, log?, client?)` — returns entity collections + `services.postgrestClient`
20
20
  - `dataAccessWrapper(fn)` (default export) for Helix/Lambda style handlers
21
21
  - Entity collections/models with stable external API shape for services
22
+ - `services.postgrestClient` for direct PostgREST queries against non-entity tables
22
23
 
23
24
  ## Quick Start
24
25
 
@@ -89,6 +90,29 @@ The wrapper reads from `context.env`:
89
90
  - `S3_CONFIG_BUCKET`
90
91
  - `AWS_REGION`
91
92
 
93
+ ## Direct PostgREST Queries
94
+
95
+ For querying PostgREST tables that are not modeled as entities (e.g. analytics views, reporting tables), the `postgrestClient` is available under `dataAccess.services`:
96
+
97
+ ```js
98
+ const { Site } = dataAccess; // entity collections
99
+ const { postgrestClient } = dataAccess.services; // raw PostgREST client
100
+
101
+ // Use entity collections as usual
102
+ const site = await Site.findById(siteId);
103
+
104
+ // Direct queries against non-entity tables
105
+ const { data, error } = await postgrestClient
106
+ .from('brand_presence_executions')
107
+ .select('execution_date, visibility_score, sentiment')
108
+ .eq('site_id', siteId)
109
+ .gte('execution_date', '2025-01-01')
110
+ .order('execution_date', { ascending: false })
111
+ .limit(100);
112
+ ```
113
+
114
+ This is the same `@supabase/postgrest-js` `PostgrestClient` instance used internally by the entity collections. Full IDE autocomplete is available for the query builder chain.
115
+
92
116
  ## Field Mapping Behavior
93
117
 
94
118
  Public model API remains camelCase while Postgres/PostgREST tables are snake_case.
@@ -383,7 +407,10 @@ export MYSTICAT_DATA_SERVICE_REPOSITORY=682033462621.dkr.ecr.us-east-1.amazonaws
383
407
 
384
408
  Type definitions are shipped from:
385
409
  - `src/index.d.ts`
386
- - `src/models/**/index.d.ts`
410
+ - `src/service/index.d.ts` — `DataAccess` and `DataAccessServices` interfaces
411
+ - `src/models/**/index.d.ts` — per-entity collection and model interfaces
412
+
413
+ The `DataAccess` interface provides full typing for all entity collections and `services.postgrestClient` (typed as `PostgrestClient` from `@supabase/postgrest-js`).
387
414
 
388
415
  Use the package directly in TS projects; no extra setup required.
389
416
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-data-access",
3
- "version": "3.6.0",
3
+ "version": "3.6.2",
4
4
  "description": "Shared modules of the Spacecat Services - Data Access",
5
5
  "type": "module",
6
6
  "engines": {
@@ -104,21 +104,29 @@ class BaseCollection {
104
104
  #isInvalidInputError(error) {
105
105
  let current = error;
106
106
  while (current) {
107
- if (current?.code === '22P02') {
108
- return true;
109
- }
107
+ if (current?.code === '22P02') return true;
110
108
  current = current.cause;
111
109
  }
112
110
  return false;
113
111
  }
114
112
 
115
113
  #logAndThrowError(message, cause) {
116
- const error = new DataAccessError(message, this, cause);
117
- this.log.error(`Base Collection Error [${this.entityName}]`, error);
118
- if (isNonEmptyArray(error.cause?.fields)) {
119
- this.log.error(`Validation errors: ${JSON.stringify(error.cause.fields)}`);
114
+ const parts = [message];
115
+ if (cause?.code) parts.push(`[${cause.code}] ${cause.message}`);
116
+ if (cause?.details) parts.push(cause.details);
117
+ if (cause?.hint) parts.push(`hint: ${cause.hint}`);
118
+
119
+ this.log.error(`[${this.entityName}] ${parts.join(' - ')}`);
120
+
121
+ if (isNonEmptyArray(cause?.fields)) {
122
+ this.log.error(`Validation errors: ${JSON.stringify(cause.fields)}`);
120
123
  }
121
- throw error;
124
+
125
+ throw new DataAccessError(
126
+ message,
127
+ { entityName: this.entityName, tableName: this.tableName },
128
+ cause,
129
+ );
122
130
  }
123
131
 
124
132
  #initializeCollectionMethods() {
@@ -547,7 +555,7 @@ class BaseCollection {
547
555
  const instances = this.#createInstances(allRows);
548
556
  return shouldReturnCursor ? { data: instances, cursor } : instances;
549
557
  } catch (error) {
550
- if (error instanceof DataAccessError) {
558
+ if (error.constructor === DataAccessError) {
551
559
  throw error;
552
560
  }
553
561
  return this.#logAndThrowError('Failed to query', error);
@@ -601,6 +609,7 @@ class BaseCollection {
601
609
  return isNonEmptyObject(item);
602
610
  }
603
611
 
612
+ // eslint-disable-next-line consistent-return
604
613
  async batchGetByKeys(keys, options = {}) {
605
614
  guardArray('keys', keys, this.entityName, 'any');
606
615
 
@@ -677,8 +686,11 @@ class BaseCollection {
677
686
  unprocessed: [],
678
687
  };
679
688
  } catch (error) {
680
- this.log.error(`Failed to batch get by keys [${this.entityName}]`, error);
681
- throw new DataAccessError('Failed to batch get by keys', this, error);
689
+ /* c8 ignore next 3 -- re-throw guard (exact match; excludes ValidationError subclass) */
690
+ if (error.constructor === DataAccessError) {
691
+ throw error;
692
+ }
693
+ this.#logAndThrowError('Failed to batch get by keys', error);
682
694
  }
683
695
  }
684
696
 
@@ -721,6 +733,8 @@ class BaseCollection {
721
733
  await this.#onCreate(instance);
722
734
  return instance;
723
735
  } catch (error) {
736
+ /* c8 ignore next -- re-throw guard (exact match; excludes ValidationError subclass) */
737
+ if (error.constructor === DataAccessError) throw error;
724
738
  return this.#logAndThrowError('Failed to create', error);
725
739
  }
726
740
  }
@@ -833,6 +847,8 @@ class BaseCollection {
833
847
  await this.#onCreateMany({ createdItems, errorItems });
834
848
  return { createdItems, errorItems };
835
849
  } catch (error) {
850
+ /* c8 ignore next -- re-throw guard (exact match; excludes ValidationError subclass) */
851
+ if (error.constructor === DataAccessError) throw error;
836
852
  return this.#logAndThrowError('Failed to create many', error);
837
853
  }
838
854
  }
@@ -856,7 +872,7 @@ class BaseCollection {
856
872
 
857
873
  const { error } = await query.select().maybeSingle();
858
874
  if (error) {
859
- throw new DataAccessError('Failed to update entity', this, error);
875
+ this.#logAndThrowError('Failed to update entity', error);
860
876
  }
861
877
  }
862
878
 
@@ -917,6 +933,8 @@ class BaseCollection {
917
933
  this.#invalidateCache();
918
934
  return undefined;
919
935
  } catch (error) {
936
+ /* c8 ignore next -- re-throw guard (exact match; excludes ValidationError subclass) */
937
+ if (error.constructor === DataAccessError) throw error;
920
938
  return this.#logAndThrowError('Failed to save many', error);
921
939
  }
922
940
  }
@@ -947,6 +965,8 @@ class BaseCollection {
947
965
  this.#invalidateCache();
948
966
  return undefined;
949
967
  } catch (error) {
968
+ /* c8 ignore next -- re-throw guard (exact match; excludes ValidationError subclass) */
969
+ if (error.constructor === DataAccessError) throw error;
950
970
  return this.#logAndThrowError('Failed to remove by IDs', error);
951
971
  }
952
972
  }
@@ -344,6 +344,12 @@ export const configSchema = Joi.object({
344
344
  edgeOptimizeConfig: Joi.object({
345
345
  enabled: Joi.boolean().optional(),
346
346
  opted: Joi.number().optional(),
347
+ stagingDomains: Joi.array().items(
348
+ Joi.object({
349
+ domain: Joi.string().required(),
350
+ id: Joi.string().required(),
351
+ }),
352
+ ).optional(),
347
353
  }).optional(),
348
354
  contentAiConfig: Joi.object({
349
355
  index: Joi.string().optional(),