rads-db 3.0.22 → 3.0.24
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/dist/index.cjs +56 -158
- package/dist/index.d.ts +8 -1
- package/dist/index.mjs +54 -159
- package/features/eventSourcing.cjs +118 -0
- package/features/eventSourcing.d.ts +2 -0
- package/features/eventSourcing.mjs +113 -0
- package/integrations/restEndpoints.cjs +1 -1
- package/integrations/restEndpoints.mjs +2 -2
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -463,163 +463,6 @@ async function handleEffectsAfterPut(context, docs, beforePutResults, ctx) {
|
|
|
463
463
|
);
|
|
464
464
|
}
|
|
465
465
|
|
|
466
|
-
const mergeFn = createMerge__default({
|
|
467
|
-
mergeArray(options) {
|
|
468
|
-
const clone = options.clone;
|
|
469
|
-
return function(target, source) {
|
|
470
|
-
return clone(source);
|
|
471
|
-
};
|
|
472
|
-
}
|
|
473
|
-
});
|
|
474
|
-
function merge(target, source) {
|
|
475
|
-
cleanUndefined(source);
|
|
476
|
-
const result = mergeFn(target, source);
|
|
477
|
-
return result;
|
|
478
|
-
}
|
|
479
|
-
function cleanUndefined(obj) {
|
|
480
|
-
if (!obj || !___default.isPlainObject(obj))
|
|
481
|
-
return;
|
|
482
|
-
cleanUndefinedInner(obj);
|
|
483
|
-
}
|
|
484
|
-
function cleanUndefinedInner(obj) {
|
|
485
|
-
for (const key in obj) {
|
|
486
|
-
if (obj[key] === void 0)
|
|
487
|
-
delete obj[key];
|
|
488
|
-
if (___default.isPlainObject(obj[key]))
|
|
489
|
-
cleanUndefinedInner(obj[key]);
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
function diff(object, base, includeDenormFields = false) {
|
|
494
|
-
if (!base)
|
|
495
|
-
return object;
|
|
496
|
-
return ___default.transform(object, (result, value, key) => {
|
|
497
|
-
const oldValue = base[key];
|
|
498
|
-
if (!___default.isEqual(value, oldValue)) {
|
|
499
|
-
if (mustUseDeepDiff(value, oldValue)) {
|
|
500
|
-
const isRelationField = (value.id || oldValue.id) && key !== "change";
|
|
501
|
-
if (isRelationField && !includeDenormFields) {
|
|
502
|
-
if (value.id !== oldValue.id)
|
|
503
|
-
result[key] = value;
|
|
504
|
-
} else {
|
|
505
|
-
const subDiff = diff(value, oldValue, includeDenormFields);
|
|
506
|
-
if (!___default.isEmpty(subDiff))
|
|
507
|
-
result[key] = subDiff;
|
|
508
|
-
}
|
|
509
|
-
} else {
|
|
510
|
-
result[key] = value;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
});
|
|
514
|
-
}
|
|
515
|
-
function mustUseDeepDiff(value, oldValue) {
|
|
516
|
-
return ___default.isObject(value) && ___default.isObject(oldValue) && !___default.isArray(value) && !___default.isArray(oldValue);
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
function verifyEventSourcingSetup(schema, effects) {
|
|
520
|
-
for (const entityName in schema) {
|
|
521
|
-
if (!schema[entityName].decorators.entity)
|
|
522
|
-
continue;
|
|
523
|
-
if (schema[entityName].decorators.precomputed?.preset !== "eventSourcing")
|
|
524
|
-
continue;
|
|
525
|
-
const eventEntityName = `${entityName}Event`;
|
|
526
|
-
const aggregateRelationField = ___default.lowerFirst(entityName);
|
|
527
|
-
validateEventSourcingSetup(schema, entityName, eventEntityName, aggregateRelationField);
|
|
528
|
-
effects[eventEntityName].push({
|
|
529
|
-
async beforePut(context, docs, ctx) {
|
|
530
|
-
const docsByAggId = {};
|
|
531
|
-
const docsById = {};
|
|
532
|
-
for (const d of docs) {
|
|
533
|
-
if (!d.doc.id || docsById[d.doc.id])
|
|
534
|
-
continue;
|
|
535
|
-
docsById[d.doc.id] = d.doc;
|
|
536
|
-
const aggId = d.doc[aggregateRelationField].id;
|
|
537
|
-
if (!aggId)
|
|
538
|
-
throw new Error(`Missing ${entityName}.${aggregateRelationField}.id`);
|
|
539
|
-
if (d.oldDoc && aggId !== d.oldDoc[aggregateRelationField].id) {
|
|
540
|
-
throw new Error(`Field ${entityName}.${aggregateRelationField}.id cannot be changed`);
|
|
541
|
-
}
|
|
542
|
-
if (!docsByAggId[aggId])
|
|
543
|
-
docsByAggId[aggId] = [];
|
|
544
|
-
docsByAggId[aggId].push(d.doc);
|
|
545
|
-
}
|
|
546
|
-
const existingEvents = await context.drivers[eventEntityName].getAll(
|
|
547
|
-
{
|
|
548
|
-
where: {
|
|
549
|
-
[aggregateRelationField]: { id_in: ___default.keys(docsByAggId) },
|
|
550
|
-
_not: { id_in: docs.map((d) => d.doc.id) }
|
|
551
|
-
}
|
|
552
|
-
},
|
|
553
|
-
ctx
|
|
554
|
-
);
|
|
555
|
-
const existingEventsByAggId = ___default.groupBy(existingEvents, (ev) => ev[aggregateRelationField].id);
|
|
556
|
-
const existingAggregates = await context.drivers[entityName].getAll(
|
|
557
|
-
{ where: { id_in: ___default.keys(docsByAggId) } },
|
|
558
|
-
ctx
|
|
559
|
-
);
|
|
560
|
-
const existingAggregatesById = ___default.keyBy(existingAggregates, "id");
|
|
561
|
-
const result = [];
|
|
562
|
-
for (const aggId in docsByAggId) {
|
|
563
|
-
const events = ___default.orderBy([...docsByAggId[aggId], ...existingEventsByAggId[aggId] || []], ["date"], "asc");
|
|
564
|
-
if (events[0].type !== "creation")
|
|
565
|
-
throw new Error(`First event must have type = "creation". (type: ${events[0].type}, id: ${events[0].id})`);
|
|
566
|
-
if (events.slice(1).some((ev) => ev.type === "creation")) {
|
|
567
|
-
throw new Error(`Only first event may have type = "creation"`);
|
|
568
|
-
}
|
|
569
|
-
let aggDoc = { id: aggId };
|
|
570
|
-
for (const ev of events) {
|
|
571
|
-
const newAggDoc = context.validators[entityName](merge(aggDoc, ev.change));
|
|
572
|
-
ev.change = diff(newAggDoc, aggDoc);
|
|
573
|
-
aggDoc = newAggDoc;
|
|
574
|
-
}
|
|
575
|
-
result.push({ aggDoc, events });
|
|
576
|
-
}
|
|
577
|
-
await handlePrecomputed(
|
|
578
|
-
{ ...context, typeName: entityName },
|
|
579
|
-
result.map((v) => ({ doc: v.aggDoc, events: v.events, oldDoc: existingAggregatesById[v.aggDoc.id] })),
|
|
580
|
-
ctx
|
|
581
|
-
);
|
|
582
|
-
return result;
|
|
583
|
-
},
|
|
584
|
-
async afterPut(context, docs, beforePutResult, ctx) {
|
|
585
|
-
const aggs = beforePutResult.map((d) => d.aggDoc);
|
|
586
|
-
await context.drivers[entityName].putMany(aggs, ctx);
|
|
587
|
-
}
|
|
588
|
-
});
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
function validateEventSourcingSetup(schema, entityName, eventEntityName, aggregateRelationField) {
|
|
592
|
-
const eventEntity = schema[eventEntityName];
|
|
593
|
-
if (!eventEntity)
|
|
594
|
-
throw new Error(
|
|
595
|
-
`You must create entity with name "${eventEntityName}" to use eventSourcing feature with "${entityName}"`
|
|
596
|
-
);
|
|
597
|
-
if (!eventEntity.decorators.entity)
|
|
598
|
-
throw new Error(`"${eventEntityName}" must have "@entity()" decorator.`);
|
|
599
|
-
const expectedFields = {
|
|
600
|
-
id: "string",
|
|
601
|
-
type: "enum",
|
|
602
|
-
change: entityName,
|
|
603
|
-
[aggregateRelationField]: entityName,
|
|
604
|
-
date: "string"
|
|
605
|
-
};
|
|
606
|
-
for (const f in expectedFields) {
|
|
607
|
-
const field = eventEntity.fields?.[f];
|
|
608
|
-
if (!field)
|
|
609
|
-
throw new Error(`"${eventEntityName}" must have field "${f}"`);
|
|
610
|
-
if (expectedFields[f] === "enum") {
|
|
611
|
-
if (!schema[field.type]?.enumValues?.creation)
|
|
612
|
-
throw new Error(`Field "${eventEntityName}.${f}" must be enum that accepts value 'creation'`);
|
|
613
|
-
} else {
|
|
614
|
-
if (field.type !== expectedFields[f])
|
|
615
|
-
throw new Error(`"${eventEntityName}.${f}" must have type "${expectedFields[f]}"`);
|
|
616
|
-
if (f === "change" && !field.isChange) {
|
|
617
|
-
throw new Error(`"${eventEntityName}.${f}" must have type "Change<${expectedFields[f]}>"`);
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
|
|
623
466
|
function verifyRelationsSetup(schema, effects) {
|
|
624
467
|
for (const entityName in schema) {
|
|
625
468
|
const entity = schema[entityName];
|
|
@@ -709,6 +552,59 @@ function cleanUndefinedAndNull(obj) {
|
|
|
709
552
|
}
|
|
710
553
|
}
|
|
711
554
|
|
|
555
|
+
const mergeFn = createMerge__default({
|
|
556
|
+
mergeArray(options) {
|
|
557
|
+
const clone = options.clone;
|
|
558
|
+
return function(target, source) {
|
|
559
|
+
return clone(source);
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
function merge(target, source) {
|
|
564
|
+
cleanUndefined(source);
|
|
565
|
+
const result = mergeFn(target, source);
|
|
566
|
+
return result;
|
|
567
|
+
}
|
|
568
|
+
function cleanUndefined(obj) {
|
|
569
|
+
if (!obj || !___default.isPlainObject(obj))
|
|
570
|
+
return;
|
|
571
|
+
cleanUndefinedInner(obj);
|
|
572
|
+
}
|
|
573
|
+
function cleanUndefinedInner(obj) {
|
|
574
|
+
for (const key in obj) {
|
|
575
|
+
if (obj[key] === void 0)
|
|
576
|
+
delete obj[key];
|
|
577
|
+
if (___default.isPlainObject(obj[key]))
|
|
578
|
+
cleanUndefinedInner(obj[key]);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function diff(object, base, includeDenormFields = false) {
|
|
583
|
+
if (!base)
|
|
584
|
+
return object;
|
|
585
|
+
return ___default.transform(object, (result, value, key) => {
|
|
586
|
+
const oldValue = base[key];
|
|
587
|
+
if (!___default.isEqual(value, oldValue)) {
|
|
588
|
+
if (mustUseDeepDiff(value, oldValue)) {
|
|
589
|
+
const isRelationField = (value.id || oldValue.id) && key !== "change";
|
|
590
|
+
if (isRelationField && !includeDenormFields) {
|
|
591
|
+
if (value.id !== oldValue.id)
|
|
592
|
+
result[key] = value;
|
|
593
|
+
} else {
|
|
594
|
+
const subDiff = diff(value, oldValue, includeDenormFields);
|
|
595
|
+
if (!___default.isEmpty(subDiff))
|
|
596
|
+
result[key] = subDiff;
|
|
597
|
+
}
|
|
598
|
+
} else {
|
|
599
|
+
result[key] = value;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
function mustUseDeepDiff(value, oldValue) {
|
|
605
|
+
return ___default.isObject(value) && ___default.isObject(oldValue) && !___default.isArray(value) && !___default.isArray(oldValue);
|
|
606
|
+
}
|
|
607
|
+
|
|
712
608
|
function getRadsDbMethods(key, computedContext, driverInstance) {
|
|
713
609
|
const { schema, options, validators } = computedContext;
|
|
714
610
|
const mustCleanNulls = driverInstance.driverName !== "restApi";
|
|
@@ -1007,7 +903,6 @@ function generateMethods(schema, validators, options) {
|
|
|
1007
903
|
if (!opts.noComputed) {
|
|
1008
904
|
verifyComputedPresense(schema, opts.computed, effects);
|
|
1009
905
|
}
|
|
1010
|
-
verifyEventSourcingSetup(schema, effects);
|
|
1011
906
|
verifyRelationsSetup(schema, effects);
|
|
1012
907
|
const computedContextGlobal = {
|
|
1013
908
|
schema,
|
|
@@ -1253,9 +1148,12 @@ function createRadsDbClient(args) {
|
|
|
1253
1148
|
exports.computed = computed;
|
|
1254
1149
|
exports.createRadsDb = createRadsDb;
|
|
1255
1150
|
exports.createRadsDbClient = createRadsDbClient;
|
|
1151
|
+
exports.diff = diff;
|
|
1256
1152
|
exports.entity = entity;
|
|
1257
1153
|
exports.field = field;
|
|
1258
1154
|
exports.getDriverInstance = getDriverInstance;
|
|
1155
|
+
exports.handlePrecomputed = handlePrecomputed;
|
|
1156
|
+
exports.merge = merge;
|
|
1259
1157
|
exports.precomputed = precomputed;
|
|
1260
1158
|
exports.ui = ui;
|
|
1261
1159
|
exports.validate = validate;
|
package/dist/index.d.ts
CHANGED
|
@@ -386,6 +386,13 @@ declare function computed(meta?: ComputedDecoratorArgs): (a: any, b?: ClassField
|
|
|
386
386
|
|
|
387
387
|
declare function getDriverInstance(schema: Schema, key: string, driverConstructor: DriverConstructor, driverInstances: Record<string, Driver>): Driver;
|
|
388
388
|
|
|
389
|
+
declare function handlePrecomputed(context: ComputedContext, docs: any[], ctx: RadsRequestContext): Promise<void>;
|
|
390
|
+
|
|
391
|
+
/** Performs immutable deep merge */
|
|
392
|
+
declare function merge<T extends Record<string, any>>(target: T, source: any): T;
|
|
393
|
+
|
|
394
|
+
declare function diff(object: any, base: any, includeDenormFields?: boolean): any;
|
|
395
|
+
|
|
389
396
|
/**
|
|
390
397
|
* Creates instance of rads db - object that provides access to all entities.
|
|
391
398
|
* Intended to be used on the backend. On the client you may want to opt for "createRadsDbClient" instead.
|
|
@@ -398,4 +405,4 @@ declare function createRadsDb(args?: CreateRadsDbArgs): RadsDb;
|
|
|
398
405
|
*/
|
|
399
406
|
declare function createRadsDbClient(args?: CreateRadsDbClientArgs): RadsDb;
|
|
400
407
|
|
|
401
|
-
export { Change, ComputedContext, ComputedContextGlobal, ComputedDecoratorArgs, CreateRadsArgsDrivers, CreateRadsDbArgs, CreateRadsDbArgsNormalized, CreateRadsDbClientArgs, DeepPartial, Driver, DriverConstructor, EntityDecoratorArgs, EntityMethods, EnumDefinition, FieldDecoratorArgs, FieldDefinition, FileSystemNode, FileUploadArgs, FileUploadDriver, FileUploadResult, GenerateClientNormalizedOptions, GenerateClientOptions, GetAggArgs, GetAggArgsAgg, GetAggArgsAny, GetAggResponse, GetArgs, GetArgsAny, GetArgsInclude, GetManyArgs, GetManyArgsAny, GetManyResponse, GetResponse, GetResponseInclude, GetResponseIncludeSelect, GetResponseNoInclude, GetRestRoutesArgs, GetRestRoutesOptions, GetRestRoutesResponse, MinimalDriver, PutArgs, PutEffect, RadsFeature, RadsRequestContext, RadsUiSlotDefinition, RadsUiSlotName, RadsVitePluginOptions, Relation, RequiredFields, RestDriverOptions, RestFileUploadDriverOptions, Schema, SchemaValidators, TypeDefinition, UiDecoratorArgs, UiFieldDecoratorArgs, ValidateEntityDecoratorArgs, ValidateFieldDecoratorArgs, ValidateStringDecoratorArgs, VerifyManyArgs, VerifyManyArgsAny, VerifyManyResponse, computed, createRadsDb, createRadsDbClient, entity, field, getDriverInstance, precomputed, ui, validate };
|
|
408
|
+
export { Change, ComputedContext, ComputedContextGlobal, ComputedDecoratorArgs, CreateRadsArgsDrivers, CreateRadsDbArgs, CreateRadsDbArgsNormalized, CreateRadsDbClientArgs, DeepPartial, Driver, DriverConstructor, EntityDecoratorArgs, EntityMethods, EnumDefinition, FieldDecoratorArgs, FieldDefinition, FileSystemNode, FileUploadArgs, FileUploadDriver, FileUploadResult, GenerateClientNormalizedOptions, GenerateClientOptions, GetAggArgs, GetAggArgsAgg, GetAggArgsAny, GetAggResponse, GetArgs, GetArgsAny, GetArgsInclude, GetManyArgs, GetManyArgsAny, GetManyResponse, GetResponse, GetResponseInclude, GetResponseIncludeSelect, GetResponseNoInclude, GetRestRoutesArgs, GetRestRoutesOptions, GetRestRoutesResponse, MinimalDriver, PutArgs, PutEffect, RadsFeature, RadsRequestContext, RadsUiSlotDefinition, RadsUiSlotName, RadsVitePluginOptions, Relation, RequiredFields, RestDriverOptions, RestFileUploadDriverOptions, Schema, SchemaValidators, TypeDefinition, UiDecoratorArgs, UiFieldDecoratorArgs, ValidateEntityDecoratorArgs, ValidateFieldDecoratorArgs, ValidateStringDecoratorArgs, VerifyManyArgs, VerifyManyArgsAny, VerifyManyResponse, computed, createRadsDb, createRadsDbClient, diff, entity, field, getDriverInstance, handlePrecomputed, merge, precomputed, ui, validate };
|
package/dist/index.mjs
CHANGED
|
@@ -455,163 +455,6 @@ async function handleEffectsAfterPut(context, docs, beforePutResults, ctx) {
|
|
|
455
455
|
);
|
|
456
456
|
}
|
|
457
457
|
|
|
458
|
-
const mergeFn = createMerge({
|
|
459
|
-
mergeArray(options) {
|
|
460
|
-
const clone = options.clone;
|
|
461
|
-
return function(target, source) {
|
|
462
|
-
return clone(source);
|
|
463
|
-
};
|
|
464
|
-
}
|
|
465
|
-
});
|
|
466
|
-
function merge(target, source) {
|
|
467
|
-
cleanUndefined(source);
|
|
468
|
-
const result = mergeFn(target, source);
|
|
469
|
-
return result;
|
|
470
|
-
}
|
|
471
|
-
function cleanUndefined(obj) {
|
|
472
|
-
if (!obj || !_.isPlainObject(obj))
|
|
473
|
-
return;
|
|
474
|
-
cleanUndefinedInner(obj);
|
|
475
|
-
}
|
|
476
|
-
function cleanUndefinedInner(obj) {
|
|
477
|
-
for (const key in obj) {
|
|
478
|
-
if (obj[key] === void 0)
|
|
479
|
-
delete obj[key];
|
|
480
|
-
if (_.isPlainObject(obj[key]))
|
|
481
|
-
cleanUndefinedInner(obj[key]);
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
function diff(object, base, includeDenormFields = false) {
|
|
486
|
-
if (!base)
|
|
487
|
-
return object;
|
|
488
|
-
return _.transform(object, (result, value, key) => {
|
|
489
|
-
const oldValue = base[key];
|
|
490
|
-
if (!_.isEqual(value, oldValue)) {
|
|
491
|
-
if (mustUseDeepDiff(value, oldValue)) {
|
|
492
|
-
const isRelationField = (value.id || oldValue.id) && key !== "change";
|
|
493
|
-
if (isRelationField && !includeDenormFields) {
|
|
494
|
-
if (value.id !== oldValue.id)
|
|
495
|
-
result[key] = value;
|
|
496
|
-
} else {
|
|
497
|
-
const subDiff = diff(value, oldValue, includeDenormFields);
|
|
498
|
-
if (!_.isEmpty(subDiff))
|
|
499
|
-
result[key] = subDiff;
|
|
500
|
-
}
|
|
501
|
-
} else {
|
|
502
|
-
result[key] = value;
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
});
|
|
506
|
-
}
|
|
507
|
-
function mustUseDeepDiff(value, oldValue) {
|
|
508
|
-
return _.isObject(value) && _.isObject(oldValue) && !_.isArray(value) && !_.isArray(oldValue);
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
function verifyEventSourcingSetup(schema, effects) {
|
|
512
|
-
for (const entityName in schema) {
|
|
513
|
-
if (!schema[entityName].decorators.entity)
|
|
514
|
-
continue;
|
|
515
|
-
if (schema[entityName].decorators.precomputed?.preset !== "eventSourcing")
|
|
516
|
-
continue;
|
|
517
|
-
const eventEntityName = `${entityName}Event`;
|
|
518
|
-
const aggregateRelationField = _.lowerFirst(entityName);
|
|
519
|
-
validateEventSourcingSetup(schema, entityName, eventEntityName, aggregateRelationField);
|
|
520
|
-
effects[eventEntityName].push({
|
|
521
|
-
async beforePut(context, docs, ctx) {
|
|
522
|
-
const docsByAggId = {};
|
|
523
|
-
const docsById = {};
|
|
524
|
-
for (const d of docs) {
|
|
525
|
-
if (!d.doc.id || docsById[d.doc.id])
|
|
526
|
-
continue;
|
|
527
|
-
docsById[d.doc.id] = d.doc;
|
|
528
|
-
const aggId = d.doc[aggregateRelationField].id;
|
|
529
|
-
if (!aggId)
|
|
530
|
-
throw new Error(`Missing ${entityName}.${aggregateRelationField}.id`);
|
|
531
|
-
if (d.oldDoc && aggId !== d.oldDoc[aggregateRelationField].id) {
|
|
532
|
-
throw new Error(`Field ${entityName}.${aggregateRelationField}.id cannot be changed`);
|
|
533
|
-
}
|
|
534
|
-
if (!docsByAggId[aggId])
|
|
535
|
-
docsByAggId[aggId] = [];
|
|
536
|
-
docsByAggId[aggId].push(d.doc);
|
|
537
|
-
}
|
|
538
|
-
const existingEvents = await context.drivers[eventEntityName].getAll(
|
|
539
|
-
{
|
|
540
|
-
where: {
|
|
541
|
-
[aggregateRelationField]: { id_in: _.keys(docsByAggId) },
|
|
542
|
-
_not: { id_in: docs.map((d) => d.doc.id) }
|
|
543
|
-
}
|
|
544
|
-
},
|
|
545
|
-
ctx
|
|
546
|
-
);
|
|
547
|
-
const existingEventsByAggId = _.groupBy(existingEvents, (ev) => ev[aggregateRelationField].id);
|
|
548
|
-
const existingAggregates = await context.drivers[entityName].getAll(
|
|
549
|
-
{ where: { id_in: _.keys(docsByAggId) } },
|
|
550
|
-
ctx
|
|
551
|
-
);
|
|
552
|
-
const existingAggregatesById = _.keyBy(existingAggregates, "id");
|
|
553
|
-
const result = [];
|
|
554
|
-
for (const aggId in docsByAggId) {
|
|
555
|
-
const events = _.orderBy([...docsByAggId[aggId], ...existingEventsByAggId[aggId] || []], ["date"], "asc");
|
|
556
|
-
if (events[0].type !== "creation")
|
|
557
|
-
throw new Error(`First event must have type = "creation". (type: ${events[0].type}, id: ${events[0].id})`);
|
|
558
|
-
if (events.slice(1).some((ev) => ev.type === "creation")) {
|
|
559
|
-
throw new Error(`Only first event may have type = "creation"`);
|
|
560
|
-
}
|
|
561
|
-
let aggDoc = { id: aggId };
|
|
562
|
-
for (const ev of events) {
|
|
563
|
-
const newAggDoc = context.validators[entityName](merge(aggDoc, ev.change));
|
|
564
|
-
ev.change = diff(newAggDoc, aggDoc);
|
|
565
|
-
aggDoc = newAggDoc;
|
|
566
|
-
}
|
|
567
|
-
result.push({ aggDoc, events });
|
|
568
|
-
}
|
|
569
|
-
await handlePrecomputed(
|
|
570
|
-
{ ...context, typeName: entityName },
|
|
571
|
-
result.map((v) => ({ doc: v.aggDoc, events: v.events, oldDoc: existingAggregatesById[v.aggDoc.id] })),
|
|
572
|
-
ctx
|
|
573
|
-
);
|
|
574
|
-
return result;
|
|
575
|
-
},
|
|
576
|
-
async afterPut(context, docs, beforePutResult, ctx) {
|
|
577
|
-
const aggs = beforePutResult.map((d) => d.aggDoc);
|
|
578
|
-
await context.drivers[entityName].putMany(aggs, ctx);
|
|
579
|
-
}
|
|
580
|
-
});
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
function validateEventSourcingSetup(schema, entityName, eventEntityName, aggregateRelationField) {
|
|
584
|
-
const eventEntity = schema[eventEntityName];
|
|
585
|
-
if (!eventEntity)
|
|
586
|
-
throw new Error(
|
|
587
|
-
`You must create entity with name "${eventEntityName}" to use eventSourcing feature with "${entityName}"`
|
|
588
|
-
);
|
|
589
|
-
if (!eventEntity.decorators.entity)
|
|
590
|
-
throw new Error(`"${eventEntityName}" must have "@entity()" decorator.`);
|
|
591
|
-
const expectedFields = {
|
|
592
|
-
id: "string",
|
|
593
|
-
type: "enum",
|
|
594
|
-
change: entityName,
|
|
595
|
-
[aggregateRelationField]: entityName,
|
|
596
|
-
date: "string"
|
|
597
|
-
};
|
|
598
|
-
for (const f in expectedFields) {
|
|
599
|
-
const field = eventEntity.fields?.[f];
|
|
600
|
-
if (!field)
|
|
601
|
-
throw new Error(`"${eventEntityName}" must have field "${f}"`);
|
|
602
|
-
if (expectedFields[f] === "enum") {
|
|
603
|
-
if (!schema[field.type]?.enumValues?.creation)
|
|
604
|
-
throw new Error(`Field "${eventEntityName}.${f}" must be enum that accepts value 'creation'`);
|
|
605
|
-
} else {
|
|
606
|
-
if (field.type !== expectedFields[f])
|
|
607
|
-
throw new Error(`"${eventEntityName}.${f}" must have type "${expectedFields[f]}"`);
|
|
608
|
-
if (f === "change" && !field.isChange) {
|
|
609
|
-
throw new Error(`"${eventEntityName}.${f}" must have type "Change<${expectedFields[f]}>"`);
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
|
|
615
458
|
function verifyRelationsSetup(schema, effects) {
|
|
616
459
|
for (const entityName in schema) {
|
|
617
460
|
const entity = schema[entityName];
|
|
@@ -701,6 +544,59 @@ function cleanUndefinedAndNull(obj) {
|
|
|
701
544
|
}
|
|
702
545
|
}
|
|
703
546
|
|
|
547
|
+
const mergeFn = createMerge({
|
|
548
|
+
mergeArray(options) {
|
|
549
|
+
const clone = options.clone;
|
|
550
|
+
return function(target, source) {
|
|
551
|
+
return clone(source);
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
function merge(target, source) {
|
|
556
|
+
cleanUndefined(source);
|
|
557
|
+
const result = mergeFn(target, source);
|
|
558
|
+
return result;
|
|
559
|
+
}
|
|
560
|
+
function cleanUndefined(obj) {
|
|
561
|
+
if (!obj || !_.isPlainObject(obj))
|
|
562
|
+
return;
|
|
563
|
+
cleanUndefinedInner(obj);
|
|
564
|
+
}
|
|
565
|
+
function cleanUndefinedInner(obj) {
|
|
566
|
+
for (const key in obj) {
|
|
567
|
+
if (obj[key] === void 0)
|
|
568
|
+
delete obj[key];
|
|
569
|
+
if (_.isPlainObject(obj[key]))
|
|
570
|
+
cleanUndefinedInner(obj[key]);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function diff(object, base, includeDenormFields = false) {
|
|
575
|
+
if (!base)
|
|
576
|
+
return object;
|
|
577
|
+
return _.transform(object, (result, value, key) => {
|
|
578
|
+
const oldValue = base[key];
|
|
579
|
+
if (!_.isEqual(value, oldValue)) {
|
|
580
|
+
if (mustUseDeepDiff(value, oldValue)) {
|
|
581
|
+
const isRelationField = (value.id || oldValue.id) && key !== "change";
|
|
582
|
+
if (isRelationField && !includeDenormFields) {
|
|
583
|
+
if (value.id !== oldValue.id)
|
|
584
|
+
result[key] = value;
|
|
585
|
+
} else {
|
|
586
|
+
const subDiff = diff(value, oldValue, includeDenormFields);
|
|
587
|
+
if (!_.isEmpty(subDiff))
|
|
588
|
+
result[key] = subDiff;
|
|
589
|
+
}
|
|
590
|
+
} else {
|
|
591
|
+
result[key] = value;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
function mustUseDeepDiff(value, oldValue) {
|
|
597
|
+
return _.isObject(value) && _.isObject(oldValue) && !_.isArray(value) && !_.isArray(oldValue);
|
|
598
|
+
}
|
|
599
|
+
|
|
704
600
|
function getRadsDbMethods(key, computedContext, driverInstance) {
|
|
705
601
|
const { schema, options, validators } = computedContext;
|
|
706
602
|
const mustCleanNulls = driverInstance.driverName !== "restApi";
|
|
@@ -999,7 +895,6 @@ function generateMethods(schema, validators, options) {
|
|
|
999
895
|
if (!opts.noComputed) {
|
|
1000
896
|
verifyComputedPresense(schema, opts.computed, effects);
|
|
1001
897
|
}
|
|
1002
|
-
verifyEventSourcingSetup(schema, effects);
|
|
1003
898
|
verifyRelationsSetup(schema, effects);
|
|
1004
899
|
const computedContextGlobal = {
|
|
1005
900
|
schema,
|
|
@@ -1242,4 +1137,4 @@ function createRadsDbClient(args) {
|
|
|
1242
1137
|
return generateMethods(s, validators, radsDbArgs);
|
|
1243
1138
|
}
|
|
1244
1139
|
|
|
1245
|
-
export { computed, createRadsDb, createRadsDbClient, entity, field, getDriverInstance, precomputed, ui, validate };
|
|
1140
|
+
export { computed, createRadsDb, createRadsDbClient, diff, entity, field, getDriverInstance, handlePrecomputed, merge, precomputed, ui, validate };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
module.exports = void 0;
|
|
7
|
+
var _lodash = _interopRequireDefault(require("lodash"));
|
|
8
|
+
var _radsDb = require("rads-db");
|
|
9
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
10
|
+
var _default = () => {
|
|
11
|
+
return {
|
|
12
|
+
name: "eventSourcing",
|
|
13
|
+
init(db, context) {
|
|
14
|
+
verifyEventSourcingSetup(context.schema, context.effects);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
module.exports = _default;
|
|
19
|
+
function verifyEventSourcingSetup(schema, effects) {
|
|
20
|
+
for (const entityName in schema) {
|
|
21
|
+
if (!schema[entityName].decorators.entity) continue;
|
|
22
|
+
if (schema[entityName].decorators.precomputed?.preset !== "eventSourcing") continue;
|
|
23
|
+
const eventEntityName = `${entityName}Event`;
|
|
24
|
+
const aggregateRelationField = _lodash.default.lowerFirst(entityName);
|
|
25
|
+
validateEventSourcingSetup(schema, entityName, eventEntityName, aggregateRelationField);
|
|
26
|
+
effects[eventEntityName].push({
|
|
27
|
+
async beforePut(context, docs, ctx) {
|
|
28
|
+
const docsByAggId = {};
|
|
29
|
+
const docsById = {};
|
|
30
|
+
for (const d of docs) {
|
|
31
|
+
if (!d.doc.id || docsById[d.doc.id]) continue;
|
|
32
|
+
docsById[d.doc.id] = d.doc;
|
|
33
|
+
const aggId = d.doc[aggregateRelationField].id;
|
|
34
|
+
if (!aggId) throw new Error(`Missing ${entityName}.${aggregateRelationField}.id`);
|
|
35
|
+
if (d.oldDoc && aggId !== d.oldDoc[aggregateRelationField].id) {
|
|
36
|
+
throw new Error(`Field ${entityName}.${aggregateRelationField}.id cannot be changed`);
|
|
37
|
+
}
|
|
38
|
+
if (!docsByAggId[aggId]) docsByAggId[aggId] = [];
|
|
39
|
+
docsByAggId[aggId].push(d.doc);
|
|
40
|
+
}
|
|
41
|
+
const existingEvents = await context.drivers[eventEntityName].getAll({
|
|
42
|
+
where: {
|
|
43
|
+
[aggregateRelationField]: {
|
|
44
|
+
id_in: _lodash.default.keys(docsByAggId)
|
|
45
|
+
},
|
|
46
|
+
_not: {
|
|
47
|
+
id_in: docs.map(d => d.doc.id)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}, ctx);
|
|
51
|
+
const existingEventsByAggId = _lodash.default.groupBy(existingEvents, ev => ev[aggregateRelationField].id);
|
|
52
|
+
const existingAggregates = await context.drivers[entityName].getAll({
|
|
53
|
+
where: {
|
|
54
|
+
id_in: _lodash.default.keys(docsByAggId)
|
|
55
|
+
}
|
|
56
|
+
}, ctx);
|
|
57
|
+
const existingAggregatesById = _lodash.default.keyBy(existingAggregates, "id");
|
|
58
|
+
const result = [];
|
|
59
|
+
for (const aggId in docsByAggId) {
|
|
60
|
+
const events = _lodash.default.orderBy([...docsByAggId[aggId], ...(existingEventsByAggId[aggId] || [])], ["date"], "asc");
|
|
61
|
+
if (events[0].type !== "creation") throw new Error(`First event must have type = "creation". (type: ${events[0].type}, id: ${events[0].id})`);
|
|
62
|
+
if (events.slice(1).some(ev => ev.type === "creation")) {
|
|
63
|
+
throw new Error(`Only first event may have type = "creation"`);
|
|
64
|
+
}
|
|
65
|
+
let aggDoc = {
|
|
66
|
+
id: aggId
|
|
67
|
+
};
|
|
68
|
+
for (const ev of events) {
|
|
69
|
+
const newAggDoc = context.validators[entityName]((0, _radsDb.merge)(aggDoc, ev.change));
|
|
70
|
+
ev.change = (0, _radsDb.diff)(newAggDoc, aggDoc);
|
|
71
|
+
aggDoc = newAggDoc;
|
|
72
|
+
}
|
|
73
|
+
result.push({
|
|
74
|
+
aggDoc,
|
|
75
|
+
events
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
await (0, _radsDb.handlePrecomputed)({
|
|
79
|
+
...context,
|
|
80
|
+
typeName: entityName
|
|
81
|
+
}, result.map(v => ({
|
|
82
|
+
doc: v.aggDoc,
|
|
83
|
+
events: v.events,
|
|
84
|
+
oldDoc: existingAggregatesById[v.aggDoc.id]
|
|
85
|
+
})), ctx);
|
|
86
|
+
return result;
|
|
87
|
+
},
|
|
88
|
+
async afterPut(context, docs, beforePutResult, ctx) {
|
|
89
|
+
const aggs = beforePutResult.map(d => d.aggDoc);
|
|
90
|
+
await context.drivers[entityName].putMany(aggs, ctx);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function validateEventSourcingSetup(schema, entityName, eventEntityName, aggregateRelationField) {
|
|
96
|
+
const eventEntity = schema[eventEntityName];
|
|
97
|
+
if (!eventEntity) throw new Error(`You must create entity with name "${eventEntityName}" to use eventSourcing feature with "${entityName}"`);
|
|
98
|
+
if (!eventEntity.decorators.entity) throw new Error(`"${eventEntityName}" must have "@entity()" decorator.`);
|
|
99
|
+
const expectedFields = {
|
|
100
|
+
id: "string",
|
|
101
|
+
type: "enum",
|
|
102
|
+
change: entityName,
|
|
103
|
+
[aggregateRelationField]: entityName,
|
|
104
|
+
date: "string"
|
|
105
|
+
};
|
|
106
|
+
for (const f in expectedFields) {
|
|
107
|
+
const field = eventEntity.fields?.[f];
|
|
108
|
+
if (!field) throw new Error(`"${eventEntityName}" must have field "${f}"`);
|
|
109
|
+
if (expectedFields[f] === "enum") {
|
|
110
|
+
if (!schema[field.type]?.enumValues?.creation) throw new Error(`Field "${eventEntityName}.${f}" must be enum that accepts value 'creation'`);
|
|
111
|
+
} else {
|
|
112
|
+
if (field.type !== expectedFields[f]) throw new Error(`"${eventEntityName}.${f}" must have type "${expectedFields[f]}"`);
|
|
113
|
+
if (f === "change" && !field.isChange) {
|
|
114
|
+
throw new Error(`"${eventEntityName}.${f}" must have type "Change<${expectedFields[f]}>"`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import _ from "lodash";
|
|
2
|
+
import { diff, handlePrecomputed, merge } from "rads-db";
|
|
3
|
+
export default () => {
|
|
4
|
+
return {
|
|
5
|
+
name: "eventSourcing",
|
|
6
|
+
init(db, context) {
|
|
7
|
+
verifyEventSourcingSetup(context.schema, context.effects);
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
function verifyEventSourcingSetup(schema, effects) {
|
|
12
|
+
for (const entityName in schema) {
|
|
13
|
+
if (!schema[entityName].decorators.entity)
|
|
14
|
+
continue;
|
|
15
|
+
if (schema[entityName].decorators.precomputed?.preset !== "eventSourcing")
|
|
16
|
+
continue;
|
|
17
|
+
const eventEntityName = `${entityName}Event`;
|
|
18
|
+
const aggregateRelationField = _.lowerFirst(entityName);
|
|
19
|
+
validateEventSourcingSetup(schema, entityName, eventEntityName, aggregateRelationField);
|
|
20
|
+
effects[eventEntityName].push({
|
|
21
|
+
async beforePut(context, docs, ctx) {
|
|
22
|
+
const docsByAggId = {};
|
|
23
|
+
const docsById = {};
|
|
24
|
+
for (const d of docs) {
|
|
25
|
+
if (!d.doc.id || docsById[d.doc.id])
|
|
26
|
+
continue;
|
|
27
|
+
docsById[d.doc.id] = d.doc;
|
|
28
|
+
const aggId = d.doc[aggregateRelationField].id;
|
|
29
|
+
if (!aggId)
|
|
30
|
+
throw new Error(`Missing ${entityName}.${aggregateRelationField}.id`);
|
|
31
|
+
if (d.oldDoc && aggId !== d.oldDoc[aggregateRelationField].id) {
|
|
32
|
+
throw new Error(`Field ${entityName}.${aggregateRelationField}.id cannot be changed`);
|
|
33
|
+
}
|
|
34
|
+
if (!docsByAggId[aggId])
|
|
35
|
+
docsByAggId[aggId] = [];
|
|
36
|
+
docsByAggId[aggId].push(d.doc);
|
|
37
|
+
}
|
|
38
|
+
const existingEvents = await context.drivers[eventEntityName].getAll(
|
|
39
|
+
{
|
|
40
|
+
where: {
|
|
41
|
+
[aggregateRelationField]: { id_in: _.keys(docsByAggId) },
|
|
42
|
+
_not: { id_in: docs.map((d) => d.doc.id) }
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
ctx
|
|
46
|
+
);
|
|
47
|
+
const existingEventsByAggId = _.groupBy(existingEvents, (ev) => ev[aggregateRelationField].id);
|
|
48
|
+
const existingAggregates = await context.drivers[entityName].getAll(
|
|
49
|
+
{ where: { id_in: _.keys(docsByAggId) } },
|
|
50
|
+
ctx
|
|
51
|
+
);
|
|
52
|
+
const existingAggregatesById = _.keyBy(existingAggregates, "id");
|
|
53
|
+
const result = [];
|
|
54
|
+
for (const aggId in docsByAggId) {
|
|
55
|
+
const events = _.orderBy([...docsByAggId[aggId], ...existingEventsByAggId[aggId] || []], ["date"], "asc");
|
|
56
|
+
if (events[0].type !== "creation")
|
|
57
|
+
throw new Error(`First event must have type = "creation". (type: ${events[0].type}, id: ${events[0].id})`);
|
|
58
|
+
if (events.slice(1).some((ev) => ev.type === "creation")) {
|
|
59
|
+
throw new Error(`Only first event may have type = "creation"`);
|
|
60
|
+
}
|
|
61
|
+
let aggDoc = { id: aggId };
|
|
62
|
+
for (const ev of events) {
|
|
63
|
+
const newAggDoc = context.validators[entityName](merge(aggDoc, ev.change));
|
|
64
|
+
ev.change = diff(newAggDoc, aggDoc);
|
|
65
|
+
aggDoc = newAggDoc;
|
|
66
|
+
}
|
|
67
|
+
result.push({ aggDoc, events });
|
|
68
|
+
}
|
|
69
|
+
await handlePrecomputed(
|
|
70
|
+
{ ...context, typeName: entityName },
|
|
71
|
+
result.map((v) => ({ doc: v.aggDoc, events: v.events, oldDoc: existingAggregatesById[v.aggDoc.id] })),
|
|
72
|
+
ctx
|
|
73
|
+
);
|
|
74
|
+
return result;
|
|
75
|
+
},
|
|
76
|
+
async afterPut(context, docs, beforePutResult, ctx) {
|
|
77
|
+
const aggs = beforePutResult.map((d) => d.aggDoc);
|
|
78
|
+
await context.drivers[entityName].putMany(aggs, ctx);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function validateEventSourcingSetup(schema, entityName, eventEntityName, aggregateRelationField) {
|
|
84
|
+
const eventEntity = schema[eventEntityName];
|
|
85
|
+
if (!eventEntity)
|
|
86
|
+
throw new Error(
|
|
87
|
+
`You must create entity with name "${eventEntityName}" to use eventSourcing feature with "${entityName}"`
|
|
88
|
+
);
|
|
89
|
+
if (!eventEntity.decorators.entity)
|
|
90
|
+
throw new Error(`"${eventEntityName}" must have "@entity()" decorator.`);
|
|
91
|
+
const expectedFields = {
|
|
92
|
+
id: "string",
|
|
93
|
+
type: "enum",
|
|
94
|
+
change: entityName,
|
|
95
|
+
[aggregateRelationField]: entityName,
|
|
96
|
+
date: "string"
|
|
97
|
+
};
|
|
98
|
+
for (const f in expectedFields) {
|
|
99
|
+
const field = eventEntity.fields?.[f];
|
|
100
|
+
if (!field)
|
|
101
|
+
throw new Error(`"${eventEntityName}" must have field "${f}"`);
|
|
102
|
+
if (expectedFields[f] === "enum") {
|
|
103
|
+
if (!schema[field.type]?.enumValues?.creation)
|
|
104
|
+
throw new Error(`Field "${eventEntityName}.${f}" must be enum that accepts value 'creation'`);
|
|
105
|
+
} else {
|
|
106
|
+
if (field.type !== expectedFields[f])
|
|
107
|
+
throw new Error(`"${eventEntityName}.${f}" must have type "${expectedFields[f]}"`);
|
|
108
|
+
if (f === "change" && !field.isChange) {
|
|
109
|
+
throw new Error(`"${eventEntityName}.${f}" must have type "Change<${expectedFields[f]}>"`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -11,6 +11,7 @@ var _lodash = _interopRequireDefault(require("lodash"));
|
|
|
11
11
|
var _restEndpointsDev = _interopRequireDefault(require("./restEndpointsDev.cjs"));
|
|
12
12
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
13
13
|
function getRadsUiUser(radsAuthHeader, apiKey) {
|
|
14
|
+
if (!radsAuthHeader) return void 0;
|
|
14
15
|
apiKey = apiKey || process.env.RadsApiKey;
|
|
15
16
|
if (!apiKey) {
|
|
16
17
|
throw (0, _h.createError)({
|
|
@@ -18,7 +19,6 @@ function getRadsUiUser(radsAuthHeader, apiKey) {
|
|
|
18
19
|
message: `Please, provide "apiKey" option to "getRadsUiUser" call.`
|
|
19
20
|
});
|
|
20
21
|
}
|
|
21
|
-
if (!radsAuthHeader) return void 0;
|
|
22
22
|
const [keyType, key, email, pretendAsUserId] = radsAuthHeader.split(" ");
|
|
23
23
|
if (!email || !keyType || !key || keyType !== "ApiKey") {
|
|
24
24
|
throw (0, _h.createError)({
|
|
@@ -2,12 +2,12 @@ import { createError } from "h3";
|
|
|
2
2
|
import _ from "lodash";
|
|
3
3
|
import restEndpointsDev from "./restEndpointsDev.mjs";
|
|
4
4
|
export function getRadsUiUser(radsAuthHeader, apiKey) {
|
|
5
|
+
if (!radsAuthHeader)
|
|
6
|
+
return void 0;
|
|
5
7
|
apiKey = apiKey || process.env.RadsApiKey;
|
|
6
8
|
if (!apiKey) {
|
|
7
9
|
throw createError({ statusCode: 401, message: `Please, provide "apiKey" option to "getRadsUiUser" call.` });
|
|
8
10
|
}
|
|
9
|
-
if (!radsAuthHeader)
|
|
10
|
-
return void 0;
|
|
11
11
|
const [keyType, key, email, pretendAsUserId] = radsAuthHeader.split(" ");
|
|
12
12
|
if (!email || !keyType || !key || keyType !== "ApiKey") {
|
|
13
13
|
throw createError({ statusCode: 401, message: `Unauthorized - malformed authHeader` });
|