@adobe/spacecat-shared-data-access 2.109.0 → 3.0.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.
@@ -50,15 +50,15 @@ class BaseModel {
50
50
  /**
51
51
  * Constructs an instance of BaseModel.
52
52
  * @constructor
53
- * @param {Object} electroService - The ElectroDB service used for managing entities.
53
+ * @param {Object} postgrestService - The PostgREST client used for managing entities.
54
54
  * @param {EntityRegistry} entityRegistry - The registry holding entities, their schema
55
55
  * and collection.
56
56
  * @param {Schema} schema - The schema for the entity.
57
57
  * @param {Object} record - The initial data for the entity instance.
58
58
  * @param {Object} log - A log for capturing logging information.
59
59
  */
60
- constructor(electroService, entityRegistry, schema, record, log) {
61
- this.electroService = electroService;
60
+ constructor(postgrestService, entityRegistry, schema, record, log) {
61
+ this.postgrestService = postgrestService;
62
62
  this.entityRegistry = entityRegistry;
63
63
  this.schema = schema;
64
64
  this.record = record;
@@ -68,9 +68,8 @@ class BaseModel {
68
68
  this.idName = entityNameToIdName(this.entityName);
69
69
 
70
70
  this.collection = entityRegistry.getCollection(schema.getCollectionName());
71
- this.entity = electroService.entities[this.entityName];
72
71
 
73
- this.patcher = new Patcher(this.entity, this.schema, this.record);
72
+ this.patcher = new Patcher(this.collection, this.schema, this.record);
74
73
 
75
74
  this._accessorCache = {};
76
75
 
@@ -283,7 +282,15 @@ class BaseModel {
283
282
 
284
283
  await Promise.all(removePromises);
285
284
 
286
- await this.entity.remove(this.generateCompositeKeys()).go();
285
+ if (this.collection && typeof this.collection.removeByIndexKeys === 'function') {
286
+ await this.collection.removeByIndexKeys([this.generateCompositeKeys()]);
287
+ } else if (this.postgrestService?.entities?.[this.entityName]?.remove) {
288
+ await this.postgrestService.entities[this.entityName]
289
+ .remove(this.generateCompositeKeys())
290
+ .go();
291
+ } else {
292
+ throw new DataAccessError(`No remove strategy available for ${this.entityName}`, this);
293
+ }
287
294
 
288
295
  this.#invalidateCache();
289
296
 
@@ -86,11 +86,13 @@ import SentimentTopicSchema from '../sentiment-topic/sentiment-topic.schema.js';
86
86
  class EntityRegistry {
87
87
  static entities = {};
88
88
 
89
+ static defaultEntities = {};
90
+
89
91
  /**
90
92
  * Constructs an instance of EntityRegistry.
91
93
  * @constructor
92
94
  * @param {Object} services - Dictionary of services keyed by datastore type.
93
- * @param {Object} services.dynamo - The ElectroDB service instance for DynamoDB operations.
95
+ * @param {Object} services.postgrest - The PostgREST client instance for Postgres operations.
94
96
  * @param {{s3Client: S3Client, s3Bucket: string}|null} [services.s3] - S3 service configuration.
95
97
  * @param {Object} config - Configuration object containing environment-derived settings.
96
98
  * @param {Object} log - A logger for capturing and logging information.
@@ -107,14 +109,14 @@ class EntityRegistry {
107
109
  /**
108
110
  * Initializes the collections managed by the EntityRegistry.
109
111
  * This method creates instances of each collection and stores them in an internal map.
110
- * ElectroDB-based collections are initialized with the dynamo service.
112
+ * PostgREST-based collections are initialized with the postgrest service.
111
113
  * Configuration is handled specially as it's a standalone S3-based collection.
112
114
  * @private
113
115
  */
114
116
  #initialize() {
115
- // Initialize ElectroDB-based collections
117
+ // Initialize PostgREST-based collections
116
118
  Object.values(EntityRegistry.entities).forEach(({ collection: Collection, schema }) => {
117
- const collection = new Collection(this.services.dynamo, this, schema, this.log);
119
+ const collection = new Collection(this.services.postgrest, this, schema, this.log);
118
120
  this.collections.set(Collection.COLLECTION_NAME, collection);
119
121
  });
120
122
 
@@ -164,6 +166,10 @@ class EntityRegistry {
164
166
  static registerEntity(schema, collection) {
165
167
  this.entities[decapitalize(schema.getEntityName())] = { schema, collection };
166
168
  }
169
+
170
+ static resetEntities() {
171
+ this.entities = { ...this.defaultEntities };
172
+ }
167
173
  }
168
174
 
169
175
  // Register ElectroDB-based entities only (Configuration is handled separately)
@@ -198,5 +204,6 @@ EntityRegistry.registerEntity(TrialUserActivitySchema, TrialUserActivityCollecti
198
204
  EntityRegistry.registerEntity(PageCitabilitySchema, PageCitabilityCollection);
199
205
  EntityRegistry.registerEntity(SentimentGuidelineSchema, SentimentGuidelineCollection);
200
206
  EntityRegistry.registerEntity(SentimentTopicSchema, SentimentTopicCollection);
207
+ EntityRegistry.defaultEntities = { ...EntityRegistry.entities };
201
208
 
202
209
  export default EntityRegistry;
@@ -36,6 +36,7 @@ const DEFAULT_SERVICE_NAME = 'SpaceCat';
36
36
  */
37
37
  const ID_ATTRIBUTE_DATA = {
38
38
  type: 'string',
39
+ postgrestField: 'id',
39
40
  required: true,
40
41
  readOnly: true,
41
42
  // https://electrodb.dev/en/modeling/attributes/#default
@@ -215,6 +216,7 @@ class SchemaBuilder {
215
216
 
216
217
  this.addAttribute('recordExpiresAt', {
217
218
  type: 'number',
219
+ postgrestIgnore: true,
218
220
  required: true,
219
221
  readOnly: true,
220
222
  default: () => Math.floor(Date.now() / 1000) + ttlInDays * 24 * 60 * 60,
@@ -156,6 +156,17 @@ class Schema {
156
156
  return result;
157
157
  }
158
158
 
159
+ // Internal helper: v3 schemas use `composite` keys (legacy Electro used `facets`).
160
+ static #getKeyParts(indexPart) {
161
+ if (Array.isArray(indexPart?.composite)) {
162
+ return indexPart.composite;
163
+ }
164
+ if (Array.isArray(indexPart?.facets)) {
165
+ return indexPart.facets;
166
+ }
167
+ return [];
168
+ }
169
+
159
170
  getIndexByName(indexName) {
160
171
  return this.indexes[indexName];
161
172
  }
@@ -167,10 +178,10 @@ class Schema {
167
178
  const subKeyNames = sortKeys.slice(0, length);
168
179
  const index = Object.values(this.indexes).find((candidate) => {
169
180
  const { pk, sk } = candidate;
170
- const allKeys = [...(pk?.facets || []), ...(sk?.facets || [])];
181
+ const allKeys = [...Schema.#getKeyParts(pk), ...Schema.#getKeyParts(sk)];
171
182
 
172
183
  // check if all keys in the index are in the sort keys
173
- const pkKeys = Array.isArray(pk?.facets) ? pk.facets : [];
184
+ const pkKeys = Schema.#getKeyParts(pk);
174
185
  return pkKeys.every((key) => subKeyNames.includes(key))
175
186
  && subKeyNames.every((key) => allKeys.includes(key));
176
187
  });
@@ -246,8 +257,8 @@ class Schema {
246
257
  return [];
247
258
  }
248
259
 
249
- const pkKeys = Array.isArray(index.pk?.facets) ? index.pk.facets : [];
250
- const skKeys = Array.isArray(index.sk?.facets) ? index.sk.facets : [];
260
+ const pkKeys = Schema.#getKeyParts(index.pk);
261
+ const skKeys = Schema.#getKeyParts(index.sk);
251
262
 
252
263
  return [...pkKeys, ...skKeys];
253
264
  }
@@ -25,6 +25,21 @@ class FixEntitySuggestion extends BaseModel {
25
25
 
26
26
  static DEFAULT_UPDATED_BY = 'spacecat';
27
27
 
28
+ getId() {
29
+ const id = super.getId();
30
+ if (id) {
31
+ return id;
32
+ }
33
+
34
+ const suggestionId = this.getSuggestionId();
35
+ const fixEntityId = this.getFixEntityId();
36
+ if (suggestionId && fixEntityId) {
37
+ return `${suggestionId}#${fixEntityId}`;
38
+ }
39
+
40
+ return undefined;
41
+ }
42
+
28
43
  /**
29
44
  * Generates the composite keys for the FixEntitySuggestion model.
30
45
  * @returns {Object} - The composite keys.
@@ -23,6 +23,17 @@ Indexes Doc: https://electrodb.dev/en/modeling/indexes/
23
23
  const schema = new SchemaBuilder(FixEntitySuggestion, FixEntitySuggestionCollection)
24
24
  .withPrimaryPartitionKeys(['suggestionId'])
25
25
  .withPrimarySortKeys(['fixEntityId'])
26
+ .addAttribute('fixEntitySuggestionId', {
27
+ type: 'string',
28
+ required: false,
29
+ readOnly: true,
30
+ postgrestIgnore: true,
31
+ })
32
+ .addAttribute('updatedBy', {
33
+ type: 'string',
34
+ required: false,
35
+ postgrestIgnore: true,
36
+ })
26
37
  .addReference('belongs_to', 'FixEntity')
27
38
  .addReference('belongs_to', 'Suggestion')
28
39
  .addAttribute('opportunityId', {
@@ -38,6 +49,7 @@ const schema = new SchemaBuilder(FixEntitySuggestion, FixEntitySuggestionCollect
38
49
  .addAttribute('fixEntityCreatedDate', {
39
50
  type: 'string',
40
51
  readOnly: true,
52
+ postgrestIgnore: true,
41
53
  watch: ['fixEntityCreatedAt'],
42
54
  set: (_, { fixEntityCreatedAt }) => (fixEntityCreatedAt ? fixEntityCreatedAt.split('T')[0] : undefined),
43
55
  })
@@ -11,6 +11,7 @@
11
11
  */
12
12
 
13
13
  import BaseCollection from '../base/base.collection.js';
14
+ import DataAccessError from '../../errors/data-access.error.js';
14
15
 
15
16
  /**
16
17
  * KeyEventCollection - A collection class responsible for managing KeyEvent entities.
@@ -22,7 +23,28 @@ import BaseCollection from '../base/base.collection.js';
22
23
  class KeyEventCollection extends BaseCollection {
23
24
  static COLLECTION_NAME = 'KeyEventCollection';
24
25
 
25
- // add custom methods here
26
+ #throwDeprecated() {
27
+ throw new DataAccessError(
28
+ 'KeyEvent is deprecated in data-access v3. Use Audit/LatestAudit and related Postgres-backed entities instead.',
29
+ this,
30
+ );
31
+ }
32
+
33
+ async all() { return this.#throwDeprecated(); }
34
+
35
+ async allByIndexKeys() { return this.#throwDeprecated(); }
36
+
37
+ async findById() { return this.#throwDeprecated(); }
38
+
39
+ async findByIndexKeys() { return this.#throwDeprecated(); }
40
+
41
+ async create() { return this.#throwDeprecated(); }
42
+
43
+ async createMany() { return this.#throwDeprecated(); }
44
+
45
+ async removeByIds() { return this.#throwDeprecated(); }
46
+
47
+ async removeByIndexKeys() { return this.#throwDeprecated(); }
26
48
  }
27
49
 
28
50
  export default KeyEventCollection;
@@ -10,8 +10,10 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import BaseCollection from '../base/base.collection.js';
13
+ import { isNonEmptyArray } from '@adobe/spacecat-shared-utils';
14
+ import DataAccessError from '../../errors/data-access.error.js';
14
15
  import { guardId, guardString } from '../../util/index.js';
16
+ import BaseCollection from '../base/base.collection.js';
15
17
 
16
18
  /**
17
19
  * LatestAuditCollection - A collection class responsible for managing LatestAudit entities.
@@ -23,8 +25,80 @@ import { guardId, guardString } from '../../util/index.js';
23
25
  class LatestAuditCollection extends BaseCollection {
24
26
  static COLLECTION_NAME = 'LatestAuditCollection';
25
27
 
26
- async create(item) {
27
- return super.create(item, { upsert: true });
28
+ // LatestAudit is a virtual view in v3; writes are not supported.
29
+ // eslint-disable-next-line class-methods-use-this
30
+ async create() {
31
+ throw new DataAccessError('LatestAudit is derived from Audit in v3 and cannot be created directly', this);
32
+ }
33
+
34
+ // eslint-disable-next-line class-methods-use-this
35
+ async createMany() {
36
+ throw new DataAccessError('LatestAudit is derived from Audit in v3 and cannot be created directly', this);
37
+ }
38
+
39
+ static #groupLatest(items, groupFields) {
40
+ const grouped = new Map();
41
+ items.forEach((item) => {
42
+ const key = groupFields.map((field) => item.record[field]).join('#');
43
+ const existing = grouped.get(key);
44
+ if (!existing || existing.getAuditedAt() < item.getAuditedAt()) {
45
+ grouped.set(key, item);
46
+ }
47
+ });
48
+ return [...grouped.values()];
49
+ }
50
+
51
+ async #allAuditsByKeys(keys, options = {}) {
52
+ const auditCollection = this.entityRegistry.getCollection('AuditCollection');
53
+ return auditCollection.allByIndexKeys(keys, {
54
+ ...options,
55
+ fetchAllPages: true,
56
+ order: 'desc',
57
+ returnCursor: false,
58
+ });
59
+ }
60
+
61
+ async all(sortKeys = {}, options = {}) {
62
+ return this.allByIndexKeys(sortKeys, options);
63
+ }
64
+
65
+ async findByAll(sortKeys = {}, options = {}) {
66
+ return this.findByIndexKeys(sortKeys, options);
67
+ }
68
+
69
+ async findByIndexKeys(keys, options = {}) {
70
+ const auditCollection = this.entityRegistry.getCollection('AuditCollection');
71
+
72
+ if (keys.siteId && keys.auditType) {
73
+ return auditCollection.findByIndexKeys(keys, { ...options, order: 'desc' });
74
+ }
75
+
76
+ // For single-key lookups, we only need the latest row, not all pages.
77
+ return auditCollection.findByIndexKeys(keys, { ...options, order: 'desc' });
78
+ }
79
+
80
+ async allByIndexKeys(keys, options = {}) {
81
+ const audits = await this.#allAuditsByKeys(keys, options);
82
+ if (!isNonEmptyArray(audits)) {
83
+ return options.returnCursor ? { data: [], cursor: null } : [];
84
+ }
85
+
86
+ let groupFields = ['siteId', 'auditType'];
87
+ if (keys.siteId && !keys.auditType) {
88
+ groupFields = ['auditType'];
89
+ } else if (!keys.siteId && keys.auditType) {
90
+ groupFields = ['siteId'];
91
+ }
92
+
93
+ const latest = LatestAuditCollection.#groupLatest(audits, groupFields);
94
+ // Preserve v2 behavior: default order is descending (most recent first).
95
+ const ascending = options.order === 'asc';
96
+ latest.sort((a, b) => (ascending
97
+ ? a.getAuditedAt().localeCompare(b.getAuditedAt())
98
+ : b.getAuditedAt().localeCompare(a.getAuditedAt())));
99
+ const limited = Number.isInteger(options.limit) ? latest.slice(0, options.limit) : latest;
100
+
101
+ return options.returnCursor ? { data: limited, cursor: null } : limited;
28
102
  }
29
103
 
30
104
  async allByAuditType(auditType) {
@@ -42,6 +42,44 @@ class ScrapeJobCollection extends BaseCollection {
42
42
  },
43
43
  });
44
44
  }
45
+
46
+ async allByBaseURLAndProcessingTypeAndOptEnableJavascriptAndOptHideConsentBanner(
47
+ baseURL,
48
+ processingType,
49
+ optEnableJavascript,
50
+ optHideConsentBanner,
51
+ options = {},
52
+ ) {
53
+ const keys = {
54
+ baseURL,
55
+ processingType,
56
+ optEnableJavascript,
57
+ optHideConsentBanner,
58
+ };
59
+
60
+ return this.allByIndexKeys(keys, options);
61
+ }
62
+
63
+ async findByBaseURLAndProcessingTypeAndOptEnableJavascriptAndOptHideConsentBanner(
64
+ baseURL,
65
+ processingType,
66
+ optEnableJavascript,
67
+ optHideConsentBanner,
68
+ options = {},
69
+ ) {
70
+ const jobs = await this
71
+ .allByBaseURLAndProcessingTypeAndOptEnableJavascriptAndOptHideConsentBanner(
72
+ baseURL,
73
+ processingType,
74
+ optEnableJavascript,
75
+ optHideConsentBanner,
76
+ { ...options, limit: options.limit || 1 },
77
+ );
78
+ if (Array.isArray(jobs)) {
79
+ return jobs[0] || null;
80
+ }
81
+ return jobs || null;
82
+ }
45
83
  }
46
84
 
47
85
  export default ScrapeJobCollection;
@@ -130,17 +130,19 @@ const schema = new SchemaBuilder(ScrapeJob, ScrapeJobCollection)
130
130
  })
131
131
  .addAttribute('optEnableJavascript', {
132
132
  type: 'string',
133
+ postgrestIgnore: true,
133
134
  hidden: true,
134
135
  readOnly: true,
135
136
  watch: ['options'],
136
- set: (_, { options }) => (options[ScrapeJob.ScrapeOptions.ENABLE_JAVASCRIPT] ? 'T' : 'F'),
137
+ set: (_, { options }) => (options?.[ScrapeJob.ScrapeOptions.ENABLE_JAVASCRIPT] ? 'T' : 'F'),
137
138
  })
138
139
  .addAttribute('optHideConsentBanner', {
139
140
  type: 'string',
141
+ postgrestIgnore: true,
140
142
  hidden: true,
141
143
  readOnly: true,
142
144
  watch: ['options'],
143
- set: (_, { options }) => (options[ScrapeJob.ScrapeOptions.HIDE_CONSENT_BANNER] ? 'T' : 'F'),
145
+ set: (_, { options }) => (options?.[ScrapeJob.ScrapeOptions.HIDE_CONSENT_BANNER] ? 'T' : 'F'),
144
146
  })
145
147
  // access pattern: get all jobs sorted by startedAt
146
148
  .addAllIndex(['startedAt'])
@@ -102,6 +102,7 @@ const schema = new SchemaBuilder(Site, SiteCollection)
102
102
  })
103
103
  .addAttribute('gitHubURL', {
104
104
  type: 'string',
105
+ postgrestField: 'github_url',
105
106
  validate: (value) => !value || isValidUrl(value),
106
107
  })
107
108
  .addAttribute('deliveryConfig', {
@@ -22,7 +22,17 @@ import BaseCollection from '../base/base.collection.js';
22
22
  class SiteEnrollmentCollection extends BaseCollection {
23
23
  static COLLECTION_NAME = 'SiteEnrollmentCollection';
24
24
 
25
- // add your custom collection methods here
25
+ async create(item, options = {}) {
26
+ if (item?.siteId && item?.entitlementId) {
27
+ const existing = await this.findByIndexKeys({
28
+ siteId: item.siteId,
29
+ entitlementId: item.entitlementId,
30
+ });
31
+ if (existing) return existing;
32
+ }
33
+
34
+ return super.create(item, options);
35
+ }
26
36
  }
27
37
 
28
38
  export default SiteEnrollmentCollection;
@@ -526,6 +526,33 @@ export const DATA_SCHEMAS = {
526
526
  },
527
527
  },
528
528
 
529
+ // consent-banner opportunity type.
530
+ // Note: The DB stores opportunity type as 'consent-banner', NOT the OPPORTUNITY_TYPES
531
+ // constant PAID_COOKIE_CONSENT ('paid-cookie-consent'). Using string literal to match DB.
532
+ 'consent-banner': {
533
+ schema: Joi.object({
534
+ mobile: Joi.string().allow(null).optional(),
535
+ desktop: Joi.string().allow(null).optional(),
536
+ recommendations: Joi.array().items(
537
+ Joi.object({
538
+ pageUrl: Joi.string().uri().optional(),
539
+ id: Joi.string().optional(),
540
+ }).unknown(true),
541
+ ).optional(),
542
+ impact: Joi.object({
543
+ business: Joi.string().allow(null).optional(),
544
+ user: Joi.string().allow(null).optional(),
545
+ }).unknown(true).optional(),
546
+ aggregationKey: Joi.string().allow(null).optional(),
547
+ }).unknown(true),
548
+ projections: {
549
+ minimal: {
550
+ fields: ['mobile', 'desktop', 'recommendations', 'impact'],
551
+ transformers: {},
552
+ },
553
+ },
554
+ },
555
+
529
556
  // ========== SCHEMAS TO BE ADDED ==========
530
557
  // TODO: The following opportunity types need schemas to be added.
531
558
  // Research actual suggestion data for these types and add schemas following the pattern:
@@ -552,6 +579,5 @@ export const DATA_SCHEMAS = {
552
579
  // - SECURITY_PERMISSIONS_REDUNDANT (may use SECURITY_PERMISSIONS schema
553
580
  // or need separate schema)
554
581
  // - GENERIC_OPPORTUNITY
555
- // - PAID_COOKIE_CONSENT
556
582
  // - WIKIPEDIA_ANALYSIS
557
583
  };
@@ -21,8 +21,6 @@ import BaseCollection from '../base/base.collection.js';
21
21
  */
22
22
  class TrialUserCollection extends BaseCollection {
23
23
  static COLLECTION_NAME = 'TrialUserCollection';
24
-
25
- // add custom methods here
26
24
  }
27
25
 
28
26
  export default TrialUserCollection;
@@ -54,6 +54,10 @@ const schema = new SchemaBuilder(TrialUser, TrialUserCollection)
54
54
  type: 'any',
55
55
  validate: (value) => !value || isObject(value),
56
56
  })
57
- .addAllIndex(['emailId']);
57
+ .addAllIndex(['emailId'])
58
+ .addIndex(
59
+ { composite: ['organizationId'] },
60
+ { composite: ['updatedAt'] },
61
+ );
58
62
 
59
63
  export default schema.build();
@@ -11,10 +11,16 @@
11
11
  */
12
12
 
13
13
  interface DataAccessConfig {
14
- tableNameData: string;
14
+ postgrestUrl: string;
15
+ postgrestSchema?: string;
16
+ postgrestApiKey?: string;
17
+ postgrestHeaders?: object;
18
+ s3Bucket?: string;
19
+ region?: string;
15
20
  }
16
21
 
17
22
  export function createDataAccess(
18
23
  config: DataAccessConfig,
19
24
  logger: object,
25
+ client?: object,
20
26
  ): object;
@@ -10,10 +10,8 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import { DynamoDB } from '@aws-sdk/client-dynamodb';
14
- import { DynamoDBDocument } from '@aws-sdk/lib-dynamodb';
15
13
  import { S3Client } from '@aws-sdk/client-s3';
16
- import { Service } from 'electrodb';
14
+ import { PostgrestClient } from '@supabase/postgrest-js';
17
15
 
18
16
  import { instrumentAWSClient } from '@adobe/spacecat-shared-utils';
19
17
  import { EntityRegistry } from '../models/index.js';
@@ -23,47 +21,31 @@ export * from '../errors/index.js';
23
21
  export * from '../models/index.js';
24
22
  export * from '../util/index.js';
25
23
 
26
- let defaultDynamoDBClient;
27
- const documentClientCache = new WeakMap();
28
-
29
- const createRawClient = (client = undefined) => {
30
- const rawClient = client || (() => {
31
- if (!defaultDynamoDBClient) {
32
- defaultDynamoDBClient = new DynamoDB();
33
- }
34
- return defaultDynamoDBClient;
35
- })();
36
-
37
- let documentClient = documentClientCache.get(rawClient);
38
- if (!documentClient) {
39
- documentClient = DynamoDBDocument.from(instrumentAWSClient(rawClient), {
40
- marshallOptions: {
41
- convertEmptyValues: true,
42
- removeUndefinedValues: true,
43
- },
44
- });
45
- documentClientCache.set(rawClient, documentClient);
24
+ const createPostgrestService = (config, client = undefined) => {
25
+ if (client) {
26
+ return client;
46
27
  }
47
28
 
48
- return documentClient;
49
- };
29
+ const {
30
+ postgrestUrl,
31
+ postgrestSchema = 'public',
32
+ postgrestApiKey,
33
+ postgrestHeaders = {},
34
+ } = config;
35
+
36
+ if (!postgrestUrl) {
37
+ throw new Error('postgrestUrl is required to create data access');
38
+ }
50
39
 
51
- const createElectroService = (client, config, log) => {
52
- const { tableNameData: table } = config;
53
- /* c8 ignore start */
54
- const logger = (event) => {
55
- log.debug(JSON.stringify(event, null, 4));
40
+ const headers = {
41
+ ...postgrestHeaders,
42
+ ...postgrestApiKey ? { apikey: postgrestApiKey, Authorization: `Bearer ${postgrestApiKey}` } : {},
56
43
  };
57
- /* c8 ignore end */
58
-
59
- return new Service(
60
- EntityRegistry.getEntities(),
61
- {
62
- client,
63
- table,
64
- logger,
65
- },
66
- );
44
+
45
+ return new PostgrestClient(postgrestUrl, {
46
+ schema: postgrestSchema,
47
+ headers,
48
+ });
67
49
  };
68
50
 
69
51
  /**
@@ -91,30 +73,29 @@ const createS3Service = (config) => {
91
73
  * Creates a services dictionary containing all datastore services.
92
74
  * Each collection can declare which service it needs via its DATASTORE_TYPE.
93
75
  *
94
- * @param {object} electroService - The ElectroDB service for DynamoDB operations
76
+ * @param {PostgrestClient} postgrestService - PostgREST client
95
77
  * @param {object} config - Configuration object
96
- * @returns {object} Services dictionary with dynamo and s3 services
78
+ * @returns {object} Services dictionary with postgrest and s3 services
97
79
  */
98
- const createServices = (electroService, config) => ({
99
- dynamo: electroService,
80
+ const createServices = (postgrestService, config) => ({
81
+ postgrest: postgrestService,
100
82
  s3: createS3Service(config),
101
83
  });
102
84
 
103
85
  /**
104
- * Creates a data access layer for interacting with DynamoDB using ElectroDB.
86
+ * Creates a data access layer for interacting with Postgres via PostgREST.
105
87
  *
106
- * @param {{tableNameData: string, s3Bucket?: string, region?: string}} config - Configuration
107
- * object containing table name and optional S3 configuration
88
+ * @param {{postgrestUrl: string, postgrestSchema?: string, postgrestApiKey?: string,
89
+ * postgrestHeaders?: object, s3Bucket?: string, region?: string}} config - Configuration object
108
90
  * @param {object} log - Logger instance, defaults to console
109
- * @param {DynamoDB} [client] - Optional custom DynamoDB client instance
91
+ * @param {PostgrestClient} [client] - Optional custom Postgrest client instance
110
92
  * @returns {object} Data access collections for interacting with entities
111
93
  */
112
94
  export const createDataAccess = (config, log = console, client = undefined) => {
113
95
  registerLogger(log);
114
96
 
115
- const rawClient = createRawClient(client);
116
- const electroService = createElectroService(rawClient, config, log);
117
- const services = createServices(electroService, config);
97
+ const postgrestService = createPostgrestService(config, client);
98
+ const services = createServices(postgrestService, config);
118
99
  const entityRegistry = new EntityRegistry(services, config, log);
119
100
 
120
101
  return entityRegistry.getCollections();
package/src/util/index.js CHANGED
@@ -25,6 +25,7 @@ export {
25
25
  export {
26
26
  registerLogger,
27
27
  getLogger,
28
+ resetLoggerRegistry,
28
29
  } from './logger-registry.js';
29
30
 
30
31
  /**
@@ -33,6 +34,6 @@ export {
33
34
  * @enum {string}
34
35
  */
35
36
  export const DATASTORE_TYPE = Object.freeze({
36
- DYNAMO: 'dynamo',
37
+ POSTGREST: 'postgrest',
37
38
  S3: 's3',
38
39
  });