@adobe/spacecat-shared-data-access 1.60.1 → 1.60.3
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 +14 -0
- package/package.json +4 -4
- package/src/v2/models/api-key/api-key.schema.js +0 -2
- package/src/v2/models/base/base.collection.js +19 -21
- package/src/v2/models/base/index.d.ts +9 -2
- package/src/v2/models/base/schema.builder.js +52 -122
- package/src/v2/models/base/schema.js +26 -1
- package/src/v2/models/configuration/configuration.collection.js +5 -1
- package/src/v2/models/configuration/configuration.schema.js +9 -2
- package/src/v2/models/import-job/import-job.schema.js +1 -2
- package/src/v2/models/organization/organization.schema.js +1 -1
- package/src/v2/models/site/site.schema.js +1 -2
- package/src/v2/models/site-candidate/site-candidate.schema.js +1 -1
- package/src/v2/readme.md +1 -1
- package/src/v2/util/util.js +8 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [@adobe/spacecat-shared-data-access-v1.60.3](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v1.60.2...@adobe/spacecat-shared-data-access-v1.60.3) (2024-12-22)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **deps:** update external fixes ([#502](https://github.com/adobe/spacecat-shared/issues/502)) ([6ea4ef2](https://github.com/adobe/spacecat-shared/commit/6ea4ef2889ac665160adc9ebdad38b41b67d0782))
|
|
7
|
+
|
|
8
|
+
# [@adobe/spacecat-shared-data-access-v1.60.2](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v1.60.1...@adobe/spacecat-shared-data-access-v1.60.2) (2024-12-19)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* reduce number of indexes ([#499](https://github.com/adobe/spacecat-shared/issues/499)) ([95601cc](https://github.com/adobe/spacecat-shared/commit/95601cca8ca37f989a650fa841e0dae678cebc25))
|
|
14
|
+
|
|
1
15
|
# [@adobe/spacecat-shared-data-access-v1.60.1](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v1.60.0...@adobe/spacecat-shared-data-access-v1.60.1) (2024-12-18)
|
|
2
16
|
|
|
3
17
|
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/spacecat-shared-data-access",
|
|
3
|
-
"version": "1.60.
|
|
3
|
+
"version": "1.60.3",
|
|
4
4
|
"description": "Shared modules of the Spacecat Services - Data Access",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=20.0.0 <23.0.0",
|
|
8
|
-
"npm": ">=10.0.0 <
|
|
8
|
+
"npm": ">=10.0.0 <12.0.0"
|
|
9
9
|
},
|
|
10
10
|
"main": "src/index.js",
|
|
11
11
|
"types": "src/index.d.ts",
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@adobe/spacecat-shared-dynamo": "1.4.0",
|
|
38
38
|
"@adobe/spacecat-shared-utils": "1.23.1",
|
|
39
|
-
"@aws-sdk/client-dynamodb": "3.
|
|
40
|
-
"@aws-sdk/lib-dynamodb": "3.
|
|
39
|
+
"@aws-sdk/client-dynamodb": "3.716.0",
|
|
40
|
+
"@aws-sdk/lib-dynamodb": "3.716.0",
|
|
41
41
|
"@types/joi": "17.2.3",
|
|
42
42
|
"aws-xray-sdk": "3.10.2",
|
|
43
43
|
"electrodb": "3.0.1",
|
|
@@ -69,12 +69,10 @@ const schema = new SchemaBuilder(ApiKey, ApiKeyCollection)
|
|
|
69
69
|
},
|
|
70
70
|
})
|
|
71
71
|
.addIndex(
|
|
72
|
-
'byHashedApiKey',
|
|
73
72
|
{ composite: ['hashedApiKey'] },
|
|
74
73
|
{ composite: ['updatedAt'] },
|
|
75
74
|
)
|
|
76
75
|
.addIndex(
|
|
77
|
-
'byImsOrgIdAndImsUserId',
|
|
78
76
|
{ composite: ['imsOrgId', 'imsUserId'] },
|
|
79
77
|
{ composite: ['updatedAt'] },
|
|
80
78
|
);
|
|
@@ -24,7 +24,6 @@ import { guardId } from '../../util/guards.js';
|
|
|
24
24
|
import {
|
|
25
25
|
entityNameToAllPKValue,
|
|
26
26
|
isNonEmptyArray,
|
|
27
|
-
keyNamesToIndexName,
|
|
28
27
|
removeElectroProperties,
|
|
29
28
|
} from '../../util/util.js';
|
|
30
29
|
import { INDEX_TYPES } from './constants.js';
|
|
@@ -40,26 +39,24 @@ function isValidParent(parent, child) {
|
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* @param {
|
|
48
|
-
* @
|
|
49
|
-
* @returns {object} The found index.
|
|
42
|
+
* Finds the index name by the keys provided. The index is searched
|
|
43
|
+
* keys to match the combination of partition and sort keys. If no
|
|
44
|
+
* index is found, we fall back to the "all" index, then the "primary".
|
|
45
|
+
* @param {Schema} schema - The schema to search for the index.
|
|
46
|
+
* @param {Object} keys - The keys to search for.
|
|
47
|
+
* @return {*|string} - The index name.
|
|
50
48
|
*/
|
|
51
|
-
function findIndexNameByKeys(
|
|
49
|
+
function findIndexNameByKeys(schema, keys) {
|
|
52
50
|
const keyNames = Object.keys(keys);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return candidateName;
|
|
58
|
-
}
|
|
51
|
+
|
|
52
|
+
const index = schema.findIndexBySortKeys(keyNames);
|
|
53
|
+
if (index) {
|
|
54
|
+
return index.index;
|
|
59
55
|
}
|
|
60
56
|
|
|
61
|
-
|
|
62
|
-
|
|
57
|
+
const allIndex = schema.findIndexByType(INDEX_TYPES.ALL);
|
|
58
|
+
if (allIndex) {
|
|
59
|
+
return allIndex.index;
|
|
63
60
|
}
|
|
64
61
|
|
|
65
62
|
return INDEX_TYPES.PRIMARY;
|
|
@@ -176,11 +173,11 @@ class BaseCollection {
|
|
|
176
173
|
throw new Error(message);
|
|
177
174
|
}
|
|
178
175
|
|
|
179
|
-
const indexName = options.index || findIndexNameByKeys(this.
|
|
176
|
+
const indexName = options.index || findIndexNameByKeys(this.schema, keys);
|
|
180
177
|
const index = this.entity.query[indexName];
|
|
181
178
|
|
|
182
179
|
if (!index) {
|
|
183
|
-
const message = `Failed to query [${this.entityName}]:
|
|
180
|
+
const message = `Failed to query [${this.entityName}]: query proxy [${indexName}] not found`;
|
|
184
181
|
this.log.error(message);
|
|
185
182
|
throw new Error(message);
|
|
186
183
|
}
|
|
@@ -243,7 +240,8 @@ class BaseCollection {
|
|
|
243
240
|
* Finds a single entity from the "all" index. Requires an index named "all" with a partition key
|
|
244
241
|
* named "pk" with a static value of "ALL_<ENTITYNAME>".
|
|
245
242
|
* @param {Object} [sortKeys] - The sort keys to use for the query.
|
|
246
|
-
* @param {{index?: string, attributes?: string[]}} [options] -
|
|
243
|
+
* @param {{index?: string, attributes?: string[], order?: string}} [options] -
|
|
244
|
+
* Additional options for the query.
|
|
247
245
|
* @return {Promise<BaseModel|Array<BaseModel>|null>}
|
|
248
246
|
*/
|
|
249
247
|
async findByAll(sortKeys = {}, options = {}) {
|
|
@@ -254,7 +252,7 @@ class BaseCollection {
|
|
|
254
252
|
}
|
|
255
253
|
|
|
256
254
|
const keys = { pk: entityNameToAllPKValue(this.entityName), ...sortKeys };
|
|
257
|
-
return this.#queryByIndexKeys(keys, { ...options,
|
|
255
|
+
return this.#queryByIndexKeys(keys, { ...options, limit: 1 });
|
|
258
256
|
}
|
|
259
257
|
|
|
260
258
|
/**
|
|
@@ -58,12 +58,20 @@ export interface Reference {
|
|
|
58
58
|
isRemoveDependents(): boolean;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
export interface IndexAccessor {
|
|
62
|
+
indexName: string;
|
|
63
|
+
keySets: string[][];
|
|
64
|
+
}
|
|
65
|
+
|
|
61
66
|
export interface Schema {
|
|
67
|
+
findIndexBySortKeys(sortKeys: string[]): object | null;
|
|
68
|
+
findIndexByType(type: string): object | null;
|
|
62
69
|
getAttribute(name: string): object;
|
|
63
70
|
getAttributes(): object;
|
|
64
71
|
getCollectionName(): string;
|
|
65
72
|
getEntityName(): string;
|
|
66
73
|
getIdName(): string;
|
|
74
|
+
getIndexAccessors(): Array<IndexAccessor>;
|
|
67
75
|
getIndexes(): object;
|
|
68
76
|
getIndexKeys(indexName: string): string[];
|
|
69
77
|
getModelClass(): object;
|
|
@@ -75,8 +83,7 @@ export interface Schema {
|
|
|
75
83
|
|
|
76
84
|
export interface SchemaBuilder {
|
|
77
85
|
addAttribute(name: string, data: object): SchemaBuilder;
|
|
78
|
-
|
|
79
|
-
addAllIndexWithTemplateField(fieldName: string, template: string): SchemaBuilder;
|
|
86
|
+
addAllIndex(sortKeys: string[]): SchemaBuilder;
|
|
80
87
|
addIndex(name: string, partitionKey: object, sortKey: object): SchemaBuilder;
|
|
81
88
|
addReference(referenceType: string, entityName: string, sortKeys?: string[]): SchemaBuilder;
|
|
82
89
|
build(): Schema;
|
|
@@ -15,10 +15,10 @@ import { hasText, isInteger, isNonEmptyObject } from '@adobe/spacecat-shared-uti
|
|
|
15
15
|
import { v4 as uuid, validate as uuidValidate } from 'uuid';
|
|
16
16
|
|
|
17
17
|
import {
|
|
18
|
-
capitalize,
|
|
19
18
|
decapitalize,
|
|
20
19
|
entityNameToAllPKValue,
|
|
21
|
-
entityNameToIdName,
|
|
20
|
+
entityNameToIdName,
|
|
21
|
+
isNonEmptyArray,
|
|
22
22
|
} from '../../util/util.js';
|
|
23
23
|
|
|
24
24
|
import { INDEX_TYPES } from './constants.js';
|
|
@@ -70,52 +70,6 @@ const UPDATED_AT_ATTRIBUTE_DATA = {
|
|
|
70
70
|
set: () => new Date().toISOString(),
|
|
71
71
|
};
|
|
72
72
|
|
|
73
|
-
/** Certain index names (primary, all) are reserved and cannot be reused. */
|
|
74
|
-
const RESERVED_INDEX_NAMES = [INDEX_TYPES.PRIMARY, INDEX_TYPES.ALL];
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Constructs a fully qualified index name.
|
|
78
|
-
* @param {string} service - The name of the service.
|
|
79
|
-
* @param {string} entity - The name of the entity.
|
|
80
|
-
* @param {string} name - The index name (e.g., 'all', 'byForeignKey').
|
|
81
|
-
* @returns {string} The fully qualified index name.
|
|
82
|
-
*/
|
|
83
|
-
const createdIndexName = (service, entity, name) => `${service.toLowerCase()}-data-${entity}-${name}`;
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Sorts an indexes object by its keys alphabetically.
|
|
87
|
-
* @param {object} indexes - An object whose keys are index names and values are index definitions.
|
|
88
|
-
* @returns {object} A new object with the same entries, but keys sorted alphabetically.
|
|
89
|
-
*/
|
|
90
|
-
const sortIndexes = (indexes) => Object.fromEntries(
|
|
91
|
-
Object.entries(indexes).sort((a, b) => a[0].localeCompare(b[0])),
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Assigns GSI field names to indexes that don't have them yet.
|
|
96
|
-
* Ensures that if an "all" index exists, it uses gsi1 (already assigned)
|
|
97
|
-
* and other indexes continue numbering from gsi2 onwards.
|
|
98
|
-
*
|
|
99
|
-
* @param {object} indexes - Object of indexes that require naming.
|
|
100
|
-
* @param {object|null} all - The "all" index object if present, null otherwise.
|
|
101
|
-
*/
|
|
102
|
-
const numberGSIsIndexes = (indexes, all) => {
|
|
103
|
-
// if there's an "all" index, we start indexing subsequent GSIs from 2,
|
|
104
|
-
// because "all" index already occupies gsi1.
|
|
105
|
-
// if no "all" index exists, start from 1.
|
|
106
|
-
let gsiCounter = isNonEmptyObject(all) ? 1 : 0;
|
|
107
|
-
|
|
108
|
-
Object.values(indexes).forEach((index) => { /* eslint-disable no-param-reassign */
|
|
109
|
-
// only assign new field names and number through if none are provided.
|
|
110
|
-
if (!index.pk.field || !index.sk.field) {
|
|
111
|
-
gsiCounter += 1;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
index.pk.field = index.pk.field || `gsi${gsiCounter}pk`;
|
|
115
|
-
index.sk.field = index.sk.field || `gsi${gsiCounter}sk`;
|
|
116
|
-
});
|
|
117
|
-
};
|
|
118
|
-
|
|
119
73
|
/**
|
|
120
74
|
* The SchemaBuilder class allows for constructing a schema definition
|
|
121
75
|
* including attributes, indexes, and references to other entities.
|
|
@@ -159,9 +113,9 @@ class SchemaBuilder {
|
|
|
159
113
|
|
|
160
114
|
this.rawIndexes = {
|
|
161
115
|
primary: null,
|
|
162
|
-
all:
|
|
163
|
-
belongs_to:
|
|
164
|
-
other:
|
|
116
|
+
all: [],
|
|
117
|
+
belongs_to: [],
|
|
118
|
+
other: [],
|
|
165
119
|
};
|
|
166
120
|
|
|
167
121
|
this.attributes = {};
|
|
@@ -189,16 +143,14 @@ class SchemaBuilder {
|
|
|
189
143
|
};
|
|
190
144
|
}
|
|
191
145
|
|
|
192
|
-
#internalAddIndex(
|
|
193
|
-
const indexFullName = createdIndexName(this.serviceName, this.entityName, name);
|
|
194
|
-
|
|
146
|
+
#internalAddIndex(partitionKey, sortKey, type) {
|
|
195
147
|
// store index config without assigning fields yet
|
|
196
148
|
// the fields will be assigned in build phase based on sorting and presence of "all" index
|
|
197
|
-
this.rawIndexes[type]
|
|
198
|
-
|
|
149
|
+
this.rawIndexes[type].push({
|
|
150
|
+
type,
|
|
199
151
|
pk: { ...partitionKey },
|
|
200
152
|
sk: { ...sortKey },
|
|
201
|
-
};
|
|
153
|
+
});
|
|
202
154
|
}
|
|
203
155
|
|
|
204
156
|
/**
|
|
@@ -224,52 +176,24 @@ class SchemaBuilder {
|
|
|
224
176
|
}
|
|
225
177
|
|
|
226
178
|
/**
|
|
227
|
-
* Adds an "all" index based
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
* Will overwrite any existing "all" index.
|
|
179
|
+
* Adds an "all" index with composite partition and sort keys, or a template-based sort key.
|
|
180
|
+
* Useful for querying all entities of this type. Only one "all" index is allowed and a
|
|
181
|
+
* pre-existing "all" index will be overwritten.
|
|
231
182
|
*
|
|
232
|
-
* @param {
|
|
183
|
+
* @param {Array<string>} sortKeys - The attributes to form the sort key.
|
|
233
184
|
* @returns {SchemaBuilder} Returns this builder for method chaining.
|
|
234
|
-
* @throws {Error} If
|
|
185
|
+
* @throws {Error} If composite attribute names or template are not provided.
|
|
235
186
|
*/
|
|
236
|
-
|
|
237
|
-
if (
|
|
238
|
-
throw new Error('
|
|
187
|
+
addAllIndex(sortKeys) {
|
|
188
|
+
if (!isNonEmptyArray(sortKeys)) {
|
|
189
|
+
throw new Error('Sort keys are required and must be a non-empty array.');
|
|
239
190
|
}
|
|
240
191
|
|
|
241
|
-
this
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
return this;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Adds an "all" index with a template-based sort key.
|
|
252
|
-
* Useful if a single value template defines how entries are sorted.
|
|
253
|
-
*
|
|
254
|
-
* @param {string} fieldName - The sort key field name.
|
|
255
|
-
* @param {string} template - A template string defining how to generate the sort key value.
|
|
256
|
-
* @returns {SchemaBuilder} Returns this builder for method chaining.
|
|
257
|
-
* @throws {Error} If fieldName or template are not valid strings.
|
|
258
|
-
*/
|
|
259
|
-
addAllIndexWithTemplateField(fieldName, template) {
|
|
260
|
-
if (!hasText(fieldName)) {
|
|
261
|
-
throw new Error('fieldName is required and must be a non-empty string.');
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
if (!hasText(template)) {
|
|
265
|
-
throw new Error('template is required and must be a non-empty string.');
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
this.rawIndexes.all = {
|
|
269
|
-
index: createdIndexName(this.serviceName, this.entityName, 'all'),
|
|
270
|
-
pk: { field: 'gsi1pk', template: entityNameToAllPKValue(this.entityName) },
|
|
271
|
-
sk: { field: fieldName, template },
|
|
272
|
-
};
|
|
192
|
+
this.#internalAddIndex(
|
|
193
|
+
{ template: entityNameToAllPKValue(this.entityName) },
|
|
194
|
+
{ composite: sortKeys },
|
|
195
|
+
INDEX_TYPES.ALL,
|
|
196
|
+
);
|
|
273
197
|
|
|
274
198
|
return this;
|
|
275
199
|
}
|
|
@@ -277,22 +201,13 @@ class SchemaBuilder {
|
|
|
277
201
|
/**
|
|
278
202
|
* Adds a generic secondary index (GSI).
|
|
279
203
|
*
|
|
280
|
-
* @param {string} name - The index name. Cannot be 'primary' or 'all'.
|
|
281
204
|
* @param {object} partitionKey - The partition key definition
|
|
282
205
|
* (e.g., { composite: [attributeName] }).
|
|
283
206
|
* @param {object} sortKey - The sort key definition.
|
|
284
207
|
* @returns {SchemaBuilder} Returns this builder for method chaining.
|
|
285
208
|
* @throws {Error} If index name is reserved or pk/sk configs are invalid.
|
|
286
209
|
*/
|
|
287
|
-
addIndex(
|
|
288
|
-
if (!hasText(name)) {
|
|
289
|
-
throw new Error('Index name is required and must be a non-empty string.');
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
if (RESERVED_INDEX_NAMES.includes(name)) {
|
|
293
|
-
throw new Error(`Index name "${name}" is reserved.`);
|
|
294
|
-
}
|
|
295
|
-
|
|
210
|
+
addIndex(partitionKey, sortKey) {
|
|
296
211
|
if (!isNonEmptyObject(partitionKey)) {
|
|
297
212
|
throw new Error('Partition key configuration (pk) is required and must be a non-empty object.');
|
|
298
213
|
}
|
|
@@ -301,7 +216,7 @@ class SchemaBuilder {
|
|
|
301
216
|
throw new Error('Sort key configuration (sk) is required and must be a non-empty object.');
|
|
302
217
|
}
|
|
303
218
|
|
|
304
|
-
this.#internalAddIndex(
|
|
219
|
+
this.#internalAddIndex(partitionKey, sortKey, INDEX_TYPES.OTHER);
|
|
305
220
|
|
|
306
221
|
return this;
|
|
307
222
|
}
|
|
@@ -357,7 +272,6 @@ class SchemaBuilder {
|
|
|
357
272
|
});
|
|
358
273
|
|
|
359
274
|
this.#internalAddIndex(
|
|
360
|
-
`by${capitalize(foreignKeyName)}`,
|
|
361
275
|
{ composite: [decapitalize(foreignKeyName)] },
|
|
362
276
|
{ composite: isNonEmptyArray(sortKeys) ? sortKeys : ['updatedAt'] },
|
|
363
277
|
INDEX_TYPES.BELONGS_TO,
|
|
@@ -378,21 +292,37 @@ class SchemaBuilder {
|
|
|
378
292
|
*/
|
|
379
293
|
#buildIndexes() {
|
|
380
294
|
// eslint-disable-next-line camelcase
|
|
381
|
-
const { belongs_to, other } = this.rawIndexes;
|
|
295
|
+
const { all, belongs_to, other } = this.rawIndexes;
|
|
296
|
+
|
|
297
|
+
// set the order of indexes
|
|
298
|
+
const orderedIndexes = [
|
|
299
|
+
...all,
|
|
300
|
+
// eslint-disable-next-line camelcase
|
|
301
|
+
...belongs_to,
|
|
302
|
+
...other,
|
|
303
|
+
];
|
|
304
|
+
|
|
305
|
+
if (orderedIndexes.length > 5) {
|
|
306
|
+
throw new Error('Cannot have more than 5 indexes.');
|
|
307
|
+
}
|
|
382
308
|
|
|
383
|
-
|
|
384
|
-
const indexes = {
|
|
385
|
-
...sortIndexes(belongs_to),
|
|
386
|
-
...sortIndexes(other),
|
|
387
|
-
};
|
|
309
|
+
this.indexes = { primary: this.rawIndexes.primary };
|
|
388
310
|
|
|
389
|
-
|
|
311
|
+
let indexCounter = 0;
|
|
312
|
+
Object.values(orderedIndexes).forEach((index) => {
|
|
313
|
+
indexCounter += 1;
|
|
390
314
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
315
|
+
const pkFieldName = `gsi${indexCounter}pk`;
|
|
316
|
+
const skFieldName = `gsi${indexCounter}sk`;
|
|
317
|
+
const indexName = `${this.serviceName.toLowerCase()}-data-${pkFieldName}-${skFieldName}`;
|
|
318
|
+
|
|
319
|
+
this.indexes[indexName] = {
|
|
320
|
+
index: indexName,
|
|
321
|
+
indexType: index.type,
|
|
322
|
+
pk: { field: pkFieldName, ...index.pk },
|
|
323
|
+
sk: { field: skFieldName, ...index.sk },
|
|
324
|
+
};
|
|
325
|
+
});
|
|
396
326
|
}
|
|
397
327
|
|
|
398
328
|
/**
|
|
@@ -140,6 +140,31 @@ class Schema {
|
|
|
140
140
|
return this.indexes[indexName];
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
findIndexBySortKeys(sortKeys) {
|
|
144
|
+
// find index that has same sort keys, then remove the last sort key
|
|
145
|
+
// and find the index that has the remaining sort keys, etc.
|
|
146
|
+
for (let { length } = sortKeys; length > 0; length -= 1) {
|
|
147
|
+
const subKeyNames = sortKeys.slice(0, length);
|
|
148
|
+
const index = Object.values(this.indexes).find((candidate) => {
|
|
149
|
+
const { pk, sk } = candidate;
|
|
150
|
+
const allKeys = [...(pk?.facets || []), ...(sk?.facets || [])];
|
|
151
|
+
|
|
152
|
+
// check if all keys in the index are in the sort keys
|
|
153
|
+
return subKeyNames.every((key) => allKeys.includes(key));
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
if (isNonEmptyObject(index)) {
|
|
157
|
+
return index;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
findIndexByType(type) {
|
|
165
|
+
return Object.values(this.indexes).find((index) => index.indexType === type) || null;
|
|
166
|
+
}
|
|
167
|
+
|
|
143
168
|
/**
|
|
144
169
|
* Returns the indexes for the schema. By default, this returns all indexes.
|
|
145
170
|
* You can use the `exclude` parameter to exclude certain indexes.
|
|
@@ -170,7 +195,7 @@ class Schema {
|
|
|
170
195
|
}
|
|
171
196
|
|
|
172
197
|
const pkKeys = Array.isArray(index.pk?.facets) ? index.pk.facets : [];
|
|
173
|
-
const skKeys = Array.isArray(index.sk?.facets) ? index.sk.facets : [
|
|
198
|
+
const skKeys = Array.isArray(index.sk?.facets) ? index.sk.facets : [];
|
|
174
199
|
|
|
175
200
|
return [...pkKeys, ...skKeys];
|
|
176
201
|
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { incrementVersion, sanitizeIdAndAuditFields } from '../../util/util.js';
|
|
13
|
+
import { incrementVersion, sanitizeIdAndAuditFields, zeroPad } from '../../util/util.js';
|
|
14
14
|
import BaseCollection from '../base/base.collection.js';
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -31,6 +31,10 @@ class ConfigurationCollection extends BaseCollection {
|
|
|
31
31
|
return super.create(sanitizedData);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
async findByVersion(version) {
|
|
35
|
+
return this.findByAll({ versionString: zeroPad(version, 10) });
|
|
36
|
+
}
|
|
37
|
+
|
|
34
38
|
async findLatest() {
|
|
35
39
|
return this.findByAll({}, { order: 'desc' });
|
|
36
40
|
}
|
|
@@ -19,6 +19,7 @@ import Joi from 'joi';
|
|
|
19
19
|
import SchemaBuilder from '../base/schema.builder.js';
|
|
20
20
|
import Configuration from './configuration.model.js';
|
|
21
21
|
import ConfigurationCollection from './configuration.collection.js';
|
|
22
|
+
import { zeroPad } from '../../util/util.js';
|
|
22
23
|
|
|
23
24
|
const handlerSchema = Joi.object().pattern(Joi.string(), Joi.object(
|
|
24
25
|
{
|
|
@@ -97,7 +98,13 @@ const schema = new SchemaBuilder(Configuration, ConfigurationCollection)
|
|
|
97
98
|
required: true,
|
|
98
99
|
readOnly: true,
|
|
99
100
|
})
|
|
100
|
-
//
|
|
101
|
-
|
|
101
|
+
.addAttribute('versionString', { // used for indexing/sorting
|
|
102
|
+
type: 'string',
|
|
103
|
+
required: true,
|
|
104
|
+
readOnly: true,
|
|
105
|
+
default: '0', // setting the default forces set() to run, to transform the version number to a string
|
|
106
|
+
set: (value, all) => zeroPad(all.version, 10),
|
|
107
|
+
})
|
|
108
|
+
.addAllIndex(['versionString']);
|
|
102
109
|
|
|
103
110
|
export default schema.build();
|
|
@@ -142,9 +142,8 @@ const schema = new SchemaBuilder(ImportJob, ImportJobCollection)
|
|
|
142
142
|
default: 0,
|
|
143
143
|
validate: (value) => !value || isInteger(value),
|
|
144
144
|
})
|
|
145
|
-
.
|
|
145
|
+
.addAllIndex(['startedAt'])
|
|
146
146
|
.addIndex(
|
|
147
|
-
'byStatus',
|
|
148
147
|
{ composite: ['status'] },
|
|
149
148
|
{ composite: ['updatedAt'] },
|
|
150
149
|
);
|
|
@@ -81,9 +81,8 @@ const schema = new SchemaBuilder(Site, SiteCollection)
|
|
|
81
81
|
set: () => new Date().toISOString(),
|
|
82
82
|
validate: (value) => !value || isIsoDate(value),
|
|
83
83
|
})
|
|
84
|
-
.
|
|
84
|
+
.addAllIndex(['baseURL'])
|
|
85
85
|
.addIndex(
|
|
86
|
-
'byDeliveryType',
|
|
87
86
|
{ composite: ['deliveryType'] },
|
|
88
87
|
{ composite: ['updatedAt'] },
|
|
89
88
|
);
|
package/src/v2/readme.md
CHANGED
|
@@ -135,7 +135,7 @@ const userSchema = new SchemaBuilder(User, UserCollection)
|
|
|
135
135
|
validate: (value) => value.includes('@'),
|
|
136
136
|
})
|
|
137
137
|
.addAttribute('name', { type: 'string', required: true })
|
|
138
|
-
.
|
|
138
|
+
.addAllIndex(['email'])
|
|
139
139
|
.addReference('belongs_to', 'Organization') // Adds organizationId and byOrganizationId index
|
|
140
140
|
.build();
|
|
141
141
|
|
package/src/v2/util/util.js
CHANGED
|
@@ -82,6 +82,13 @@ const incrementVersion = (version) => (isInteger(version) ? parseInt(version, 10
|
|
|
82
82
|
|
|
83
83
|
const isNonEmptyArray = (value) => Array.isArray(value) && value.length > 0;
|
|
84
84
|
|
|
85
|
+
const zeroPad = (num, length) => {
|
|
86
|
+
const str = String(num);
|
|
87
|
+
return str.length >= length
|
|
88
|
+
? str
|
|
89
|
+
: '0'.repeat(length - str.length) + str;
|
|
90
|
+
};
|
|
91
|
+
|
|
85
92
|
export {
|
|
86
93
|
capitalize,
|
|
87
94
|
classExtends,
|
|
@@ -101,4 +108,5 @@ export {
|
|
|
101
108
|
removeElectroProperties,
|
|
102
109
|
sanitizeIdAndAuditFields,
|
|
103
110
|
sanitizeTimestamps,
|
|
111
|
+
zeroPad,
|
|
104
112
|
};
|