@pocket-architect/core 0.1.19 → 0.1.23

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 (54) hide show
  1. package/.eslintignore +1 -0
  2. package/build/Entity.d.ts +2 -1
  3. package/build/Entity.js +9 -2
  4. package/build/Entity.js.map +1 -1
  5. package/build/Entity.spec.js +9 -2
  6. package/build/Entity.spec.js.map +1 -1
  7. package/build/EntityId.d.ts +1 -0
  8. package/build/EntityId.js +3 -0
  9. package/build/EntityId.js.map +1 -1
  10. package/build/application/Application.d.ts +10 -0
  11. package/build/application/Application.js +43 -0
  12. package/build/application/Application.js.map +1 -0
  13. package/build/application/ApplicationModule.d.ts +5 -0
  14. package/build/application/ApplicationModule.js +10 -0
  15. package/build/application/ApplicationModule.js.map +1 -0
  16. package/build/bootstrap.d.ts +14 -0
  17. package/build/bootstrap.js +88 -0
  18. package/build/bootstrap.js.map +1 -0
  19. package/build/bootstrap.spec.d.ts +1 -0
  20. package/build/bootstrap.spec.js +144 -0
  21. package/build/bootstrap.spec.js.map +1 -0
  22. package/build/eventBus/InMemoryEventBus.d.ts +6 -2
  23. package/build/eventBus/InMemoryEventBus.js +15 -10
  24. package/build/eventBus/InMemoryEventBus.js.map +1 -1
  25. package/build/helpers.d.ts +1 -0
  26. package/build/helpers.js +37 -0
  27. package/build/helpers.js.map +1 -0
  28. package/build/helpers.spec.d.ts +1 -0
  29. package/build/helpers.spec.js +70 -0
  30. package/build/helpers.spec.js.map +1 -0
  31. package/build/index.d.ts +8 -1
  32. package/build/index.js +11 -1
  33. package/build/index.js.map +1 -1
  34. package/package.json +2 -1
  35. package/src/Entity.spec.ts +7 -2
  36. package/src/Entity.ts +12 -4
  37. package/src/EntityId.ts +4 -0
  38. package/src/application/Application.ts +24 -0
  39. package/src/application/ApplicationModule.ts +7 -0
  40. package/src/bootstrap.spec.ts +100 -0
  41. package/src/bootstrap.ts +87 -0
  42. package/src/eventBus/InMemoryEventBus.ts +19 -5
  43. package/src/helpers.spec.ts +65 -0
  44. package/src/helpers.ts +32 -0
  45. package/src/index.ts +17 -1
  46. package/tests/testapp/domain/test/service/TestService.d.ts +4 -0
  47. package/tests/testapp/domain/test/service/TestService.js +13 -0
  48. package/tests/testapp/domain/test/service/TestService.js.map +1 -0
  49. package/tests/testapp/domain/test/service/TestService.ts +8 -0
  50. package/tests/testapp/infra/test/providers/TestProvider.d.ts +4 -0
  51. package/tests/testapp/infra/test/providers/TestProvider.js +13 -0
  52. package/tests/testapp/infra/test/providers/TestProvider.js.map +1 -0
  53. package/tests/testapp/infra/test/providers/TestProvider.ts +8 -0
  54. package/tsconfig.json +1 -1
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ var tslib_1 = require("tslib");
4
+ var helpers_1 = require("./helpers");
5
+ var EntityId_1 = require("./EntityId");
6
+ var Entity_1 = require("./Entity");
7
+ var TestEntity = /** @class */ (function (_super) {
8
+ tslib_1.__extends(TestEntity, _super);
9
+ function TestEntity() {
10
+ return _super !== null && _super.apply(this, arguments) || this;
11
+ }
12
+ TestEntity.create = function (props, id) {
13
+ return new TestEntity(props, id);
14
+ };
15
+ Object.defineProperty(TestEntity.prototype, "name", {
16
+ set: function (name) {
17
+ this.props.name = name;
18
+ },
19
+ enumerable: false,
20
+ configurable: true
21
+ });
22
+ Object.defineProperty(TestEntity.prototype, "testId", {
23
+ set: function (val) {
24
+ this.props.testId = val;
25
+ },
26
+ enumerable: false,
27
+ configurable: true
28
+ });
29
+ TestEntity.prototype.toSnapshot = function () {
30
+ return tslib_1.__assign(tslib_1.__assign({}, this.props), { testId: undefined });
31
+ };
32
+ return TestEntity;
33
+ }(Entity_1.Entity));
34
+ describe('helpers', function () {
35
+ test('createSnapshot', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
36
+ var original, snapshot, item, itemSnapshot;
37
+ return tslib_1.__generator(this, function (_a) {
38
+ original = {
39
+ a: 1,
40
+ b: 'test',
41
+ c: {
42
+ d: 2,
43
+ e: 'nested',
44
+ f: {
45
+ g: 3
46
+ }
47
+ },
48
+ id: new EntityId_1.EntityId('1'),
49
+ };
50
+ snapshot = (0, helpers_1.createSnapshot)(original);
51
+ expect(snapshot).toEqual(tslib_1.__assign(tslib_1.__assign({}, original), { id: '3D4WX' }));
52
+ expect(snapshot).not.toBe(original);
53
+ expect(snapshot.c).not.toBe(original.c);
54
+ expect(snapshot.c.f).not.toBe(original.c.f);
55
+ expect(snapshot.id).not.toBe(original.id);
56
+ expect(snapshot.id).toEqual('3D4WX');
57
+ item = TestEntity.create({
58
+ name: 'test',
59
+ testId: new EntityId_1.EntityId('2')
60
+ }, '2');
61
+ itemSnapshot = (0, helpers_1.createSnapshot)(item);
62
+ expect(itemSnapshot).toEqual({
63
+ name: 'test',
64
+ testId: undefined
65
+ });
66
+ return [2 /*return*/];
67
+ });
68
+ }); });
69
+ });
70
+ //# sourceMappingURL=helpers.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.spec.js","sourceRoot":"","sources":["../src/helpers.spec.ts"],"names":[],"mappings":";;;AAAA,qCAAyC;AACzC,uCAAoC;AACpC,mCAAgC;AAMhC;IAAyB,sCAA2C;IAApE;;IAmBA,CAAC;IAlBQ,iBAAM,GAAb,UAAc,KAAgB,EAAE,EAA2B;QACzD,OAAO,IAAI,UAAU,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACnC,CAAC;IAED,sBAAI,4BAAI;aAAR,UAAS,IAAY;YACnB,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC;QACzB,CAAC;;;OAAA;IAED,sBAAI,8BAAM;aAAV,UAAW,GAAqB;YAC9B,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC;QAC1B,CAAC;;;OAAA;IAED,+BAAU,GAAV;QACE,OAAO,sCACF,IAAI,CAAC,KAAK,KACb,MAAM,EAAE,SAAS,GACT,CAAC;IACb,CAAC;IACH,iBAAC;AAAD,CAAC,AAnBD,CAAyB,eAAM,GAmB9B;AAED,QAAQ,CAAC,SAAS,EAAE;IAClB,IAAI,CAAC,gBAAgB,EAAE;;;YACf,QAAQ,GAAG;gBACf,CAAC,EAAE,CAAC;gBACJ,CAAC,EAAE,MAAM;gBACT,CAAC,EAAE;oBACD,CAAC,EAAE,CAAC;oBACJ,CAAC,EAAE,QAAQ;oBACX,CAAC,EAAE;wBACD,CAAC,EAAE,CAAC;qBACL;iBACF;gBACD,EAAE,EAAE,IAAI,mBAAQ,CAAS,GAAG,CAAC;aAC9B,CAAC;YACI,QAAQ,GAAG,IAAA,wBAAc,EAAC,QAAQ,CAAC,CAAC;YAC1C,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,uCACnB,QAAQ,KACX,EAAE,EAAE,OAAO,IACX,CAAC;YACH,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACpC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;YACxC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC5C,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YAC1C,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YAE/B,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC;gBAC7B,IAAI,EAAE,MAAM;gBACZ,MAAM,EAAE,IAAI,mBAAQ,CAAC,GAAG,CAAC;aAC1B,EAAE,GAAG,CAAC,CAAC;YACF,YAAY,GAAG,IAAA,wBAAc,EAAC,IAAI,CAAC,CAAC;YAC1C,MAAM,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC;gBAC3B,IAAI,EAAE,MAAM;gBACZ,MAAM,EAAE,SAAS;aAClB,CAAC,CAAC;;;SACJ,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
package/build/index.d.ts CHANGED
@@ -3,4 +3,11 @@ import { EntityId } from './EntityId';
3
3
  import { ValueObject } from './ValueObject';
4
4
  import { AggregateRoot } from './AggregateRoot';
5
5
  import { HashError } from './error/HashError';
6
- export { Entity, EntityId, ValueObject, AggregateRoot, HashError };
6
+ import { DomainEvent } from './DomainEvent';
7
+ import { DomainEventSubscriber } from './DomainEventSubscriber';
8
+ import { EventBus } from './EventBus';
9
+ import { Eventable } from './mixins/Eventable';
10
+ import { Application } from './application/Application';
11
+ import { ApplicationModule } from './application/ApplicationModule';
12
+ import { bootstrap } from './bootstrap';
13
+ export { Entity, EntityId, ValueObject, AggregateRoot, HashError, DomainEvent, DomainEventSubscriber, EventBus, Eventable, Application, ApplicationModule, bootstrap };
package/build/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.HashError = exports.AggregateRoot = exports.ValueObject = exports.EntityId = exports.Entity = void 0;
3
+ exports.bootstrap = exports.ApplicationModule = exports.Application = exports.Eventable = exports.DomainEvent = exports.HashError = exports.AggregateRoot = exports.ValueObject = exports.EntityId = exports.Entity = void 0;
4
4
  var Entity_1 = require("./Entity");
5
5
  Object.defineProperty(exports, "Entity", { enumerable: true, get: function () { return Entity_1.Entity; } });
6
6
  var EntityId_1 = require("./EntityId");
@@ -11,4 +11,14 @@ var AggregateRoot_1 = require("./AggregateRoot");
11
11
  Object.defineProperty(exports, "AggregateRoot", { enumerable: true, get: function () { return AggregateRoot_1.AggregateRoot; } });
12
12
  var HashError_1 = require("./error/HashError");
13
13
  Object.defineProperty(exports, "HashError", { enumerable: true, get: function () { return HashError_1.HashError; } });
14
+ var DomainEvent_1 = require("./DomainEvent");
15
+ Object.defineProperty(exports, "DomainEvent", { enumerable: true, get: function () { return DomainEvent_1.DomainEvent; } });
16
+ var Eventable_1 = require("./mixins/Eventable");
17
+ Object.defineProperty(exports, "Eventable", { enumerable: true, get: function () { return Eventable_1.Eventable; } });
18
+ var Application_1 = require("./application/Application");
19
+ Object.defineProperty(exports, "Application", { enumerable: true, get: function () { return Application_1.Application; } });
20
+ var ApplicationModule_1 = require("./application/ApplicationModule");
21
+ Object.defineProperty(exports, "ApplicationModule", { enumerable: true, get: function () { return ApplicationModule_1.ApplicationModule; } });
22
+ var bootstrap_1 = require("./bootstrap");
23
+ Object.defineProperty(exports, "bootstrap", { enumerable: true, get: function () { return bootstrap_1.bootstrap; } });
14
24
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,mCAAkC;AAOhC,uFAPO,eAAM,OAOP;AANR,uCAAsC;AAOpC,yFAPO,mBAAQ,OAOP;AANV,6CAA4C;AAO1C,4FAPO,yBAAW,OAOP;AANb,iDAAgD;AAO9C,8FAPO,6BAAa,OAOP;AANf,+CAA8C;AAO5C,0FAPO,qBAAS,OAOP"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,mCAAkC;AAchC,uFAdO,eAAM,OAcP;AAbR,uCAAsC;AAcpC,yFAdO,mBAAQ,OAcP;AAbV,6CAA4C;AAc1C,4FAdO,yBAAW,OAcP;AAbb,iDAAgD;AAc9C,8FAdO,6BAAa,OAcP;AAbf,+CAA8C;AAc5C,0FAdO,qBAAS,OAcP;AAbX,6CAA4C;AAc1C,4FAdO,yBAAW,OAcP;AAXb,gDAA+C;AAc7C,0FAdO,qBAAS,OAcP;AAbX,yDAAwD;AAetD,4FAfO,yBAAW,OAeP;AAdb,qEAAoE;AAelE,kGAfO,qCAAiB,OAeP;AAdnB,yCAAwC;AAgBtC,0FAhBO,qBAAS,OAgBP"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pocket-architect/core",
3
- "version": "0.1.19",
3
+ "version": "0.1.23",
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
+ "awilix": "^10.0.2",
20
21
  "hashids": "^2.3.0",
21
22
  "shallow-equal-object": "^1.1.1",
22
23
  "uuid-by-string": "^4.0.0"
@@ -3,6 +3,7 @@ import { EntityId } from './EntityId';
3
3
 
4
4
  interface TestProps {
5
5
  name: string;
6
+ testId?: EntityId<string>;
6
7
  }
7
8
  class TestEntity extends Entity<TestProps, string, EntityId<string>> {
8
9
  static create(props: TestProps, id?:EntityId<string>|string): TestEntity {
@@ -12,13 +13,17 @@ class TestEntity extends Entity<TestProps, string, EntityId<string>> {
12
13
  set name(name: string) {
13
14
  this.props.name = name;
14
15
  }
16
+
17
+ set testId(val: EntityId<string>) {
18
+ this.props.testId = val;
19
+ }
15
20
  }
16
21
 
17
22
  describe('Entity', () => {
18
23
  let entity: TestEntity;
19
24
 
20
25
  beforeAll(() => {
21
- entity = TestEntity.create({ name: 'test' }, '1');
26
+ entity = TestEntity.create({ name: 'test', testId: new EntityId('1') }, '1');
22
27
  });
23
28
 
24
29
  test('base', async () => {
@@ -41,7 +46,7 @@ describe('Entity', () => {
41
46
 
42
47
  test('snapshot', async () => {
43
48
  const snapshot = entity.snapshot();
44
- expect(snapshot).toEqual({ name: 'test' });
49
+ expect(snapshot).toEqual({ name: 'test', testId: '3D4WX' });
45
50
 
46
51
  expect(() => {
47
52
  snapshot.name = 'changed';
package/src/Entity.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { EntityId } from './EntityId';
2
+ import {createSnapshot} from "./helpers";
2
3
 
3
4
  const isEntity = <T, E, M extends EntityId<E>>(v: Entity<T, E, M>): v is Entity<T, E, M> => {
4
5
  return v instanceof Entity
@@ -22,8 +23,9 @@ export abstract class Entity<T, E, H extends EntityId<E>> {
22
23
  }
23
24
  const diffs: Partial<T> = {};
24
25
  const copy = JSON.parse(this._snapshot);
26
+ const snapshotObj = createSnapshot(this.props);
25
27
  for (const key in this.props) {
26
- if (JSON.stringify(this.props[key]) !== JSON.stringify(copy[key])) {
28
+ if (JSON.stringify(snapshotObj[key]) !== JSON.stringify(copy[key])) {
27
29
  diffs[key] = this.props[key];
28
30
  }
29
31
  }
@@ -34,7 +36,7 @@ export abstract class Entity<T, E, H extends EntityId<E>> {
34
36
  if (!this._snapshot) {
35
37
  return false;
36
38
  }
37
- return this._snapshot !== JSON.stringify(this.props);
39
+ return this._snapshot !== JSON.stringify(createSnapshot(this.props));
38
40
  }
39
41
 
40
42
  protected constructor(props: T, id?: H|E) {
@@ -70,7 +72,13 @@ export abstract class Entity<T, E, H extends EntityId<E>> {
70
72
  return this.props
71
73
  }
72
74
 
73
- toJSON(): T {
74
- return this.toPrimitive()
75
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
76
+ toJSON(): any {
77
+ return this.toPrimitive();
78
+ }
79
+
80
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
81
+ toSnapshot(): any {
82
+ return this.toPrimitive();
75
83
  }
76
84
  }
package/src/EntityId.ts CHANGED
@@ -55,6 +55,10 @@ export class EntityId<T> {
55
55
  return this._uuid;
56
56
  }
57
57
 
58
+ toJSON(): string {
59
+ return this.toHash();
60
+ }
61
+
58
62
  equals(id: EntityId<T>): boolean {
59
63
  return this._uuid === id._uuid;
60
64
  }
@@ -0,0 +1,24 @@
1
+ import {BaseBootstrapEnv} from "../bootstrap";
2
+ import type {ApplicationModule} from "./ApplicationModule";
3
+
4
+ export
5
+ abstract class Application {
6
+ protected _modules: ApplicationModule[] = [];
7
+
8
+ abstract initApplication(scope: BaseBootstrapEnv):Promise<void>;
9
+
10
+ prepareApplication?(scope: BaseBootstrapEnv): Promise<void>;
11
+
12
+ postInitApplication?(scope: BaseBootstrapEnv): Promise<void>;
13
+
14
+ async initModules(modules: ApplicationModule[], scope: BaseBootstrapEnv): Promise<void> {
15
+ for (const module of modules) {
16
+ await module.initModule(this, scope);
17
+ this._modules.push(module);
18
+ }
19
+ }
20
+
21
+ get modules(): ApplicationModule[] {
22
+ return this._modules;
23
+ }
24
+ }
@@ -0,0 +1,7 @@
1
+ import {BaseBootstrapEnv} from "../bootstrap";
2
+ import {Application} from "./Application";
3
+
4
+ export
5
+ abstract class ApplicationModule {
6
+ abstract initModule(app: Application, scope: BaseBootstrapEnv):Promise<void>;
7
+ }
@@ -0,0 +1,100 @@
1
+ import {bootstrap, BaseBootstrapEnv} from "./bootstrap";
2
+ import {Application} from "./application/Application";
3
+ import {asValue} from "awilix";
4
+ import {ApplicationModule} from "./application/ApplicationModule";
5
+ import {InMemoryEventBus} from "./eventBus/InMemoryEventBus";
6
+ import {DomainEventSubscriber} from "./DomainEventSubscriber";
7
+ import {DomainEventClass, DomainEvent} from "./DomainEvent";
8
+
9
+ class TestApplication extends Application {
10
+ result: number[] = [];
11
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
+ scope: any;
13
+
14
+ eventBus: InMemoryEventBus;
15
+
16
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
+ async initApplication({DIContainer: scope}: BaseBootstrapEnv) {
18
+ this.eventBus = new InMemoryEventBus();
19
+
20
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
+ let provider: any = scope.resolve('TestProvider');
22
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
+ let service: any = scope.resolve('TestService');
24
+ provider.inc();
25
+ service.inc();
26
+
27
+ let newScope = scope.createScope();
28
+ provider = newScope.resolve('TestProvider')
29
+ service = newScope.resolve('TestService');
30
+ provider.inc();
31
+ service.inc();
32
+
33
+ newScope = scope.createScope();
34
+ provider = newScope.resolve('TestProvider')
35
+ service = newScope.resolve('TestService');
36
+ provider.inc();
37
+ service.inc();
38
+
39
+ this.result = [provider.value, service.value];
40
+
41
+ scope.register({
42
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
43
+ // @ts-ignore
44
+ context: asValue({userId: '1'}),
45
+ TestModule: asValue(new TestModule())
46
+ });
47
+ this.scope = scope;
48
+
49
+ await this.initModules([
50
+ scope.resolve('TestModule')
51
+ ], scope.cradle);
52
+ }
53
+ }
54
+
55
+ class TestModule extends ApplicationModule implements DomainEventSubscriber {
56
+ events:string[] = [];
57
+
58
+ subscribedTo(): DomainEventClass[] {
59
+ return [TestDomainEvent];
60
+ }
61
+
62
+ async on(domainEvent: TestDomainEvent): Promise<void> {
63
+ this.events.push(domainEvent.test);
64
+ }
65
+
66
+ async initModule(app: TestApplication) {
67
+ app.eventBus.addSubscribers(this);
68
+ }
69
+ }
70
+
71
+ class TestDomainEvent extends DomainEvent<{ test: string }> {
72
+ get test() {
73
+ return this.props.test;
74
+ }
75
+ }
76
+
77
+ describe('bootstrap', () => {
78
+ test('bootstrap', async () => {
79
+ const app = new TestApplication();
80
+ await bootstrap(app, {
81
+ appFolder: `${__dirname}/../tests/testapp/app`,
82
+ domainsFolder: `${__dirname}/../tests/testapp/domain`,
83
+ infraFolder: `${__dirname}/../tests/testapp/infra`,
84
+ });
85
+
86
+ // провайдер в інфрі синглтон, сервіс - ні
87
+ expect(app.result).toEqual([3, 1]);
88
+
89
+ await app.eventBus.push(new TestDomainEvent({test: 'data'}));
90
+ expect(app.eventBus.listenerCount('TestDomainEvent')).toBe(1);
91
+ await app.eventBus.publish();
92
+
93
+ expect((app.modules[0] as TestModule).events).toEqual(['data']);
94
+ });
95
+
96
+ test('bootstrap error', async () => {
97
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
98
+ await expect(() => bootstrap(new TestApplication(), <any>{})).rejects.toThrow('`appFolder` is required');
99
+ });
100
+ });
@@ -0,0 +1,87 @@
1
+ import {createContainer, InjectionMode, asValue, Lifetime, AwilixContainer} from 'awilix';
2
+ import {Application} from "./application/Application";
3
+
4
+ export interface BootstrapOptions {
5
+ appFolder: string;
6
+ domainsFolder: string;
7
+ infraFolder: string;
8
+ scanFolders?: string[]
9
+ }
10
+
11
+ export interface BaseBootstrapEnv {
12
+ app: Application;
13
+ env: string;
14
+ DIContainer: AwilixContainer<BaseBootstrapEnv>;
15
+ }
16
+
17
+ export
18
+ async function bootstrap(app: Application, { appFolder, domainsFolder, infraFolder, scanFolders }: BootstrapOptions, env: string = 'dev'):Promise<void> {
19
+ try {
20
+ if (!appFolder) {
21
+ throw new Error('`appFolder` is required')
22
+ }
23
+ if (!domainsFolder) {
24
+ throw new Error('`domainsFolder` is required')
25
+ }
26
+ if (!infraFolder) {
27
+ throw new Error('`infraFolder` is required')
28
+ }
29
+
30
+ const rootContainer = createContainer({
31
+ injectionMode: InjectionMode.PROXY
32
+ });
33
+ rootContainer.register({
34
+ app: asValue(app),
35
+ env: asValue(env),
36
+ });
37
+ const scope = rootContainer.createScope();
38
+ scope.register({
39
+ DIContainer: asValue(scope)
40
+ });
41
+ const folders = [];
42
+
43
+ // app folder
44
+ // folders.push(`${appFolder}/**/!(*.spec|*.d|index).{ts,js}`);
45
+
46
+ // domains folder
47
+ folders.push(`${domainsFolder}/**/!(*.spec|*.d).{ts,js}`);
48
+ folders.push(`${domainsFolder}/**/service/**/!(*.spec|*.d).{ts,js}`);
49
+
50
+ // infra folder
51
+ folders.push(`${infraFolder}/**/repository/**/!(*.spec|*.d).{ts,js}`);
52
+
53
+ // провайдери реєструються у глобальному контейнері як синглтони і потім через warmupSingletons створюються одразу
54
+ // щоб перевірити чи не використовують вони залежності з transient lifetime
55
+ rootContainer.loadModules([
56
+ [`${infraFolder}/**/providers/**/!(*.spec|*.d).{ts,js}`, { lifetime: Lifetime.SINGLETON }]
57
+ ], { resolverOptions: {} });
58
+ scope.loadModules([...folders, ...(scanFolders ?? [])], { resolverOptions: {} });
59
+
60
+ warmupSingletons(scope);
61
+ if (app.prepareApplication) {
62
+ await app.prepareApplication(scope.cradle);
63
+ }
64
+ await app.initApplication(scope.cradle);
65
+
66
+ if (app.postInitApplication) {
67
+ await app.postInitApplication(scope.cradle);
68
+ }
69
+ } catch (err) {
70
+ console.info(err);
71
+ throw err;
72
+ }
73
+
74
+ function warmupSingletons(container: AwilixContainer) {
75
+ const regs = container.registrations ?? {};
76
+ const names = Object.keys(regs);
77
+
78
+ for (const name of names) {
79
+ const r = regs[name];
80
+ // Awilix зберігає lifetime всередині resolver'а (зазвичай r.lifetime)
81
+ if (r?.lifetime === Lifetime.SINGLETON) {
82
+ // важливо: резолвимо саме з ROOT
83
+ container.resolve(name);
84
+ }
85
+ }
86
+ }
87
+ }
@@ -3,8 +3,13 @@ import { AnyDomainEvent } from '../DomainEvent';
3
3
  import { EventBus } from '../EventBus';
4
4
  import {DomainEventSubscriber} from "../DomainEventSubscriber";
5
5
 
6
- export class InMemoryEventBus extends EventEmitter implements EventBus {
6
+ export class InMemoryEventBus implements EventBus {
7
7
  protected _events: AnyDomainEvent[] = [];
8
+ protected _emitter: EventEmitter;
9
+
10
+ constructor() {
11
+ this._emitter = new EventEmitter();
12
+ }
8
13
 
9
14
  async push(event: AnyDomainEvent): Promise<void> {
10
15
  this._events.push(event);
@@ -17,16 +22,25 @@ export class InMemoryEventBus extends EventEmitter implements EventBus {
17
22
  async publish(events: AnyDomainEvent[] = []): Promise<void> {
18
23
  const allEvents = (this._events || []).concat(events ?? []);
19
24
  for (const event of allEvents) {
20
- this.emit(event.eventName, event);
25
+ this._emitter.emit(event.eventName, event);
21
26
  }
22
27
  await this.clear();
23
28
  }
24
29
 
25
- addSubscribers(subscribers: DomainEventSubscriber[]): void {
26
- for (const subscriber of subscribers) {
30
+ on(event: string, listener: (...args: never[]) => void): void {
31
+ this._emitter.on(event, listener);
32
+ }
33
+
34
+ listenerCount(event: string): number {
35
+ return this._emitter.listenerCount(event);
36
+ }
37
+
38
+ addSubscribers(subscribers: DomainEventSubscriber[] | DomainEventSubscriber): void {
39
+ const subsArray = Array.isArray(subscribers) ? subscribers : [subscribers];
40
+ for (const subscriber of subsArray) {
27
41
  const events = subscriber.subscribedTo();
28
42
  for (const event of events) {
29
- this.on(event.EVENT_NAME ?? event.name, subscriber.on.bind(subscriber));
43
+ this._emitter.on(event.EVENT_NAME ?? event.name, subscriber.on.bind(subscriber));
30
44
  }
31
45
  }
32
46
  }
@@ -0,0 +1,65 @@
1
+ import {createSnapshot} from "./helpers";
2
+ import {EntityId} from "./EntityId";
3
+ import {Entity} from "./Entity";
4
+
5
+ interface TestProps {
6
+ name: string;
7
+ testId?: EntityId<string>;
8
+ }
9
+ class TestEntity extends Entity<TestProps, string, EntityId<string>> {
10
+ static create(props: TestProps, id?:EntityId<string>|string): TestEntity {
11
+ return new TestEntity(props, id);
12
+ }
13
+
14
+ set name(name: string) {
15
+ this.props.name = name;
16
+ }
17
+
18
+ set testId(val: EntityId<string>) {
19
+ this.props.testId = val;
20
+ }
21
+
22
+ toSnapshot():never {
23
+ return {
24
+ ...this.props,
25
+ testId: undefined
26
+ } as never;
27
+ }
28
+ }
29
+
30
+ describe('helpers', () => {
31
+ test('createSnapshot', async () => {
32
+ const original = {
33
+ a: 1,
34
+ b: 'test',
35
+ c: {
36
+ d: 2,
37
+ e: 'nested',
38
+ f: {
39
+ g: 3
40
+ }
41
+ },
42
+ id: new EntityId<string>('1'),
43
+ };
44
+ const snapshot = createSnapshot(original);
45
+ expect(snapshot).toEqual({
46
+ ...original,
47
+ id: '3D4WX'
48
+ });
49
+ expect(snapshot).not.toBe(original);
50
+ expect(snapshot.c).not.toBe(original.c);
51
+ expect(snapshot.c.f).not.toBe(original.c.f);
52
+ expect(snapshot.id).not.toBe(original.id);
53
+ expect(snapshot.id).toEqual('3D4WX');
54
+
55
+ const item = TestEntity.create({
56
+ name: 'test',
57
+ testId: new EntityId('2')
58
+ }, '2');
59
+ const itemSnapshot = createSnapshot(item);
60
+ expect(itemSnapshot).toEqual({
61
+ name: 'test',
62
+ testId: undefined
63
+ });
64
+ });
65
+ });
package/src/helpers.ts ADDED
@@ -0,0 +1,32 @@
1
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2
+ export function createSnapshot<T>(obj: T): T {
3
+ if (!obj) {
4
+ return obj;
5
+ }
6
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
7
+ // @ts-ignore
8
+ if (typeof obj.toSnapshot === 'function') {
9
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
10
+ // @ts-ignore
11
+ return obj.toSnapshot() as T;
12
+ }
13
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
14
+ // @ts-ignore
15
+ if (typeof obj.toJSON === 'function') {
16
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
17
+ // @ts-ignore
18
+ return obj.toJSON() as T;
19
+ }
20
+ const copy:T = {} as T;
21
+ for (const key in obj) {
22
+ if (!Object.prototype.hasOwnProperty.call(obj, key)) {
23
+ continue;
24
+ }
25
+ if (typeof obj[key] !== 'object') {
26
+ copy[key] = obj[key];
27
+ continue;
28
+ }
29
+ copy[key] = createSnapshot(obj[key]);
30
+ }
31
+ return copy;
32
+ }
package/src/index.ts CHANGED
@@ -3,11 +3,27 @@ import { EntityId } from './EntityId';
3
3
  import { ValueObject } from './ValueObject';
4
4
  import { AggregateRoot } from './AggregateRoot';
5
5
  import { HashError } from './error/HashError';
6
+ import { DomainEvent } from './DomainEvent';
7
+ import { DomainEventSubscriber } from './DomainEventSubscriber';
8
+ import { EventBus } from './EventBus';
9
+ import { Eventable } from './mixins/Eventable';
10
+ import { Application } from './application/Application';
11
+ import { ApplicationModule } from './application/ApplicationModule';
12
+ import { bootstrap } from './bootstrap';
6
13
 
7
14
  export {
8
15
  Entity,
9
16
  EntityId,
10
17
  ValueObject,
11
18
  AggregateRoot,
12
- HashError
19
+ HashError,
20
+ DomainEvent,
21
+ DomainEventSubscriber,
22
+ EventBus,
23
+ Eventable,
24
+
25
+ Application,
26
+ ApplicationModule,
27
+
28
+ bootstrap
13
29
  }
@@ -0,0 +1,4 @@
1
+ export default class TestService {
2
+ value: number;
3
+ inc(): void;
4
+ }
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ var TestService = /** @class */ (function () {
4
+ function TestService() {
5
+ this.value = 0;
6
+ }
7
+ TestService.prototype.inc = function () {
8
+ this.value += 1;
9
+ };
10
+ return TestService;
11
+ }());
12
+ exports.default = TestService;
13
+ //# sourceMappingURL=TestService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TestService.js","sourceRoot":"","sources":["TestService.ts"],"names":[],"mappings":";;AAAA;IAAA;QAES,UAAK,GAAG,CAAC,CAAC;IAKnB,CAAC;IAHC,yBAAG,GAAH;QACE,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC;IAClB,CAAC;IACH,kBAAC;AAAD,CAAC,AAPD,IAOC"}
@@ -0,0 +1,8 @@
1
+ export default
2
+ class TestService {
3
+ public value = 0;
4
+
5
+ inc() {
6
+ this.value += 1;
7
+ }
8
+ }
@@ -0,0 +1,4 @@
1
+ export default class TestProvider {
2
+ value: number;
3
+ inc(): void;
4
+ }