@adobe/spacecat-shared-data-access 1.59.2 → 1.60.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 +7 -0
- package/package.json +2 -2
- package/src/models/site/config.js +1 -1
- package/src/service/audits/accessPatterns.js +7 -7
- package/src/service/experiments/accessPatterns.js +2 -2
- package/src/service/import-job/accessPatterns.js +1 -1
- package/src/service/import-url/accessPatterns.js +2 -2
- package/src/service/index.js +10 -18
- package/src/service/key-events/accessPatterns.js +3 -3
- package/src/service/organizations/accessPatterns.js +3 -3
- package/src/service/site-candidates/accessPatterns.js +1 -1
- package/src/service/sites/accessPatterns.js +11 -11
- package/src/v2/models/api-key/api-key.collection.js +26 -0
- package/src/v2/models/api-key/api-key.model.js +59 -0
- package/src/v2/models/api-key/api-key.schema.js +82 -0
- package/src/v2/models/api-key/index.d.ts +37 -0
- package/src/v2/models/api-key/index.js +19 -0
- package/src/v2/models/audit/audit.collection.js +26 -0
- package/src/v2/models/audit/audit.model.js +89 -0
- package/src/v2/models/audit/audit.schema.js +66 -0
- package/src/v2/models/audit/index.d.ts +40 -0
- package/src/v2/models/audit/index.js +19 -0
- package/src/v2/models/base/base.collection.js +450 -0
- package/src/v2/models/{base.model.js → base/base.model.js} +109 -89
- package/src/v2/models/base/constants.js +17 -0
- package/src/v2/models/base/entity.registry.js +137 -0
- package/src/v2/models/base/index.d.ts +83 -0
- package/src/v2/models/base/index.js +27 -0
- package/src/v2/models/base/reference.js +159 -0
- package/src/v2/models/base/schema.builder.js +420 -0
- package/src/v2/models/base/schema.js +283 -0
- package/src/v2/models/configuration/configuration.collection.js +39 -0
- package/src/v2/models/configuration/configuration.model.js +160 -0
- package/src/v2/models/configuration/configuration.schema.js +103 -0
- package/src/v2/models/configuration/index.d.ts +111 -0
- package/src/v2/models/configuration/index.js +19 -0
- package/src/v2/models/experiment/experiment.collection.js +26 -0
- package/src/v2/models/experiment/experiment.model.js +28 -0
- package/src/v2/models/experiment/experiment.schema.js +70 -0
- package/src/v2/models/experiment/index.d.ts +49 -0
- package/src/v2/models/experiment/index.js +19 -0
- package/src/v2/models/import-job/import-job.collection.js +45 -0
- package/src/v2/models/import-job/import-job.model.js +55 -0
- package/src/v2/models/import-job/import-job.schema.js +152 -0
- package/src/v2/models/import-job/index.d.ts +51 -0
- package/src/v2/models/import-job/index.js +19 -0
- package/src/v2/models/import-url/import-url.collection.js +26 -0
- package/src/v2/models/import-url/import-url.model.js +28 -0
- package/src/v2/models/import-url/import-url.schema.js +59 -0
- package/src/v2/models/import-url/index.d.ts +35 -0
- package/src/v2/models/import-url/index.js +19 -0
- package/src/v2/models/index.d.ts +11 -99
- package/src/v2/models/index.js +14 -15
- package/src/v2/models/key-event/index.d.ts +28 -0
- package/src/v2/models/key-event/index.js +19 -0
- package/src/v2/models/key-event/key-event.collection.js +26 -0
- package/src/v2/models/key-event/key-event.model.js +37 -0
- package/src/v2/models/key-event/key-event.schema.js +45 -0
- package/src/v2/models/opportunity/index.d.ts +46 -0
- package/src/v2/models/opportunity/index.js +19 -0
- package/src/v2/models/opportunity/opportunity.collection.js +26 -0
- package/src/v2/models/{opportunity.model.js → opportunity/opportunity.model.js} +15 -2
- package/src/v2/models/opportunity/opportunity.schema.js +69 -0
- package/src/v2/models/organization/index.d.ts +28 -0
- package/src/v2/models/organization/index.js +19 -0
- package/src/v2/models/organization/organization.collection.js +26 -0
- package/src/v2/models/organization/organization.model.js +31 -0
- package/src/v2/models/organization/organization.schema.js +51 -0
- package/src/v2/models/site/index.d.ts +43 -0
- package/src/v2/models/site/index.js +20 -0
- package/src/v2/models/site/site.collection.js +28 -0
- package/src/v2/models/site/site.model.js +47 -0
- package/src/v2/models/site/site.schema.js +91 -0
- package/src/v2/models/site-candidate/index.d.ts +38 -0
- package/src/v2/models/site-candidate/index.js +19 -0
- package/src/v2/models/site-candidate/site-candidate.collection.js +27 -0
- package/src/v2/models/site-candidate/site-candidate.model.js +41 -0
- package/src/v2/models/site-candidate/site-candidate.schema.js +59 -0
- package/src/v2/models/site-top-page/index.d.ts +35 -0
- package/src/v2/models/site-top-page/index.js +19 -0
- package/src/v2/models/site-top-page/site-top-page.collection.js +44 -0
- package/src/v2/models/site-top-page/site-top-page.model.js +28 -0
- package/src/v2/models/site-top-page/site-top-page.schema.js +65 -0
- package/src/v2/models/suggestion/index.d.ts +34 -0
- package/src/v2/models/suggestion/index.js +19 -0
- package/src/v2/models/suggestion/suggestion.collection.js +55 -0
- package/src/v2/models/{suggestion.model.js → suggestion/suggestion.model.js} +16 -1
- package/src/v2/models/suggestion/suggestion.schema.js +53 -0
- package/src/v2/readme.md +201 -256
- package/src/v2/util/accessor.utils.js +158 -0
- package/src/v2/util/guards.d.ts +7 -0
- package/src/v2/util/guards.js +21 -4
- package/src/v2/util/index.js +1 -0
- package/src/v2/util/patcher.js +54 -25
- package/src/v2/util/util.js +84 -0
- package/src/v2/models/base.collection.js +0 -275
- package/src/v2/models/model.factory.js +0 -74
- package/src/v2/models/opportunity.collection.js +0 -74
- package/src/v2/models/suggestion.collection.js +0 -104
- package/src/v2/schema/opportunity.schema.js +0 -159
- package/src/v2/schema/suggestion.schema.js +0 -132
- package/src/v2/util/reference.js +0 -41
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { BaseCollection, BaseModel, Opportunity } from '../index';
|
|
14
|
+
|
|
15
|
+
export interface Suggestion extends BaseModel {
|
|
16
|
+
getData(): object;
|
|
17
|
+
getKpiDeltas(): object;
|
|
18
|
+
getOpportunity(): Promise<Opportunity>;
|
|
19
|
+
getOpportunityId(): string;
|
|
20
|
+
getRank(): number;
|
|
21
|
+
getStatus(): string;
|
|
22
|
+
getType(): string;
|
|
23
|
+
setData(data: object): Suggestion;
|
|
24
|
+
setKpiDeltas(kpiDeltas: object): Suggestion;
|
|
25
|
+
setOpportunityId(opportunityId: string): Suggestion;
|
|
26
|
+
setRank(rank: number): Suggestion;
|
|
27
|
+
setStatus(status: string): Suggestion;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface SuggestionCollection extends BaseCollection<Suggestion> {
|
|
31
|
+
allByOpportunityId(opportunityId: string): Promise<Suggestion[]>;
|
|
32
|
+
allByOpportunityIdAndStatus(opportunityId: string, status: string): Promise<Suggestion[]>;
|
|
33
|
+
bulkUpdateStatus(suggestions: Suggestion[], status: string): Promise<Suggestion[]>;
|
|
34
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import Suggestion from './suggestion.model.js';
|
|
14
|
+
import SuggestionCollection from './suggestion.collection.js';
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
Suggestion,
|
|
18
|
+
SuggestionCollection,
|
|
19
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import BaseCollection from '../base/base.collection.js';
|
|
14
|
+
import { STATUSES } from './suggestion.model.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* SuggestionCollection - A collection class responsible for managing Suggestion entities.
|
|
18
|
+
* Extends the BaseCollection to provide specific methods for interacting with Suggestion records.
|
|
19
|
+
*
|
|
20
|
+
* @class SuggestionCollection
|
|
21
|
+
* @extends BaseCollection
|
|
22
|
+
*/
|
|
23
|
+
class SuggestionCollection extends BaseCollection {
|
|
24
|
+
/**
|
|
25
|
+
* Updates the status of multiple given suggestions. The given status must conform
|
|
26
|
+
* to the status enum defined in the Suggestion schema.
|
|
27
|
+
* Saves the updated suggestions to the database automatically.
|
|
28
|
+
* You don't need to call save() on the suggestions after calling this method.
|
|
29
|
+
* @async
|
|
30
|
+
* @param {Suggestion[]} suggestions - An array of Suggestion instances to update.
|
|
31
|
+
* @param {string} status - The new status to set for the suggestions.
|
|
32
|
+
* @return {Promise<*>} - A promise that resolves to the updated suggestions.
|
|
33
|
+
* @throws {Error} - Throws an error if the suggestions are not provided
|
|
34
|
+
* or if the status is invalid.
|
|
35
|
+
*/
|
|
36
|
+
async bulkUpdateStatus(suggestions, status) {
|
|
37
|
+
if (!Array.isArray(suggestions)) {
|
|
38
|
+
throw new Error('Suggestions must be an array');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!Object.values(STATUSES).includes(status)) {
|
|
42
|
+
throw new Error(`Invalid status: ${status}. Must be one of: ${Object.values(STATUSES).join(', ')}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
suggestions.forEach((suggestion) => {
|
|
46
|
+
suggestion.setStatus(status);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
await this._saveMany(suggestions);
|
|
50
|
+
|
|
51
|
+
return suggestions;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export default SuggestionCollection;
|
|
@@ -10,7 +10,22 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import BaseModel from '
|
|
13
|
+
import BaseModel from '../base/base.model.js';
|
|
14
|
+
|
|
15
|
+
export const STATUSES = {
|
|
16
|
+
NEW: 'NEW',
|
|
17
|
+
APPROVED: 'APPROVED',
|
|
18
|
+
SKIPPED: 'SKIPPED',
|
|
19
|
+
FIXED: 'FIXED',
|
|
20
|
+
ERROR: 'ERROR',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const TYPES = {
|
|
24
|
+
CODE_CHANGE: 'CODE_CHANGE',
|
|
25
|
+
CONTENT_UPDATE: 'CONTENT_UPDATE',
|
|
26
|
+
REDIRECT_UPDATE: 'REDIRECT_UPDATE',
|
|
27
|
+
METADATA_UPDATE: 'METADATA_UPDATE',
|
|
28
|
+
};
|
|
14
29
|
|
|
15
30
|
/**
|
|
16
31
|
* Suggestion - A class representing a Suggestion entity.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/* c8 ignore start */
|
|
14
|
+
|
|
15
|
+
import { isNonEmptyObject } from '@adobe/spacecat-shared-utils';
|
|
16
|
+
|
|
17
|
+
import SchemaBuilder from '../base/schema.builder.js';
|
|
18
|
+
import Suggestion, { STATUSES, TYPES } from './suggestion.model.js';
|
|
19
|
+
import SuggestionCollection from './suggestion.collection.js';
|
|
20
|
+
|
|
21
|
+
/*
|
|
22
|
+
Schema Doc: https://electrodb.dev/en/modeling/schema/
|
|
23
|
+
Attribute Doc: https://electrodb.dev/en/modeling/attributes/
|
|
24
|
+
Indexes Doc: https://electrodb.dev/en/modeling/indexes/
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const schema = new SchemaBuilder(Suggestion, SuggestionCollection)
|
|
28
|
+
.addReference('belongs_to', 'Opportunity', ['status', 'rank'])
|
|
29
|
+
.addAttribute('type', {
|
|
30
|
+
type: Object.values(TYPES),
|
|
31
|
+
required: true,
|
|
32
|
+
readOnly: true,
|
|
33
|
+
})
|
|
34
|
+
.addAttribute('rank', {
|
|
35
|
+
type: 'number',
|
|
36
|
+
required: true,
|
|
37
|
+
})
|
|
38
|
+
.addAttribute('data', {
|
|
39
|
+
type: 'any',
|
|
40
|
+
required: true,
|
|
41
|
+
validate: (value) => isNonEmptyObject(value),
|
|
42
|
+
})
|
|
43
|
+
.addAttribute('kpiDeltas', {
|
|
44
|
+
type: 'any',
|
|
45
|
+
validate: (value) => !value || isNonEmptyObject(value),
|
|
46
|
+
})
|
|
47
|
+
.addAttribute('status', {
|
|
48
|
+
type: Object.values(STATUSES),
|
|
49
|
+
required: true,
|
|
50
|
+
default: STATUSES.NEW,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export default schema.build();
|
package/src/v2/readme.md
CHANGED
|
@@ -1,272 +1,217 @@
|
|
|
1
|
-
# ElectroDB
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
+
### Collections
|
|
24
|
+
A *Collection* operates on sets of entities. While `Model` focuses on individual records, `Collection` is for batch and query-level operations:
|
|
25
|
+
|
|
26
|
+
- Query methods like `findById()`, `all()`, and index-derived methods.
|
|
27
|
+
- Batch creation and update methods (`createMany`, `_saveMany`).
|
|
28
|
+
- Automatic generation of `allBy...` and `findBy...` convenience methods based on defined indexes.
|
|
29
|
+
|
|
30
|
+
Collections extend `BaseCollection`, which generates query methods at runtime based on your schema definitions.
|
|
31
|
+
|
|
32
|
+
### Schema Builder
|
|
33
|
+
The `SchemaBuilder` is a fluent API to define an entity’s schema:
|
|
34
|
+
|
|
35
|
+
- **Attributes:** Configure entity fields and their validation.
|
|
36
|
+
- **Indexes:** Specify primary and secondary indexes for common queries.
|
|
37
|
+
- **References:** Define entity relationships (e.g., `User` belongs to `Organization`).
|
|
38
|
+
|
|
39
|
+
The `SchemaBuilder` enforces naming conventions and sets defaults, reducing repetitive configuration.
|
|
40
|
+
|
|
41
|
+
**Note on Indexes:** Add indexes thoughtfully. Every extra index adds cost and complexity. Only create indexes for well-understood, frequently-needed query patterns.
|
|
42
|
+
|
|
43
|
+
### Entity Registry
|
|
44
|
+
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.
|
|
45
|
+
|
|
46
|
+
## Default Attributes and Indexes
|
|
47
|
+
|
|
48
|
+
When you create a schema with `SchemaBuilder`, the following attributes are automatically defined:
|
|
49
|
+
|
|
50
|
+
1. **ID (Primary Key):** A UUID-based primary key (`${entityName}Id`), ensuring unique identification.
|
|
51
|
+
2. **createdAt:** A timestamp (ISO string) set at entity creation.
|
|
52
|
+
3. **updatedAt:** A timestamp (ISO string) updated on each modification.
|
|
53
|
+
|
|
54
|
+
A primary index is also set up, keyed by the `${entityName}Id` attribute, guaranteeing a straightforward way to retrieve entities by their unique ID.
|
|
55
|
+
|
|
56
|
+
## Auto-Generated Methods
|
|
57
|
+
|
|
58
|
+
### `BaseCollection`
|
|
59
|
+
|
|
60
|
+
`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:
|
|
61
|
+
|
|
62
|
+
- `allByOpportunityId(opportunityId, options?)`
|
|
63
|
+
- `findByOpportunityId(opportunityId, options?)`
|
|
64
|
+
- `allByOpportunityIdAndStatus(opportunityId, status, options?)`
|
|
65
|
+
- `findByOpportunityIdAndStatus(opportunityId, status, options?)`
|
|
66
|
+
- `allByOpportunityIdAndStatusAndCreatedAt(opportunityId, status, createdAt, options?)`
|
|
67
|
+
- `findByOpportunityIdAndStatusAndCreatedAt(opportunityId, status, createdAt, options?)`
|
|
68
|
+
|
|
69
|
+
**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.
|
|
70
|
+
|
|
71
|
+
**Example:**
|
|
72
|
+
```js
|
|
73
|
+
const Suggestion = dataAccess.Suggestion;
|
|
74
|
+
|
|
75
|
+
// Retrieve all suggestions by `opportunityId`
|
|
76
|
+
const results = await Suggestion.allByOpportunityId('op-12345');
|
|
77
|
+
|
|
78
|
+
// Retrieve a single suggestion by `opportunityId` and `status`
|
|
79
|
+
const single = await Suggestion.findByOpportunityIdAndStatus('op-12345', 'OPEN');
|
|
58
80
|
```
|
|
59
81
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
- **Opportunity**: Represents a specific issue identified on a website. It includes attributes like `title`, `description`, `siteId`, and `status`.
|
|
63
|
-
- **Suggestion**: Represents a proposed fix for an Opportunity. Attributes include `opportunityId`, `type`, `status`, and `rank`.
|
|
64
|
-
- **Relationships**: Opportunities have many Suggestions. This relationship is implemented through `OpportunityCollection` and `SuggestionCollection`, which interact via ElectroDB-managed DynamoDB relationships.
|
|
65
|
-
|
|
66
|
-
## Getting Started
|
|
67
|
-
|
|
68
|
-
1. **Install Dependencies**
|
|
69
|
-
```bash
|
|
70
|
-
npm install
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
2. **Setup DynamoDB**
|
|
74
|
-
- Ensure AWS credentials are configured and a DynamoDB table is set up.
|
|
75
|
-
- Configure the DynamoDB table name and related settings in `index.js`.
|
|
76
|
-
|
|
77
|
-
3. **Usage Example**
|
|
78
|
-
```javascript
|
|
79
|
-
import { createDataAccess } from './index.js';
|
|
80
|
-
|
|
81
|
-
const config = { tableNameData: 'YOUR_TABLE_NAME' };
|
|
82
|
-
const log = console;
|
|
83
|
-
const dao = createDataAccess(config, log);
|
|
84
|
-
|
|
85
|
-
// Create a new Opportunity
|
|
86
|
-
const opportunityData = { title: 'Broken Links', siteId: 'site123', type: 'broken-backlinks' };
|
|
87
|
-
const newOpportunity = await dao.Opportunity.create(opportunityData);
|
|
88
|
-
console.log('New Opportunity Created:', newOpportunity);
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
4. **Extending Functionality**
|
|
92
|
-
- Add new models by extending `BaseModel` and new collections by extending `BaseCollection`.
|
|
93
|
-
- Register new models in the `ModelFactory` for unified access.
|
|
94
|
-
|
|
95
|
-
## Adding a New ElectroDB-Based Entity
|
|
96
|
-
|
|
97
|
-
This guide provides a step-by-step overview for adding a new ElectroDB-based entity to the application.
|
|
98
|
-
|
|
99
|
-
### Step 1: Define the Entity Schema
|
|
100
|
-
|
|
101
|
-
1. **Create Entity Schema File**: Define the entity schema in a new file (e.g., `myNewEntity.schema.js`) within the `/schemas/` directory.
|
|
102
|
-
|
|
103
|
-
```javascript
|
|
104
|
-
export const MyNewEntitySchema = {
|
|
105
|
-
model: {
|
|
106
|
-
entity: 'MyNewEntity',
|
|
107
|
-
service: 'MyService',
|
|
108
|
-
version: '1',
|
|
109
|
-
},
|
|
110
|
-
attributes: {
|
|
111
|
-
myNewEntityId: {
|
|
112
|
-
type: 'string',
|
|
113
|
-
required: true,
|
|
114
|
-
},
|
|
115
|
-
name: {
|
|
116
|
-
type: 'string',
|
|
117
|
-
required: true,
|
|
118
|
-
},
|
|
119
|
-
status: {
|
|
120
|
-
type: 'string',
|
|
121
|
-
enum: ['NEW', 'IN_PROGRESS', 'COMPLETED'],
|
|
122
|
-
required: true,
|
|
123
|
-
},
|
|
124
|
-
createdAt: {
|
|
125
|
-
type: 'string',
|
|
126
|
-
required: true,
|
|
127
|
-
default: () => new Date().toISOString(),
|
|
128
|
-
},
|
|
129
|
-
},
|
|
130
|
-
indexes: {
|
|
131
|
-
myNewEntityIndex: {
|
|
132
|
-
pk: {
|
|
133
|
-
field: 'pk',
|
|
134
|
-
facets: ['myNewEntityId'],
|
|
135
|
-
},
|
|
136
|
-
sk: {
|
|
137
|
-
field: 'sk',
|
|
138
|
-
facets: ['status'],
|
|
139
|
-
},
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
references: {
|
|
143
|
-
belongs_to: [
|
|
144
|
-
{ type: 'belongs_to', target: 'Opportunity' },
|
|
145
|
-
],
|
|
146
|
-
},
|
|
147
|
-
};
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
2. **Declare References**: Use the `references` field to define relationships between entities. This sets up associations for easy fetching and managing of related entities, allowing for automatic generation of reference getter methods.
|
|
151
|
-
|
|
152
|
-
### Step 2: Add a Model Class
|
|
153
|
-
|
|
154
|
-
1. **Create the Model Class**: In the `/models/` directory, add `myNewEntity.model.js`.
|
|
155
|
-
|
|
156
|
-
```javascript
|
|
157
|
-
import BaseModel from './base.model.js';
|
|
158
|
-
|
|
159
|
-
class MyNewEntity extends BaseModel {
|
|
160
|
-
constructor(electroService, modelFactory, record, log) {
|
|
161
|
-
super(electroService, modelFactory, record, log);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export default MyNewEntity;
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
Note: By using `BaseModel`, entity classes can remain empty unless there is a need to:
|
|
169
|
-
- Override automatically generated getters or setters for specific attributes.
|
|
170
|
-
- Add custom methods specific to the entity.
|
|
171
|
-
|
|
172
|
-
### Automatic Getter and Setter Methods
|
|
173
|
-
|
|
174
|
-
The `BaseModel` automatically generates getter and setter methods for each attribute defined in the entity schema:
|
|
175
|
-
|
|
176
|
-
- **Utility Methods**: `BaseModel` provides `getId()`, `getCreatedAt()`, and `getUpdatedAt()` methods out of the box for accessing common entity information like the unique identifier, creation timestamp, and last update timestamp.
|
|
177
|
-
|
|
178
|
-
- **Getters**: Follow the convention `get<AttributeName>()` to access attribute values.
|
|
179
|
-
- **Setters**: Follow the convention `set<AttributeName>(value)` to modify entity values, while handling patching.
|
|
180
|
-
|
|
181
|
-
Example:
|
|
182
|
-
|
|
183
|
-
- If an attribute is named `name`, `BaseModel` will automatically generate:
|
|
184
|
-
- `getName()`: Retrieve the value of `name`.
|
|
185
|
-
- `setName(value)`: Update the value of `name`.
|
|
186
|
-
|
|
187
|
-
This reduces boilerplate and ensures consistency.
|
|
188
|
-
|
|
189
|
-
### Automatic Reference Getter Methods
|
|
190
|
-
|
|
191
|
-
If references are defined in the schema (e.g., `belongs_to`, `has_many`), `BaseModel` generates reference getter methods:
|
|
192
|
-
|
|
193
|
-
- **References Getter Naming**:
|
|
194
|
-
- Methods are named `get<RelatedEntity>()`, where `<RelatedEntity>` corresponds to the target specified in the `references` field.
|
|
195
|
-
|
|
196
|
-
Example:
|
|
197
|
-
```javascript
|
|
198
|
-
references: {
|
|
199
|
-
belongs_to: [
|
|
200
|
-
{ type: 'belongs_to', target: 'Opportunity' },
|
|
201
|
-
],
|
|
202
|
-
},
|
|
203
|
-
```
|
|
204
|
-
This results in a `getOpportunity()` method for accessing the related `Opportunity` entity.
|
|
205
|
-
|
|
206
|
-
### Step 3: Add a Collection Class
|
|
207
|
-
|
|
208
|
-
1. **Create the Collection Class**: Add `myNewEntity.collection.js` in the `/collections/` directory.
|
|
209
|
-
|
|
210
|
-
```javascript
|
|
211
|
-
import BaseCollection from './base.collection.js';
|
|
212
|
-
import MyNewEntity from '../models/myNewEntity.model.js';
|
|
213
|
-
|
|
214
|
-
class MyNewEntityCollection extends BaseCollection {
|
|
215
|
-
constructor(service, modelFactory, log) {
|
|
216
|
-
super(service, modelFactory, MyNewEntity, log);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
async allByStatus(status) {
|
|
220
|
-
return this.findByIndexKeys({ status });
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
export default MyNewEntityCollection;
|
|
225
|
-
```
|
|
82
|
+
### `BaseModel`
|
|
226
83
|
|
|
227
|
-
|
|
84
|
+
`BaseModel` provides methods for CRUD operations and reference handling:
|
|
228
85
|
|
|
229
|
-
|
|
86
|
+
- `save()`: Persists changes to the entity.
|
|
87
|
+
- `remove()`: Deletes the entity from the database.
|
|
88
|
+
- `get...()`: Getters for entity attributes.
|
|
89
|
+
- `set...()`: Setters for entity attributes.
|
|
230
90
|
|
|
231
|
-
|
|
232
|
-
|
|
91
|
+
Additionally, `BaseModel` generates methods to fetch referenced entities.
|
|
92
|
+
For example, if `User` belongs to `Organization`, `BaseModel` will create:
|
|
233
93
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
this.service,
|
|
238
|
-
this,
|
|
239
|
-
this.logger,
|
|
240
|
-
);
|
|
94
|
+
- `getOrganization()`: Fetch the referenced `Organization` entity.
|
|
95
|
+
- `getOrganizationId()`: Retrieve the `Organization` ID.
|
|
96
|
+
- `setOrganizationId(organizationId)`: Update the `Organization` reference.
|
|
241
97
|
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
```
|
|
98
|
+
Conversely, the `Organization` entity will have:
|
|
246
99
|
|
|
247
|
-
|
|
100
|
+
- `getUsers()`: Fetch all `User` entities referencing this `Organization`.
|
|
101
|
+
- And with the `User`-Schema's `belongs_to` reciprocal reference expressing filterable sort keys, e.g. "email", "location":
|
|
102
|
+
- `getUsersByEmail(email)`: Fetch all `User` entities referencing this `Organization` with a specific email."
|
|
103
|
+
- `getUsersByEmailAndLocation(email, location)`: Fetch all `User` entities referencing this `Organization` with a specific email and location.
|
|
248
104
|
|
|
249
|
-
|
|
250
|
-
|
|
105
|
+
**Example:**
|
|
106
|
+
```js
|
|
107
|
+
const user = await User.findById('usr-abc123');
|
|
251
108
|
|
|
252
|
-
|
|
253
|
-
|
|
109
|
+
// Work with attributes
|
|
110
|
+
console.log(user.getEmail()); // e.g. "john@example.com"
|
|
111
|
+
user.setName('John Smith');
|
|
112
|
+
await user.save();
|
|
254
113
|
|
|
255
|
-
|
|
114
|
+
// Fetch referenced entity
|
|
115
|
+
const org = await user.getOrganization();
|
|
116
|
+
console.log(org.getName());
|
|
117
|
+
```
|
|
256
118
|
|
|
257
|
-
|
|
119
|
+
## Step-by-Step: Adding a New Entity
|
|
258
120
|
|
|
259
|
-
|
|
260
|
-
2. **Update Type Definitions**: Modify `index.d.ts` to include new interfaces and types for the entity.
|
|
121
|
+
Follow these steps to introduce a new entity into the framework.
|
|
261
122
|
|
|
262
|
-
###
|
|
123
|
+
### 1. Define the Schema
|
|
124
|
+
Create `user.schema.js`:
|
|
125
|
+
|
|
126
|
+
```js
|
|
127
|
+
import SchemaBuilder from '../base/schema.builder.js';
|
|
128
|
+
import User from './user.model.js';
|
|
129
|
+
import UserCollection from './user.collection.js';
|
|
130
|
+
|
|
131
|
+
const userSchema = new SchemaBuilder(User, UserCollection)
|
|
132
|
+
.addAttribute('email', {
|
|
133
|
+
type: 'string',
|
|
134
|
+
required: true,
|
|
135
|
+
validate: (value) => value.includes('@'),
|
|
136
|
+
})
|
|
137
|
+
.addAttribute('name', { type: 'string', required: true })
|
|
138
|
+
.addAllIndexWithComposite('email')
|
|
139
|
+
.addReference('belongs_to', 'Organization') // Adds organizationId and byOrganizationId index
|
|
140
|
+
.build();
|
|
141
|
+
|
|
142
|
+
export default userSchema;
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 2. Implement the Model
|
|
146
|
+
Create `user.model.js`:
|
|
147
|
+
|
|
148
|
+
```js
|
|
149
|
+
import BaseModel from '../base/base.model.js';
|
|
150
|
+
|
|
151
|
+
class UserModel extends BaseModel {
|
|
152
|
+
// Additional domain logic methods can be added here if needed.
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export default UserModel;
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### 3. Implement the Collection
|
|
159
|
+
Create `user.collection.js`:
|
|
160
|
+
|
|
161
|
+
```js
|
|
162
|
+
import BaseCollection from '../base/base.collection.js';
|
|
163
|
+
import UserModel from './user.model.js';
|
|
164
|
+
import userSchema from './user.schema.js';
|
|
165
|
+
|
|
166
|
+
class UserCollection extends BaseCollection {
|
|
167
|
+
// Additional domain logic collection methods can be added here if needed.
|
|
168
|
+
async findByEmail(email) {
|
|
169
|
+
return this.findByIndexKeys({ email });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export default UserCollection;
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### 4. Register the Entity
|
|
177
|
+
In `entity.registry.js` (or equivalent):
|
|
178
|
+
|
|
179
|
+
```js
|
|
180
|
+
import UserSchema from '../user/user.schema.js';
|
|
181
|
+
import UserCollection from '../user/user.collection.js';
|
|
182
|
+
|
|
183
|
+
EntityRegistry.registerEntity(UserSchema, UserCollection);
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### 5. Update DynamoDB Configuration and `schema.json`
|
|
187
|
+
|
|
188
|
+
After defining indexes in the schema, **manually add these indexes to your DynamoDB table configuration**. DynamoDB does not automatically create GSIs. You must:
|
|
189
|
+
|
|
190
|
+
- Use the AWS Console, CLI, or CloudFormation/Terraform templates to define these GSIs.
|
|
191
|
+
- 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.
|
|
192
|
+
|
|
193
|
+
### 6. Use the Entity
|
|
194
|
+
```js
|
|
195
|
+
const { User, Organization } = dataAccess;
|
|
196
|
+
|
|
197
|
+
// Create a user
|
|
198
|
+
const newUser = await User.create({ email: 'john@example.com', name: 'John Doe' });
|
|
199
|
+
|
|
200
|
+
// Find user by ID
|
|
201
|
+
const user = await User.findById(newUser.getId());
|
|
202
|
+
|
|
203
|
+
// Get the user organization
|
|
204
|
+
const org = await user.getOrganization();
|
|
205
|
+
|
|
206
|
+
// ...or in reverse
|
|
207
|
+
const anOrg = await Organization.findById(user.getOrganizationId());
|
|
208
|
+
const orgUsers = await anOrg.getUsers();
|
|
209
|
+
|
|
210
|
+
// Update user and save
|
|
211
|
+
user.setName('John X. Doe');
|
|
212
|
+
await user.save();
|
|
213
|
+
```
|
|
263
214
|
|
|
264
|
-
|
|
265
|
-
```bash
|
|
266
|
-
npm run test && npm run test:it
|
|
267
|
-
```
|
|
215
|
+
## Consideration for Indexes
|
|
268
216
|
|
|
269
|
-
|
|
270
|
-
```bash
|
|
271
|
-
npm run lint
|
|
272
|
-
```
|
|
217
|
+
Indexes cost money and complexity. Do not add indexes lightly. Determine which query patterns you truly need and only then introduce additional indexes.
|