@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 +12 -0
- package/CLAUDE.md +18 -1
- package/README.md +29 -2
- package/package.json +1 -1
- package/src/models/base/base.collection.js +32 -12
- package/src/models/site/config.js +6 -0
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/
|
|
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
|
@@ -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
|
|
117
|
-
|
|
118
|
-
if (
|
|
119
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
681
|
-
|
|
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
|
-
|
|
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(),
|