@adobe/spacecat-shared-data-access 3.45.2 → 3.47.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.
- package/CHANGELOG.md +17 -0
- package/README.md +16 -3
- package/package.json +4 -4
- package/src/models/api-key/api-key.schema.js +0 -6
- package/src/models/audit/audit.schema.js +1 -7
- package/src/models/audit-url/audit-url.model.js +1 -1
- package/src/models/audit-url/audit-url.schema.js +0 -4
- package/src/models/base/entity.registry.js +2 -2
- package/src/models/base/index.d.ts +1 -1
- package/src/models/base/schema.builder.js +1 -3
- package/src/models/base/schema.js +3 -3
- package/src/models/configuration/configuration.collection.js +1 -1
- package/src/models/configuration/configuration.model.js +1 -1
- package/src/models/configuration/configuration.schema.js +1 -1
- package/src/models/consumer/consumer.schema.js +0 -6
- package/src/models/entitlement/entitlement.model.js +1 -0
- package/src/models/entitlement/entitlement.schema.js +0 -6
- package/src/models/entitlement/index.d.ts +1 -1
- package/src/models/experiment/experiment.schema.js +0 -6
- package/src/models/fix-entity/fix-entity.schema.js +0 -6
- package/src/models/fix-entity-suggestion/fix-entity-suggestion.schema.js +0 -6
- package/src/models/geo-experiment/geo-experiment.collection.js +22 -0
- package/src/models/geo-experiment/geo-experiment.model.js +37 -3
- package/src/models/geo-experiment/geo-experiment.schema.js +2 -1
- package/src/models/geo-experiment/index.d.ts +1 -0
- package/src/models/import-job/import-job.schema.js +0 -6
- package/src/models/import-url/import-url.schema.js +0 -6
- package/src/models/key-event/key-event.schema.js +0 -6
- package/src/models/latest-audit/latest-audit.schema.js +1 -7
- package/src/models/opportunity/opportunity.schema.js +0 -6
- package/src/models/organization/organization.schema.js +0 -6
- package/src/models/page-citability/page-citability.schema.js +0 -6
- package/src/models/page-intent/README.md +1 -1
- package/src/models/page-intent/page-intent.schema.js +0 -6
- package/src/models/plg-onboarding/plg-onboarding.schema.js +0 -6
- package/src/models/project/project.schema.js +0 -6
- package/src/models/report/report.schema.js +0 -6
- package/src/models/scrape-job/scrape-job.schema.js +0 -6
- package/src/models/scrape-url/scrape-url.schema.js +0 -6
- package/src/models/sentiment-guideline/sentiment-guideline.schema.js +0 -4
- package/src/models/sentiment-topic/sentiment-topic.schema.js +0 -4
- package/src/models/site/index.d.ts +11 -0
- package/src/models/site/site.schema.js +3 -6
- package/src/models/site-candidate/site-candidate.schema.js +0 -6
- package/src/models/site-enrollment/site-enrollment.schema.js +0 -6
- package/src/models/site-top-form/site-top-form.schema.js +0 -6
- package/src/models/site-top-page/site-top-page.schema.js +0 -6
- package/src/models/suggestion/suggestion.schema.js +0 -6
- package/src/models/trial-user/trial-user.schema.js +0 -6
- package/src/models/trial-user-activity/trial-user-activity.schema.js +0 -6
- package/migration.sh +0 -137
- 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
|
-
|