@cashfarm/plow
Version:
Library for validating input data and parameters
111 lines (110 loc) • 4.09 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const lang_1 = require("@cashfarm/lang");
const class_transformer_1 = require("class-transformer");
const domain_1 = require("../domain");
const symbols_1 = require("../symbols");
const config_1 = require("../config");
const APPLY_CHANGE = Symbol('@cashfarm/plow:ESAggregate.APPLY_CHANGE');
const LOAD_FROM_EVENTS = Symbol('@cashfarm/plow:ESAggregate.LOAD_FROM_EVENTS');
/**
* Returns a symbol used to created methods that apply events to agrregates
*
* @export
* @param {(DomainEvent & Type)} e
* @returns {symbol}
*/
function Apply(e) {
return Symbol.for(e.prototype.constructor.name);
}
exports.Apply = Apply;
/**
* Base implementation for an event sourced aggregate root.
*
* Extend this class and implement `[Apply(EvtClass)]()` methods for
* each event of your aggregate.
*/
class ESAggregateRoot extends domain_1.AggregateRoot {
constructor() {
super(...arguments);
this._version = -1;
}
static load(constructor, events) {
const t = Object.create(constructor.prototype);
t._version = -1;
t._events = [];
const mappedEvts = events.map(ee => {
// Get the event class
const klass = lang_1.requireByFQN(ee.eventType);
// deserialize to a class instance
return class_transformer_1.plainToClass(klass, ee.event);
});
t[LOAD_FROM_EVENTS](mappedEvts);
return t;
}
get version() {
return this._version;
}
get uncommittedChanges() {
return this._events;
}
markChangesAsCommitted() {
this._version += this._events.length;
this._events.length = 0;
}
applyChange(event) {
this[APPLY_CHANGE](event, true);
}
/**
* Hidden method to load an aggregate from it's events
*
* @private
* @param {IDomainEvent[]} history The list of events to load
* @memberof ESAggregateRoot
*/
[LOAD_FROM_EVENTS](history) {
this._version = history.length - 1;
history.forEach(event => this[APPLY_CHANGE](event, false));
}
/**
* Hidden method to apply an event
*
* @private
* @param {IDomainEvent} event The event to be applied
* @param {boolean} isNew Wheter the event is new or not
* @memberof ESAggregateRoot
*/
[APPLY_CHANGE](event, isNew) {
const evtName = Reflect.getMetadata(symbols_1.Symbols.EventName, event.constructor) || event.constructor.name;
// Find out the method to apply the function to
let applyEvent = `apply${evtName}`;
if (!this[applyEvent]) {
const constructor = Object.getPrototypeOf(event) ? Object.getPrototypeOf(event).constructor : null;
if (constructor && this[Apply(constructor)] instanceof Function)
applyEvent = Apply(constructor);
}
if (this[applyEvent] instanceof Function) {
this[applyEvent](event);
}
else {
if (config_1.PlowConfig.requireApplyForEachEvent) {
const actualImpl = this[applyEvent] instanceof Function ?
`The aggregate ${this.constructor.name} property [Apply(${evtName})] is a ${typeof this[applyEvent]}` :
`The Aggregate ${this.constructor.name} has no apply method for ${evtName}`;
throw new Error(`For each event, an aggregate MUST implement an apply method called either apply${evtName}
or using a symbol generated by the Apply function, e.g. protected [Apply(${evtName})](evt: ${evtName}): void {}.
${actualImpl}`);
}
config_1.PlowConfig.defaultApplyFn(this, event);
}
if (isNew)
this._events.push(event);
}
}
tslib_1.__decorate([
class_transformer_1.Exclude(),
tslib_1.__metadata("design:type", Number)
], ESAggregateRoot.prototype, "_version", void 0);
exports.ESAggregateRoot = ESAggregateRoot;
//# sourceMappingURL=esAggregateRoot.js.map