UNPKG

@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
// 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 };