nest-eventsource-kit 1.0.0 → 1.1.0
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/CHANGELOG.md +14 -0
- package/CONTRIBUTING.md +14 -0
- package/dist/base/aggregate-root.d.ts +11 -0
- package/dist/base/aggregate-root.js +26 -0
- package/dist/handlers/command-handler.interface.d.ts +6 -0
- package/dist/handlers/command-handler.interface.js +2 -0
- package/dist/handlers/query-handler.interface.d.ts +6 -0
- package/dist/handlers/query-handler.interface.js +2 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/services/event-bus.service.d.ts +9 -0
- package/dist/services/event-bus.service.js +78 -0
- package/dist/services/event-bus.service.spec.d.ts +1 -0
- package/dist/services/event-bus.service.spec.js +38 -0
- package/package.json +1 -1
- package/jest.config.js +0 -7
- package/src/event-sourcing.module.ts +0 -14
- package/src/index.ts +0 -3
- package/src/interfaces/event.interface.ts +0 -12
- package/src/services/event-store.service.spec.ts +0 -30
- package/src/services/event-store.service.ts +0 -15
- package/tsconfig.json +0 -10
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [1.1.0] - 2026-01-31
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Aggregate Root base class
|
|
7
|
+
- Event Bus implementation
|
|
8
|
+
- Command and Query handler interfaces
|
|
9
|
+
- Docker and docker-compose setup
|
|
10
|
+
- CI/CD pipeline
|
|
11
|
+
- Additional tests
|
|
12
|
+
|
|
13
|
+
## [1.0.0] - 2026-01-31
|
|
14
|
+
- Initial release
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { DomainEvent } from '../interfaces/event.interface';
|
|
2
|
+
export declare abstract class AggregateRoot {
|
|
3
|
+
private uncommittedEvents;
|
|
4
|
+
protected version: number;
|
|
5
|
+
abstract get aggregateId(): string;
|
|
6
|
+
protected applyEvent(event: DomainEvent): void;
|
|
7
|
+
getUncommittedEvents(): DomainEvent[];
|
|
8
|
+
clearUncommittedEvents(): void;
|
|
9
|
+
loadFromHistory(events: DomainEvent[]): void;
|
|
10
|
+
protected abstract apply(event: DomainEvent): void;
|
|
11
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AggregateRoot = void 0;
|
|
4
|
+
class AggregateRoot {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.uncommittedEvents = [];
|
|
7
|
+
this.version = 0;
|
|
8
|
+
}
|
|
9
|
+
applyEvent(event) {
|
|
10
|
+
this.uncommittedEvents.push(event);
|
|
11
|
+
this.version++;
|
|
12
|
+
}
|
|
13
|
+
getUncommittedEvents() {
|
|
14
|
+
return [...this.uncommittedEvents];
|
|
15
|
+
}
|
|
16
|
+
clearUncommittedEvents() {
|
|
17
|
+
this.uncommittedEvents = [];
|
|
18
|
+
}
|
|
19
|
+
loadFromHistory(events) {
|
|
20
|
+
events.forEach(event => {
|
|
21
|
+
this.apply(event);
|
|
22
|
+
this.version = event.version;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.AggregateRoot = AggregateRoot;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
export * from './event-sourcing.module';
|
|
2
2
|
export * from './services/event-store.service';
|
|
3
|
+
export * from './services/event-bus.service';
|
|
3
4
|
export * from './interfaces/event.interface';
|
|
5
|
+
export * from './base/aggregate-root';
|
|
6
|
+
export * from './handlers/command-handler.interface';
|
|
7
|
+
export * from './handlers/query-handler.interface';
|
package/dist/index.js
CHANGED
|
@@ -16,4 +16,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./event-sourcing.module"), exports);
|
|
18
18
|
__exportStar(require("./services/event-store.service"), exports);
|
|
19
|
+
__exportStar(require("./services/event-bus.service"), exports);
|
|
19
20
|
__exportStar(require("./interfaces/event.interface"), exports);
|
|
21
|
+
__exportStar(require("./base/aggregate-root"), exports);
|
|
22
|
+
__exportStar(require("./handlers/command-handler.interface"), exports);
|
|
23
|
+
__exportStar(require("./handlers/query-handler.interface"), exports);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { DomainEvent } from '../interfaces/event.interface';
|
|
2
|
+
type EventHandler = (event: DomainEvent) => void | Promise<void>;
|
|
3
|
+
export declare class EventBusService {
|
|
4
|
+
private handlers;
|
|
5
|
+
subscribe(eventType: string, handler: EventHandler): void;
|
|
6
|
+
publish(event: DomainEvent): Promise<void>;
|
|
7
|
+
publishAll(events: DomainEvent[]): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
3
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
4
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
5
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
6
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
7
|
+
var _, done = false;
|
|
8
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
9
|
+
var context = {};
|
|
10
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
11
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
12
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
13
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
14
|
+
if (kind === "accessor") {
|
|
15
|
+
if (result === void 0) continue;
|
|
16
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
17
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
18
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
19
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
20
|
+
}
|
|
21
|
+
else if (_ = accept(result)) {
|
|
22
|
+
if (kind === "field") initializers.unshift(_);
|
|
23
|
+
else descriptor[key] = _;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
27
|
+
done = true;
|
|
28
|
+
};
|
|
29
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
30
|
+
var useValue = arguments.length > 2;
|
|
31
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
32
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
33
|
+
}
|
|
34
|
+
return useValue ? value : void 0;
|
|
35
|
+
};
|
|
36
|
+
var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) {
|
|
37
|
+
if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
|
|
38
|
+
return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
|
|
39
|
+
};
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.EventBusService = void 0;
|
|
42
|
+
const common_1 = require("@nestjs/common");
|
|
43
|
+
let EventBusService = (() => {
|
|
44
|
+
let _classDecorators = [(0, common_1.Injectable)()];
|
|
45
|
+
let _classDescriptor;
|
|
46
|
+
let _classExtraInitializers = [];
|
|
47
|
+
let _classThis;
|
|
48
|
+
var EventBusService = _classThis = class {
|
|
49
|
+
constructor() {
|
|
50
|
+
this.handlers = new Map();
|
|
51
|
+
}
|
|
52
|
+
subscribe(eventType, handler) {
|
|
53
|
+
if (!this.handlers.has(eventType)) {
|
|
54
|
+
this.handlers.set(eventType, []);
|
|
55
|
+
}
|
|
56
|
+
this.handlers.get(eventType).push(handler);
|
|
57
|
+
}
|
|
58
|
+
async publish(event) {
|
|
59
|
+
const handlers = this.handlers.get(event.eventType) || [];
|
|
60
|
+
await Promise.all(handlers.map(handler => handler(event)));
|
|
61
|
+
}
|
|
62
|
+
async publishAll(events) {
|
|
63
|
+
for (const event of events) {
|
|
64
|
+
await this.publish(event);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
__setFunctionName(_classThis, "EventBusService");
|
|
69
|
+
(() => {
|
|
70
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
71
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
72
|
+
EventBusService = _classThis = _classDescriptor.value;
|
|
73
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
74
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
75
|
+
})();
|
|
76
|
+
return EventBusService = _classThis;
|
|
77
|
+
})();
|
|
78
|
+
exports.EventBusService = EventBusService;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const event_bus_service_1 = require("./event-bus.service");
|
|
4
|
+
describe('EventBusService', () => {
|
|
5
|
+
let service;
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
service = new event_bus_service_1.EventBusService();
|
|
8
|
+
});
|
|
9
|
+
it('should publish events to subscribers', async () => {
|
|
10
|
+
const handler = jest.fn();
|
|
11
|
+
service.subscribe('TestEvent', handler);
|
|
12
|
+
const event = {
|
|
13
|
+
aggregateId: 'test-1',
|
|
14
|
+
eventType: 'TestEvent',
|
|
15
|
+
data: {},
|
|
16
|
+
timestamp: new Date(),
|
|
17
|
+
version: 1,
|
|
18
|
+
};
|
|
19
|
+
await service.publish(event);
|
|
20
|
+
expect(handler).toHaveBeenCalledWith(event);
|
|
21
|
+
});
|
|
22
|
+
it('should publish to multiple handlers', async () => {
|
|
23
|
+
const handler1 = jest.fn();
|
|
24
|
+
const handler2 = jest.fn();
|
|
25
|
+
service.subscribe('TestEvent', handler1);
|
|
26
|
+
service.subscribe('TestEvent', handler2);
|
|
27
|
+
const event = {
|
|
28
|
+
aggregateId: 'test-1',
|
|
29
|
+
eventType: 'TestEvent',
|
|
30
|
+
data: {},
|
|
31
|
+
timestamp: new Date(),
|
|
32
|
+
version: 1,
|
|
33
|
+
};
|
|
34
|
+
await service.publish(event);
|
|
35
|
+
expect(handler1).toHaveBeenCalled();
|
|
36
|
+
expect(handler2).toHaveBeenCalled();
|
|
37
|
+
});
|
|
38
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nest-eventsource-kit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Event Sourcing and CQRS toolkit for NestJS with support for event store, command/query separation, and saga patterns",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
package/jest.config.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { Module, DynamicModule } from '@nestjs/common';
|
|
2
|
-
import { EventStoreService } from './services/event-store.service';
|
|
3
|
-
|
|
4
|
-
@Module({})
|
|
5
|
-
export class EventSourcingModule {
|
|
6
|
-
static forRoot(): DynamicModule {
|
|
7
|
-
return {
|
|
8
|
-
module: EventSourcingModule,
|
|
9
|
-
providers: [EventStoreService],
|
|
10
|
-
exports: [EventStoreService],
|
|
11
|
-
global: true,
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export interface DomainEvent {
|
|
2
|
-
aggregateId: string;
|
|
3
|
-
eventType: string;
|
|
4
|
-
data: any;
|
|
5
|
-
timestamp: Date;
|
|
6
|
-
version: number;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface EventStore {
|
|
10
|
-
append(event: DomainEvent): Promise<void>;
|
|
11
|
-
getEvents(aggregateId: string): Promise<DomainEvent[]>;
|
|
12
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { Test } from '@nestjs/testing';
|
|
2
|
-
import { EventStoreService } from './event-store.service';
|
|
3
|
-
|
|
4
|
-
describe('EventStoreService', () => {
|
|
5
|
-
let service: EventStoreService;
|
|
6
|
-
|
|
7
|
-
beforeEach(async () => {
|
|
8
|
-
const module = await Test.createTestingModule({
|
|
9
|
-
providers: [EventStoreService],
|
|
10
|
-
}).compile();
|
|
11
|
-
|
|
12
|
-
service = module.get<EventStoreService>(EventStoreService);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('should append and retrieve events', async () => {
|
|
16
|
-
const event = {
|
|
17
|
-
aggregateId: 'test-1',
|
|
18
|
-
eventType: 'TestEvent',
|
|
19
|
-
data: { value: 42 },
|
|
20
|
-
timestamp: new Date(),
|
|
21
|
-
version: 1,
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
await service.append(event);
|
|
25
|
-
const events = await service.getEvents('test-1');
|
|
26
|
-
|
|
27
|
-
expect(events).toHaveLength(1);
|
|
28
|
-
expect(events[0]).toEqual(event);
|
|
29
|
-
});
|
|
30
|
-
});
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { Injectable } from '@nestjs/common';
|
|
2
|
-
import { DomainEvent, EventStore } from '../interfaces/event.interface';
|
|
3
|
-
|
|
4
|
-
@Injectable()
|
|
5
|
-
export class EventStoreService implements EventStore {
|
|
6
|
-
private events: DomainEvent[] = [];
|
|
7
|
-
|
|
8
|
-
async append(event: DomainEvent): Promise<void> {
|
|
9
|
-
this.events.push(event);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
async getEvents(aggregateId: string): Promise<DomainEvent[]> {
|
|
13
|
-
return this.events.filter(e => e.aggregateId === aggregateId);
|
|
14
|
-
}
|
|
15
|
-
}
|