@adobe/spacecat-shared-data-access 2.85.0 → 2.86.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
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [@adobe/spacecat-shared-data-access-v2.86.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v2.85.1...@adobe/spacecat-shared-data-access-v2.86.0) (2025-11-19)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* add configuration management APIs ([#1063](https://github.com/adobe/spacecat-shared/issues/1063)) ([d6988a9](https://github.com/adobe/spacecat-shared/commit/d6988a94ad79016c238f3f4d9426c328b52c3131))
|
|
7
|
+
|
|
8
|
+
# [@adobe/spacecat-shared-data-access-v2.85.1](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v2.85.0...@adobe/spacecat-shared-data-access-v2.85.1) (2025-11-18)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **deps:** update shared utils in data access ([#1145](https://github.com/adobe/spacecat-shared/issues/1145)) ([5e38e70](https://github.com/adobe/spacecat-shared/commit/5e38e7032cf1e95a363c6fab67d67065a50ea7a0))
|
|
14
|
+
|
|
1
15
|
# [@adobe/spacecat-shared-data-access-v2.85.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v2.84.0...@adobe/spacecat-shared-data-access-v2.85.0) (2025-11-18)
|
|
2
16
|
|
|
3
17
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/spacecat-shared-data-access",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.86.0",
|
|
4
4
|
"description": "Shared modules of the Spacecat Services - Data Access",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"access": "public"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@adobe/spacecat-shared-utils": "1.
|
|
41
|
+
"@adobe/spacecat-shared-utils": "1.74.0",
|
|
42
42
|
"@aws-sdk/client-dynamodb": "3.932.0",
|
|
43
43
|
"@aws-sdk/lib-dynamodb": "3.932.0",
|
|
44
44
|
"@types/joi": "17.2.3",
|
|
@@ -14,7 +14,6 @@ import { isNonEmptyObject, isNonEmptyArray } from '@adobe/spacecat-shared-utils'
|
|
|
14
14
|
|
|
15
15
|
import { sanitizeIdAndAuditFields } from '../../util/util.js';
|
|
16
16
|
import BaseModel from '../base/base.model.js';
|
|
17
|
-
import { Audit } from '../audit/index.js';
|
|
18
17
|
import { Entitlement } from '../entitlement/index.js';
|
|
19
18
|
|
|
20
19
|
/**
|
|
@@ -47,6 +46,11 @@ class Configuration extends BaseModel {
|
|
|
47
46
|
FORTNIGHTLY_SUNDAY: 'fortnightly-sunday',
|
|
48
47
|
MONTHLY: 'monthly',
|
|
49
48
|
};
|
|
49
|
+
|
|
50
|
+
static AUDIT_NAME_REGEX = /^[a-z0-9-]+$/;
|
|
51
|
+
|
|
52
|
+
static AUDIT_NAME_MAX_LENGTH = 37;
|
|
53
|
+
|
|
50
54
|
// add your custom methods or overrides here
|
|
51
55
|
|
|
52
56
|
getHandler(type) {
|
|
@@ -253,23 +257,186 @@ class Configuration extends BaseModel {
|
|
|
253
257
|
this.updateHandlerOrgs(type, orgId, false);
|
|
254
258
|
}
|
|
255
259
|
|
|
260
|
+
/**
|
|
261
|
+
* Updates the queue URLs configuration by merging with existing queues.
|
|
262
|
+
* Only the specified queue URLs will be updated; others remain unchanged.
|
|
263
|
+
*
|
|
264
|
+
* @param {object} queues - Queue URLs to update (merged with existing)
|
|
265
|
+
* @throws {Error} If queues object is empty or invalid
|
|
266
|
+
*/
|
|
267
|
+
updateQueues(queues) {
|
|
268
|
+
if (!isNonEmptyObject(queues)) {
|
|
269
|
+
throw new Error('Queues configuration cannot be empty');
|
|
270
|
+
}
|
|
271
|
+
const existingQueues = this.getQueues() || {};
|
|
272
|
+
const mergedQueues = { ...existingQueues, ...queues };
|
|
273
|
+
this.setQueues(mergedQueues);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Updates a job's properties (interval, group).
|
|
278
|
+
*
|
|
279
|
+
* @param {string} type - The job type to update
|
|
280
|
+
* @param {object} properties - Properties to update (interval, group)
|
|
281
|
+
* @throws {Error} If job not found or properties are invalid
|
|
282
|
+
*/
|
|
283
|
+
updateJob(type, properties) {
|
|
284
|
+
const jobs = this.getJobs();
|
|
285
|
+
const jobIndex = jobs.findIndex((job) => job.type === type);
|
|
286
|
+
|
|
287
|
+
if (jobIndex === -1) {
|
|
288
|
+
throw new Error(`Job type "${type}" not found in configuration`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (properties.interval && !Object.values(Configuration.JOB_INTERVALS)
|
|
292
|
+
.includes(properties.interval)) {
|
|
293
|
+
throw new Error(`Invalid interval "${properties.interval}". Must be one of: ${Object.values(Configuration.JOB_INTERVALS).join(', ')}`);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (properties.group && !Object.values(Configuration.JOB_GROUPS).includes(properties.group)) {
|
|
297
|
+
throw new Error(`Invalid group "${properties.group}". Must be one of: ${Object.values(Configuration.JOB_GROUPS).join(', ')}`);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
jobs[jobIndex] = { ...jobs[jobIndex], ...properties };
|
|
301
|
+
this.setJobs(jobs);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Updates a handler's properties.
|
|
306
|
+
*
|
|
307
|
+
* @param {string} type - The handler type to update
|
|
308
|
+
* @param {object} properties - Properties to update
|
|
309
|
+
* @throws {Error} If handler not found or properties are invalid
|
|
310
|
+
*/
|
|
311
|
+
updateHandlerProperties(type, properties) {
|
|
312
|
+
const handlers = this.getHandlers();
|
|
313
|
+
if (!handlers[type]) {
|
|
314
|
+
throw new Error(`Handler "${type}" not found in configuration`);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (properties.productCodes !== undefined) {
|
|
318
|
+
if (!isNonEmptyArray(properties.productCodes)) {
|
|
319
|
+
throw new Error('productCodes must be a non-empty array');
|
|
320
|
+
}
|
|
321
|
+
const validProductCodes = Object.values(Entitlement.PRODUCT_CODES);
|
|
322
|
+
if (!properties.productCodes.every((pc) => validProductCodes.includes(pc))) {
|
|
323
|
+
throw new Error('Invalid product codes provided');
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (isNonEmptyArray(properties.dependencies)) {
|
|
328
|
+
for (const dep of properties.dependencies) {
|
|
329
|
+
if (!handlers[dep.handler]) {
|
|
330
|
+
throw new Error(`Dependency handler "${dep.handler}" does not exist in configuration`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (properties.movingAvgThreshold !== undefined && properties.movingAvgThreshold < 1) {
|
|
336
|
+
throw new Error('movingAvgThreshold must be greater than or equal to 1');
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (properties.percentageChangeThreshold !== undefined && properties
|
|
340
|
+
.percentageChangeThreshold < 1) {
|
|
341
|
+
throw new Error('percentageChangeThreshold must be greater than or equal to 1');
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
handlers[type] = { ...handlers[type], ...properties };
|
|
345
|
+
this.setHandlers(handlers);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Updates the configuration by merging changes into existing sections.
|
|
350
|
+
* This is a flexible update method that allows updating one or more sections at once.
|
|
351
|
+
* Changes are merged, not replaced - existing data is preserved.
|
|
352
|
+
*
|
|
353
|
+
* @param {object} data - Configuration data to update
|
|
354
|
+
* @param {object} [data.handlers] - Handlers to merge (adds new, updates existing)
|
|
355
|
+
* @param {Array} [data.jobs] - Jobs to merge (updates matching jobs by type)
|
|
356
|
+
* @param {object} [data.queues] - Queues to merge (updates specific queue URLs)
|
|
357
|
+
* @throws {Error} If validation fails
|
|
358
|
+
*/
|
|
359
|
+
updateConfiguration(data) {
|
|
360
|
+
if (!isNonEmptyObject(data)) {
|
|
361
|
+
throw new Error('Configuration data cannot be empty');
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (data.handlers !== undefined) {
|
|
365
|
+
if (!isNonEmptyObject(data.handlers)) {
|
|
366
|
+
throw new Error('Handlers must be a non-empty object if provided');
|
|
367
|
+
}
|
|
368
|
+
const existingHandlers = this.getHandlers() || {};
|
|
369
|
+
const mergedHandlers = { ...existingHandlers };
|
|
370
|
+
|
|
371
|
+
Object.keys(data.handlers).forEach((handlerType) => {
|
|
372
|
+
mergedHandlers[handlerType] = {
|
|
373
|
+
...existingHandlers[handlerType],
|
|
374
|
+
...data.handlers[handlerType],
|
|
375
|
+
};
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
this.setHandlers(mergedHandlers);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (data.jobs !== undefined) {
|
|
382
|
+
if (!isNonEmptyArray(data.jobs)) {
|
|
383
|
+
throw new Error('Jobs must be a non-empty array if provided');
|
|
384
|
+
}
|
|
385
|
+
const existingJobs = this.getJobs() || [];
|
|
386
|
+
const mergedJobs = [...existingJobs];
|
|
387
|
+
|
|
388
|
+
data.jobs.forEach((newJob) => {
|
|
389
|
+
const existingIndex = mergedJobs.findIndex(
|
|
390
|
+
(job) => job.type === newJob.type && job.group === newJob.group,
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
if (existingIndex !== -1) {
|
|
394
|
+
mergedJobs[existingIndex] = { ...mergedJobs[existingIndex], ...newJob };
|
|
395
|
+
} else {
|
|
396
|
+
mergedJobs.push(newJob);
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
this.setJobs(mergedJobs);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (data.queues !== undefined) {
|
|
404
|
+
if (!isNonEmptyObject(data.queues)) {
|
|
405
|
+
throw new Error('Queues must be a non-empty object if provided');
|
|
406
|
+
}
|
|
407
|
+
const existingQueues = this.getQueues() || {};
|
|
408
|
+
const mergedQueues = { ...existingQueues, ...data.queues };
|
|
409
|
+
|
|
410
|
+
this.setQueues(mergedQueues);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
256
414
|
registerAudit(
|
|
257
415
|
type,
|
|
258
416
|
enabledByDefault = false,
|
|
259
417
|
interval = Configuration.JOB_INTERVALS.NEVER,
|
|
260
418
|
productCodes = [],
|
|
261
419
|
) {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
420
|
+
if (!type || typeof type !== 'string' || type.trim() === '') {
|
|
421
|
+
throw new Error('Audit type must be a non-empty string');
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (type.length > Configuration.AUDIT_NAME_MAX_LENGTH) {
|
|
425
|
+
throw new Error(`Audit type must not exceed ${Configuration.AUDIT_NAME_MAX_LENGTH} characters`);
|
|
426
|
+
}
|
|
427
|
+
if (!Configuration.AUDIT_NAME_REGEX.test(type)) {
|
|
428
|
+
throw new Error('Audit type can only contain lowercase letters, numbers, and hyphens');
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const handlers = this.getHandlers();
|
|
432
|
+
if (handlers && handlers[type]) {
|
|
433
|
+
throw new Error(`Audit type "${type}" is already registered`);
|
|
265
434
|
}
|
|
266
435
|
|
|
267
|
-
// Validate job interval
|
|
268
436
|
if (!Object.values(Configuration.JOB_INTERVALS).includes(interval)) {
|
|
269
437
|
throw new Error(`Invalid interval ${interval}`);
|
|
270
438
|
}
|
|
271
439
|
|
|
272
|
-
// Validate product codes
|
|
273
440
|
if (!isNonEmptyArray(productCodes)) {
|
|
274
441
|
throw new Error('No product codes provided');
|
|
275
442
|
}
|
|
@@ -277,26 +444,22 @@ class Configuration extends BaseModel {
|
|
|
277
444
|
throw new Error('Invalid product codes provided');
|
|
278
445
|
}
|
|
279
446
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
this.setHandlers(handlers);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// Add to jobs if not already registered
|
|
447
|
+
const updatedHandlers = handlers || {};
|
|
448
|
+
updatedHandlers[type] = {
|
|
449
|
+
enabledByDefault,
|
|
450
|
+
enabled: {
|
|
451
|
+
sites: [],
|
|
452
|
+
orgs: [],
|
|
453
|
+
},
|
|
454
|
+
disabled: {
|
|
455
|
+
sites: [],
|
|
456
|
+
orgs: [],
|
|
457
|
+
},
|
|
458
|
+
dependencies: [],
|
|
459
|
+
productCodes,
|
|
460
|
+
};
|
|
461
|
+
this.setHandlers(updatedHandlers);
|
|
462
|
+
|
|
300
463
|
const jobs = this.getJobs();
|
|
301
464
|
const exists = jobs.find((job) => job.group === 'audits' && job.type === type);
|
|
302
465
|
if (!exists) {
|
|
@@ -310,19 +473,18 @@ class Configuration extends BaseModel {
|
|
|
310
473
|
}
|
|
311
474
|
|
|
312
475
|
unregisterAudit(type) {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
throw new Error(`Audit type ${type} is not a valid audit type in the data model`);
|
|
476
|
+
if (!type || typeof type !== 'string' || type.trim() === '') {
|
|
477
|
+
throw new Error('Audit type must be a non-empty string');
|
|
316
478
|
}
|
|
317
479
|
|
|
318
|
-
// Remove from handlers
|
|
319
480
|
const handlers = this.getHandlers();
|
|
320
|
-
if (handlers[type]) {
|
|
321
|
-
|
|
322
|
-
this.setHandlers(handlers);
|
|
481
|
+
if (!handlers || !handlers[type]) {
|
|
482
|
+
throw new Error(`Audit type "${type}" is not registered`);
|
|
323
483
|
}
|
|
324
484
|
|
|
325
|
-
|
|
485
|
+
delete handlers[type];
|
|
486
|
+
this.setHandlers(handlers);
|
|
487
|
+
|
|
326
488
|
const jobs = this.getJobs();
|
|
327
489
|
const jobIndex = jobs.findIndex((job) => job.group === 'audits' && job.type === type);
|
|
328
490
|
if (jobIndex !== -1) {
|
|
@@ -40,7 +40,7 @@ const handlerSchema = Joi.object().pattern(Joi.string(), Joi.object(
|
|
|
40
40
|
actions: Joi.array().items(Joi.string()),
|
|
41
41
|
},
|
|
42
42
|
)),
|
|
43
|
-
productCodes: Joi.array().items(Joi.string()),
|
|
43
|
+
productCodes: Joi.array().items(Joi.string()).min(1).required(),
|
|
44
44
|
},
|
|
45
45
|
)).unknown(true);
|
|
46
46
|
|
|
@@ -12,8 +12,10 @@
|
|
|
12
12
|
|
|
13
13
|
import Configuration from './configuration.model.js';
|
|
14
14
|
import ConfigurationCollection from './configuration.collection.js';
|
|
15
|
+
import { checkConfiguration } from './configuration.schema.js';
|
|
15
16
|
|
|
16
17
|
export {
|
|
17
18
|
Configuration,
|
|
18
19
|
ConfigurationCollection,
|
|
20
|
+
checkConfiguration,
|
|
19
21
|
};
|