@adobe/spacecat-shared-data-access 3.46.0 → 3.48.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.
Files changed (53) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +1 -3
  3. package/package.json +4 -4
  4. package/src/models/api-key/api-key.schema.js +0 -6
  5. package/src/models/audit/audit.schema.js +1 -7
  6. package/src/models/audit-url/audit-url.model.js +1 -1
  7. package/src/models/audit-url/audit-url.schema.js +0 -4
  8. package/src/models/base/entity.registry.js +2 -2
  9. package/src/models/base/index.d.ts +1 -1
  10. package/src/models/base/schema.builder.js +1 -3
  11. package/src/models/base/schema.js +3 -3
  12. package/src/models/configuration/configuration.collection.js +1 -1
  13. package/src/models/configuration/configuration.model.js +1 -1
  14. package/src/models/configuration/configuration.schema.js +1 -1
  15. package/src/models/consumer/consumer.schema.js +0 -6
  16. package/src/models/entitlement/entitlement.model.js +1 -0
  17. package/src/models/entitlement/entitlement.schema.js +0 -6
  18. package/src/models/entitlement/index.d.ts +1 -1
  19. package/src/models/experiment/experiment.schema.js +0 -6
  20. package/src/models/fix-entity/fix-entity.schema.js +0 -6
  21. package/src/models/fix-entity-suggestion/fix-entity-suggestion.schema.js +0 -6
  22. package/src/models/geo-experiment/geo-experiment.collection.js +22 -0
  23. package/src/models/geo-experiment/geo-experiment.model.js +37 -3
  24. package/src/models/geo-experiment/geo-experiment.schema.js +2 -1
  25. package/src/models/geo-experiment/index.d.ts +1 -0
  26. package/src/models/import-job/import-job.schema.js +0 -6
  27. package/src/models/import-url/import-url.schema.js +0 -6
  28. package/src/models/key-event/key-event.schema.js +0 -6
  29. package/src/models/latest-audit/latest-audit.schema.js +1 -7
  30. package/src/models/opportunity/opportunity.schema.js +0 -6
  31. package/src/models/organization/organization.schema.js +0 -6
  32. package/src/models/page-citability/page-citability.schema.js +0 -6
  33. package/src/models/page-intent/README.md +1 -1
  34. package/src/models/page-intent/page-intent.schema.js +0 -6
  35. package/src/models/plg-onboarding/index.d.ts +14 -1
  36. package/src/models/plg-onboarding/plg-onboarding.model.js +6 -0
  37. package/src/models/plg-onboarding/plg-onboarding.schema.js +19 -6
  38. package/src/models/project/project.schema.js +0 -6
  39. package/src/models/report/report.schema.js +0 -6
  40. package/src/models/scrape-job/scrape-job.schema.js +0 -6
  41. package/src/models/scrape-url/scrape-url.schema.js +0 -6
  42. package/src/models/sentiment-guideline/sentiment-guideline.schema.js +0 -4
  43. package/src/models/sentiment-topic/sentiment-topic.schema.js +0 -4
  44. package/src/models/site/site.schema.js +0 -6
  45. package/src/models/site-candidate/site-candidate.schema.js +0 -6
  46. package/src/models/site-enrollment/site-enrollment.schema.js +0 -6
  47. package/src/models/site-top-form/site-top-form.schema.js +0 -6
  48. package/src/models/site-top-page/site-top-page.schema.js +0 -6
  49. package/src/models/suggestion/suggestion.schema.js +0 -6
  50. package/src/models/trial-user/trial-user.schema.js +0 -6
  51. package/src/models/trial-user-activity/trial-user-activity.schema.js +0 -6
  52. package/migration.sh +0 -137
  53. package/src/readme.md +0 -463
package/src/readme.md DELETED
@@ -1,463 +0,0 @@
1
- # ElectroDB Entity Framework
2
-
3
- ## Overview
4
-
5
- This entity framework streamlines the definition, querying, and manipulation of domain entities in a DynamoDB-based application. Built atop [ElectroDB](https://electrodb.dev/), it provides a consistent layer for schema definition, indexing, and robust CRUD operations, while adding conveniences like automatic indexing methods and reference handling.
6
-
7
- By adhering to this framework’s conventions, you can introduce and manage new entities with minimal boilerplate and complexity.
8
-
9
- ## Core Concepts
10
-
11
- ### Entities
12
- An *entity* represents a domain concept (e.g., `User`, `Organization`, `Order`) persisted in the database. Each entity is defined by a schema, specifying attributes, indexes, and references to other entities. The schema integrates with ElectroDB, ensuring a uniform approach to modeling data.
13
-
14
- ### Models
15
- A *Model* is a class representing a single instance of an entity. It provides:
16
-
17
- - Attribute getters and setters generated based on the schema.
18
- - Methods for persisting changes (`save()`), and removing entities (`remove()`).
19
- - Methods to fetch referenced entities (via `belongs_to`, `has_one`, `has_many` references).
20
-
21
- Models extend `BaseModel`, which handles most of the common logic.
22
-
23
- **Required:** All model classes must define a `static ENTITY_NAME` property to ensure bundler-agnostic operation.
24
-
25
- ### Collections
26
- A *Collection* operates on sets of entities. While `Model` focuses on individual records, `Collection` is for batch and query-level operations:
27
-
28
- - Query methods like `findById()`, `all()`, and index-derived methods.
29
- - Batch creation and update methods (`createMany`, `_saveMany`).
30
- - Automatic generation of `allBy...` and `findBy...` convenience methods based on defined indexes.
31
-
32
- Collections extend `BaseCollection`, which generates query methods at runtime based on your schema definitions.
33
-
34
- **Required:** All collection classes must define a `static COLLECTION_NAME` property to ensure bundler-agnostic operation.
35
-
36
- ### Schema Builder
37
- The `SchemaBuilder` is a fluent API to define an entity’s schema:
38
-
39
- - **Attributes:** Configure entity fields and their validation.
40
- - **Indexes:** Specify primary and secondary indexes for common queries.
41
- - **References:** Define entity relationships (e.g., `User` belongs to `Organization`).
42
-
43
- The `SchemaBuilder` enforces naming conventions and sets defaults, reducing repetitive configuration.
44
-
45
- **Note on Indexes:** Add indexes thoughtfully. Every extra index adds cost and complexity. Only create indexes for well-understood, frequently-needed query patterns.
46
-
47
- ### Entity and Collection Naming
48
-
49
- All model and collection classes **must** define explicit static name properties:
50
-
51
- ```js
52
- class User extends BaseModel {
53
- static ENTITY_NAME = 'User';
54
- // ...
55
- }
56
-
57
- class UserCollection extends BaseCollection {
58
- static COLLECTION_NAME = 'UserCollection';
59
- // ...
60
- }
61
- ```
62
-
63
- This requirement ensures names remain consistent regardless of build tool transformations. Modern JavaScript bundlers (webpack, esbuild) may mangle class names during the build process (e.g., `Configuration` → `_Configuration`), which would break ElectroDB's key generation and internal lookups. By explicitly declaring both entity and collection names, the framework operates correctly in all bundling scenarios.
64
-
65
- The `SchemaBuilder` validates that both `ENTITY_NAME` and `COLLECTION_NAME` are defined and will throw descriptive errors if either is missing.
66
-
67
- ### Entity Registry
68
- The `EntityRegistry` aggregates all entities, their schemas, and their collections. It ensures consistent lookup and retrieval of any registered entity’s collection. When you add a new entity, you must register it with the `EntityRegistry` so the rest of the application can discover it.
69
-
70
- ## Default Attributes and Indexes
71
-
72
- When you create a schema with `SchemaBuilder`, the following attributes are automatically defined:
73
-
74
- 1. **ID (Primary Key):** A UUID-based primary key (`${entityName}Id`), ensuring unique identification.
75
- 2. **createdAt:** A timestamp (ISO string) set at entity creation.
76
- 3. **updatedAt:** A timestamp (ISO string) updated on each modification.
77
-
78
- A primary index is also set up, keyed by the `${entityName}Id` attribute, guaranteeing a straightforward way to retrieve entities by their unique ID.
79
-
80
- ## Auto-Generated Methods
81
-
82
- ### `BaseCollection`
83
-
84
- `BaseCollection` automatically generates `allBy...` and `findBy...` methods derived from your defined indexes. For example, if your schema defines an index composed of `opportunityId`, `status`, and `createdAt`, `BaseCollection` will generate:
85
-
86
- - `allByOpportunityId(opportunityId, options?)`
87
- - `findByOpportunityId(opportunityId, options?)`
88
- - `allByOpportunityIdAndStatus(opportunityId, status, options?)`
89
- - `findByOpportunityIdAndStatus(opportunityId, status, options?)`
90
- - `allByOpportunityIdAndStatusAndCreatedAt(opportunityId, status, createdAt, options?)`
91
- - `findByOpportunityIdAndStatusAndCreatedAt(opportunityId, status, createdAt, options?)`
92
-
93
- **allBy...** methods return arrays of matching entities, while **findBy...** methods return a single (or the first matching) entity. Both can accept an optional `options` object for filtering, ordering, attribute selection, and pagination.
94
-
95
- **Example:**
96
- ```js
97
- const Suggestion = dataAccess.Suggestion;
98
-
99
- // Retrieve all suggestions by `opportunityId`
100
- const results = await Suggestion.allByOpportunityId('op-12345');
101
-
102
- // Retrieve a single suggestion by `opportunityId` and `status`
103
- const single = await Suggestion.findByOpportunityIdAndStatus('op-12345', 'OPEN');
104
- ```
105
-
106
- ### `BaseModel`
107
-
108
- `BaseModel` provides methods for CRUD operations and reference handling:
109
-
110
- - `save()`: Persists changes to the entity.
111
- - `remove()`: Deletes the entity from the database.
112
- - `get...()`: Getters for entity attributes.
113
- - `set...()`: Setters for entity attributes.
114
-
115
- Additionally, `BaseModel` generates methods to fetch referenced entities.
116
- For example, if `User` belongs to `Organization`, `BaseModel` will create:
117
-
118
- - `getOrganization()`: Fetch the referenced `Organization` entity.
119
- - `getOrganizationId()`: Retrieve the `Organization` ID.
120
- - `setOrganizationId(organizationId)`: Update the `Organization` reference.
121
-
122
- Conversely, the `Organization` entity will have:
123
-
124
- - `getUsers()`: Fetch all `User` entities referencing this `Organization`.
125
- - And with the `User`-Schema's `belongs_to` reciprocal reference expressing filterable sort keys, e.g. "email", "location":
126
- - `getUsersByEmail(email)`: Fetch all `User` entities referencing this `Organization` with a specific email."
127
- - `getUsersByEmailAndLocation(email, location)`: Fetch all `User` entities referencing this `Organization` with a specific email and location.
128
-
129
- **Example:**
130
- ```js
131
- const user = await User.findById('usr-abc123');
132
-
133
- // Work with attributes
134
- console.log(user.getEmail()); // e.g. "john@example.com"
135
- user.setName('John Smith');
136
- await user.save();
137
-
138
- // Fetch referenced entity
139
- const org = await user.getOrganization();
140
- console.log(org.getName());
141
- ```
142
-
143
- ## Pagination Support
144
-
145
- All `allBy...` methods automatically support pagination through an optional `options` parameter. Pagination is built into `BaseCollection` and available for every entity without additional configuration.
146
-
147
- ### Query Options
148
-
149
- Every `allBy...` method accepts an optional `options` object with the following parameters:
150
-
151
- ```typescript
152
- interface QueryOptions {
153
- // Pagination control
154
- limit?: number; // Limit results per page
155
- cursor?: string; // Cursor from previous page
156
- returnCursor?: boolean; // Return {data, cursor} instead of array
157
- fetchAllPages?: boolean; // true: fetch all pages, false: single page only
158
-
159
- // Query control
160
- order?: 'asc' | 'desc'; // Sort order (default: 'desc')
161
- attributes?: string[]; // Which fields to return
162
-
163
- // Range queries
164
- between?: {
165
- attribute: string;
166
- start: string | number;
167
- end: string | number;
168
- };
169
- }
170
- ```
171
-
172
- ### Pagination Behavior
173
-
174
- By default, `allBy...` methods **automatically fetch all pages** of results. You can control this behavior:
175
-
176
- - **Default (no options):** Fetches all pages automatically
177
- - **`fetchAllPages: true`:** Explicitly fetch all pages
178
- - **`fetchAllPages: false`:** Fetch only the first page
179
- - **`limit` without `fetchAllPages`:** Fetch only the first page (limited results)
180
-
181
- ### Usage Examples
182
-
183
- #### Fetch All Results (Default)
184
- ```js
185
- // Automatically fetches all pages
186
- const suggestions = await Suggestion.allByOpportunityId('op-12345');
187
- // Returns: Suggestion[] (all matching results)
188
- ```
189
-
190
- #### Manual Pagination with Cursor
191
- ```js
192
- // Fetch first page
193
- const firstPage = await Suggestion.allByOpportunityIdAndStatus(
194
- 'op-12345',
195
- 'NEW',
196
- { limit: 50, returnCursor: true }
197
- );
198
- // Returns: { data: Suggestion[], cursor: string | null }
199
-
200
- console.log(`Found ${firstPage.data.length} suggestions`);
201
-
202
- // Fetch next page if cursor exists
203
- if (firstPage.cursor) {
204
- const secondPage = await Suggestion.allByOpportunityIdAndStatus(
205
- 'op-12345',
206
- 'NEW',
207
- { limit: 50, cursor: firstPage.cursor, returnCursor: true }
208
- );
209
- }
210
- ```
211
-
212
- #### Limit Results (Single Page)
213
- ```js
214
- // Get only first 20 results (stops after first page)
215
- const limitedResults = await Suggestion.allByOpportunityId(
216
- 'op-12345',
217
- { limit: 20, fetchAllPages: false }
218
- );
219
- // Returns: Suggestion[] (max 20 items)
220
- ```
221
-
222
- #### Filter by Attributes
223
- ```js
224
- // Fetch only specific fields
225
- const suggestions = await Suggestion.allByOpportunityId(
226
- 'op-12345',
227
- { attributes: ['suggestionId', 'status', 'rank'] }
228
- );
229
- ```
230
-
231
- ### Exposing Pagination in TypeScript
232
-
233
- Pagination is implemented in `BaseCollection` but must be exposed in your entity's TypeScript definitions to provide IntelliSense support for consumers.
234
-
235
- #### Required Steps
236
-
237
- **1. Import Required Types**
238
-
239
- In your entity's `index.d.ts`, add these imports:
240
-
241
- ```typescript
242
- import type {
243
- BaseCollection,
244
- BaseModel,
245
- QueryOptions, // For options parameter
246
- PaginatedResult, // For return type
247
- // ... other imports
248
- } from '../index';
249
- ```
250
-
251
- **2. Update Method Signatures**
252
-
253
- For each `allBy...` method, add the `options` parameter and update the return type:
254
-
255
- ```typescript
256
- export interface MyEntityCollection extends BaseCollection<MyEntity> {
257
- // Before: allByParentId(parentId: string): Promise<MyEntity[]>;
258
-
259
- // After:
260
- allByParentId(
261
- parentId: string,
262
- options?: QueryOptions
263
- ): Promise<MyEntity[] | PaginatedResult<MyEntity>>;
264
-
265
- allByParentIdAndStatus(
266
- parentId: string,
267
- status: string,
268
- options?: QueryOptions
269
- ): Promise<MyEntity[] | PaginatedResult<MyEntity>>;
270
- }
271
- ```
272
-
273
- **3. Update `findBy...` Methods (Optional)**
274
-
275
- For `findBy...` methods, add the `options` parameter (keeps single-item return type):
276
-
277
- ```typescript
278
- export interface MyEntityCollection extends BaseCollection<MyEntity> {
279
- findByParentId(
280
- parentId: string,
281
- options?: QueryOptions
282
- ): Promise<MyEntity | null>;
283
- }
284
- ```
285
-
286
- #### Complete Example
287
-
288
- ```typescript
289
- import type {
290
- BaseCollection, BaseModel, QueryOptions, PaginatedResult,
291
- } from '../index';
292
-
293
- export interface Suggestion extends BaseModel {
294
- getStatus(): string;
295
- setStatus(status: string): Suggestion;
296
- // ... other methods
297
- }
298
-
299
- export interface SuggestionCollection extends BaseCollection<Suggestion> {
300
- // With pagination support
301
- allByOpportunityId(
302
- opportunityId: string,
303
- options?: QueryOptions
304
- ): Promise<Suggestion[] | PaginatedResult<Suggestion>>;
305
-
306
- allByOpportunityIdAndStatus(
307
- opportunityId: string,
308
- status: string,
309
- options?: QueryOptions
310
- ): Promise<Suggestion[] | PaginatedResult<Suggestion>>;
311
-
312
- findByOpportunityId(
313
- opportunityId: string,
314
- options?: QueryOptions
315
- ): Promise<Suggestion | null>;
316
- }
317
- ```
318
-
319
- ### Type-Safe Result Handling
320
-
321
- TypeScript will correctly infer the return type based on usage:
322
-
323
- ```typescript
324
- // Returns Suggestion[]
325
- const all = await Suggestion.allByOpportunityId('op-12345');
326
-
327
- // Returns { data: Suggestion[], cursor: string | null }
328
- const page = await Suggestion.allByOpportunityId(
329
- 'op-12345',
330
- { limit: 50, returnCursor: true }
331
- );
332
-
333
- // Type guard for handling both cases
334
- if (Array.isArray(result)) {
335
- // result is Suggestion[]
336
- console.log(result.length);
337
- } else {
338
- // result is PaginatedResult<Suggestion>
339
- console.log(result.data.length, result.cursor);
340
- }
341
- ```
342
-
343
- ## Step-by-Step: Adding a New Entity
344
-
345
- Follow these steps to introduce a new entity into the framework.
346
-
347
- ### 1. Define the Schema
348
- Create `user.schema.js`:
349
-
350
- ```js
351
- import SchemaBuilder from '../base/schema.builder.js';
352
- import User from './user.model.js';
353
- import UserCollection from './user.collection.js';
354
-
355
- const userSchema = new SchemaBuilder(User, UserCollection)
356
- .addAttribute('email', {
357
- type: 'string',
358
- required: true,
359
- validate: (value) => value.includes('@'),
360
- })
361
- .addAttribute('name', { type: 'string', required: true })
362
- .addAllIndex(['email'])
363
- .addReference('belongs_to', 'Organization') // Adds organizationId and byOrganizationId index
364
- .build();
365
-
366
- export default userSchema;
367
- ```
368
-
369
- ### 2. Implement the Model
370
- Create `user.model.js`:
371
-
372
- ```js
373
- import BaseModel from '../base/base.model.js';
374
-
375
- class User extends BaseModel {
376
- static ENTITY_NAME = 'User';
377
-
378
- // Additional domain logic methods can be added here if needed.
379
- }
380
-
381
- export default User;
382
- ```
383
-
384
- **Important:** Every model class **must** define a `static ENTITY_NAME` property. This ensures the entity name is explicit and not affected by bundler transformations (like class name mangling in webpack/esbuild). The `SchemaBuilder` will throw an error if this property is missing.
385
-
386
- ### 3. Implement the Collection
387
- Create `user.collection.js`:
388
-
389
- ```js
390
- import BaseCollection from '../base/base.collection.js';
391
-
392
- class UserCollection extends BaseCollection {
393
- static COLLECTION_NAME = 'UserCollection';
394
-
395
- // Additional domain logic collection methods can be added here if needed.
396
- async findByEmail(email) {
397
- return this.findByIndexKeys({ email });
398
- }
399
- }
400
-
401
- export default UserCollection;
402
- ```
403
-
404
- **Important:** Every collection class **must** define a `static COLLECTION_NAME` property for the same bundler-related reasons as `ENTITY_NAME`.
405
-
406
- ### 4. Register the Entity
407
- In `entity.registry.js` (or equivalent):
408
-
409
- ```js
410
- import UserSchema from '../user/user.schema.js';
411
- import UserCollection from '../user/user.collection.js';
412
-
413
- EntityRegistry.registerEntity(UserSchema, UserCollection);
414
- ```
415
-
416
- ### 5. Update DynamoDB Configuration and `schema.json`
417
-
418
- After defining indexes in the schema, **manually add these indexes to your DynamoDB table configuration**. DynamoDB does not automatically create GSIs. You must:
419
-
420
- - Use the AWS Console, CLI, or CloudFormation/Terraform templates to define these GSIs.
421
- - Update your `schema.json` or another documentation file to reflect the newly created indexes, so the team knows which indexes exist and what query patterns they support.
422
-
423
- ### 6. Use the Entity
424
- ```js
425
- const { User, Organization } = dataAccess;
426
-
427
- // Create a user
428
- const newUser = await User.create({ email: 'john@example.com', name: 'John Doe' });
429
-
430
- // Find user by ID
431
- const user = await User.findById(newUser.getId());
432
-
433
- // Get the user organization
434
- const org = await user.getOrganization();
435
-
436
- // ...or in reverse
437
- const anOrg = await Organization.findById(user.getOrganizationId());
438
- const orgUsers = await anOrg.getUsers();
439
-
440
- // Update user and save
441
- user.setName('John X. Doe');
442
- await user.save();
443
- ```
444
-
445
- ## Consideration for Indexes
446
-
447
- Indexes cost money and complexity. Do not add indexes lightly. Determine which query patterns you truly need and only then introduce additional indexes.
448
-
449
- ## Data Access Service
450
-
451
- You can use the data layer by obtaining a service instance through the `createDataAccess` function:
452
-
453
- ```javascript
454
- const { createDataAccess } = require('@adobe/spacecat-shared-data-access');
455
-
456
- const dataAccess = createDataAccess({
457
- tableNameData: 'spacecat-services-data-dev',
458
- });
459
-
460
- // You can now use the dataAccess object to interact with the data layer
461
- const sites = await dataAccess.Site.getSites();
462
- ```
463
-