@pocket-architect/core 0.1.11 → 0.1.13

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.
Files changed (36) hide show
  1. package/build/AggregateRoot.d.ts +5 -0
  2. package/build/AggregateRoot.js +11 -1
  3. package/build/AggregateRoot.js.map +1 -1
  4. package/build/AggregateRoot.spec.d.ts +1 -0
  5. package/build/AggregateRoot.spec.js +44 -0
  6. package/build/AggregateRoot.spec.js.map +1 -0
  7. package/build/DomainEvent.d.ts +16 -0
  8. package/build/DomainEvent.js +17 -0
  9. package/build/DomainEvent.js.map +1 -0
  10. package/build/DomainEventSubscriber.d.ts +5 -0
  11. package/build/DomainEventSubscriber.js +3 -0
  12. package/build/DomainEventSubscriber.js.map +1 -0
  13. package/build/EntityId.d.ts +4 -0
  14. package/build/EntityId.js +29 -1
  15. package/build/EntityId.js.map +1 -1
  16. package/build/EntityId.spec.js +24 -0
  17. package/build/EntityId.spec.js.map +1 -1
  18. package/build/EventBus.d.ts +6 -0
  19. package/build/EventBus.js +3 -0
  20. package/build/EventBus.js.map +1 -0
  21. package/build/eventBus/InMemoryEventBus.d.ts +9 -0
  22. package/build/eventBus/InMemoryEventBus.js +36 -0
  23. package/build/eventBus/InMemoryEventBus.js.map +1 -0
  24. package/build/eventBus/InMemoryEventBus.spec.d.ts +1 -0
  25. package/build/eventBus/InMemoryEventBus.spec.js +111 -0
  26. package/build/eventBus/InMemoryEventBus.spec.js.map +1 -0
  27. package/package.json +2 -1
  28. package/src/AggregateRoot.spec.ts +35 -0
  29. package/src/AggregateRoot.ts +14 -0
  30. package/src/DomainEvent.ts +32 -0
  31. package/src/DomainEventSubscriber.ts +6 -0
  32. package/src/EntityId.spec.ts +19 -0
  33. package/src/EntityId.ts +29 -1
  34. package/src/EventBus.ts +7 -0
  35. package/src/eventBus/InMemoryEventBus.spec.ts +85 -0
  36. package/src/eventBus/InMemoryEventBus.ts +21 -0
@@ -1,4 +1,9 @@
1
1
  import { Entity } from './Entity';
2
2
  import { EntityId } from './EntityId';
3
+ import { DomainEvent } from "./DomainEvent";
3
4
  export declare abstract class AggregateRoot<T, E, H extends EntityId<E>> extends Entity<T, E, H> {
5
+ private _domainEvents;
6
+ pullDomainEvents(): DomainEvent<E>[];
7
+ pushDomainEvent(event: DomainEvent<E>): void;
4
8
  }
9
+ export type AnyAggregateRoot<T> = AggregateRoot<any, T, any>;
@@ -6,8 +6,18 @@ var Entity_1 = require("./Entity");
6
6
  var AggregateRoot = /** @class */ (function (_super) {
7
7
  tslib_1.__extends(AggregateRoot, _super);
8
8
  function AggregateRoot() {
9
- return _super !== null && _super.apply(this, arguments) || this;
9
+ var _this = _super !== null && _super.apply(this, arguments) || this;
10
+ _this._domainEvents = [];
11
+ return _this;
10
12
  }
13
+ AggregateRoot.prototype.pullDomainEvents = function () {
14
+ var domainEvents = this._domainEvents.slice();
15
+ this._domainEvents = [];
16
+ return domainEvents;
17
+ };
18
+ AggregateRoot.prototype.pushDomainEvent = function (event) {
19
+ this._domainEvents.push(event);
20
+ };
11
21
  return AggregateRoot;
12
22
  }(Entity_1.Entity));
13
23
  exports.AggregateRoot = AggregateRoot;
@@ -1 +1 @@
1
- {"version":3,"file":"AggregateRoot.js","sourceRoot":"","sources":["../src/AggregateRoot.ts"],"names":[],"mappings":";;;;AAAA,mCAAkC;AAGlC;IAAyE,yCAAe;IAAxF;;IAEA,CAAC;IAAD,oBAAC;AAAD,CAAC,AAFD,CAAyE,eAAM,GAE9E;AAFqB,sCAAa"}
1
+ {"version":3,"file":"AggregateRoot.js","sourceRoot":"","sources":["../src/AggregateRoot.ts"],"names":[],"mappings":";;;;AAAA,mCAAkC;AAIlC;IAAyE,yCAAe;IAAxF;;QACU,mBAAa,GAAqB,EAAE,CAAC;;IAW/C,CAAC;IATC,wCAAgB,GAAhB;QACE,IAAM,YAAY,GAAG,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC;QAChD,IAAI,CAAC,aAAa,GAAG,EAAE,CAAC;QACxB,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,uCAAe,GAAf,UAAgB,KAAqB;QACnC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC;IACH,oBAAC;AAAD,CAAC,AAZD,CAAyE,eAAM,GAY9E;AAZqB,sCAAa"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ var tslib_1 = require("tslib");
4
+ var AggregateRoot_1 = require("./AggregateRoot");
5
+ var DomainEvent_1 = require("./DomainEvent");
6
+ var TestEntity = /** @class */ (function (_super) {
7
+ tslib_1.__extends(TestEntity, _super);
8
+ function TestEntity() {
9
+ return _super !== null && _super.apply(this, arguments) || this;
10
+ }
11
+ TestEntity.create = function (props, id) {
12
+ return new TestEntity(props, id);
13
+ };
14
+ return TestEntity;
15
+ }(AggregateRoot_1.AggregateRoot));
16
+ var TestEvent = /** @class */ (function (_super) {
17
+ tslib_1.__extends(TestEvent, _super);
18
+ function TestEvent() {
19
+ var _this = _super !== null && _super.apply(this, arguments) || this;
20
+ _this.eventName = 'TestEvent';
21
+ return _this;
22
+ }
23
+ TestEvent.prototype.toPrimitives = function () {
24
+ return {};
25
+ };
26
+ return TestEvent;
27
+ }(DomainEvent_1.DomainEvent));
28
+ describe('AggregateRoot', function () {
29
+ var root;
30
+ beforeAll(function () {
31
+ root = TestEntity.create({ name: 'test' }, 1);
32
+ });
33
+ test('events', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
34
+ var event;
35
+ return tslib_1.__generator(this, function (_a) {
36
+ event = new TestEvent(root);
37
+ root.pushDomainEvent(event);
38
+ expect(root.pullDomainEvents()).toEqual([event]);
39
+ expect(root.pullDomainEvents()).toEqual([]);
40
+ return [2 /*return*/];
41
+ });
42
+ }); });
43
+ });
44
+ //# sourceMappingURL=AggregateRoot.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AggregateRoot.spec.js","sourceRoot":"","sources":["../src/AggregateRoot.spec.ts"],"names":[],"mappings":";;;AAAA,iDAA8C;AAE9C,6CAA0C;AAK1C;IAAyB,sCAAkD;IAA3E;;IAIA,CAAC;IAHQ,iBAAM,GAAb,UAAc,KAAgB,EAAE,EAA2B;QACzD,OAAO,IAAI,UAAU,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACnC,CAAC;IACH,iBAAC;AAAD,CAAC,AAJD,CAAyB,6BAAa,GAIrC;AACD;IAAwB,qCAAmB;IAA3C;;QACW,eAAS,GAAG,WAAW,CAAC;;IAKnC,CAAC;IAHC,gCAAY,GAAZ;QACE,OAAO,EAAE,CAAC;IACZ,CAAC;IACH,gBAAC;AAAD,CAAC,AAND,CAAwB,yBAAW,GAMlC;AAED,QAAQ,CAAC,eAAe,EAAE;IACxB,IAAI,IAAgB,CAAC;IAErB,SAAS,CAAC;QACR,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,EAAE;;;YACP,KAAK,GAAG,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC;YAClC,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;YAE5B,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;YACjD,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;;;SAC7C,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { EntityId } from "./EntityId";
2
+ import { AnyAggregateRoot } from "./AggregateRoot";
3
+ export declare abstract class DomainEvent<H> {
4
+ static EVENT_NAME: string;
5
+ readonly aggregate: AnyAggregateRoot<H>;
6
+ readonly aggregateId: H;
7
+ readonly eventId: EntityId<H>;
8
+ readonly occurredOn: Date;
9
+ readonly eventName: string;
10
+ constructor(aggregate: AnyAggregateRoot<H>, eventId?: EntityId<H>, occurredOn?: Date);
11
+ abstract toPrimitives(): DomainEventAttributes;
12
+ }
13
+ export type AnyDomainEvent = DomainEvent<any>;
14
+ export type DomainEventClass = typeof DomainEvent<any>;
15
+ type DomainEventAttributes = any;
16
+ export {};
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DomainEvent = void 0;
4
+ var EntityId_1 = require("./EntityId");
5
+ var DomainEvent = /** @class */ (function () {
6
+ function DomainEvent(aggregate, eventId, occurredOn) {
7
+ var event = this.constructor;
8
+ this.eventName = event.EVENT_NAME;
9
+ this.eventId = eventId || new EntityId_1.EntityId();
10
+ this.aggregate = aggregate;
11
+ this.aggregateId = aggregate.id;
12
+ this.occurredOn = occurredOn || new Date();
13
+ }
14
+ return DomainEvent;
15
+ }());
16
+ exports.DomainEvent = DomainEvent;
17
+ //# sourceMappingURL=DomainEvent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DomainEvent.js","sourceRoot":"","sources":["../src/DomainEvent.ts"],"names":[],"mappings":";;;AAAA,uCAAoC;AAGpC;IASE,qBAAY,SAA8B,EAAE,OAAqB,EAAE,UAAiB;QAClF,IAAM,KAAK,GAAuB,IAAI,CAAC,WAAW,CAAC;QACnD,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC,UAAU,CAAC;QAClC,IAAI,CAAC,OAAO,GAAG,OAAO,IAAI,IAAI,mBAAQ,EAAK,CAAC;QAC5C,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,WAAW,GAAG,SAAS,CAAC,EAAE,CAAC;QAChC,IAAI,CAAC,UAAU,GAAG,UAAU,IAAI,IAAI,IAAI,EAAE,CAAC;IAC7C,CAAC;IAGH,kBAAC;AAAD,CAAC,AAnBD,IAmBC;AAnBqB,kCAAW"}
@@ -0,0 +1,5 @@
1
+ import { AnyDomainEvent, DomainEventClass } from './DomainEvent';
2
+ export interface DomainEventSubscriber {
3
+ subscribedTo(): DomainEventClass[];
4
+ on(domainEvent: AnyDomainEvent): Promise<void>;
5
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=DomainEventSubscriber.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DomainEventSubscriber.js","sourceRoot":"","sources":["../src/DomainEventSubscriber.ts"],"names":[],"mappings":""}
@@ -2,6 +2,10 @@ export declare class EntityId<T> {
2
2
  protected _recordId: T | null;
3
3
  protected _uuid: string;
4
4
  constructor(recordId?: T, uuid?: string);
5
+ private createUUID;
6
+ get hashOptions(): [string, number];
7
+ toHash(): string;
8
+ fromHash(hash: string): EntityId<T>;
5
9
  toPrimitive(): T;
6
10
  toString(): string;
7
11
  equals(id: EntityId<T>): boolean;
package/build/EntityId.js CHANGED
@@ -4,6 +4,7 @@ exports.EntityId = void 0;
4
4
  var tslib_1 = require("tslib");
5
5
  var uuid_by_string_1 = tslib_1.__importDefault(require("uuid-by-string"));
6
6
  var cuid2_1 = require("@paralleldrive/cuid2");
7
+ var hashids_1 = tslib_1.__importDefault(require("hashids"));
7
8
  var EntityId = /** @class */ (function () {
8
9
  function EntityId(recordId, uuid) {
9
10
  if (recordId === void 0) { recordId = null; }
@@ -12,9 +13,36 @@ var EntityId = /** @class */ (function () {
12
13
  this._uuid = null;
13
14
  this._recordId = recordId ? recordId : null;
14
15
  if (!uuid) {
15
- this._uuid = (0, uuid_by_string_1.default)(recordId ? recordId.toString() : (0, cuid2_1.createId)(), (0, uuid_by_string_1.default)(this.constructor.name));
16
+ this.createUUID();
16
17
  }
17
18
  }
19
+ EntityId.prototype.createUUID = function () {
20
+ this._uuid = (0, uuid_by_string_1.default)(this._recordId ? this._recordId.toString() : (0, cuid2_1.createId)(), (0, uuid_by_string_1.default)(this.constructor.name));
21
+ };
22
+ Object.defineProperty(EntityId.prototype, "hashOptions", {
23
+ get: function () {
24
+ return [this.constructor.name, 5];
25
+ },
26
+ enumerable: false,
27
+ configurable: true
28
+ });
29
+ EntityId.prototype.toHash = function () {
30
+ if (!this._recordId) {
31
+ throw new Error('Cannot hash an empty recordId');
32
+ }
33
+ var hashids = new (hashids_1.default.bind.apply(hashids_1.default, tslib_1.__spreadArray([void 0], this.hashOptions, false)))();
34
+ return hashids.encode(this.toPrimitive().toString());
35
+ };
36
+ EntityId.prototype.fromHash = function (hash) {
37
+ if (!this._recordId) {
38
+ throw new Error('Cannot hash an empty recordId');
39
+ }
40
+ var hashids = new (hashids_1.default.bind.apply(hashids_1.default, tslib_1.__spreadArray([void 0], this.hashOptions, false)))();
41
+ var num = hashids.decode(hash)[0];
42
+ this._recordId = (typeof this._recordId === 'string' ? num.toString() : num);
43
+ this.createUUID();
44
+ return this;
45
+ };
18
46
  EntityId.prototype.toPrimitive = function () {
19
47
  return this._recordId;
20
48
  };
@@ -1 +1 @@
1
- {"version":3,"file":"EntityId.js","sourceRoot":"","sources":["../src/EntityId.ts"],"names":[],"mappings":";;;;AAAA,0EAAuC;AACvC,8CAA+C;AAE/C;IAIE,kBAAY,QAAkB,EAAE,IAAmB;QAAvC,yBAAA,EAAA,eAAkB;QAAE,qBAAA,EAAA,WAAmB;QAHzC,cAAS,GAAW,IAAI,CAAC;QACzB,UAAK,GAAW,IAAI,CAAC;QAG7B,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;QAC5C,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,IAAI,CAAC,KAAK,GAAG,IAAA,wBAAU,EAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAA,gBAAQ,GAAE,EAAE,IAAA,wBAAU,EAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;QAC1G,CAAC;IACH,CAAC;IAED,8BAAW,GAAX;QACE,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED,2BAAQ,GAAR;QACE,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,yBAAM,GAAN,UAAO,EAAe;QACpB,OAAO,IAAI,CAAC,KAAK,KAAK,EAAE,CAAC,KAAK,CAAC;IACjC,CAAC;IACH,eAAC;AAAD,CAAC,AAtBD,IAsBC;AAtBY,4BAAQ"}
1
+ {"version":3,"file":"EntityId.js","sourceRoot":"","sources":["../src/EntityId.ts"],"names":[],"mappings":";;;;AAAA,0EAAuC;AACvC,8CAA+C;AAC/C,4DAA8B;AAE9B;IAIE,kBAAY,QAAkB,EAAE,IAAmB;QAAvC,yBAAA,EAAA,eAAkB;QAAE,qBAAA,EAAA,WAAmB;QAHzC,cAAS,GAAW,IAAI,CAAC;QACzB,UAAK,GAAW,IAAI,CAAC;QAG7B,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;QAC5C,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,IAAI,CAAC,UAAU,EAAE,CAAC;QACpB,CAAC;IACH,CAAC;IAEO,6BAAU,GAAlB;QACE,IAAI,CAAC,KAAK,GAAG,IAAA,wBAAU,EAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAA,gBAAQ,GAAE,EAAE,IAAA,wBAAU,EAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;IACtH,CAAC;IAED,sBAAI,iCAAW;aAAf;YACE,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACpC,CAAC;;;OAAA;IAED,yBAAM,GAAN;QACE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;QACnD,CAAC;QACD,IAAM,OAAO,QAAO,iBAAO,YAAP,iBAAO,kCAAI,IAAI,CAAC,WAAW,YAAC,CAAC;QACjD,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC;IACvD,CAAC;IAED,2BAAQ,GAAR,UAAS,IAAY;QACnB,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;QACnD,CAAC;QACD,IAAM,OAAO,QAAO,iBAAO,YAAP,iBAAO,kCAAI,IAAI,CAAC,WAAW,YAAC,CAAC;QACjD,IAAM,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QACpC,IAAI,CAAC,SAAS,GAAM,CAAC,OAAO,IAAI,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAChF,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,8BAAW,GAAX;QACE,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED,2BAAQ,GAAR;QACE,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,yBAAM,GAAN,UAAO,EAAe;QACpB,OAAO,IAAI,CAAC,KAAK,KAAK,EAAE,CAAC,KAAK,CAAC;IACjC,CAAC;IACH,eAAC;AAAD,CAAC,AAjDD,IAiDC;AAjDY,4BAAQ"}
@@ -9,6 +9,13 @@ var TestId = /** @class */ (function (_super) {
9
9
  }
10
10
  return TestId;
11
11
  }(EntityId_1.EntityId));
12
+ var Test2Id = /** @class */ (function (_super) {
13
+ tslib_1.__extends(Test2Id, _super);
14
+ function Test2Id() {
15
+ return _super !== null && _super.apply(this, arguments) || this;
16
+ }
17
+ return Test2Id;
18
+ }(EntityId_1.EntityId));
12
19
  describe('ValueObject', function () {
13
20
  test('base', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
14
21
  var id, id2, id3;
@@ -21,5 +28,22 @@ describe('ValueObject', function () {
21
28
  return [2 /*return*/];
22
29
  });
23
30
  }); });
31
+ test('toHash', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
32
+ var id, id2, id3;
33
+ return tslib_1.__generator(this, function (_a) {
34
+ id = new Test2Id('123');
35
+ expect(id.toHash()).toEqual('9KdMR');
36
+ expect(id.fromHash('VLoxm').toPrimitive()).toEqual('1');
37
+ expect(id.toPrimitive()).toEqual('1');
38
+ expect(id.toString()).toEqual('090bb5d1-267b-5967-84c8-95bc1bda380f');
39
+ id2 = new TestId(123);
40
+ expect(id2.toHash()).toEqual('BJejP');
41
+ expect(id2.fromHash('BJejP').toPrimitive()).toEqual(123);
42
+ id3 = new Test2Id();
43
+ expect(function () { return id3.toHash(); }).toThrow('Cannot hash an empty recordId');
44
+ expect(function () { return id3.fromHash('123'); }).toThrow('Cannot hash an empty recordId');
45
+ return [2 /*return*/];
46
+ });
47
+ }); });
24
48
  });
25
49
  //# sourceMappingURL=EntityId.spec.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"EntityId.spec.js","sourceRoot":"","sources":["../src/EntityId.spec.ts"],"names":[],"mappings":";;;AAAA,uCAAsC;AAEtC;IAAqB,kCAAgB;IAArC;;IACA,CAAC;IAAD,aAAC;AAAD,CAAC,AADD,CAAqB,mBAAQ,GAC5B;AAED,QAAQ,CAAC,aAAa,EAAE;IACtB,IAAI,CAAC,MAAM,EAAE;;;YACL,EAAE,GAAG,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC;YACnB,GAAG,GAAG,IAAI,mBAAQ,CAAC,GAAG,CAAC,CAAC;YACxB,GAAG,GAAG,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC;YAC1B,MAAM,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACxB,MAAM,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;;;SAC7B,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"EntityId.spec.js","sourceRoot":"","sources":["../src/EntityId.spec.ts"],"names":[],"mappings":";;;AAAA,uCAAsC;AAEtC;IAAqB,kCAAgB;IAArC;;IACA,CAAC;IAAD,aAAC;AAAD,CAAC,AADD,CAAqB,mBAAQ,GAC5B;AAED;IAAsB,mCAAgB;IAAtC;;IACA,CAAC;IAAD,cAAC;AAAD,CAAC,AADD,CAAsB,mBAAQ,GAC7B;AAED,QAAQ,CAAC,aAAa,EAAE;IACtB,IAAI,CAAC,MAAM,EAAE;;;YACL,EAAE,GAAG,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC;YACnB,GAAG,GAAG,IAAI,mBAAQ,CAAC,GAAG,CAAC,CAAC;YACxB,GAAG,GAAG,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC;YAC1B,MAAM,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACxB,MAAM,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;;;SAC7B,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,EAAE;;;YACP,EAAE,GAAG,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC;YAC9B,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YACrC,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACxD,MAAM,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACtC,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,sCAAsC,CAAC,CAAC;YAEhE,GAAG,GAAG,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC;YAC5B,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YACtC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAEnD,GAAG,GAAG,IAAI,OAAO,EAAE,CAAC;YAC1B,MAAM,CAAC,cAAM,OAAA,GAAG,CAAC,MAAM,EAAE,EAAZ,CAAY,CAAC,CAAC,OAAO,CAAC,+BAA+B,CAAC,CAAC;YACpE,MAAM,CAAC,cAAM,OAAA,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAnB,CAAmB,CAAC,CAAC,OAAO,CAAC,+BAA+B,CAAC,CAAC;;;SAC5E,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { AnyDomainEvent } from './DomainEvent';
2
+ import { DomainEventSubscriber } from "./DomainEventSubscriber";
3
+ export interface EventBus {
4
+ publish(events: AnyDomainEvent[]): Promise<void>;
5
+ addSubscribers(subscribers: DomainEventSubscriber[]): void;
6
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=EventBus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EventBus.js","sourceRoot":"","sources":["../src/EventBus.ts"],"names":[],"mappings":""}
@@ -0,0 +1,9 @@
1
+ /// <reference types="node" />
2
+ import { EventEmitter } from 'events';
3
+ import { AnyDomainEvent } from '../DomainEvent';
4
+ import { EventBus } from '../EventBus';
5
+ import { DomainEventSubscriber } from "../DomainEventSubscriber";
6
+ export declare class InMemoryEventBus extends EventEmitter implements EventBus {
7
+ publish(events: AnyDomainEvent[]): Promise<void>;
8
+ addSubscribers(subscribers: DomainEventSubscriber[]): void;
9
+ }
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InMemoryEventBus = void 0;
4
+ var tslib_1 = require("tslib");
5
+ var events_1 = require("events");
6
+ var InMemoryEventBus = /** @class */ (function (_super) {
7
+ tslib_1.__extends(InMemoryEventBus, _super);
8
+ function InMemoryEventBus() {
9
+ return _super !== null && _super.apply(this, arguments) || this;
10
+ }
11
+ InMemoryEventBus.prototype.publish = function (events) {
12
+ return tslib_1.__awaiter(this, void 0, void 0, function () {
13
+ var _i, events_2, event;
14
+ return tslib_1.__generator(this, function (_a) {
15
+ for (_i = 0, events_2 = events; _i < events_2.length; _i++) {
16
+ event = events_2[_i];
17
+ this.emit(event.eventName, event);
18
+ }
19
+ return [2 /*return*/];
20
+ });
21
+ });
22
+ };
23
+ InMemoryEventBus.prototype.addSubscribers = function (subscribers) {
24
+ for (var _i = 0, subscribers_1 = subscribers; _i < subscribers_1.length; _i++) {
25
+ var subscriber = subscribers_1[_i];
26
+ var events = subscriber.subscribedTo();
27
+ for (var _a = 0, events_3 = events; _a < events_3.length; _a++) {
28
+ var event = events_3[_a];
29
+ this.on(event.EVENT_NAME, subscriber.on.bind(subscriber));
30
+ }
31
+ }
32
+ };
33
+ return InMemoryEventBus;
34
+ }(events_1.EventEmitter));
35
+ exports.InMemoryEventBus = InMemoryEventBus;
36
+ //# sourceMappingURL=InMemoryEventBus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InMemoryEventBus.js","sourceRoot":"","sources":["../../src/eventBus/InMemoryEventBus.ts"],"names":[],"mappings":";;;;AAAA,iCAAsC;AAKtC;IAAsC,4CAAY;IAAlD;;IAeA,CAAC;IAdO,kCAAO,GAAb,UAAc,MAAwB;;;;gBACpC,WAA0B,EAAN,iBAAM,EAAN,oBAAM,EAAN,IAAM,EAAE,CAAC;oBAAlB,KAAK;oBACd,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;gBACpC,CAAC;;;;KACF;IAED,yCAAc,GAAd,UAAe,WAAoC;QACjD,KAAyB,UAAW,EAAX,2BAAW,EAAX,yBAAW,EAAX,IAAW,EAAE,CAAC;YAAlC,IAAM,UAAU,oBAAA;YACnB,IAAM,MAAM,GAAG,UAAU,CAAC,YAAY,EAAE,CAAC;YACzC,KAAoB,UAAM,EAAN,iBAAM,EAAN,oBAAM,EAAN,IAAM,EAAE,CAAC;gBAAxB,IAAM,KAAK,eAAA;gBACd,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;YAC5D,CAAC;QACH,CAAC;IACH,CAAC;IACH,uBAAC;AAAD,CAAC,AAfD,CAAsC,qBAAY,GAejD;AAfY,4CAAgB"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ var tslib_1 = require("tslib");
4
+ var InMemoryEventBus_1 = require("./InMemoryEventBus");
5
+ var DomainEvent_1 = require("../DomainEvent");
6
+ var EntityId_1 = require("../EntityId");
7
+ var AggregateRoot_1 = require("../AggregateRoot");
8
+ var TestAggregate = /** @class */ (function (_super) {
9
+ tslib_1.__extends(TestAggregate, _super);
10
+ function TestAggregate() {
11
+ return _super !== null && _super.apply(this, arguments) || this;
12
+ }
13
+ TestAggregate.create = function (props, id) {
14
+ return new TestAggregate(props, id);
15
+ };
16
+ return TestAggregate;
17
+ }(AggregateRoot_1.AggregateRoot));
18
+ var TestEvent = /** @class */ (function (_super) {
19
+ tslib_1.__extends(TestEvent, _super);
20
+ function TestEvent() {
21
+ return _super !== null && _super.apply(this, arguments) || this;
22
+ }
23
+ TestEvent.prototype.toPrimitives = function () {
24
+ return {};
25
+ };
26
+ TestEvent.EVENT_NAME = 'TestEvent';
27
+ return TestEvent;
28
+ }(DomainEvent_1.DomainEvent));
29
+ var TestSubscriber = /** @class */ (function () {
30
+ function TestSubscriber() {
31
+ }
32
+ TestSubscriber.prototype.subscribedTo = function () {
33
+ return [TestEvent];
34
+ };
35
+ TestSubscriber.prototype.on = function (event) {
36
+ console.info(event);
37
+ return Promise.resolve();
38
+ };
39
+ return TestSubscriber;
40
+ }());
41
+ jest.mock('events', function () {
42
+ return {
43
+ EventEmitter: /** @class */ (function () {
44
+ function class_1() {
45
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
46
+ this.listeners = {};
47
+ }
48
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
49
+ class_1.prototype.on = function (event, listener) {
50
+ this.listeners[event] = listener;
51
+ };
52
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
53
+ class_1.prototype.emit = function (event) {
54
+ var _a;
55
+ var args = [];
56
+ for (var _i = 1; _i < arguments.length; _i++) {
57
+ args[_i - 1] = arguments[_i];
58
+ }
59
+ (_a = this.listeners)[event].apply(_a, args);
60
+ };
61
+ class_1.prototype.listenerCount = function (event) {
62
+ return this.listeners[event] ? 1 : 0;
63
+ };
64
+ return class_1;
65
+ }())
66
+ };
67
+ });
68
+ describe('InMemoryEventBus', function () {
69
+ var bus;
70
+ var aggregate;
71
+ beforeEach(function () {
72
+ bus = new InMemoryEventBus_1.InMemoryEventBus();
73
+ aggregate = TestAggregate.create({}, new EntityId_1.EntityId(1));
74
+ });
75
+ test('publish', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
76
+ var event, mock;
77
+ return tslib_1.__generator(this, function (_a) {
78
+ switch (_a.label) {
79
+ case 0:
80
+ event = new TestEvent(aggregate);
81
+ mock = jest.fn();
82
+ bus.on('TestEvent', mock);
83
+ return [4 /*yield*/, bus.publish([event])];
84
+ case 1:
85
+ _a.sent();
86
+ expect(bus.listenerCount('TestEvent')).toBe(1);
87
+ expect(mock).toHaveBeenCalledWith(event);
88
+ return [2 /*return*/];
89
+ }
90
+ });
91
+ }); });
92
+ test('addSubscribers', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
93
+ var subscriber, event;
94
+ return tslib_1.__generator(this, function (_a) {
95
+ switch (_a.label) {
96
+ case 0:
97
+ subscriber = new TestSubscriber();
98
+ subscriber.on = jest.fn();
99
+ bus.addSubscribers([subscriber]);
100
+ event = new TestEvent(aggregate);
101
+ return [4 /*yield*/, bus.publish([event])];
102
+ case 1:
103
+ _a.sent();
104
+ expect(bus.listenerCount('TestEvent')).toBe(1);
105
+ expect(subscriber.on).toHaveBeenCalledWith(event);
106
+ return [2 /*return*/];
107
+ }
108
+ });
109
+ }); });
110
+ });
111
+ //# sourceMappingURL=InMemoryEventBus.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InMemoryEventBus.spec.js","sourceRoot":"","sources":["../../src/eventBus/InMemoryEventBus.spec.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pocket-architect/core",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "",
5
5
  "main": "build/index.js",
6
6
  "scripts": {
@@ -17,6 +17,7 @@
17
17
  },
18
18
  "dependencies": {
19
19
  "@paralleldrive/cuid2": "^2.2.2",
20
+ "hashids": "^2.3.0",
20
21
  "shallow-equal-object": "^1.1.1",
21
22
  "uuid-by-string": "^4.0.0"
22
23
  },
@@ -0,0 +1,35 @@
1
+ import {AggregateRoot} from "./AggregateRoot";
2
+ import {EntityId} from "./EntityId";
3
+ import {DomainEvent} from "./DomainEvent";
4
+
5
+ interface TestProps {
6
+ name: string;
7
+ }
8
+ class TestEntity extends AggregateRoot<TestProps, number, EntityId<number>> {
9
+ static create(props: TestProps, id?:EntityId<number>|number): TestEntity {
10
+ return new TestEntity(props, id);
11
+ }
12
+ }
13
+ class TestEvent extends DomainEvent<number> {
14
+ readonly eventName = 'TestEvent';
15
+
16
+ toPrimitives() {
17
+ return {};
18
+ }
19
+ }
20
+
21
+ describe('AggregateRoot', () => {
22
+ let root: TestEntity;
23
+
24
+ beforeAll(() => {
25
+ root = TestEntity.create({ name: 'test' }, 1);
26
+ });
27
+
28
+ test('events', async () => {
29
+ const event = new TestEvent(root);
30
+ root.pushDomainEvent(event);
31
+
32
+ expect(root.pullDomainEvents()).toEqual([event]);
33
+ expect(root.pullDomainEvents()).toEqual([]);
34
+ });
35
+ });
@@ -1,6 +1,20 @@
1
1
  import { Entity } from './Entity';
2
2
  import { EntityId } from './EntityId';
3
+ import {DomainEvent} from "./DomainEvent";
3
4
 
4
5
  export abstract class AggregateRoot<T, E, H extends EntityId<E>> extends Entity<T, E, H> {
6
+ private _domainEvents: DomainEvent<E>[] = [];
5
7
 
8
+ pullDomainEvents(): DomainEvent<E>[] {
9
+ const domainEvents = this._domainEvents.slice();
10
+ this._domainEvents = [];
11
+ return domainEvents;
12
+ }
13
+
14
+ pushDomainEvent(event: DomainEvent<E>): void {
15
+ this._domainEvents.push(event);
16
+ }
6
17
  }
18
+
19
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
+ export type AnyAggregateRoot<T> = AggregateRoot<any, T, any>;
@@ -0,0 +1,32 @@
1
+ import {EntityId} from "./EntityId";
2
+ import {AnyAggregateRoot} from "./AggregateRoot";
3
+
4
+ export abstract class DomainEvent<H> {
5
+ static EVENT_NAME: string;
6
+
7
+ readonly aggregate: AnyAggregateRoot<H>;
8
+ readonly aggregateId: H;
9
+ readonly eventId: EntityId<H>;
10
+ readonly occurredOn: Date;
11
+ readonly eventName: string;
12
+
13
+ constructor(aggregate: AnyAggregateRoot<H>, eventId?: EntityId<H>, occurredOn?: Date) {
14
+ const event = <typeof DomainEvent>this.constructor;
15
+ this.eventName = event.EVENT_NAME;
16
+ this.eventId = eventId || new EntityId<H>();
17
+ this.aggregate = aggregate;
18
+ this.aggregateId = aggregate.id;
19
+ this.occurredOn = occurredOn || new Date();
20
+ }
21
+
22
+ abstract toPrimitives(): DomainEventAttributes;
23
+ }
24
+
25
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
26
+ export type AnyDomainEvent = DomainEvent<any>;
27
+
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
+ export type DomainEventClass = typeof DomainEvent<any>;
30
+
31
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
+ type DomainEventAttributes = any;
@@ -0,0 +1,6 @@
1
+ import {AnyDomainEvent, DomainEventClass} from './DomainEvent';
2
+
3
+ export interface DomainEventSubscriber {
4
+ subscribedTo(): DomainEventClass[];
5
+ on(domainEvent: AnyDomainEvent): Promise<void>;
6
+ }
@@ -3,6 +3,9 @@ import { EntityId } from './EntityId';
3
3
  class TestId extends EntityId<number> {
4
4
  }
5
5
 
6
+ class Test2Id extends EntityId<string> {
7
+ }
8
+
6
9
  describe('ValueObject', () => {
7
10
  test('base', async () => {
8
11
  const id = new TestId(1);
@@ -11,4 +14,20 @@ describe('ValueObject', () => {
11
14
  expect(id).toEqual(id3);
12
15
  expect(id).not.toEqual(id2);
13
16
  });
17
+
18
+ test('toHash', async () => {
19
+ const id = new Test2Id('123');
20
+ expect(id.toHash()).toEqual('9KdMR');
21
+ expect(id.fromHash('VLoxm').toPrimitive()).toEqual('1');
22
+ expect(id.toPrimitive()).toEqual('1');
23
+ expect(id.toString()).toEqual('090bb5d1-267b-5967-84c8-95bc1bda380f');
24
+
25
+ const id2 = new TestId(123);
26
+ expect(id2.toHash()).toEqual('BJejP');
27
+ expect(id2.fromHash('BJejP').toPrimitive()).toEqual(123);
28
+
29
+ const id3 = new Test2Id();
30
+ expect(() => id3.toHash()).toThrow('Cannot hash an empty recordId');
31
+ expect(() => id3.fromHash('123')).toThrow('Cannot hash an empty recordId');
32
+ });
14
33
  });
package/src/EntityId.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import createUUID from 'uuid-by-string'
2
2
  import { createId } from '@paralleldrive/cuid2'
3
+ import Hashids from 'hashids';
3
4
 
4
5
  export class EntityId<T> {
5
6
  protected _recordId: T|null = null;
@@ -8,10 +9,37 @@ export class EntityId<T> {
8
9
  constructor(recordId: T = null, uuid: string = null) {
9
10
  this._recordId = recordId ? recordId : null;
10
11
  if (!uuid) {
11
- this._uuid = createUUID(recordId ? recordId.toString() : createId(), createUUID(this.constructor.name));
12
+ this.createUUID();
12
13
  }
13
14
  }
14
15
 
16
+ private createUUID():void {
17
+ this._uuid = createUUID(this._recordId ? this._recordId.toString() : createId(), createUUID(this.constructor.name));
18
+ }
19
+
20
+ get hashOptions() : [string, number] {
21
+ return [this.constructor.name, 5];
22
+ }
23
+
24
+ toHash(): string {
25
+ if (!this._recordId) {
26
+ throw new Error('Cannot hash an empty recordId');
27
+ }
28
+ const hashids = new Hashids(...this.hashOptions);
29
+ return hashids.encode(this.toPrimitive().toString());
30
+ }
31
+
32
+ fromHash(hash: string) : EntityId<T> {
33
+ if (!this._recordId) {
34
+ throw new Error('Cannot hash an empty recordId');
35
+ }
36
+ const hashids = new Hashids(...this.hashOptions);
37
+ const num = hashids.decode(hash)[0];
38
+ this._recordId = <T>(typeof this._recordId === 'string' ? num.toString() : num);
39
+ this.createUUID();
40
+ return this;
41
+ }
42
+
15
43
  toPrimitive(): T {
16
44
  return this._recordId;
17
45
  }
@@ -0,0 +1,7 @@
1
+ import {AnyDomainEvent} from './DomainEvent';
2
+ import {DomainEventSubscriber} from "./DomainEventSubscriber";
3
+
4
+ export interface EventBus {
5
+ publish(events: AnyDomainEvent[]): Promise<void>;
6
+ addSubscribers(subscribers: DomainEventSubscriber[]): void;
7
+ }
@@ -0,0 +1,85 @@
1
+ import {InMemoryEventBus} from "./InMemoryEventBus";
2
+ import {DomainEvent, DomainEventClass} from "../DomainEvent";
3
+ import {EntityId} from "../EntityId";
4
+ import {DomainEventSubscriber} from "../DomainEventSubscriber";
5
+ import {AggregateRoot} from "../AggregateRoot";
6
+
7
+ interface ITestAggregate {}
8
+
9
+ class TestAggregate extends AggregateRoot<ITestAggregate, number, EntityId<number>> {
10
+ static create(props: ITestAggregate, id?:EntityId<number>|number): TestAggregate {
11
+ return new TestAggregate(props, id);
12
+ }
13
+ }
14
+
15
+ class TestEvent extends DomainEvent<number> {
16
+ static EVENT_NAME = 'TestEvent';
17
+
18
+ toPrimitives() {
19
+ return {};
20
+ }
21
+ }
22
+
23
+ class TestSubscriber implements DomainEventSubscriber {
24
+ subscribedTo():DomainEventClass[] {
25
+ return [TestEvent];
26
+ }
27
+
28
+ on(event:TestEvent):Promise<void> {
29
+ console.info(event);
30
+ return Promise.resolve();
31
+ }
32
+ }
33
+
34
+ jest.mock('events', () => {
35
+ return {
36
+ EventEmitter: class {
37
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
+ listeners: any = {};
39
+
40
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
41
+ on(event: string, listener: any) {
42
+ this.listeners[event] = listener;
43
+ }
44
+
45
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
46
+ emit(event: string, ...args: any[]) {
47
+ this.listeners[event](...args);
48
+ }
49
+
50
+ listenerCount(event: string) {
51
+ return this.listeners[event] ? 1 : 0;
52
+ }
53
+ }
54
+ };
55
+ });
56
+
57
+ describe('InMemoryEventBus', () => {
58
+ let bus: InMemoryEventBus;
59
+ let aggregate: TestAggregate;
60
+
61
+ beforeEach(() => {
62
+ bus = new InMemoryEventBus();
63
+ aggregate = TestAggregate.create({}, new EntityId(1));
64
+ });
65
+
66
+ test('publish', async () => {
67
+ const event = new TestEvent(aggregate);
68
+ const mock = jest.fn();
69
+ bus.on('TestEvent', mock);
70
+ await bus.publish([event]);
71
+ expect(bus.listenerCount('TestEvent')).toBe(1);
72
+ expect(mock).toHaveBeenCalledWith(event);
73
+ });
74
+
75
+ test('addSubscribers', async () => {
76
+ const subscriber = new TestSubscriber();
77
+ subscriber.on = jest.fn();
78
+ bus.addSubscribers([subscriber]);
79
+
80
+ const event = new TestEvent(aggregate);
81
+ await bus.publish([event]);
82
+ expect(bus.listenerCount('TestEvent')).toBe(1);
83
+ expect(subscriber.on).toHaveBeenCalledWith(event);
84
+ });
85
+ });
@@ -0,0 +1,21 @@
1
+ import { EventEmitter } from 'events';
2
+ import { AnyDomainEvent } from '../DomainEvent';
3
+ import { EventBus } from '../EventBus';
4
+ import {DomainEventSubscriber} from "../DomainEventSubscriber";
5
+
6
+ export class InMemoryEventBus extends EventEmitter implements EventBus {
7
+ async publish(events: AnyDomainEvent[]): Promise<void> {
8
+ for (const event of events) {
9
+ this.emit(event.eventName, event);
10
+ }
11
+ }
12
+
13
+ addSubscribers(subscribers: DomainEventSubscriber[]): void {
14
+ for (const subscriber of subscribers) {
15
+ const events = subscriber.subscribedTo();
16
+ for (const event of events) {
17
+ this.on(event.EVENT_NAME, subscriber.on.bind(subscriber));
18
+ }
19
+ }
20
+ }
21
+ }