UNPKG

@cashfarm/plow

Version:

Library for validating input data and parameters

111 lines (110 loc) 4.09 kB
"use strict"; 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