@archi-code/ddd 0.0.0-stage → 3.0.0-alpha.2
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/LICENSE +46 -0
- package/README.md +2795 -2
- package/SKILL.md +106 -0
- package/ddd-skills/archi-code-ddd/imports.md +94 -0
- package/ddd-skills/archi-code-ddd/layers.md +314 -0
- package/ddd-skills/archi-code-ddd/patterns.md +683 -0
- package/ddd-skills/archi-code-ddd/what-not-to-do.md +196 -0
- package/ddd-skills/archi-code-ddd/where-each-class-goes.md +64 -0
- package/ddd-skills/ddd-agregados/GUIA.md +114 -0
- package/ddd-skills/ddd-agregados/references/consistencia-eventual.md +158 -0
- package/ddd-skills/ddd-agregados/references/ejemplo-ecommerce.md +52 -0
- package/ddd-skills/ddd-agregados/references/fabricas.md +193 -0
- package/ddd-skills/ddd-agregados/references/implementacion-ts.md +706 -0
- package/ddd-skills/ddd-arquitectura/GUIA.md +80 -0
- package/ddd-skills/ddd-arquitectura/assets/dependency-cruiser.cjs +99 -0
- package/ddd-skills/ddd-arquitectura/references/estilos-adicionales.md +51 -0
- package/ddd-skills/ddd-arquitectura/references/estructura-hexagonal-ts.md +406 -0
- package/ddd-skills/ddd-arquitectura/references/modulos.md +48 -0
- package/ddd-skills/ddd-auditoria-refactorizacion/GUIA.md +85 -0
- package/ddd-skills/ddd-auditoria-refactorizacion/references/ejemplo-auditoria.md +314 -0
- package/ddd-skills/ddd-auditoria-refactorizacion/references/lista-comprobacion.md +54 -0
- package/ddd-skills/ddd-auditoria-refactorizacion/references/plan-migracion.md +73 -0
- package/ddd-skills/ddd-auditoria-refactorizacion/scripts/scan_ddd_smells.py +264 -0
- package/ddd-skills/ddd-context-mapping/GUIA.md +83 -0
- package/ddd-skills/ddd-context-mapping/references/anticorrupcion-ts.md +419 -0
- package/ddd-skills/ddd-context-mapping/references/estilos-integracion.md +152 -0
- package/ddd-skills/ddd-event-sourcing-cqrs/GUIA.md +92 -0
- package/ddd-skills/ddd-event-sourcing-cqrs/references/event-sourcing-ts.md +476 -0
- package/ddd-skills/ddd-event-sourcing-cqrs/references/proyecciones-cqrs-ts.md +285 -0
- package/ddd-skills/ddd-eventos-dominio/GUIA.md +106 -0
- package/ddd-skills/ddd-eventos-dominio/references/diseno-eventos.md +130 -0
- package/ddd-skills/ddd-eventos-dominio/references/ejemplo-ecommerce.md +240 -0
- package/ddd-skills/ddd-eventos-dominio/references/publicacion-ts.md +529 -0
- package/ddd-skills/ddd-orquestador/GUIA.md +40 -0
- package/ddd-skills/ddd-orquestador/references/chuleta-reglas.md +35 -0
- package/ddd-skills/ddd-repositorios-persistencia/GUIA.md +121 -0
- package/ddd-skills/ddd-repositorios-persistencia/references/implementacion-ts.md +314 -0
- package/ddd-skills/ddd-repositorios-persistencia/references/tests-repositorios.md +191 -0
- package/ddd-skills/ddd-repositorios-persistencia/references/transacciones-ts.md +301 -0
- package/ddd-skills/ddd-servicios-capa-aplicacion/GUIA.md +99 -0
- package/ddd-skills/ddd-servicios-capa-aplicacion/references/capa-aplicacion-ts.md +264 -0
- package/ddd-skills/ddd-servicios-capa-aplicacion/references/ejemplo-ecommerce.md +321 -0
- package/ddd-skills/ddd-servicios-capa-aplicacion/references/servicios-dominio-ts.md +190 -0
- package/ddd-skills/ddd-value-objects-entidades/GUIA.md +118 -0
- package/ddd-skills/ddd-value-objects-entidades/references/ejemplo-ecommerce.md +87 -0
- package/ddd-skills/ddd-value-objects-entidades/references/entidades-ts.md +218 -0
- package/ddd-skills/ddd-value-objects-entidades/references/persistencia-valores.md +88 -0
- package/ddd-skills/ddd-value-objects-entidades/references/specification-y-diseno-flexible.md +119 -0
- package/ddd-skills/ddd-value-objects-entidades/references/validacion.md +96 -0
- package/ddd-skills/ddd-value-objects-entidades/references/value-objects-ts.md +376 -0
- package/dist/cjs/application/event/event-bus.d.ts +4 -0
- package/dist/cjs/application/event/event-bus.js +2 -0
- package/dist/cjs/application/event/event-listener.d.ts +4 -0
- package/dist/cjs/application/event/event-listener.js +6 -0
- package/dist/cjs/application/event/listens-to.d.ts +15 -0
- package/dist/cjs/application/event/listens-to.js +28 -0
- package/dist/cjs/application/exceptions/aggregate-not-found.exception.d.ts +5 -0
- package/dist/cjs/application/exceptions/aggregate-not-found.exception.js +17 -0
- package/dist/cjs/application/exceptions/application-exception-code.d.ts +7 -0
- package/dist/cjs/application/exceptions/application-exception-code.js +11 -0
- package/dist/cjs/application/exceptions/application.exception.d.ts +6 -0
- package/dist/cjs/application/exceptions/application.exception.js +16 -0
- package/dist/cjs/application/exceptions/concurrency.exception.d.ts +5 -0
- package/dist/cjs/application/exceptions/concurrency.exception.js +17 -0
- package/dist/cjs/application/exceptions/not-allowed.exception.d.ts +5 -0
- package/dist/cjs/application/exceptions/not-allowed.exception.js +17 -0
- package/dist/cjs/application/exceptions/unexpected-error.exception.d.ts +5 -0
- package/dist/cjs/application/exceptions/unexpected-error.exception.js +17 -0
- package/dist/cjs/application/index.d.ts +14 -0
- package/dist/cjs/application/index.js +23 -0
- package/dist/cjs/application/ports/clock.d.ts +3 -0
- package/dist/cjs/application/ports/clock.js +2 -0
- package/dist/cjs/application/ports/logger.d.ts +8 -0
- package/dist/cjs/application/ports/logger.js +2 -0
- package/dist/cjs/application/use-case/use-case.d.ts +6 -0
- package/dist/cjs/application/use-case/use-case.js +25 -0
- package/dist/cjs/application/value-object-builder/value-object-builder.d.ts +16 -0
- package/dist/cjs/application/value-object-builder/value-object-builder.js +51 -0
- package/dist/cjs/domain/aggregate/aggregate-root.d.ts +9 -0
- package/dist/cjs/domain/aggregate/aggregate-root.js +17 -0
- package/dist/cjs/domain/entity/entity.d.ts +9 -0
- package/dist/cjs/domain/entity/entity.js +19 -0
- package/dist/cjs/domain/event/event-base.d.ts +23 -0
- package/dist/cjs/domain/event/event-base.js +21 -0
- package/dist/cjs/domain/exceptions/domain-exception-code.d.ts +7 -0
- package/dist/cjs/domain/exceptions/domain-exception-code.js +11 -0
- package/dist/cjs/domain/exceptions/domain.exception.d.ts +6 -0
- package/dist/cjs/domain/exceptions/domain.exception.js +16 -0
- package/dist/cjs/domain/exceptions/invalid-event-definition.exception.d.ts +4 -0
- package/dist/cjs/domain/exceptions/invalid-event-definition.exception.js +19 -0
- package/dist/cjs/domain/exceptions/required-value.exception.d.ts +4 -0
- package/dist/cjs/domain/exceptions/required-value.exception.js +20 -0
- package/dist/cjs/domain/exceptions/type-primitive.exception.d.ts +4 -0
- package/dist/cjs/domain/exceptions/type-primitive.exception.js +20 -0
- package/dist/cjs/domain/exceptions/validation.exception.d.ts +14 -0
- package/dist/cjs/domain/exceptions/validation.exception.js +23 -0
- package/dist/cjs/domain/index.d.ts +17 -0
- package/dist/cjs/domain/index.js +36 -0
- package/dist/cjs/domain/primitive/primitive-types.d.ts +20 -0
- package/dist/cjs/domain/primitive/primitive-types.js +2 -0
- package/dist/cjs/domain/repository/aggregate-finder.d.ts +4 -0
- package/dist/cjs/domain/repository/aggregate-finder.js +2 -0
- package/dist/cjs/domain/repository/aggregate-remover.d.ts +4 -0
- package/dist/cjs/domain/repository/aggregate-remover.js +2 -0
- package/dist/cjs/domain/repository/aggregate-saver.d.ts +4 -0
- package/dist/cjs/domain/repository/aggregate-saver.js +2 -0
- package/dist/cjs/domain/specification/specification.d.ts +9 -0
- package/dist/cjs/domain/specification/specification.js +44 -0
- package/dist/cjs/domain/type/abstract-type.d.ts +18 -0
- package/dist/cjs/domain/type/abstract-type.js +61 -0
- package/dist/cjs/domain/type/array-type.d.ts +17 -0
- package/dist/cjs/domain/type/array-type.js +64 -0
- package/dist/cjs/domain/type/boolean-type.d.ts +4 -0
- package/dist/cjs/domain/type/boolean-type.js +16 -0
- package/dist/cjs/domain/type/composite/composite-brand.d.ts +4 -0
- package/dist/cjs/domain/type/composite/composite-brand.js +15 -0
- package/dist/cjs/domain/type/composite/composite-field.d.ts +18 -0
- package/dist/cjs/domain/type/composite/composite-field.js +86 -0
- package/dist/cjs/domain/type/composite/composite-issues.d.ts +6 -0
- package/dist/cjs/domain/type/composite/composite-issues.js +35 -0
- package/dist/cjs/domain/type/composite/composite.d.ts +26 -0
- package/dist/cjs/domain/type/composite/composite.js +51 -0
- package/dist/cjs/domain/type/date-type.d.ts +19 -0
- package/dist/cjs/domain/type/date-type.js +70 -0
- package/dist/cjs/domain/type/enum-type.d.ts +7 -0
- package/dist/cjs/domain/type/enum-type.js +30 -0
- package/dist/cjs/domain/type/id-type.d.ts +7 -0
- package/dist/cjs/domain/type/id-type.js +17 -0
- package/dist/cjs/domain/type/index.d.ts +15 -0
- package/dist/cjs/domain/type/index.js +29 -0
- package/dist/cjs/domain/type/json-type.d.ts +9 -0
- package/dist/cjs/domain/type/json-type.js +46 -0
- package/dist/cjs/domain/type/number-type.d.ts +4 -0
- package/dist/cjs/domain/type/number-type.js +16 -0
- package/dist/cjs/domain/type/required/required-mark.d.ts +8 -0
- package/dist/cjs/domain/type/required/required-mark.js +13 -0
- package/dist/cjs/domain/type/required/required-type.d.ts +7 -0
- package/dist/cjs/domain/type/required/required-type.js +21 -0
- package/dist/cjs/domain/type/string-type.d.ts +4 -0
- package/dist/cjs/domain/type/string-type.js +16 -0
- package/dist/cjs/domain/type/uuid-type.d.ts +7 -0
- package/dist/cjs/domain/type/uuid-type.js +27 -0
- package/dist/cjs/infrastructure/clock/fixed-clock.d.ts +8 -0
- package/dist/cjs/infrastructure/clock/fixed-clock.js +19 -0
- package/dist/cjs/infrastructure/clock/system-clock.d.ts +4 -0
- package/dist/cjs/infrastructure/clock/system-clock.js +9 -0
- package/dist/cjs/infrastructure/event/domain-event-bus.d.ts +16 -0
- package/dist/cjs/infrastructure/event/domain-event-bus.js +60 -0
- package/dist/cjs/infrastructure/event/event-channel.d.ts +9 -0
- package/dist/cjs/infrastructure/event/event-channel.js +2 -0
- package/dist/cjs/infrastructure/event/event-registry.d.ts +9 -0
- package/dist/cjs/infrastructure/event/event-registry.js +43 -0
- package/dist/cjs/infrastructure/event/in-memory/emitter.d.ts +11 -0
- package/dist/cjs/infrastructure/event/in-memory/emitter.js +54 -0
- package/dist/cjs/infrastructure/event/in-memory/in-memory-event-channel.d.ts +16 -0
- package/dist/cjs/infrastructure/event/in-memory/in-memory-event-channel.js +50 -0
- package/dist/cjs/infrastructure/exceptions/event-delivery.exception.d.ts +5 -0
- package/dist/cjs/infrastructure/exceptions/event-delivery.exception.js +20 -0
- package/dist/cjs/infrastructure/exceptions/infrastructure-exception-code.d.ts +6 -0
- package/dist/cjs/infrastructure/exceptions/infrastructure-exception-code.js +10 -0
- package/dist/cjs/infrastructure/exceptions/infrastructure.exception.d.ts +6 -0
- package/dist/cjs/infrastructure/exceptions/infrastructure.exception.js +16 -0
- package/dist/cjs/infrastructure/exceptions/internal-error.exception.d.ts +5 -0
- package/dist/cjs/infrastructure/exceptions/internal-error.exception.js +17 -0
- package/dist/cjs/infrastructure/exceptions/kind/exception-classifier.d.ts +4 -0
- package/dist/cjs/infrastructure/exceptions/kind/exception-classifier.js +34 -0
- package/dist/cjs/infrastructure/exceptions/kind/exception-kind.d.ts +9 -0
- package/dist/cjs/infrastructure/exceptions/kind/exception-kind.js +13 -0
- package/dist/cjs/infrastructure/exceptions/unknown-event.exception.d.ts +4 -0
- package/dist/cjs/infrastructure/exceptions/unknown-event.exception.js +19 -0
- package/dist/cjs/infrastructure/index.d.ts +21 -0
- package/dist/cjs/infrastructure/index.js +33 -0
- package/dist/cjs/infrastructure/logging/console-logger.d.ts +17 -0
- package/dist/cjs/infrastructure/logging/console-logger.js +37 -0
- package/dist/cjs/infrastructure/logging/in-memory-logger.d.ts +15 -0
- package/dist/cjs/infrastructure/logging/in-memory-logger.js +27 -0
- package/dist/cjs/infrastructure/logging/log-serializer.d.ts +7 -0
- package/dist/cjs/infrastructure/logging/log-serializer.js +59 -0
- package/dist/cjs/infrastructure/logging/silent-logger.d.ts +7 -0
- package/dist/cjs/infrastructure/logging/silent-logger.js +18 -0
- package/dist/cjs/shared/exceptions/abstract.exception.d.ts +25 -0
- package/dist/cjs/shared/exceptions/abstract.exception.js +59 -0
- package/dist/cjs/shared/exceptions/brand/exception-brand.d.ts +6 -0
- package/dist/cjs/shared/exceptions/brand/exception-brand.js +29 -0
- package/dist/cjs/shared/exceptions/received/received-value.d.ts +4 -0
- package/dist/cjs/shared/exceptions/received/received-value.js +13 -0
- package/dist/cjs/shared/index.d.ts +8 -0
- package/dist/cjs/shared/index.js +13 -0
- package/dist/cjs/shared/issue-collection-error.d.ts +9 -0
- package/dist/cjs/shared/issue-collection-error.js +11 -0
- package/dist/cjs/shared/issue-collector.d.ts +8 -0
- package/dist/cjs/shared/issue-collector.js +41 -0
- package/dist/esm/application/event/event-bus.d.ts +4 -0
- package/dist/esm/application/event/event-bus.js +1 -0
- package/dist/esm/application/event/event-listener.d.ts +4 -0
- package/dist/esm/application/event/event-listener.js +2 -0
- package/dist/esm/application/event/listens-to.d.ts +15 -0
- package/dist/esm/application/event/listens-to.js +25 -0
- package/dist/esm/application/exceptions/aggregate-not-found.exception.d.ts +5 -0
- package/dist/esm/application/exceptions/aggregate-not-found.exception.js +13 -0
- package/dist/esm/application/exceptions/application-exception-code.d.ts +7 -0
- package/dist/esm/application/exceptions/application-exception-code.js +8 -0
- package/dist/esm/application/exceptions/application.exception.d.ts +6 -0
- package/dist/esm/application/exceptions/application.exception.js +12 -0
- package/dist/esm/application/exceptions/concurrency.exception.d.ts +5 -0
- package/dist/esm/application/exceptions/concurrency.exception.js +13 -0
- package/dist/esm/application/exceptions/not-allowed.exception.d.ts +5 -0
- package/dist/esm/application/exceptions/not-allowed.exception.js +13 -0
- package/dist/esm/application/exceptions/unexpected-error.exception.d.ts +5 -0
- package/dist/esm/application/exceptions/unexpected-error.exception.js +13 -0
- package/dist/esm/application/index.d.ts +14 -0
- package/dist/esm/application/index.js +10 -0
- package/dist/esm/application/ports/clock.d.ts +3 -0
- package/dist/esm/application/ports/clock.js +1 -0
- package/dist/esm/application/ports/logger.d.ts +8 -0
- package/dist/esm/application/ports/logger.js +1 -0
- package/dist/esm/application/use-case/use-case.d.ts +6 -0
- package/dist/esm/application/use-case/use-case.js +21 -0
- package/dist/esm/application/value-object-builder/value-object-builder.d.ts +16 -0
- package/dist/esm/application/value-object-builder/value-object-builder.js +47 -0
- package/dist/esm/domain/aggregate/aggregate-root.d.ts +9 -0
- package/dist/esm/domain/aggregate/aggregate-root.js +13 -0
- package/dist/esm/domain/entity/entity.d.ts +9 -0
- package/dist/esm/domain/entity/entity.js +15 -0
- package/dist/esm/domain/event/event-base.d.ts +23 -0
- package/dist/esm/domain/event/event-base.js +17 -0
- package/dist/esm/domain/exceptions/domain-exception-code.d.ts +7 -0
- package/dist/esm/domain/exceptions/domain-exception-code.js +8 -0
- package/dist/esm/domain/exceptions/domain.exception.d.ts +6 -0
- package/dist/esm/domain/exceptions/domain.exception.js +12 -0
- package/dist/esm/domain/exceptions/invalid-event-definition.exception.d.ts +4 -0
- package/dist/esm/domain/exceptions/invalid-event-definition.exception.js +15 -0
- package/dist/esm/domain/exceptions/required-value.exception.d.ts +4 -0
- package/dist/esm/domain/exceptions/required-value.exception.js +16 -0
- package/dist/esm/domain/exceptions/type-primitive.exception.d.ts +4 -0
- package/dist/esm/domain/exceptions/type-primitive.exception.js +16 -0
- package/dist/esm/domain/exceptions/validation.exception.d.ts +14 -0
- package/dist/esm/domain/exceptions/validation.exception.js +19 -0
- package/dist/esm/domain/index.d.ts +17 -0
- package/dist/esm/domain/index.js +12 -0
- package/dist/esm/domain/primitive/primitive-types.d.ts +20 -0
- package/dist/esm/domain/primitive/primitive-types.js +1 -0
- package/dist/esm/domain/repository/aggregate-finder.d.ts +4 -0
- package/dist/esm/domain/repository/aggregate-finder.js +1 -0
- package/dist/esm/domain/repository/aggregate-remover.d.ts +4 -0
- package/dist/esm/domain/repository/aggregate-remover.js +1 -0
- package/dist/esm/domain/repository/aggregate-saver.d.ts +4 -0
- package/dist/esm/domain/repository/aggregate-saver.js +1 -0
- package/dist/esm/domain/specification/specification.d.ts +9 -0
- package/dist/esm/domain/specification/specification.js +40 -0
- package/dist/esm/domain/type/abstract-type.d.ts +18 -0
- package/dist/esm/domain/type/abstract-type.js +57 -0
- package/dist/esm/domain/type/array-type.d.ts +17 -0
- package/dist/esm/domain/type/array-type.js +60 -0
- package/dist/esm/domain/type/boolean-type.d.ts +4 -0
- package/dist/esm/domain/type/boolean-type.js +12 -0
- package/dist/esm/domain/type/composite/composite-brand.d.ts +4 -0
- package/dist/esm/domain/type/composite/composite-brand.js +11 -0
- package/dist/esm/domain/type/composite/composite-field.d.ts +18 -0
- package/dist/esm/domain/type/composite/composite-field.js +82 -0
- package/dist/esm/domain/type/composite/composite-issues.d.ts +6 -0
- package/dist/esm/domain/type/composite/composite-issues.js +31 -0
- package/dist/esm/domain/type/composite/composite.d.ts +26 -0
- package/dist/esm/domain/type/composite/composite.js +48 -0
- package/dist/esm/domain/type/date-type.d.ts +19 -0
- package/dist/esm/domain/type/date-type.js +64 -0
- package/dist/esm/domain/type/enum-type.d.ts +7 -0
- package/dist/esm/domain/type/enum-type.js +26 -0
- package/dist/esm/domain/type/id-type.d.ts +7 -0
- package/dist/esm/domain/type/id-type.js +13 -0
- package/dist/esm/domain/type/index.d.ts +15 -0
- package/dist/esm/domain/type/index.js +12 -0
- package/dist/esm/domain/type/json-type.d.ts +9 -0
- package/dist/esm/domain/type/json-type.js +42 -0
- package/dist/esm/domain/type/number-type.d.ts +4 -0
- package/dist/esm/domain/type/number-type.js +12 -0
- package/dist/esm/domain/type/required/required-mark.d.ts +8 -0
- package/dist/esm/domain/type/required/required-mark.js +9 -0
- package/dist/esm/domain/type/required/required-type.d.ts +7 -0
- package/dist/esm/domain/type/required/required-type.js +18 -0
- package/dist/esm/domain/type/string-type.d.ts +4 -0
- package/dist/esm/domain/type/string-type.js +12 -0
- package/dist/esm/domain/type/uuid-type.d.ts +7 -0
- package/dist/esm/domain/type/uuid-type.js +23 -0
- package/dist/esm/infrastructure/clock/fixed-clock.d.ts +8 -0
- package/dist/esm/infrastructure/clock/fixed-clock.js +15 -0
- package/dist/esm/infrastructure/clock/system-clock.d.ts +4 -0
- package/dist/esm/infrastructure/clock/system-clock.js +5 -0
- package/dist/esm/infrastructure/event/domain-event-bus.d.ts +16 -0
- package/dist/esm/infrastructure/event/domain-event-bus.js +56 -0
- package/dist/esm/infrastructure/event/event-channel.d.ts +9 -0
- package/dist/esm/infrastructure/event/event-channel.js +1 -0
- package/dist/esm/infrastructure/event/event-registry.d.ts +9 -0
- package/dist/esm/infrastructure/event/event-registry.js +39 -0
- package/dist/esm/infrastructure/event/in-memory/emitter.d.ts +11 -0
- package/dist/esm/infrastructure/event/in-memory/emitter.js +50 -0
- package/dist/esm/infrastructure/event/in-memory/in-memory-event-channel.d.ts +16 -0
- package/dist/esm/infrastructure/event/in-memory/in-memory-event-channel.js +46 -0
- package/dist/esm/infrastructure/exceptions/event-delivery.exception.d.ts +5 -0
- package/dist/esm/infrastructure/exceptions/event-delivery.exception.js +16 -0
- package/dist/esm/infrastructure/exceptions/infrastructure-exception-code.d.ts +6 -0
- package/dist/esm/infrastructure/exceptions/infrastructure-exception-code.js +7 -0
- package/dist/esm/infrastructure/exceptions/infrastructure.exception.d.ts +6 -0
- package/dist/esm/infrastructure/exceptions/infrastructure.exception.js +12 -0
- package/dist/esm/infrastructure/exceptions/internal-error.exception.d.ts +5 -0
- package/dist/esm/infrastructure/exceptions/internal-error.exception.js +13 -0
- package/dist/esm/infrastructure/exceptions/kind/exception-classifier.d.ts +4 -0
- package/dist/esm/infrastructure/exceptions/kind/exception-classifier.js +30 -0
- package/dist/esm/infrastructure/exceptions/kind/exception-kind.d.ts +9 -0
- package/dist/esm/infrastructure/exceptions/kind/exception-kind.js +10 -0
- package/dist/esm/infrastructure/exceptions/unknown-event.exception.d.ts +4 -0
- package/dist/esm/infrastructure/exceptions/unknown-event.exception.js +15 -0
- package/dist/esm/infrastructure/index.d.ts +21 -0
- package/dist/esm/infrastructure/index.js +15 -0
- package/dist/esm/infrastructure/logging/console-logger.d.ts +17 -0
- package/dist/esm/infrastructure/logging/console-logger.js +33 -0
- package/dist/esm/infrastructure/logging/in-memory-logger.d.ts +15 -0
- package/dist/esm/infrastructure/logging/in-memory-logger.js +23 -0
- package/dist/esm/infrastructure/logging/log-serializer.d.ts +7 -0
- package/dist/esm/infrastructure/logging/log-serializer.js +55 -0
- package/dist/esm/infrastructure/logging/silent-logger.d.ts +7 -0
- package/dist/esm/infrastructure/logging/silent-logger.js +14 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/shared/exceptions/abstract.exception.d.ts +25 -0
- package/dist/esm/shared/exceptions/abstract.exception.js +55 -0
- package/dist/esm/shared/exceptions/brand/exception-brand.d.ts +6 -0
- package/dist/esm/shared/exceptions/brand/exception-brand.js +25 -0
- package/dist/esm/shared/exceptions/received/received-value.d.ts +4 -0
- package/dist/esm/shared/exceptions/received/received-value.js +9 -0
- package/dist/esm/shared/index.d.ts +8 -0
- package/dist/esm/shared/index.js +5 -0
- package/dist/esm/shared/issue-collection-error.d.ts +9 -0
- package/dist/esm/shared/issue-collection-error.js +7 -0
- package/dist/esm/shared/issue-collector.d.ts +8 -0
- package/dist/esm/shared/issue-collector.js +37 -0
- package/package.json +95 -4
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
# Value objects en TypeScript
|
|
2
|
+
|
|
3
|
+
Contenido: 1. Patrón base · 2. Money · 3. Identificadores · 4. Valores de un atributo · 5. Rangos · 6. Tipos estándar y estados · 7. Colecciones de valores · 8. Integración minimalista · 9. Tests
|
|
4
|
+
|
|
5
|
+
## 1. Patrón base
|
|
6
|
+
|
|
7
|
+
`[Fuera de los libros: convención de @archi-code/ddd]`. Las características de IDDD cap. 6 y *DDD in PHP* (inmutable, válido desde su creación, igualdad por valor) ya las da el paquete; no se escribe una clase base `ValueObject` propia:
|
|
8
|
+
|
|
9
|
+
| Necesitas | Clase del paquete (`@archi-code/ddd/domain`) |
|
|
10
|
+
|---|---|
|
|
11
|
+
| Un valor de un atributo (texto, número, booleano, fecha, UUID) | Subclase con nombre de `StringType`, `NumberType`, `BooleanType`, `DateType`, `UuidType`; obligatoria con `Required(XType)` |
|
|
12
|
+
| Un id | `IdType` (UUID obligatorio, `XId.generate()`) |
|
|
13
|
+
| Un estado o tipo estándar | `EnumType<E>` |
|
|
14
|
+
| Varios atributos que forman un todo | `Composite({ campo: Clase, … })` |
|
|
15
|
+
| Un tipo propio | `AbstractType<T>` implementando solo `protected filter(value: unknown): T` |
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { Validations } from '@archi-code/validation';
|
|
19
|
+
import { Required, StringType } from '@archi-code/ddd/domain';
|
|
20
|
+
|
|
21
|
+
@Validations([{ validator: 'isEmail' }])
|
|
22
|
+
export class Email extends Required(StringType) {}
|
|
23
|
+
|
|
24
|
+
new Email('ana@example.com').equals(new Email('ana@example.com')); // true
|
|
25
|
+
new Email('x'); // ValidationException: Validation failed, Email: Value does not satisfy isEmail
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Lo que ya hace el paquete y no se reescribe:
|
|
29
|
+
* El constructor convierte la entrada, comprueba `Required` y ejecuta todas las reglas de `@Validations` (de `@archi-code/validation`): un value object que existe es válido. No hay factoría `of()` ni `assertValid()`; se crea con `new`.
|
|
30
|
+
* `_value` es `readonly`, las fechas son inmutables y un `Composite` está congelado: `readonly` protege en compilación y el paquete también en ejecución.
|
|
31
|
+
* `equals` compara clase y valor: fechas por instante, listas y objetos por contenido. Con una comparación por referencia, dos `DateRange` con las mismas fechas no serían iguales, y eso rompe la igualdad por valor [IDDD cap. 6].
|
|
32
|
+
* Cada valor del dominio es una subclase con nombre: el nombre aparece en los errores y `equals` distingue `BillingCountry('ES')` de `ShippingCountry('ES')`.
|
|
33
|
+
|
|
34
|
+
Lo que no es opcional, con o sin paquete, es la inmutabilidad y la igualdad por valor [IDDD cap. 6].
|
|
35
|
+
|
|
36
|
+
## 2. Money
|
|
37
|
+
|
|
38
|
+
El ejemplo canónico de *DDD in PHP* (moneda e importe juntos) [PHP: Value Objects]. Nunca `number` en coma flotante para dinero `[Fuera de los libros]`: aquí, céntimos enteros en un `Composite`. Los libros usarían `bigint`; el paquete no tiene tipo `bigint`, así que `Cents` es `Required(NumberType)` con `isInt`, exacto hasta `Number.MAX_SAFE_INTEGER` céntimos `[Fuera de los libros: convención de @archi-code/ddd]`.
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { Validations } from '@archi-code/validation';
|
|
42
|
+
import { Composite, DomainException, NumberType, Required, StringType } from '@archi-code/ddd/domain';
|
|
43
|
+
|
|
44
|
+
export class CurrencyMismatch extends DomainException {
|
|
45
|
+
constructor(expected: string, received: string) {
|
|
46
|
+
super(`Cannot add ${received} to ${expected}`, ['PAY001']);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export class InvalidMultiplier extends DomainException {
|
|
51
|
+
constructor(factor: string) {
|
|
52
|
+
super(`${factor} is not a valid multiplier`, ['PAY002']);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@Validations([{ validator: 'isInt' }])
|
|
57
|
+
export class Cents extends Required(NumberType) {}
|
|
58
|
+
|
|
59
|
+
@Validations([{ validator: 'isISO4217' }])
|
|
60
|
+
export class Currency extends Required(StringType) {
|
|
61
|
+
protected filter(value: unknown): string {
|
|
62
|
+
return super.filter(value).trim().toUpperCase();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type Rounding = 'HALF_EVEN' | 'HALF_UP';
|
|
67
|
+
|
|
68
|
+
export class Money extends Composite({ amount: Cents, currency: Currency }) {
|
|
69
|
+
static zero(currency: Currency): Money {
|
|
70
|
+
return new Money({ amount: new Cents(0), currency });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
add(other: Money): Money {
|
|
74
|
+
this.assertSameCurrency(other);
|
|
75
|
+
return this.withCents(this.amount.value + other.amount.value);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
times(quantity: Quantity): Money {
|
|
79
|
+
return this.withCents(this.amount.value * quantity.value);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
multiplyBy(factor: string, rounding: Rounding = 'HALF_EVEN'): Money {
|
|
83
|
+
if (!/^\d+(\.\d{1,6})?$/.test(factor)) {
|
|
84
|
+
throw new InvalidMultiplier(factor);
|
|
85
|
+
}
|
|
86
|
+
const [whole, decimals = ''] = factor.split('.');
|
|
87
|
+
const scaled = BigInt(whole + decimals.padEnd(6, '0'));
|
|
88
|
+
return this.withCents(
|
|
89
|
+
Number(Money.roundDiv(BigInt(this.amount.value) * scaled, 1_000_000n, rounding)),
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
percentage(percent: string, rounding: Rounding = 'HALF_EVEN'): Money {
|
|
94
|
+
if (!/^\d+(\.\d{1,4})?$/.test(percent)) {
|
|
95
|
+
throw new InvalidMultiplier(percent);
|
|
96
|
+
}
|
|
97
|
+
const [whole, decimals = ''] = percent.split('.');
|
|
98
|
+
const millionths = BigInt(whole + decimals.padEnd(4, '0')).toString().padStart(7, '0');
|
|
99
|
+
return this.multiplyBy(`${millionths.slice(0, -6)}.${millionths.slice(-6)}`, rounding);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
covers(other: Money): boolean {
|
|
103
|
+
return this.currency.equals(other.currency) && this.amount.value >= other.amount.value;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
isZero(): boolean {
|
|
107
|
+
return this.amount.value === 0;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
toString(): string {
|
|
111
|
+
const cents = Math.abs(this.amount.value);
|
|
112
|
+
const sign = this.amount.value < 0 ? '-' : '';
|
|
113
|
+
return `${sign}${Math.trunc(cents / 100)}.${String(cents % 100).padStart(2, '0')} ${this.currency.value}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private withCents(cents: number): Money {
|
|
117
|
+
return new Money({ amount: new Cents(cents), currency: this.currency });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private assertSameCurrency(other: Money): void {
|
|
121
|
+
if (!this.currency.equals(other.currency)) {
|
|
122
|
+
throw new CurrencyMismatch(this.currency.value, other.currency.value);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private static roundDiv(dividend: bigint, divisor: bigint, mode: Rounding): bigint {
|
|
127
|
+
const quotient = dividend / divisor;
|
|
128
|
+
const remainder = dividend % divisor;
|
|
129
|
+
if (remainder === 0n) {
|
|
130
|
+
return quotient;
|
|
131
|
+
}
|
|
132
|
+
const sign = dividend < 0n ? -1n : 1n;
|
|
133
|
+
const twice = 2n * (remainder < 0n ? -remainder : remainder);
|
|
134
|
+
if (twice > divisor) {
|
|
135
|
+
return quotient + sign;
|
|
136
|
+
}
|
|
137
|
+
if (twice < divisor) {
|
|
138
|
+
return quotient;
|
|
139
|
+
}
|
|
140
|
+
return mode === 'HALF_UP' || quotient % 2n !== 0n ? quotient + sign : quotient;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const price = Money.fromPrimitives({ amount: 850, currency: ' eur ' });
|
|
145
|
+
price.percentage('21').toPrimitives(); // { amount: 178, currency: 'EUR' }: 178,5 redondeado al par
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Es el `Money` del pedido de `ddd-skills/ddd-agregados/references/implementacion-ts.md` (`add`, `times`, `covers`, con `Cents`, `Currency` y `CurrencyMismatch`), más las operaciones que un proyecto añade cuando las necesita (`zero`, `multiplyBy`, `percentage`, `isZero`, `toString`); `Quantity` está en la sección 7.
|
|
149
|
+
|
|
150
|
+
Por qué dos métodos: `times` es exacto (precio por cantidad) y `multiplyBy` obliga a decidir el redondeo, que en dinero nunca debe quedar implícito `[Fuera de los libros]`. El cálculo intermedio va en `bigint` para no perder exactitud; el resultado vuelve a `Cents`, que rechaza lo que no sea entero. Si el dominio reparte importes (pagos fraccionados), añade un `allocate` que reparta el resto de céntimos en lugar de redondear cada parte.
|
|
151
|
+
|
|
152
|
+
Todas las operaciones son funciones sin efectos secundarios: devuelven un `Money` nuevo [IDDD cap. 6]. El `Composite` está congelado; los valores derivados (`isZero()`) son métodos, nunca getters.
|
|
153
|
+
|
|
154
|
+
## 3. Identificadores
|
|
155
|
+
|
|
156
|
+
Los value objects son buenos contenedores de identidad: inmutables, estables y con el comportamiento de identidad centralizado [IDDD cap. 5].
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
import { IdType } from '@archi-code/ddd/domain';
|
|
160
|
+
|
|
161
|
+
export class OrderId extends IdType<'OrderId'> {}
|
|
162
|
+
export class CustomerId extends IdType<'CustomerId'> {}
|
|
163
|
+
|
|
164
|
+
const orderId = OrderId.generate(); // un OrderId con un UUID v4
|
|
165
|
+
new OrderId('9B2F6C1E-4D3A-4F8B-A1C2-3E4D5F6A7B8C').value; // '9b2f6c1e-4d3a-4f8b-a1c2-3e4d5f6a7b8c'
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`IdType` es obligatorio, acepta cualquier versión de UUID, lo guarda en minúsculas y rechaza el UUID nulo. Para ids con orden temporal en índices, `new OrderId(uuidv7())` `[Fuera de los libros]`. Una identidad que aporta el usuario o asigna otro contexto (código de campaña, id del CRM) es un `Required(StringType)` con nombre y sus reglas; un UUID estable a partir de una clave externa sale de `UuidType.fromValue(key)`:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
import { Validations } from '@archi-code/validation';
|
|
172
|
+
import { Required, StringType } from '@archi-code/ddd/domain';
|
|
173
|
+
|
|
174
|
+
@Validations([{ validator: 'matches', properties: { pattern: '^[A-Z0-9]{4,12}$' } }])
|
|
175
|
+
export class CampaignCode extends Required(StringType) {
|
|
176
|
+
protected filter(value: unknown): string {
|
|
177
|
+
return super.filter(value).trim().toUpperCase();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Un tipo por identificador (`OrderId`, `CustomerId`) hace que `equals` nunca confunda un id de cliente con uno de pedido, porque compara la clase. En compilación, TypeScript es estructural y dos clases con la misma forma se aceptarían una por otra; por eso `IdType` recibe una marca con el nombre de la clase, que solo existe en los tipos, y el compilador las distingue `[Fuera de los libros: convención de @archi-code/ddd]`:
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
import { IdType } from '@archi-code/ddd/domain';
|
|
186
|
+
|
|
187
|
+
export class OrderId extends IdType<'OrderId'> {}
|
|
188
|
+
export class CustomerId extends IdType<'CustomerId'> {}
|
|
189
|
+
|
|
190
|
+
const orderId: OrderId = CustomerId.generate(); // error de compilación
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Sin marca (`extends IdType {}`) la clase compila, pero se acepta donde se espera cualquier otro id: pon siempre el nombre de la clase. Un `IdType` sin marca sirve como tipo para cualquier id (`AggregateRoot<IdType>`).
|
|
194
|
+
|
|
195
|
+
## 4. Valores de un atributo
|
|
196
|
+
|
|
197
|
+
¿Merece la pena envolver un único atributo? Si tiene reglas o comportamiento (formato, normalización, comparación), sí. Un booleano o un número autocontenido sin comportamiento puede quedarse como primitivo; envolver de más es un error menor y se corrige refactorizando [IDDD cap. 6]. Con el paquete, todo el estado de un agregado es value objects, también un booleano (`class Shipped extends Required(BooleanType) {}`): así infraestructura lo guarda sin leer campos sueltos `[Fuera de los libros: convención de @archi-code/ddd]`.
|
|
198
|
+
|
|
199
|
+
La normalización (recortar, minúsculas) va en `filter`, llamando primero a `super.filter(value)`; las reglas se ejecutan sobre el valor ya normalizado:
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
import { Validations } from '@archi-code/validation';
|
|
203
|
+
import { Required, StringType } from '@archi-code/ddd/domain';
|
|
204
|
+
|
|
205
|
+
@Validations([{ validator: 'isEmail' }])
|
|
206
|
+
export class Email extends Required(StringType) {
|
|
207
|
+
protected filter(value: unknown): string {
|
|
208
|
+
return super.filter(value).trim().toLowerCase();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
domain(): string {
|
|
212
|
+
return this.value.split('@')[1];
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
new Email(' Ana@Example.COM ').domain(); // 'example.com'
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Un value object es opcional por defecto (`class Nickname extends StringType {}`: `value: string | null`) y `Required(XType)` lo hace obligatorio (`RequiredValueException` si llega vacío) `[Fuera de los libros: convención de @archi-code/ddd]`; los libros crean siempre el valor con contenido y modelan lo opcional en el campo (`nickname: Nickname | null`). Con el paquete valen las dos formas.
|
|
220
|
+
|
|
221
|
+
## 5. Rangos y todos conceptuales
|
|
222
|
+
|
|
223
|
+
Atributos que solo tienen sentido juntos forman un todo conceptual [IDDD cap. 6]. Si una entidad tiene `startDate` y `endDate` sueltos, falta un value object: un `Composite`, con la regla que combina sus campos como regla `custom` de `@Validations` (su `fn` recibe el composite ya construido).
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
import { Validations } from '@archi-code/validation';
|
|
227
|
+
import { Composite, DateType, Required } from '@archi-code/ddd/domain';
|
|
228
|
+
|
|
229
|
+
export class StartDate extends Required(DateType) {}
|
|
230
|
+
export class EndDate extends Required(DateType) {}
|
|
231
|
+
|
|
232
|
+
@Validations([
|
|
233
|
+
{
|
|
234
|
+
custom: 'endsAfterItStarts',
|
|
235
|
+
fn: (range) => (range as DateRange).end.value >= (range as DateRange).start.value,
|
|
236
|
+
message: 'A date range cannot end before it starts',
|
|
237
|
+
},
|
|
238
|
+
])
|
|
239
|
+
export class DateRange extends Composite({ start: StartDate, end: EndDate }) {
|
|
240
|
+
contains(moment: Date): boolean {
|
|
241
|
+
return moment >= this.start.value && moment <= this.end.value;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
overlaps(other: DateRange): boolean {
|
|
245
|
+
return this.start.value <= other.end.value && other.start.value <= this.end.value;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
extendTo(end: EndDate): DateRange {
|
|
249
|
+
return new DateRange({ start: this.start, end });
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
`extendTo` devuelve un rango nuevo, que vuelve a pasar la regla: reemplazo, no mutación. `Date` es mutable en JavaScript, pero `DateType` guarda una fecha inmutable (sus setters lanzan), así que no hace falta copiarla al construir. `DateType` rechaza fechas con hora sin zona horaria.
|
|
255
|
+
|
|
256
|
+
## 6. Tipos estándar y estados
|
|
257
|
+
|
|
258
|
+
Los tipos estándar describen el tipo de algo (moneda, tipo de cliente, estado) [IDDD cap. 6]. IDDD los implementa como enum con comportamiento (patrón State). Con el paquete, un `EnumType` y una subclase obligatoria con el comportamiento:
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
import { EnumType, Required } from '@archi-code/ddd/domain';
|
|
262
|
+
|
|
263
|
+
export enum Status {
|
|
264
|
+
Pending = 'pending',
|
|
265
|
+
Confirmed = 'confirmed',
|
|
266
|
+
Cancelled = 'cancelled',
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export class StatusType extends EnumType<Status> {
|
|
270
|
+
protected getEnum(): typeof Status {
|
|
271
|
+
return Status;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export class OrderStatus extends Required(StatusType) {
|
|
276
|
+
isPending(): boolean {
|
|
277
|
+
return this.value === Status.Pending;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
isConfirmed(): boolean {
|
|
281
|
+
return this.value === Status.Confirmed;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
canBeCancelled(): boolean {
|
|
285
|
+
return this.value !== Status.Cancelled;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Las reglas de transición viven en el tipo, no en `if` repartidos. Su primitivo es la opción del enum, así que `fromPrimitives` lo rehace sin conversión: `new OrderStatus(primitives.status)`. El estado de disponibilidad de IDDD (`ADD_ON_NOT_ENABLED`, `NOT_REQUESTED`, `REQUESTED`, `READY`) es un ejemplo de este patrón aplicado a integración [IDDD cap. 3].
|
|
291
|
+
|
|
292
|
+
## 7. Colecciones de valores
|
|
293
|
+
|
|
294
|
+
Una colección de valores también puede ser un valor con comportamiento propio `[Fuera de los libros]`. Con el paquete es un `Composite` con un campo lista (`[OrderLine]`) y sus reglas como `custom`; `ArrayType` solo guarda value objects simples, nunca composites:
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
import { Validations } from '@archi-code/validation';
|
|
298
|
+
import { Composite, NumberType, Required, StringType } from '@archi-code/ddd/domain';
|
|
299
|
+
|
|
300
|
+
export class Sku extends Required(StringType) {}
|
|
301
|
+
|
|
302
|
+
@Validations([{ validator: 'isInt', properties: { min: 1 } }])
|
|
303
|
+
export class Quantity extends Required(NumberType) {}
|
|
304
|
+
|
|
305
|
+
export class OrderLine extends Composite({ sku: Sku, quantity: Quantity, unitPrice: Money }) {
|
|
306
|
+
subtotal(): Money {
|
|
307
|
+
return this.unitPrice.times(this.quantity);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
@Validations([
|
|
312
|
+
{
|
|
313
|
+
custom: 'hasLines',
|
|
314
|
+
fn: (lines) => (lines as OrderLines).items.length > 0,
|
|
315
|
+
message: 'An order needs at least one line',
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
custom: 'uniqueSku',
|
|
319
|
+
fn: (lines) => {
|
|
320
|
+
const skus = (lines as OrderLines).items.map((line) => line.sku.value);
|
|
321
|
+
return new Set(skus).size === skus.length;
|
|
322
|
+
},
|
|
323
|
+
message: 'Each SKU appears in one line only',
|
|
324
|
+
},
|
|
325
|
+
])
|
|
326
|
+
export class OrderLines extends Composite({ items: [OrderLine] }) {
|
|
327
|
+
withLine(line: OrderLine): OrderLines {
|
|
328
|
+
return new OrderLines({ items: [...this.items, line] });
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
total(): Money {
|
|
332
|
+
const [first, ...rest] = this.items;
|
|
333
|
+
return rest.reduce((sum, line) => sum.add(line.subtotal()), first.subtotal());
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Son los del pedido de `ddd-skills/ddd-agregados/references/implementacion-ts.md`. Una regla rota lanza `ValidationException` con el nombre de la regla como `code` (`hasLines`, `uniqueSku`). `OrderLines.fromPrimitives(data)` informa de cada campo inválido con su ruta (`items.1.quantity`). Líneas en monedas distintas no llegan a sumarse: `total()` lanza `CurrencyMismatch` al sumar sus `Money`.
|
|
339
|
+
|
|
340
|
+
## 8. Integración minimalista
|
|
341
|
+
|
|
342
|
+
Cuando entra información de otro contexto, modélala como value object en el contexto receptor siempre que se pueda: menos atributos de los que hacerse cargo y ninguna obligación de mantenerlos sincronizados. En IDDD, un `Moderator` de Colaboración es un valor creado a partir de `User` y `Role` de otro contexto, con pocos atributos y el rol expresado en el nombre de la clase [IDDD cap. 6]. Con el paquete sería un `Composite` con nombre (`class Moderator extends Composite({ identity: UserIdentity, name: FullName, email: Email }) {}`). Si el estado remoto debe seguir cambiando localmente, entonces sí, un agregado [IDDD cap. 6].
|
|
343
|
+
|
|
344
|
+
## 9. Tests
|
|
345
|
+
|
|
346
|
+
Los tests de value objects deben tener significado de dominio, no ser mecánicos [IDDD cap. 6]:
|
|
347
|
+
|
|
348
|
+
```ts
|
|
349
|
+
import { describe, expect, it } from 'vitest';
|
|
350
|
+
|
|
351
|
+
const euros = (amount: number) => Money.fromPrimitives({ amount, currency: 'EUR' });
|
|
352
|
+
|
|
353
|
+
describe('Money', () => {
|
|
354
|
+
it('suma importes de la misma moneda', () => {
|
|
355
|
+
expect(euros(850).add(euros(850)).equals(euros(1700))).toBe(true);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('no permite sumar euros y dólares', () => {
|
|
359
|
+
expect(() => euros(100).add(Money.fromPrimitives({ amount: 100, currency: 'USD' }))).toThrow(
|
|
360
|
+
CurrencyMismatch,
|
|
361
|
+
);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('es inmutable', () => {
|
|
365
|
+
const price = euros(850);
|
|
366
|
+
price.times(new Quantity(2));
|
|
367
|
+
expect(price.equals(euros(850))).toBe(true);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it('rechaza importes con fracciones de céntimo', () => {
|
|
371
|
+
expect(() => euros(12.5)).toThrow(ValidationException);
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
`ValidationException` se importa de `@archi-code/ddd/domain`.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { EventBase, EventClass } from '../../domain/index';
|
|
2
|
+
import type { EventListener } from './event-listener';
|
|
3
|
+
type ListenerClass<Event extends EventBase> = abstract new (...args: any[]) => EventListener<Event>;
|
|
4
|
+
export interface ListensToOptions {
|
|
5
|
+
readonly consumer: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ListenerSubscription {
|
|
8
|
+
readonly eventName: string;
|
|
9
|
+
readonly consumer: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function ListensTo<Event extends EventBase>(event: EventClass<Event>, options: ListensToOptions): (listener: ListenerClass<Event>) => void;
|
|
12
|
+
export declare namespace ListensTo {
|
|
13
|
+
var subscriptionOf: (listener: EventListener<any>) => ListenerSubscription;
|
|
14
|
+
}
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ListensTo = ListensTo;
|
|
4
|
+
const application_exception_1 = require("../exceptions/application.exception");
|
|
5
|
+
const LISTENED_EVENT = Symbol.for('@archi-code/ddd/listens-to.v1');
|
|
6
|
+
function ListensTo(event, options) {
|
|
7
|
+
return (listener) => {
|
|
8
|
+
if (Object.prototype.hasOwnProperty.call(listener, LISTENED_EVENT)) {
|
|
9
|
+
throw new application_exception_1.ApplicationException(`${listener.name} already listens to an event`);
|
|
10
|
+
}
|
|
11
|
+
const consumer = options?.consumer;
|
|
12
|
+
if (typeof consumer !== 'string' || consumer.trim() === '') {
|
|
13
|
+
throw new application_exception_1.ApplicationException(`${listener.name} needs a consumer name: @ListensTo(<event class>, { consumer: '<name>' })`);
|
|
14
|
+
}
|
|
15
|
+
const subscription = Object.freeze({
|
|
16
|
+
eventName: event.eventName,
|
|
17
|
+
consumer,
|
|
18
|
+
});
|
|
19
|
+
Object.defineProperty(listener, LISTENED_EVENT, { value: subscription });
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
ListensTo.subscriptionOf = (listener) => {
|
|
23
|
+
const listenerClass = listener.constructor;
|
|
24
|
+
if (!Object.prototype.hasOwnProperty.call(listenerClass, LISTENED_EVENT)) {
|
|
25
|
+
throw new application_exception_1.ApplicationException(`${listenerClass.name} needs @ListensTo(<event class>, { consumer: '<name>' })`);
|
|
26
|
+
}
|
|
27
|
+
return listenerClass[LISTENED_EVENT];
|
|
28
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ExceptionOptions } from '../../shared/index';
|
|
2
|
+
import { ApplicationException } from './application.exception';
|
|
3
|
+
export declare class AggregateNotFoundException extends ApplicationException {
|
|
4
|
+
constructor(entity: string, id: string, options?: ExceptionOptions);
|
|
5
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var _a;
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.AggregateNotFoundException = void 0;
|
|
5
|
+
const index_1 = require("../../shared/index");
|
|
6
|
+
const application_exception_code_1 = require("./application-exception-code");
|
|
7
|
+
const application_exception_1 = require("./application.exception");
|
|
8
|
+
class AggregateNotFoundException extends application_exception_1.ApplicationException {
|
|
9
|
+
constructor(entity, id, options = {}) {
|
|
10
|
+
super(`${entity} with ID ${id} not found.`, [application_exception_code_1.ApplicationExceptionCode.AggregateNotFound], options);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.AggregateNotFoundException = AggregateNotFoundException;
|
|
14
|
+
_a = AggregateNotFoundException;
|
|
15
|
+
(() => {
|
|
16
|
+
index_1.ExceptionBrand.brandClass(_a, application_exception_code_1.ApplicationExceptionCode.AggregateNotFound);
|
|
17
|
+
})();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ApplicationExceptionCode = void 0;
|
|
4
|
+
var ApplicationExceptionCode;
|
|
5
|
+
(function (ApplicationExceptionCode) {
|
|
6
|
+
ApplicationExceptionCode["ApplicationException"] = "APP000";
|
|
7
|
+
ApplicationExceptionCode["ConcurrencyConflict"] = "APP001";
|
|
8
|
+
ApplicationExceptionCode["AggregateNotFound"] = "APP002";
|
|
9
|
+
ApplicationExceptionCode["NotAllowed"] = "APP003";
|
|
10
|
+
ApplicationExceptionCode["UnexpectedError"] = "APP004";
|
|
11
|
+
})(ApplicationExceptionCode || (exports.ApplicationExceptionCode = ApplicationExceptionCode = {}));
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { AbstractException } from '../../shared/index';
|
|
2
|
+
import type { ExceptionOptions } from '../../shared/index';
|
|
3
|
+
import { ApplicationExceptionCode } from './application-exception-code';
|
|
4
|
+
export declare class ApplicationException extends AbstractException {
|
|
5
|
+
constructor(message: string, exceptionCodes?: (ApplicationExceptionCode | string)[], options?: ExceptionOptions);
|
|
6
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var _a;
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.ApplicationException = void 0;
|
|
5
|
+
const index_1 = require("../../shared/index");
|
|
6
|
+
const application_exception_code_1 = require("./application-exception-code");
|
|
7
|
+
class ApplicationException extends index_1.AbstractException {
|
|
8
|
+
constructor(message, exceptionCodes = [], options = {}) {
|
|
9
|
+
super(message, [application_exception_code_1.ApplicationExceptionCode.ApplicationException, ...exceptionCodes], options, application_exception_code_1.ApplicationExceptionCode);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
exports.ApplicationException = ApplicationException;
|
|
13
|
+
_a = ApplicationException;
|
|
14
|
+
(() => {
|
|
15
|
+
index_1.ExceptionBrand.brandClass(_a, application_exception_code_1.ApplicationExceptionCode.ApplicationException);
|
|
16
|
+
})();
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ExceptionOptions } from '../../shared/index';
|
|
2
|
+
import { ApplicationException } from './application.exception';
|
|
3
|
+
export declare class ConcurrencyException extends ApplicationException {
|
|
4
|
+
constructor(aggregateName: string, id: string, expectedVersion: number, options?: ExceptionOptions);
|
|
5
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var _a;
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.ConcurrencyException = void 0;
|
|
5
|
+
const index_1 = require("../../shared/index");
|
|
6
|
+
const application_exception_code_1 = require("./application-exception-code");
|
|
7
|
+
const application_exception_1 = require("./application.exception");
|
|
8
|
+
class ConcurrencyException extends application_exception_1.ApplicationException {
|
|
9
|
+
constructor(aggregateName, id, expectedVersion, options = {}) {
|
|
10
|
+
super(`${aggregateName} with ID ${id} was changed by someone else: expected version ${expectedVersion}.`, [application_exception_code_1.ApplicationExceptionCode.ConcurrencyConflict], options);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.ConcurrencyException = ConcurrencyException;
|
|
14
|
+
_a = ConcurrencyException;
|
|
15
|
+
(() => {
|
|
16
|
+
index_1.ExceptionBrand.brandClass(_a, application_exception_code_1.ApplicationExceptionCode.ConcurrencyConflict);
|
|
17
|
+
})();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var _a;
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.NotAllowedException = void 0;
|
|
5
|
+
const index_1 = require("../../shared/index");
|
|
6
|
+
const application_exception_code_1 = require("./application-exception-code");
|
|
7
|
+
const application_exception_1 = require("./application.exception");
|
|
8
|
+
class NotAllowedException extends application_exception_1.ApplicationException {
|
|
9
|
+
constructor(message, options = {}) {
|
|
10
|
+
super(message, [application_exception_code_1.ApplicationExceptionCode.NotAllowed], options);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.NotAllowedException = NotAllowedException;
|
|
14
|
+
_a = NotAllowedException;
|
|
15
|
+
(() => {
|
|
16
|
+
index_1.ExceptionBrand.brandClass(_a, application_exception_code_1.ApplicationExceptionCode.NotAllowed);
|
|
17
|
+
})();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var _a;
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.UnexpectedErrorException = void 0;
|
|
5
|
+
const index_1 = require("../../shared/index");
|
|
6
|
+
const application_exception_code_1 = require("./application-exception-code");
|
|
7
|
+
const application_exception_1 = require("./application.exception");
|
|
8
|
+
class UnexpectedErrorException extends application_exception_1.ApplicationException {
|
|
9
|
+
constructor(message, options = {}) {
|
|
10
|
+
super(`Failed, unexpected error: ${message}`, [application_exception_code_1.ApplicationExceptionCode.UnexpectedError], options);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.UnexpectedErrorException = UnexpectedErrorException;
|
|
14
|
+
_a = UnexpectedErrorException;
|
|
15
|
+
(() => {
|
|
16
|
+
index_1.ExceptionBrand.brandClass(_a, application_exception_code_1.ApplicationExceptionCode.UnexpectedError);
|
|
17
|
+
})();
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { UseCase } from './use-case/use-case';
|
|
2
|
+
export { ValueObjectBuilder } from './value-object-builder/value-object-builder';
|
|
3
|
+
export { EventListener } from './event/event-listener';
|
|
4
|
+
export { ListensTo } from './event/listens-to';
|
|
5
|
+
export type { ListenerSubscription, ListensToOptions } from './event/listens-to';
|
|
6
|
+
export type { EventBus } from './event/event-bus';
|
|
7
|
+
export type { Clock } from './ports/clock';
|
|
8
|
+
export type { LogContext, Logger, LogLevel } from './ports/logger';
|
|
9
|
+
export { ApplicationException } from './exceptions/application.exception';
|
|
10
|
+
export { ConcurrencyException } from './exceptions/concurrency.exception';
|
|
11
|
+
export { AggregateNotFoundException } from './exceptions/aggregate-not-found.exception';
|
|
12
|
+
export { NotAllowedException } from './exceptions/not-allowed.exception';
|
|
13
|
+
export { UnexpectedErrorException } from './exceptions/unexpected-error.exception';
|
|
14
|
+
export { ApplicationExceptionCode } from './exceptions/application-exception-code';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ApplicationExceptionCode = exports.UnexpectedErrorException = exports.NotAllowedException = exports.AggregateNotFoundException = exports.ConcurrencyException = exports.ApplicationException = exports.ListensTo = exports.EventListener = exports.ValueObjectBuilder = exports.UseCase = void 0;
|
|
4
|
+
var use_case_1 = require("./use-case/use-case");
|
|
5
|
+
Object.defineProperty(exports, "UseCase", { enumerable: true, get: function () { return use_case_1.UseCase; } });
|
|
6
|
+
var value_object_builder_1 = require("./value-object-builder/value-object-builder");
|
|
7
|
+
Object.defineProperty(exports, "ValueObjectBuilder", { enumerable: true, get: function () { return value_object_builder_1.ValueObjectBuilder; } });
|
|
8
|
+
var event_listener_1 = require("./event/event-listener");
|
|
9
|
+
Object.defineProperty(exports, "EventListener", { enumerable: true, get: function () { return event_listener_1.EventListener; } });
|
|
10
|
+
var listens_to_1 = require("./event/listens-to");
|
|
11
|
+
Object.defineProperty(exports, "ListensTo", { enumerable: true, get: function () { return listens_to_1.ListensTo; } });
|
|
12
|
+
var application_exception_1 = require("./exceptions/application.exception");
|
|
13
|
+
Object.defineProperty(exports, "ApplicationException", { enumerable: true, get: function () { return application_exception_1.ApplicationException; } });
|
|
14
|
+
var concurrency_exception_1 = require("./exceptions/concurrency.exception");
|
|
15
|
+
Object.defineProperty(exports, "ConcurrencyException", { enumerable: true, get: function () { return concurrency_exception_1.ConcurrencyException; } });
|
|
16
|
+
var aggregate_not_found_exception_1 = require("./exceptions/aggregate-not-found.exception");
|
|
17
|
+
Object.defineProperty(exports, "AggregateNotFoundException", { enumerable: true, get: function () { return aggregate_not_found_exception_1.AggregateNotFoundException; } });
|
|
18
|
+
var not_allowed_exception_1 = require("./exceptions/not-allowed.exception");
|
|
19
|
+
Object.defineProperty(exports, "NotAllowedException", { enumerable: true, get: function () { return not_allowed_exception_1.NotAllowedException; } });
|
|
20
|
+
var unexpected_error_exception_1 = require("./exceptions/unexpected-error.exception");
|
|
21
|
+
Object.defineProperty(exports, "UnexpectedErrorException", { enumerable: true, get: function () { return unexpected_error_exception_1.UnexpectedErrorException; } });
|
|
22
|
+
var application_exception_code_1 = require("./exceptions/application-exception-code");
|
|
23
|
+
Object.defineProperty(exports, "ApplicationExceptionCode", { enumerable: true, get: function () { return application_exception_code_1.ApplicationExceptionCode; } });
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
2
|
+
export type LogContext = Readonly<Record<string, unknown>>;
|
|
3
|
+
export interface Logger {
|
|
4
|
+
debug(message: string, context?: LogContext): void;
|
|
5
|
+
info(message: string, context?: LogContext): void;
|
|
6
|
+
warn(message: string, context?: LogContext): void;
|
|
7
|
+
error(message: string, context?: LogContext): void;
|
|
8
|
+
}
|