@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.
- package/.eslintignore +1 -0
- package/build/Entity.d.ts +2 -1
- package/build/Entity.js +9 -2
- package/build/Entity.js.map +1 -1
- package/build/Entity.spec.js +9 -2
- package/build/Entity.spec.js.map +1 -1
- package/build/EntityId.d.ts +1 -0
- package/build/EntityId.js +3 -0
- package/build/EntityId.js.map +1 -1
- package/build/application/Application.d.ts +10 -0
- package/build/application/Application.js +43 -0
- package/build/application/Application.js.map +1 -0
- package/build/application/ApplicationModule.d.ts +5 -0
- package/build/application/ApplicationModule.js +10 -0
- package/build/application/ApplicationModule.js.map +1 -0
- package/build/bootstrap.d.ts +14 -0
- package/build/bootstrap.js +88 -0
- package/build/bootstrap.js.map +1 -0
- package/build/bootstrap.spec.d.ts +1 -0
- package/build/bootstrap.spec.js +144 -0
- package/build/bootstrap.spec.js.map +1 -0
- package/build/eventBus/InMemoryEventBus.d.ts +6 -2
- package/build/eventBus/InMemoryEventBus.js +15 -10
- package/build/eventBus/InMemoryEventBus.js.map +1 -1
- package/build/helpers.d.ts +1 -0
- package/build/helpers.js +37 -0
- package/build/helpers.js.map +1 -0
- package/build/helpers.spec.d.ts +1 -0
- package/build/helpers.spec.js +70 -0
- package/build/helpers.spec.js.map +1 -0
- package/build/index.d.ts +8 -1
- package/build/index.js +11 -1
- package/build/index.js.map +1 -1
- package/package.json +2 -1
- package/src/Entity.spec.ts +7 -2
- package/src/Entity.ts +12 -4
- package/src/EntityId.ts +4 -0
- package/src/application/Application.ts +24 -0
- package/src/application/ApplicationModule.ts +7 -0
- package/src/bootstrap.spec.ts +100 -0
- package/src/bootstrap.ts +87 -0
- package/src/eventBus/InMemoryEventBus.ts +19 -5
- package/src/helpers.spec.ts +65 -0
- package/src/helpers.ts +32 -0
- package/src/index.ts +17 -1
- package/tests/testapp/domain/test/service/TestService.d.ts +4 -0
- package/tests/testapp/domain/test/service/TestService.js +13 -0
- package/tests/testapp/domain/test/service/TestService.js.map +1 -0
- package/tests/testapp/domain/test/service/TestService.ts +8 -0
- package/tests/testapp/infra/test/providers/TestProvider.d.ts +4 -0
- package/tests/testapp/infra/test/providers/TestProvider.js +13 -0
- package/tests/testapp/infra/test/providers/TestProvider.js.map +1 -0
- package/tests/testapp/infra/test/providers/TestProvider.ts +8 -0
- 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
|
-
|
|
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
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,mCAAkC;
|
|
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.
|
|
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"
|
package/src/Entity.spec.ts
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
74
|
-
|
|
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
|
@@ -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,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
|
+
});
|
package/src/bootstrap.ts
ADDED
|
@@ -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
|
|
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
|
-
|
|
26
|
-
|
|
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,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"}
|