@evyweb/simple-ddd-toolkit
Version:
A simple Typescript Domain Driven Design Toolkit to help you create your aggregates, domain events, command handlers and other stuff.
352 lines (334 loc) • 8.36 kB
JavaScript
// src/entity/Entity.ts
var Entity = class {
data;
constructor(entityData) {
this.data = entityData;
}
id() {
return this.data.id.get("value");
}
get(key) {
return this.data[key];
}
set(key, value) {
this.data[key] = value;
}
equals(other) {
return this.data.id.get("value") === other.data.id.get("value");
}
isNew() {
return this.get("id").isNew();
}
toObject() {
return Object.freeze(this.data);
}
};
// src/aggregate/Aggregate.ts
var Aggregate = class extends Entity {
domainEvents = [];
addEvent(domainEvent) {
this.domainEvents.push(domainEvent);
}
clearEvents() {
this.domainEvents = [];
}
async dispatchEvents(bus) {
for (const event of this.domainEvents) {
await bus.dispatch(event);
}
this.clearEvents();
}
dispatchEventsAsync(bus) {
setImmediate(async () => {
await this.dispatchEvents(bus);
});
}
getEvents() {
const eventsToDispatch = [...this.domainEvents];
this.clearEvents();
return eventsToDispatch;
}
};
// src/bus/Bus.ts
var Bus = class {
handlers = /* @__PURE__ */ new Map();
middlewares = [];
register(handlerFactory) {
const handler = handlerFactory();
if (!handler.__TAG) {
throw new Error("The handler must have a __TAG property to be registered.");
}
this.handlers.set(handler.__TAG, handlerFactory);
}
use(middleware) {
this.middlewares.push(middleware);
}
async execute(message) {
const handlerName = `${message.__TAG}Handler`;
const handlerFactory = this.handlers.get(handlerName);
if (!handlerFactory) {
throw new Error(
`No handler registered for ${message.__TAG}. Please check the __TAG property of both command and handler.`
);
}
const handler = handlerFactory();
const executeHandler = (finalMessage) => {
return handler.handle(finalMessage);
};
const middlewareChain = this.middlewares.reduceRight(
(next, middleware) => (msg) => middleware.execute(msg, next),
executeHandler
);
return middlewareChain(message);
}
};
// src/bus/command/Command.ts
var Command = class {
};
// src/bus/command/CommandHandler.ts
var CommandHandler = class {
};
// src/bus/middleware/CommandLoggerMiddleware.ts
var CommandLoggerMiddleware = class {
constructor(logger, middlewareId) {
this.logger = logger;
this.middlewareId = middlewareId;
}
async execute(command, next) {
const date = (/* @__PURE__ */ new Date()).toISOString();
this.logger.log(`[${date}][${this.middlewareId}][${command.__TAG}] - ${JSON.stringify(command)}`);
return next(command);
}
};
// src/bus/middleware/QueryLoggerMiddleware.ts
var QueryLoggerMiddleware = class {
constructor(logger, middlewareId) {
this.logger = logger;
this.middlewareId = middlewareId;
}
execute(query, next) {
const date = (/* @__PURE__ */ new Date()).toISOString();
this.logger.log(`[${date}][${this.middlewareId}][${query.__TAG}] - ${JSON.stringify(query)}`);
return next(query);
}
};
// src/bus/query/Query.ts
var Query = class {
};
// src/bus/query/QueryHandler.ts
var QueryHandler = class {
};
// src/domainEvent/DomainEvent.ts
var DomainEvent = class {
eventId;
occurredOn;
payload;
metadata;
constructor(eventData) {
this.eventId = eventData.eventId;
this.occurredOn = eventData?.occurredOn || /* @__PURE__ */ new Date();
this.metadata = eventData?.metadata || {};
this.payload = eventData?.payload || {};
}
};
// src/errors/CustomError.ts
var CustomError = class extends Error {
constructor(message) {
super(message);
}
};
// src/errors/DomainError.ts
var DomainError = class extends CustomError {
isDomainError() {
return true;
}
isTechnicalError() {
return false;
}
};
// src/errors/TechnicalError.ts
var TechnicalError = class extends CustomError {
isDomainError() {
return false;
}
isTechnicalError() {
return true;
}
};
// src/eventBus/EventBus.ts
var EventBus = class {
middlewares = [];
handlers = {};
use(middleware) {
this.middlewares.push(middleware);
}
on(eventType, handler) {
if (!this.handlers[eventType]) {
this.handlers[eventType] = [];
}
this.handlers[eventType].push(handler);
}
async dispatch(domainEvent) {
const handlers = this.handlers[domainEvent.__TAG];
const executeHandlers = async (event) => {
if (!handlers) return;
for (const handlerFactory of handlers) {
const handler = handlerFactory();
await handler.handle(event);
}
};
if (!handlers || handlers.length === 0) {
await executeHandlers(domainEvent);
return;
}
const middlewareChain = this.middlewares.reduceRight((next, middleware) => {
return async (event) => {
await middleware.execute(event, next);
};
}, executeHandlers);
await middlewareChain(domainEvent);
}
async dispatchEvents(events) {
for (const event of events) {
await this.dispatch(event);
}
}
dispatchEventsAsync(events) {
setImmediate(async () => {
for (const event of events) {
await this.dispatch(event);
}
});
}
};
// src/result/Result.ts
var Result = class _Result {
result;
constructor(result) {
this.result = result;
}
static ok(value) {
return new _Result({ _tag: "success", value });
}
static fail(error) {
return new _Result({ _tag: "failure", error });
}
isOk() {
return this.result._tag === "success";
}
isFail() {
return this.result._tag === "failure";
}
getValue() {
if (this.result._tag !== "success") {
throw new Error("Attempted to access 'value' of a failure result.");
}
return this.result.value;
}
getError() {
if (this.result._tag !== "failure") {
throw new Error("Attempted to access 'error' of a success result.");
}
return this.result.error;
}
};
// src/valueObject/ValueObject.ts
var ValueObject = class {
data;
constructor(data) {
this.data = this.deepFreeze(data);
}
equals(other) {
if (!other) {
return false;
}
return this.reflectiveEqual(this.data, other.data);
}
get(key) {
return this.data[key];
}
reflectiveEqual(obj1, obj2) {
if (typeof obj1 !== "object" || typeof obj2 !== "object" || obj1 === null || obj2 === null) {
return obj1 === obj2;
}
const obj1Keys = Reflect.ownKeys(obj1);
const obj2Keys = Reflect.ownKeys(obj2);
if (obj1Keys.length !== obj2Keys.length) {
return false;
}
return obj1Keys.every((key) => {
const val1 = Reflect.get(obj1, key);
const val2 = Reflect.get(obj2, key);
if (typeof val1 === "object" && val1 !== null && typeof val2 === "object" && val2 !== null) {
return this.reflectiveEqual(val1, val2);
}
return val1 === val2;
});
}
deepFreeze(object) {
if (typeof object !== "object" || object === null) {
return object;
}
const propNames = Object.getOwnPropertyNames(object);
for (const name of propNames) {
const property = object[name];
if (property && typeof property === "object" && !Object.isFrozen(property)) {
this.deepFreeze(property);
}
}
return Object.freeze(object);
}
};
// src/valueObject/uuid/UUID.ts
var UUID = class _UUID extends ValueObject {
static UUID_PATTERN = /^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$/;
static create(value, isNew = true) {
if (!_UUID.isValid(value)) {
throw new Error("Invalid UUID");
}
return new _UUID({
value,
isNew
});
}
static createFrom(value) {
return _UUID.create(value, false);
}
equals(other) {
return this.get("value") === other.get("value");
}
static isValid(uuid) {
return uuid.match(_UUID.UUID_PATTERN) !== null;
}
isNew() {
return this.get("isNew");
}
};
// src/valueObject/uuid/UUIDFactory.ts
function Uuid(value, isNew = true) {
return UUID.create(value, isNew);
}
function UuidFrom(value) {
return UUID.createFrom(value);
}
export {
Aggregate,
Bus,
Command,
CommandHandler,
CommandLoggerMiddleware,
CustomError,
DomainError,
DomainEvent,
Entity,
EventBus,
Query,
QueryHandler,
QueryLoggerMiddleware,
Result,
TechnicalError,
UUID,
Uuid,
UuidFrom,
ValueObject
};