@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.
Files changed (336) hide show
  1. package/LICENSE +46 -0
  2. package/README.md +2795 -2
  3. package/SKILL.md +106 -0
  4. package/ddd-skills/archi-code-ddd/imports.md +94 -0
  5. package/ddd-skills/archi-code-ddd/layers.md +314 -0
  6. package/ddd-skills/archi-code-ddd/patterns.md +683 -0
  7. package/ddd-skills/archi-code-ddd/what-not-to-do.md +196 -0
  8. package/ddd-skills/archi-code-ddd/where-each-class-goes.md +64 -0
  9. package/ddd-skills/ddd-agregados/GUIA.md +114 -0
  10. package/ddd-skills/ddd-agregados/references/consistencia-eventual.md +158 -0
  11. package/ddd-skills/ddd-agregados/references/ejemplo-ecommerce.md +52 -0
  12. package/ddd-skills/ddd-agregados/references/fabricas.md +193 -0
  13. package/ddd-skills/ddd-agregados/references/implementacion-ts.md +706 -0
  14. package/ddd-skills/ddd-arquitectura/GUIA.md +80 -0
  15. package/ddd-skills/ddd-arquitectura/assets/dependency-cruiser.cjs +99 -0
  16. package/ddd-skills/ddd-arquitectura/references/estilos-adicionales.md +51 -0
  17. package/ddd-skills/ddd-arquitectura/references/estructura-hexagonal-ts.md +406 -0
  18. package/ddd-skills/ddd-arquitectura/references/modulos.md +48 -0
  19. package/ddd-skills/ddd-auditoria-refactorizacion/GUIA.md +85 -0
  20. package/ddd-skills/ddd-auditoria-refactorizacion/references/ejemplo-auditoria.md +314 -0
  21. package/ddd-skills/ddd-auditoria-refactorizacion/references/lista-comprobacion.md +54 -0
  22. package/ddd-skills/ddd-auditoria-refactorizacion/references/plan-migracion.md +73 -0
  23. package/ddd-skills/ddd-auditoria-refactorizacion/scripts/scan_ddd_smells.py +264 -0
  24. package/ddd-skills/ddd-context-mapping/GUIA.md +83 -0
  25. package/ddd-skills/ddd-context-mapping/references/anticorrupcion-ts.md +419 -0
  26. package/ddd-skills/ddd-context-mapping/references/estilos-integracion.md +152 -0
  27. package/ddd-skills/ddd-event-sourcing-cqrs/GUIA.md +92 -0
  28. package/ddd-skills/ddd-event-sourcing-cqrs/references/event-sourcing-ts.md +476 -0
  29. package/ddd-skills/ddd-event-sourcing-cqrs/references/proyecciones-cqrs-ts.md +285 -0
  30. package/ddd-skills/ddd-eventos-dominio/GUIA.md +106 -0
  31. package/ddd-skills/ddd-eventos-dominio/references/diseno-eventos.md +130 -0
  32. package/ddd-skills/ddd-eventos-dominio/references/ejemplo-ecommerce.md +240 -0
  33. package/ddd-skills/ddd-eventos-dominio/references/publicacion-ts.md +529 -0
  34. package/ddd-skills/ddd-orquestador/GUIA.md +40 -0
  35. package/ddd-skills/ddd-orquestador/references/chuleta-reglas.md +35 -0
  36. package/ddd-skills/ddd-repositorios-persistencia/GUIA.md +121 -0
  37. package/ddd-skills/ddd-repositorios-persistencia/references/implementacion-ts.md +314 -0
  38. package/ddd-skills/ddd-repositorios-persistencia/references/tests-repositorios.md +191 -0
  39. package/ddd-skills/ddd-repositorios-persistencia/references/transacciones-ts.md +301 -0
  40. package/ddd-skills/ddd-servicios-capa-aplicacion/GUIA.md +99 -0
  41. package/ddd-skills/ddd-servicios-capa-aplicacion/references/capa-aplicacion-ts.md +264 -0
  42. package/ddd-skills/ddd-servicios-capa-aplicacion/references/ejemplo-ecommerce.md +321 -0
  43. package/ddd-skills/ddd-servicios-capa-aplicacion/references/servicios-dominio-ts.md +190 -0
  44. package/ddd-skills/ddd-value-objects-entidades/GUIA.md +118 -0
  45. package/ddd-skills/ddd-value-objects-entidades/references/ejemplo-ecommerce.md +87 -0
  46. package/ddd-skills/ddd-value-objects-entidades/references/entidades-ts.md +218 -0
  47. package/ddd-skills/ddd-value-objects-entidades/references/persistencia-valores.md +88 -0
  48. package/ddd-skills/ddd-value-objects-entidades/references/specification-y-diseno-flexible.md +119 -0
  49. package/ddd-skills/ddd-value-objects-entidades/references/validacion.md +96 -0
  50. package/ddd-skills/ddd-value-objects-entidades/references/value-objects-ts.md +376 -0
  51. package/dist/cjs/application/event/event-bus.d.ts +4 -0
  52. package/dist/cjs/application/event/event-bus.js +2 -0
  53. package/dist/cjs/application/event/event-listener.d.ts +4 -0
  54. package/dist/cjs/application/event/event-listener.js +6 -0
  55. package/dist/cjs/application/event/listens-to.d.ts +15 -0
  56. package/dist/cjs/application/event/listens-to.js +28 -0
  57. package/dist/cjs/application/exceptions/aggregate-not-found.exception.d.ts +5 -0
  58. package/dist/cjs/application/exceptions/aggregate-not-found.exception.js +17 -0
  59. package/dist/cjs/application/exceptions/application-exception-code.d.ts +7 -0
  60. package/dist/cjs/application/exceptions/application-exception-code.js +11 -0
  61. package/dist/cjs/application/exceptions/application.exception.d.ts +6 -0
  62. package/dist/cjs/application/exceptions/application.exception.js +16 -0
  63. package/dist/cjs/application/exceptions/concurrency.exception.d.ts +5 -0
  64. package/dist/cjs/application/exceptions/concurrency.exception.js +17 -0
  65. package/dist/cjs/application/exceptions/not-allowed.exception.d.ts +5 -0
  66. package/dist/cjs/application/exceptions/not-allowed.exception.js +17 -0
  67. package/dist/cjs/application/exceptions/unexpected-error.exception.d.ts +5 -0
  68. package/dist/cjs/application/exceptions/unexpected-error.exception.js +17 -0
  69. package/dist/cjs/application/index.d.ts +14 -0
  70. package/dist/cjs/application/index.js +23 -0
  71. package/dist/cjs/application/ports/clock.d.ts +3 -0
  72. package/dist/cjs/application/ports/clock.js +2 -0
  73. package/dist/cjs/application/ports/logger.d.ts +8 -0
  74. package/dist/cjs/application/ports/logger.js +2 -0
  75. package/dist/cjs/application/use-case/use-case.d.ts +6 -0
  76. package/dist/cjs/application/use-case/use-case.js +25 -0
  77. package/dist/cjs/application/value-object-builder/value-object-builder.d.ts +16 -0
  78. package/dist/cjs/application/value-object-builder/value-object-builder.js +51 -0
  79. package/dist/cjs/domain/aggregate/aggregate-root.d.ts +9 -0
  80. package/dist/cjs/domain/aggregate/aggregate-root.js +17 -0
  81. package/dist/cjs/domain/entity/entity.d.ts +9 -0
  82. package/dist/cjs/domain/entity/entity.js +19 -0
  83. package/dist/cjs/domain/event/event-base.d.ts +23 -0
  84. package/dist/cjs/domain/event/event-base.js +21 -0
  85. package/dist/cjs/domain/exceptions/domain-exception-code.d.ts +7 -0
  86. package/dist/cjs/domain/exceptions/domain-exception-code.js +11 -0
  87. package/dist/cjs/domain/exceptions/domain.exception.d.ts +6 -0
  88. package/dist/cjs/domain/exceptions/domain.exception.js +16 -0
  89. package/dist/cjs/domain/exceptions/invalid-event-definition.exception.d.ts +4 -0
  90. package/dist/cjs/domain/exceptions/invalid-event-definition.exception.js +19 -0
  91. package/dist/cjs/domain/exceptions/required-value.exception.d.ts +4 -0
  92. package/dist/cjs/domain/exceptions/required-value.exception.js +20 -0
  93. package/dist/cjs/domain/exceptions/type-primitive.exception.d.ts +4 -0
  94. package/dist/cjs/domain/exceptions/type-primitive.exception.js +20 -0
  95. package/dist/cjs/domain/exceptions/validation.exception.d.ts +14 -0
  96. package/dist/cjs/domain/exceptions/validation.exception.js +23 -0
  97. package/dist/cjs/domain/index.d.ts +17 -0
  98. package/dist/cjs/domain/index.js +36 -0
  99. package/dist/cjs/domain/primitive/primitive-types.d.ts +20 -0
  100. package/dist/cjs/domain/primitive/primitive-types.js +2 -0
  101. package/dist/cjs/domain/repository/aggregate-finder.d.ts +4 -0
  102. package/dist/cjs/domain/repository/aggregate-finder.js +2 -0
  103. package/dist/cjs/domain/repository/aggregate-remover.d.ts +4 -0
  104. package/dist/cjs/domain/repository/aggregate-remover.js +2 -0
  105. package/dist/cjs/domain/repository/aggregate-saver.d.ts +4 -0
  106. package/dist/cjs/domain/repository/aggregate-saver.js +2 -0
  107. package/dist/cjs/domain/specification/specification.d.ts +9 -0
  108. package/dist/cjs/domain/specification/specification.js +44 -0
  109. package/dist/cjs/domain/type/abstract-type.d.ts +18 -0
  110. package/dist/cjs/domain/type/abstract-type.js +61 -0
  111. package/dist/cjs/domain/type/array-type.d.ts +17 -0
  112. package/dist/cjs/domain/type/array-type.js +64 -0
  113. package/dist/cjs/domain/type/boolean-type.d.ts +4 -0
  114. package/dist/cjs/domain/type/boolean-type.js +16 -0
  115. package/dist/cjs/domain/type/composite/composite-brand.d.ts +4 -0
  116. package/dist/cjs/domain/type/composite/composite-brand.js +15 -0
  117. package/dist/cjs/domain/type/composite/composite-field.d.ts +18 -0
  118. package/dist/cjs/domain/type/composite/composite-field.js +86 -0
  119. package/dist/cjs/domain/type/composite/composite-issues.d.ts +6 -0
  120. package/dist/cjs/domain/type/composite/composite-issues.js +35 -0
  121. package/dist/cjs/domain/type/composite/composite.d.ts +26 -0
  122. package/dist/cjs/domain/type/composite/composite.js +51 -0
  123. package/dist/cjs/domain/type/date-type.d.ts +19 -0
  124. package/dist/cjs/domain/type/date-type.js +70 -0
  125. package/dist/cjs/domain/type/enum-type.d.ts +7 -0
  126. package/dist/cjs/domain/type/enum-type.js +30 -0
  127. package/dist/cjs/domain/type/id-type.d.ts +7 -0
  128. package/dist/cjs/domain/type/id-type.js +17 -0
  129. package/dist/cjs/domain/type/index.d.ts +15 -0
  130. package/dist/cjs/domain/type/index.js +29 -0
  131. package/dist/cjs/domain/type/json-type.d.ts +9 -0
  132. package/dist/cjs/domain/type/json-type.js +46 -0
  133. package/dist/cjs/domain/type/number-type.d.ts +4 -0
  134. package/dist/cjs/domain/type/number-type.js +16 -0
  135. package/dist/cjs/domain/type/required/required-mark.d.ts +8 -0
  136. package/dist/cjs/domain/type/required/required-mark.js +13 -0
  137. package/dist/cjs/domain/type/required/required-type.d.ts +7 -0
  138. package/dist/cjs/domain/type/required/required-type.js +21 -0
  139. package/dist/cjs/domain/type/string-type.d.ts +4 -0
  140. package/dist/cjs/domain/type/string-type.js +16 -0
  141. package/dist/cjs/domain/type/uuid-type.d.ts +7 -0
  142. package/dist/cjs/domain/type/uuid-type.js +27 -0
  143. package/dist/cjs/infrastructure/clock/fixed-clock.d.ts +8 -0
  144. package/dist/cjs/infrastructure/clock/fixed-clock.js +19 -0
  145. package/dist/cjs/infrastructure/clock/system-clock.d.ts +4 -0
  146. package/dist/cjs/infrastructure/clock/system-clock.js +9 -0
  147. package/dist/cjs/infrastructure/event/domain-event-bus.d.ts +16 -0
  148. package/dist/cjs/infrastructure/event/domain-event-bus.js +60 -0
  149. package/dist/cjs/infrastructure/event/event-channel.d.ts +9 -0
  150. package/dist/cjs/infrastructure/event/event-channel.js +2 -0
  151. package/dist/cjs/infrastructure/event/event-registry.d.ts +9 -0
  152. package/dist/cjs/infrastructure/event/event-registry.js +43 -0
  153. package/dist/cjs/infrastructure/event/in-memory/emitter.d.ts +11 -0
  154. package/dist/cjs/infrastructure/event/in-memory/emitter.js +54 -0
  155. package/dist/cjs/infrastructure/event/in-memory/in-memory-event-channel.d.ts +16 -0
  156. package/dist/cjs/infrastructure/event/in-memory/in-memory-event-channel.js +50 -0
  157. package/dist/cjs/infrastructure/exceptions/event-delivery.exception.d.ts +5 -0
  158. package/dist/cjs/infrastructure/exceptions/event-delivery.exception.js +20 -0
  159. package/dist/cjs/infrastructure/exceptions/infrastructure-exception-code.d.ts +6 -0
  160. package/dist/cjs/infrastructure/exceptions/infrastructure-exception-code.js +10 -0
  161. package/dist/cjs/infrastructure/exceptions/infrastructure.exception.d.ts +6 -0
  162. package/dist/cjs/infrastructure/exceptions/infrastructure.exception.js +16 -0
  163. package/dist/cjs/infrastructure/exceptions/internal-error.exception.d.ts +5 -0
  164. package/dist/cjs/infrastructure/exceptions/internal-error.exception.js +17 -0
  165. package/dist/cjs/infrastructure/exceptions/kind/exception-classifier.d.ts +4 -0
  166. package/dist/cjs/infrastructure/exceptions/kind/exception-classifier.js +34 -0
  167. package/dist/cjs/infrastructure/exceptions/kind/exception-kind.d.ts +9 -0
  168. package/dist/cjs/infrastructure/exceptions/kind/exception-kind.js +13 -0
  169. package/dist/cjs/infrastructure/exceptions/unknown-event.exception.d.ts +4 -0
  170. package/dist/cjs/infrastructure/exceptions/unknown-event.exception.js +19 -0
  171. package/dist/cjs/infrastructure/index.d.ts +21 -0
  172. package/dist/cjs/infrastructure/index.js +33 -0
  173. package/dist/cjs/infrastructure/logging/console-logger.d.ts +17 -0
  174. package/dist/cjs/infrastructure/logging/console-logger.js +37 -0
  175. package/dist/cjs/infrastructure/logging/in-memory-logger.d.ts +15 -0
  176. package/dist/cjs/infrastructure/logging/in-memory-logger.js +27 -0
  177. package/dist/cjs/infrastructure/logging/log-serializer.d.ts +7 -0
  178. package/dist/cjs/infrastructure/logging/log-serializer.js +59 -0
  179. package/dist/cjs/infrastructure/logging/silent-logger.d.ts +7 -0
  180. package/dist/cjs/infrastructure/logging/silent-logger.js +18 -0
  181. package/dist/cjs/shared/exceptions/abstract.exception.d.ts +25 -0
  182. package/dist/cjs/shared/exceptions/abstract.exception.js +59 -0
  183. package/dist/cjs/shared/exceptions/brand/exception-brand.d.ts +6 -0
  184. package/dist/cjs/shared/exceptions/brand/exception-brand.js +29 -0
  185. package/dist/cjs/shared/exceptions/received/received-value.d.ts +4 -0
  186. package/dist/cjs/shared/exceptions/received/received-value.js +13 -0
  187. package/dist/cjs/shared/index.d.ts +8 -0
  188. package/dist/cjs/shared/index.js +13 -0
  189. package/dist/cjs/shared/issue-collection-error.d.ts +9 -0
  190. package/dist/cjs/shared/issue-collection-error.js +11 -0
  191. package/dist/cjs/shared/issue-collector.d.ts +8 -0
  192. package/dist/cjs/shared/issue-collector.js +41 -0
  193. package/dist/esm/application/event/event-bus.d.ts +4 -0
  194. package/dist/esm/application/event/event-bus.js +1 -0
  195. package/dist/esm/application/event/event-listener.d.ts +4 -0
  196. package/dist/esm/application/event/event-listener.js +2 -0
  197. package/dist/esm/application/event/listens-to.d.ts +15 -0
  198. package/dist/esm/application/event/listens-to.js +25 -0
  199. package/dist/esm/application/exceptions/aggregate-not-found.exception.d.ts +5 -0
  200. package/dist/esm/application/exceptions/aggregate-not-found.exception.js +13 -0
  201. package/dist/esm/application/exceptions/application-exception-code.d.ts +7 -0
  202. package/dist/esm/application/exceptions/application-exception-code.js +8 -0
  203. package/dist/esm/application/exceptions/application.exception.d.ts +6 -0
  204. package/dist/esm/application/exceptions/application.exception.js +12 -0
  205. package/dist/esm/application/exceptions/concurrency.exception.d.ts +5 -0
  206. package/dist/esm/application/exceptions/concurrency.exception.js +13 -0
  207. package/dist/esm/application/exceptions/not-allowed.exception.d.ts +5 -0
  208. package/dist/esm/application/exceptions/not-allowed.exception.js +13 -0
  209. package/dist/esm/application/exceptions/unexpected-error.exception.d.ts +5 -0
  210. package/dist/esm/application/exceptions/unexpected-error.exception.js +13 -0
  211. package/dist/esm/application/index.d.ts +14 -0
  212. package/dist/esm/application/index.js +10 -0
  213. package/dist/esm/application/ports/clock.d.ts +3 -0
  214. package/dist/esm/application/ports/clock.js +1 -0
  215. package/dist/esm/application/ports/logger.d.ts +8 -0
  216. package/dist/esm/application/ports/logger.js +1 -0
  217. package/dist/esm/application/use-case/use-case.d.ts +6 -0
  218. package/dist/esm/application/use-case/use-case.js +21 -0
  219. package/dist/esm/application/value-object-builder/value-object-builder.d.ts +16 -0
  220. package/dist/esm/application/value-object-builder/value-object-builder.js +47 -0
  221. package/dist/esm/domain/aggregate/aggregate-root.d.ts +9 -0
  222. package/dist/esm/domain/aggregate/aggregate-root.js +13 -0
  223. package/dist/esm/domain/entity/entity.d.ts +9 -0
  224. package/dist/esm/domain/entity/entity.js +15 -0
  225. package/dist/esm/domain/event/event-base.d.ts +23 -0
  226. package/dist/esm/domain/event/event-base.js +17 -0
  227. package/dist/esm/domain/exceptions/domain-exception-code.d.ts +7 -0
  228. package/dist/esm/domain/exceptions/domain-exception-code.js +8 -0
  229. package/dist/esm/domain/exceptions/domain.exception.d.ts +6 -0
  230. package/dist/esm/domain/exceptions/domain.exception.js +12 -0
  231. package/dist/esm/domain/exceptions/invalid-event-definition.exception.d.ts +4 -0
  232. package/dist/esm/domain/exceptions/invalid-event-definition.exception.js +15 -0
  233. package/dist/esm/domain/exceptions/required-value.exception.d.ts +4 -0
  234. package/dist/esm/domain/exceptions/required-value.exception.js +16 -0
  235. package/dist/esm/domain/exceptions/type-primitive.exception.d.ts +4 -0
  236. package/dist/esm/domain/exceptions/type-primitive.exception.js +16 -0
  237. package/dist/esm/domain/exceptions/validation.exception.d.ts +14 -0
  238. package/dist/esm/domain/exceptions/validation.exception.js +19 -0
  239. package/dist/esm/domain/index.d.ts +17 -0
  240. package/dist/esm/domain/index.js +12 -0
  241. package/dist/esm/domain/primitive/primitive-types.d.ts +20 -0
  242. package/dist/esm/domain/primitive/primitive-types.js +1 -0
  243. package/dist/esm/domain/repository/aggregate-finder.d.ts +4 -0
  244. package/dist/esm/domain/repository/aggregate-finder.js +1 -0
  245. package/dist/esm/domain/repository/aggregate-remover.d.ts +4 -0
  246. package/dist/esm/domain/repository/aggregate-remover.js +1 -0
  247. package/dist/esm/domain/repository/aggregate-saver.d.ts +4 -0
  248. package/dist/esm/domain/repository/aggregate-saver.js +1 -0
  249. package/dist/esm/domain/specification/specification.d.ts +9 -0
  250. package/dist/esm/domain/specification/specification.js +40 -0
  251. package/dist/esm/domain/type/abstract-type.d.ts +18 -0
  252. package/dist/esm/domain/type/abstract-type.js +57 -0
  253. package/dist/esm/domain/type/array-type.d.ts +17 -0
  254. package/dist/esm/domain/type/array-type.js +60 -0
  255. package/dist/esm/domain/type/boolean-type.d.ts +4 -0
  256. package/dist/esm/domain/type/boolean-type.js +12 -0
  257. package/dist/esm/domain/type/composite/composite-brand.d.ts +4 -0
  258. package/dist/esm/domain/type/composite/composite-brand.js +11 -0
  259. package/dist/esm/domain/type/composite/composite-field.d.ts +18 -0
  260. package/dist/esm/domain/type/composite/composite-field.js +82 -0
  261. package/dist/esm/domain/type/composite/composite-issues.d.ts +6 -0
  262. package/dist/esm/domain/type/composite/composite-issues.js +31 -0
  263. package/dist/esm/domain/type/composite/composite.d.ts +26 -0
  264. package/dist/esm/domain/type/composite/composite.js +48 -0
  265. package/dist/esm/domain/type/date-type.d.ts +19 -0
  266. package/dist/esm/domain/type/date-type.js +64 -0
  267. package/dist/esm/domain/type/enum-type.d.ts +7 -0
  268. package/dist/esm/domain/type/enum-type.js +26 -0
  269. package/dist/esm/domain/type/id-type.d.ts +7 -0
  270. package/dist/esm/domain/type/id-type.js +13 -0
  271. package/dist/esm/domain/type/index.d.ts +15 -0
  272. package/dist/esm/domain/type/index.js +12 -0
  273. package/dist/esm/domain/type/json-type.d.ts +9 -0
  274. package/dist/esm/domain/type/json-type.js +42 -0
  275. package/dist/esm/domain/type/number-type.d.ts +4 -0
  276. package/dist/esm/domain/type/number-type.js +12 -0
  277. package/dist/esm/domain/type/required/required-mark.d.ts +8 -0
  278. package/dist/esm/domain/type/required/required-mark.js +9 -0
  279. package/dist/esm/domain/type/required/required-type.d.ts +7 -0
  280. package/dist/esm/domain/type/required/required-type.js +18 -0
  281. package/dist/esm/domain/type/string-type.d.ts +4 -0
  282. package/dist/esm/domain/type/string-type.js +12 -0
  283. package/dist/esm/domain/type/uuid-type.d.ts +7 -0
  284. package/dist/esm/domain/type/uuid-type.js +23 -0
  285. package/dist/esm/infrastructure/clock/fixed-clock.d.ts +8 -0
  286. package/dist/esm/infrastructure/clock/fixed-clock.js +15 -0
  287. package/dist/esm/infrastructure/clock/system-clock.d.ts +4 -0
  288. package/dist/esm/infrastructure/clock/system-clock.js +5 -0
  289. package/dist/esm/infrastructure/event/domain-event-bus.d.ts +16 -0
  290. package/dist/esm/infrastructure/event/domain-event-bus.js +56 -0
  291. package/dist/esm/infrastructure/event/event-channel.d.ts +9 -0
  292. package/dist/esm/infrastructure/event/event-channel.js +1 -0
  293. package/dist/esm/infrastructure/event/event-registry.d.ts +9 -0
  294. package/dist/esm/infrastructure/event/event-registry.js +39 -0
  295. package/dist/esm/infrastructure/event/in-memory/emitter.d.ts +11 -0
  296. package/dist/esm/infrastructure/event/in-memory/emitter.js +50 -0
  297. package/dist/esm/infrastructure/event/in-memory/in-memory-event-channel.d.ts +16 -0
  298. package/dist/esm/infrastructure/event/in-memory/in-memory-event-channel.js +46 -0
  299. package/dist/esm/infrastructure/exceptions/event-delivery.exception.d.ts +5 -0
  300. package/dist/esm/infrastructure/exceptions/event-delivery.exception.js +16 -0
  301. package/dist/esm/infrastructure/exceptions/infrastructure-exception-code.d.ts +6 -0
  302. package/dist/esm/infrastructure/exceptions/infrastructure-exception-code.js +7 -0
  303. package/dist/esm/infrastructure/exceptions/infrastructure.exception.d.ts +6 -0
  304. package/dist/esm/infrastructure/exceptions/infrastructure.exception.js +12 -0
  305. package/dist/esm/infrastructure/exceptions/internal-error.exception.d.ts +5 -0
  306. package/dist/esm/infrastructure/exceptions/internal-error.exception.js +13 -0
  307. package/dist/esm/infrastructure/exceptions/kind/exception-classifier.d.ts +4 -0
  308. package/dist/esm/infrastructure/exceptions/kind/exception-classifier.js +30 -0
  309. package/dist/esm/infrastructure/exceptions/kind/exception-kind.d.ts +9 -0
  310. package/dist/esm/infrastructure/exceptions/kind/exception-kind.js +10 -0
  311. package/dist/esm/infrastructure/exceptions/unknown-event.exception.d.ts +4 -0
  312. package/dist/esm/infrastructure/exceptions/unknown-event.exception.js +15 -0
  313. package/dist/esm/infrastructure/index.d.ts +21 -0
  314. package/dist/esm/infrastructure/index.js +15 -0
  315. package/dist/esm/infrastructure/logging/console-logger.d.ts +17 -0
  316. package/dist/esm/infrastructure/logging/console-logger.js +33 -0
  317. package/dist/esm/infrastructure/logging/in-memory-logger.d.ts +15 -0
  318. package/dist/esm/infrastructure/logging/in-memory-logger.js +23 -0
  319. package/dist/esm/infrastructure/logging/log-serializer.d.ts +7 -0
  320. package/dist/esm/infrastructure/logging/log-serializer.js +55 -0
  321. package/dist/esm/infrastructure/logging/silent-logger.d.ts +7 -0
  322. package/dist/esm/infrastructure/logging/silent-logger.js +14 -0
  323. package/dist/esm/package.json +3 -0
  324. package/dist/esm/shared/exceptions/abstract.exception.d.ts +25 -0
  325. package/dist/esm/shared/exceptions/abstract.exception.js +55 -0
  326. package/dist/esm/shared/exceptions/brand/exception-brand.d.ts +6 -0
  327. package/dist/esm/shared/exceptions/brand/exception-brand.js +25 -0
  328. package/dist/esm/shared/exceptions/received/received-value.d.ts +4 -0
  329. package/dist/esm/shared/exceptions/received/received-value.js +9 -0
  330. package/dist/esm/shared/index.d.ts +8 -0
  331. package/dist/esm/shared/index.js +5 -0
  332. package/dist/esm/shared/issue-collection-error.d.ts +9 -0
  333. package/dist/esm/shared/issue-collection-error.js +7 -0
  334. package/dist/esm/shared/issue-collector.d.ts +8 -0
  335. package/dist/esm/shared/issue-collector.js +37 -0
  336. package/package.json +95 -4
@@ -0,0 +1,264 @@
1
+ # Capa de aplicación en TypeScript
2
+
3
+ ## Plantilla de caso de uso
4
+
5
+ Un caso de uso extiende `UseCase<Input, Command, Output>` de `@archi-code/ddd/application`. `execute(input)` llama a `toCommand(input)` y después a `handle(command)`; no hay que escribir `execute`.
6
+
7
+ ```ts
8
+ import type { AggregateFinder, AggregateSaver } from '@archi-code/ddd/domain';
9
+ import { AggregateNotFoundException, NotAllowedException, UseCase, ValueObjectBuilder } from '@archi-code/ddd/application';
10
+ import type { Clock, EventBus } from '@archi-code/ddd/application';
11
+
12
+ export interface SalesPermissions {
13
+ canCancelOrders(requester: CustomerId): Promise<boolean>;
14
+ }
15
+
16
+ export interface CancelOrderInput {
17
+ orderId: string;
18
+ requesterId: string;
19
+ reason: string | null;
20
+ }
21
+
22
+ interface CancelOrderCommand {
23
+ orderId: OrderId;
24
+ requester: CustomerId;
25
+ reason: CancellationReason;
26
+ }
27
+
28
+ export class CancelOrder extends UseCase<CancelOrderInput, CancelOrderCommand, void> {
29
+ private readonly cancellation = new OrderCancellation();
30
+
31
+ constructor(
32
+ private readonly orders: AggregateFinder<Order> & AggregateSaver<Order>,
33
+ private readonly events: EventBus<OrderEvent>,
34
+ private readonly permissions: SalesPermissions,
35
+ private readonly clock: Clock,
36
+ ) {
37
+ super();
38
+ }
39
+
40
+ protected toCommand(input: CancelOrderInput): CancelOrderCommand {
41
+ return ValueObjectBuilder.build({
42
+ orderId: () => new OrderId(input.orderId),
43
+ requester: () => new CustomerId(input.requesterId),
44
+ reason: () => new CancellationReason(input.reason as string),
45
+ });
46
+ }
47
+
48
+ protected async handle(command: CancelOrderCommand): Promise<void> {
49
+ if (!(await this.permissions.canCancelOrders(command.requester))) {
50
+ throw new NotAllowedException(`Customer ${command.requester.value} may not cancel orders`);
51
+ }
52
+ const order = await this.orders.findById(command.orderId);
53
+ if (order === null) {
54
+ throw new AggregateNotFoundException('Order', command.orderId.value);
55
+ }
56
+ this.cancellation.cancel(order, command.requester, command.reason, this.clock);
57
+ await this.orders.save(order);
58
+ await this.events.publish(order.pullDomainEvents());
59
+ }
60
+ }
61
+ ```
62
+
63
+ Autorizar, cargar, invocar el dominio, guardar y publicar, y ninguna regla de negocio: que solo el cliente del pedido pueda cancelarlo lo comprueba el servicio de dominio `OrderCancellation`, y que no esté ya cancelado, `order.cancel`. Coincide con la forma de IDDD: parámetros, repositorio, comando sobre el agregado [IDDD cap. 4] y con la anatomía de *DDD in PHP* [PHP: Application].
64
+
65
+ * El caso de uso pide solo los roles de repositorio que usa (`AggregateFinder<Order> & AggregateSaver<Order>`), los puertos `EventBus` y `Clock` del paquete, y los puertos de aplicación propios (`SalesPermissions`). Nunca una dependencia de transacciones (un runner de transacciones o una unidad de trabajo): no existe puerto de transacciones `[Fuera de los libros: convención de @archi-code/ddd]`.
66
+ * `UseCase` no trae ayudas (`findOrFail`, `saveAndPublish`): todos los pasos se escriben en `handle` `[Fuera de los libros: convención de @archi-code/ddd]`.
67
+ * Se guarda antes de publicar, y se publica lo que devuelve `pullDomainEvents()`.
68
+ * La aplicación nunca abre, une ni cierra transacciones. IDDD pone el control de la transacción en el servicio de aplicación [IDDD cap. 14]; con el paquete esa responsabilidad pasa al adaptador del repositorio, que hace atómico cada `save` y `remove` (una escritura de documento en Mongo; si el agregado ocupa varias tablas SQL, abre y confirma o deshace una transacción de la base de datos dentro de `save`/`remove`, con su propia conexión o sesión), comprueba la versión y envuelve los errores del driver en `InternalErrorException` con `{ cause }` `[Fuera de los libros: convención de @archi-code/ddd]`. Ver `ddd-skills/ddd-repositorios-persistencia/references/transacciones-ts.md`.
69
+
70
+ ## Entrada
71
+
72
+ * Primitivos o DTO o comando [PHP: Application, citando a Vernon]. Con el paquete, dos tipos por caso de uso `[Fuera de los libros: convención de @archi-code/ddd]`: el `Input`, plano y de primitivos (se puede serializar y encolar si un día hace falta [PHP: Application]), y el `Command`, de value objects, que construye `toCommand`.
73
+ * La conversión a value objects ocurre en `toCommand` con `ValueObjectBuilder.build`: si algún campo no es válido lanza una sola `ValidationException` con el `field` y el `code` de cada problema (`ValueObjectBuilder.buildList` nombra los elementos de una lista por posición, `lines.1.quantity`), y `handle` no llega a ejecutarse.
74
+ * El adaptador de entrada solo comprueba la forma de la petición (que un campo sea texto); las reglas de cada valor son de los value objects.
75
+ * Nunca recibas el agregado desde el controlador.
76
+
77
+ ## Salida
78
+
79
+ IDDD presenta varias opciones [IDDD cap. 14]:
80
+
81
+ | Opción | Cuándo |
82
+ |---|---|
83
+ | Devolver un identificador o nada | Comandos (lo normal) |
84
+ | DTO construido desde el agregado | El cliente necesita el resultado inmediato |
85
+ | Transformador de datos que elige el cliente | Varios clientes con formatos distintos |
86
+ | Método sin retorno y puerto de salida | Estilo hexagonal estricto |
87
+
88
+ Con el paquete, la salida es el tercer parámetro de tipo: `UseCase<PlaceOrderInput, PlaceOrderCommand, string>` devuelve el id (`order.id.value`), `void` nada. Nunca el agregado.
89
+
90
+ Para pantallas, mejor una consulta de lectura aparte que devuelve un DTO sin pasar por el agregado: una interfaz de aplicación implementada en infraestructura directamente sobre la base de datos (ver `ddd-skills/ddd-repositorios-persistencia/GUIA.md` y `ddd-skills/ddd-event-sourcing-cqrs/GUIA.md`).
91
+
92
+ ## Command bus
93
+
94
+ *DDD in PHP* presenta el bus de comandos (Tactician) para enlazar cada comando con su manejador; los objetos request se parecen ya a comandos [PHP: Application]. El paquete no trae bus de comandos `[Fuera de los libros: convención de @archi-code/ddd]`: el composition root construye cada caso de uso y se lo pasa al punto de entrada, que llama a `execute(input)`. Lo común a todos los casos de uso ya está en `UseCase.execute` (dejar pasar las excepciones del paquete y envolver cualquier otro error en `UnexpectedErrorException` con `cause`), y la atomicidad de cada escritura es del repositorio.
95
+
96
+ ## Eventos en la capa de aplicación
97
+
98
+ * La aplicación puede registrar suscriptores para guardar y reenviar eventos, dejando el dominio libre de mensajería [IDDD cap. 4]. Con el paquete, los suscriptores son listeners (`EventListener` con `@ListensTo`) que el composition root conecta con `bus.subscribe(listener)`; cada uno ejecuta un solo caso de uso.
99
+ * Con outbox, los eventos se guardan en la transacción y la publicación ocurre fuera (`ddd-skills/ddd-eventos-dominio/GUIA.md`). Con el paquete, el caso de uso siempre llama a `events.publish(order.pullDomainEvents())` después de `save`; si `publish` falla después de `save`, el evento puede perderse. Cuando un evento no puede perderse, el repositorio escribe el agregado y sus eventos pendientes en la misma escritura atómica (un outbox dentro del mismo documento o en la misma transacción de base de datos de `save`) y un relay los entrega después por un `EventChannel`; los casos de uso no cambian `[Fuera de los libros: convención de @archi-code/ddd]`.
100
+
101
+ ## Conflictos de concurrencia
102
+
103
+ Cada repositorio comprueba la versión del agregado y lanza `ConcurrencyException` (409) si otro lo cambió después de cargarlo. Si el comando es seguro de repetir sobre el nuevo estado, el caso de uso puede reintentarlo [IDDD cap. 10; IDDD apéndice A]: un bucle en `handle` en el que cada intento vuelve a cargar el agregado y vuelve a guardarlo; pocos intentos y después se relanza. Un caso de uso que ejecuta un listener no reintenta por sí mismo: reintenta la reentrega del canal.
104
+
105
+ ```ts
106
+ import { DomainException } from '@archi-code/ddd/domain';
107
+ import { ConcurrencyException } from '@archi-code/ddd/application';
108
+
109
+ export interface ProductPricing {
110
+ priceForSale(sku: Sku): Promise<Money | null>;
111
+ }
112
+
113
+ export class ProductNotSellable extends DomainException {
114
+ constructor(sku: string) {
115
+ super(`Product ${sku} is not for sale`, ['ORD003']);
116
+ }
117
+ }
118
+
119
+ const ATTEMPTS = 3;
120
+
121
+ export class AddOrderLine extends UseCase<AddOrderLineInput, AddOrderLineCommand, void> {
122
+ constructor(
123
+ private readonly orders: AggregateFinder<Order> & AggregateSaver<Order>,
124
+ private readonly pricing: ProductPricing,
125
+ private readonly events: EventBus<OrderEvent>,
126
+ private readonly clock: Clock,
127
+ ) {
128
+ super();
129
+ }
130
+
131
+ protected toCommand(input: AddOrderLineInput): AddOrderLineCommand {
132
+ return ValueObjectBuilder.build({
133
+ orderId: () => new OrderId(input.orderId),
134
+ sku: () => new Sku(input.sku),
135
+ quantity: () => new Quantity(input.quantity),
136
+ });
137
+ }
138
+
139
+ protected async handle({ orderId, sku, quantity }: AddOrderLineCommand): Promise<void> {
140
+ const unitPrice = await this.pricing.priceForSale(sku);
141
+ if (unitPrice === null) {
142
+ throw new ProductNotSellable(sku.value);
143
+ }
144
+ const line = new OrderLine({ sku, quantity, unitPrice });
145
+ for (let attempt = 1; ; attempt += 1) {
146
+ try {
147
+ const order = await this.orders.findById(orderId);
148
+ if (order === null) {
149
+ throw new AggregateNotFoundException('Order', orderId.value);
150
+ }
151
+ order.addLine(line, this.clock);
152
+ await this.orders.save(order);
153
+ await this.events.publish(order.pullDomainEvents());
154
+ return;
155
+ } catch (error) {
156
+ if (!(error instanceof ConcurrencyException) || attempt === ATTEMPTS) {
157
+ throw error;
158
+ }
159
+ }
160
+ }
161
+ }
162
+ }
163
+ ```
164
+
165
+ `ProductPricing` es un puerto de dominio (la interfaz en `domain/` con value objects, implementada en infraestructura por la capa anticorrupción) y `ProductNotSellable` una regla de dominio. `AddOrderLineInput` (`orderId`, `sku`, `quantity` como primitivos) y `AddOrderLineCommand` (`OrderId`, `Sku`, `Quantity`) siguen el patrón de `CancelOrder`. Nunca se reintenta con el agregado ya cargado, y un caso de uso nunca se llama a sí mismo ni a otro para reintentar.
166
+
167
+ ## Errores
168
+
169
+ * El dominio lanza `DomainException` con nombre y código propio (`class OrderCannotBeCancelled extends DomainException`, `super(message, ['ORD002'])`); los value objects y `ValueObjectBuilder.build` lanzan `ValidationException`.
170
+ * La aplicación añade las suyas: `AggregateNotFoundException('Order', id)` cuando `findById` devuelve `null`, `NotAllowedException(message)` cuando el solicitante no puede ejecutar la acción, `ApplicationException` para otros errores de aplicación. Al traducir otro error, siempre con `{ cause: error }`.
171
+ * Los errores se dejan propagar: no se capturan en `handle` para relanzarlos; `execute` deja pasar las excepciones del paquete y envuelve el resto en `UnexpectedErrorException` (de `@archi-code/ddd/application`).
172
+ * El punto de entrada los clasifica con `ExceptionClassifier.classify(error)` de `@archi-code/ddd/infrastructure`, que no sabe de ningún protocolo: `NotFound` (`AggregateNotFoundException`), `NotAllowed` (`NotAllowedException`), `Conflict` (`ConcurrencyException`), `Malformed` (`TypePrimitiveException`, `RequiredValueException`), `Invalid` (`ValidationException` y cualquier otra `DomainException`), `Rejected` (otros errores de aplicación) e `Internal` (`UnexpectedErrorException`, `InvalidEventDefinitionException` y lo demás). Cada protocolo traduce el `ExceptionKind` a sus códigos (en HTTP: 404, 403, 409, 400, 422, 400 y 500), usa `error.code` para los códigos propios (`ORD001` → 409), oculta el mensaje de `Internal`, devuelve los `errors` de una `ValidationException` y registra él mismo los fallos internos `[Fuera de los libros: convención de @archi-code/ddd]`. Un controlador HTTP usa el `toHttpError` del proyecto (en `interfaces/http/`; completo en `ddd-skills/ddd-arquitectura/references/estructura-hexagonal-ts.md`):
173
+
174
+ ```ts
175
+ import type { Logger } from '@archi-code/ddd/application';
176
+ import { toHttpError } from './httpError';
177
+
178
+ interface HttpRequest {
179
+ params: { orderId: string };
180
+ auth: { customerId: string };
181
+ body: { reason?: unknown };
182
+ }
183
+
184
+ interface HttpResponse {
185
+ status: number;
186
+ body?: unknown;
187
+ }
188
+
189
+ export class CancelOrderController {
190
+ constructor(
191
+ private readonly cancelOrder: CancelOrder,
192
+ private readonly logger: Logger,
193
+ ) {}
194
+
195
+ async cancel(request: HttpRequest): Promise<HttpResponse> {
196
+ try {
197
+ await this.cancelOrder.execute({
198
+ orderId: request.params.orderId,
199
+ requesterId: request.auth.customerId,
200
+ reason: typeof request.body.reason === 'string' ? request.body.reason : null,
201
+ });
202
+ return { status: 204 };
203
+ } catch (error) {
204
+ return toHttpError(error, this.logger);
205
+ }
206
+ }
207
+ }
208
+ ```
209
+
210
+ La identidad del solicitante sale del token (`request.auth`), nunca del cuerpo.
211
+
212
+ ## Tests
213
+
214
+ Con adaptadores en memoria: `InMemoryOrderRepository`, que el proyecto escribe en `test/support/` (uno por agregado; cada `save` y `remove` es atómico y comprueba la versión; cómo se escribe, en `ddd-skills/ddd-repositorios-persistencia/references/tests-repositorios.md`), y del paquete `DomainEventBus` sobre `InMemoryEventChannel` (`channel.sent` lista lo publicado). Los puertos propios se sustituyen por objetos simples.
215
+
216
+ ```ts
217
+ import { describe, expect, it } from 'vitest';
218
+ import { NotAllowedException } from '@archi-code/ddd/application';
219
+ import {
220
+ DomainEventBus,
221
+ EventRegistry,
222
+ FixedClock,
223
+ InMemoryEventChannel,
224
+ } from '@archi-code/ddd/infrastructure';
225
+ import { InMemoryOrderRepository } from '../support/in-memory-order-repository';
226
+
227
+ const clock = new FixedClock('2026-03-01T09:30:00Z');
228
+
229
+ function salesWith(permissions: SalesPermissions) {
230
+ const orders = new InMemoryOrderRepository();
231
+ const channel = new InMemoryEventChannel();
232
+ const events = new DomainEventBus<OrderEvent>(channel, new EventRegistry<OrderEvent>());
233
+ const cancelOrder = new CancelOrder(orders, events, permissions, clock);
234
+ return { orders, channel, cancelOrder };
235
+ }
236
+
237
+ const everyoneMayCancel: SalesPermissions = { canCancelOrders: async () => true };
238
+ const nobodyMayCancel: SalesPermissions = { canCancelOrders: async () => false };
239
+
240
+ describe('Cancelling an order', () => {
241
+ it('a customer cancels her own pending order', async () => {
242
+ const { orders, channel, cancelOrder } = salesWith(everyoneMayCancel);
243
+ await orders.save(aPendingOrderOf(laura));
244
+
245
+ await cancelOrder.execute({ orderId, requesterId: laura, reason: 'Wrong size' });
246
+
247
+ expect(channel.sent).toMatchObject([
248
+ { eventName: 'OrderCancelled', orderId, reason: 'Wrong size' },
249
+ ]);
250
+ });
251
+
252
+ it('a customer whose account may not cancel orders is refused', async () => {
253
+ const { orders, channel, cancelOrder } = salesWith(nobodyMayCancel);
254
+ await orders.save(aPendingOrderOf(laura));
255
+
256
+ await expect(
257
+ cancelOrder.execute({ orderId, requesterId: laura, reason: 'Wrong size' }),
258
+ ).rejects.toThrow(NotAllowedException);
259
+ expect(channel.sent).toEqual([]);
260
+ });
261
+ });
262
+ ```
263
+
264
+ `aPendingOrderOf`, `laura` y `orderId` son fábricas y datos de `test/support/` del proyecto; el modelo (`Order`, `CancellationReason`, `OrderCancelled`…) es el de `ddd-skills/ddd-agregados/references/implementacion-ts.md` y `OrderCancellation` el de `ddd-skills/ddd-servicios-capa-aplicacion/references/ejemplo-ecommerce.md`. Estos tests comprueban orquestación. Que un pedido ya cancelado no se pueda cancelar otra vez se prueba en `Order`, y que otro cliente no pueda cancelarlo, en `OrderCancellation`; no aquí.
@@ -0,0 +1,321 @@
1
+ # Ejemplo: dónde va cada lógica en Ventas
2
+
3
+ | Lógica | Dónde | Por qué |
4
+ |---|---|---|
5
+ | El total es la suma de las líneas | `OrderLines` (composite `Composite({ items: [OrderLine] })`, método `total()`) | Cálculo sobre valores |
6
+ | Un pedido confirmado no admite líneas nuevas | `Order` (agregado) | Regla sobre su estado |
7
+ | Gastos de envío según zona y promociones | `ShippingCostCalculator` (servicio de dominio) | Necesita el pedido, la zona, las tarifas y la promoción; forzado en cualquiera de ellos |
8
+ | Envío gratis a partir de un importe | `OrderAbove` (`Specification<Order>`) | Regla con nombre que decide si un pedido cumple un criterio |
9
+ | Solo el cliente que hizo el pedido puede cancelarlo | `OrderCancellation` (servicio de dominio), con concepto `Customer`, sobre `Order.cancel` | Regla de negocio [IDDD cap. 2] |
10
+ | Quien pide cancelar tiene permiso para cancelar pedidos (rol, sesión activa) | `SalesPermissions` (puerto de aplicación) consultado por `CancelOrder`, que lanza `NotAllowedException` | Autorización del caso de uso [IDDD cap. 14] |
11
+ | Cargar pedido, cancelar, guardar, publicar | `CancelOrder` (caso de uso) | Coordinación |
12
+ | Cuando Almacén reserva, registrar en el pedido | listener `RegisterReservationOnStockReserved` y su caso de uso `RegisterReservation` `[Fuera de los libros: convención de @archi-code/ddd]` | Reacción a evento; invoca al agregado |
13
+ | Precio vigente de una referencia | `ProductPricing` (puerto de dominio, ACL en infraestructura) | Viene de otro contexto |
14
+
15
+ ## El modelo
16
+
17
+ `Order` y sus value objects (`OrderId`, `CustomerId`, `Sku`, `Quantity`, `Money`, `OrderLine`, `OrderLines`, `ShippingAddress`, `OrderStatus`, `ReservationId`, `CancellationReason`), sus eventos (`OrderPlaced`, `OrderLineAdded`, `OrderCancelled`…, unidos en `OrderEvent`) y sus errores son los de `ddd-skills/ddd-agregados/references/implementacion-ts.md`; no se redefinen aquí. Lo que usan los ejemplos de esta guía (`ddd-skills/ddd-servicios-capa-aplicacion/references/servicios-dominio-ts.md` y `ddd-skills/ddd-servicios-capa-aplicacion/references/capa-aplicacion-ts.md`):
18
+
19
+ ```ts
20
+ static place(
21
+ id: OrderId,
22
+ customerId: CustomerId,
23
+ lines: OrderLines,
24
+ address: ShippingAddress,
25
+ clock: { now(): Date },
26
+ ): Order;
27
+ addLine(line: OrderLine, clock: { now(): Date }): void;
28
+ registerStockReservation(reservation: ReservationId, clock: { now(): Date }): void;
29
+ cancel(reason: CancellationReason, clock: { now(): Date }): void;
30
+ total(): Money;
31
+ ```
32
+
33
+ Lo que añade Ventas en esta guía:
34
+
35
+ ```ts
36
+ import { AggregateRoot, BooleanType, DomainException, Required, Specification, StringType } from '@archi-code/ddd/domain';
37
+ import type { PrimitiveTypes } from '@archi-code/ddd/domain';
38
+
39
+ export class OrderAbove extends Specification<Order> {
40
+ constructor(private readonly minimum: Money) {
41
+ super();
42
+ }
43
+
44
+ isSatisfiedBy(order: Order): boolean {
45
+ return order.total().covers(this.minimum);
46
+ }
47
+ }
48
+
49
+ export class CustomerBlocked extends DomainException {
50
+ constructor(customerId: string) {
51
+ super(`Customer ${customerId} is blocked and cannot place orders`, ['CUS001']);
52
+ }
53
+ }
54
+
55
+ export class CustomerName extends Required(StringType) {}
56
+ export class Blocked extends Required(BooleanType) {}
57
+
58
+ export class Customer extends AggregateRoot<CustomerId> {
59
+ private constructor(
60
+ id: CustomerId,
61
+ readonly name: CustomerName,
62
+ private _blocked: Blocked,
63
+ ) {
64
+ super(id);
65
+ }
66
+
67
+ static fromPrimitives(primitives: PrimitiveTypes<Customer>): Customer {
68
+ return new Customer(
69
+ new CustomerId(primitives.id),
70
+ new CustomerName(primitives.name),
71
+ new Blocked(primitives.blocked),
72
+ );
73
+ }
74
+
75
+ get blocked(): Blocked {
76
+ return this._blocked;
77
+ }
78
+
79
+ placeOrder(
80
+ id: OrderId,
81
+ lines: OrderLines,
82
+ address: ShippingAddress,
83
+ clock: { now(): Date },
84
+ ): Order {
85
+ if (this._blocked.value) {
86
+ throw new CustomerBlocked(this.id.value);
87
+ }
88
+ return Order.place(id, this.id, lines, address, clock);
89
+ }
90
+
91
+ toPrimitives(): PrimitiveTypes<Customer> {
92
+ return { id: this.id.value, name: this.name.value, blocked: this._blocked.value };
93
+ }
94
+ }
95
+
96
+ export class NotTheCustomerOfOrder extends DomainException {
97
+ constructor(orderId: string) {
98
+ super(`Only the customer who placed order ${orderId} can cancel it`, ['ORD004']);
99
+ }
100
+ }
101
+
102
+ export class OrderCancellation {
103
+ cancel(
104
+ order: Order,
105
+ requester: CustomerId,
106
+ reason: CancellationReason,
107
+ clock: { now(): Date },
108
+ ): void {
109
+ if (!order.customerId.equals(requester)) {
110
+ throw new NotTheCustomerOfOrder(order.id.value);
111
+ }
112
+ order.cancel(reason, clock);
113
+ }
114
+ }
115
+ ```
116
+
117
+ * `OrderAbove` usa `Money.covers` (misma moneda e importe mayor o igual).
118
+ * `OrderCancellation` es un servicio de dominio: la regla relaciona a quien pide con el pedido (`CustomerId`), y el estado lo sigue protegiendo `Order.cancel`, que lanza `OrderCannotBeCancelled` si el pedido ya está cancelado.
119
+
120
+ ## `PlaceOrder`
121
+
122
+ `ProductPricing` y `ProductNotSellable` son los de `ddd-skills/ddd-servicios-capa-aplicacion/references/capa-aplicacion-ts.md`.
123
+
124
+ ```ts
125
+ import type { AggregateFinder, AggregateSaver } from '@archi-code/ddd/domain';
126
+ import { AggregateNotFoundException, UseCase, ValueObjectBuilder } from '@archi-code/ddd/application';
127
+ import type { Clock, EventBus } from '@archi-code/ddd/application';
128
+
129
+ export interface PlaceOrderInput {
130
+ customerId: string;
131
+ lines: { sku: string; quantity: number }[];
132
+ shippingAddress: { street: string; city: string; country: string };
133
+ }
134
+
135
+ interface PlaceOrderCommand {
136
+ customerId: CustomerId;
137
+ lines: { sku: Sku; quantity: Quantity }[];
138
+ shippingAddress: ShippingAddress;
139
+ }
140
+
141
+ export class PlaceOrder extends UseCase<PlaceOrderInput, PlaceOrderCommand, string> {
142
+ constructor(
143
+ private readonly customers: AggregateFinder<Customer>,
144
+ private readonly orders: AggregateSaver<Order>,
145
+ private readonly pricing: ProductPricing,
146
+ private readonly events: EventBus<OrderEvent>,
147
+ private readonly clock: Clock,
148
+ ) {
149
+ super();
150
+ }
151
+
152
+ protected toCommand(input: PlaceOrderInput): PlaceOrderCommand {
153
+ return ValueObjectBuilder.build({
154
+ customerId: () => new CustomerId(input.customerId),
155
+ lines: () =>
156
+ ValueObjectBuilder.buildList(input.lines, (line) =>
157
+ ValueObjectBuilder.build({
158
+ sku: () => new Sku(line.sku),
159
+ quantity: () => new Quantity(line.quantity),
160
+ }),
161
+ ),
162
+ shippingAddress: () => ShippingAddress.fromPrimitives(input.shippingAddress),
163
+ });
164
+ }
165
+
166
+ protected async handle(command: PlaceOrderCommand): Promise<string> {
167
+ const customer = await this.customers.findById(command.customerId);
168
+ if (customer === null) {
169
+ throw new AggregateNotFoundException('Customer', command.customerId.value);
170
+ }
171
+ const items = await Promise.all(
172
+ command.lines.map(async ({ sku, quantity }) => {
173
+ const unitPrice = await this.pricing.priceForSale(sku);
174
+ if (unitPrice === null) {
175
+ throw new ProductNotSellable(sku.value);
176
+ }
177
+ return new OrderLine({ sku, quantity, unitPrice });
178
+ }),
179
+ );
180
+ const lines = new OrderLines({ items });
181
+ const order = customer.placeOrder(
182
+ OrderId.generate(),
183
+ lines,
184
+ command.shippingAddress,
185
+ this.clock,
186
+ );
187
+ await this.orders.save(order);
188
+ await this.events.publish(order.pullDomainEvents());
189
+ return order.id.value;
190
+ }
191
+ }
192
+ ```
193
+
194
+ * Las lecturas de otros agregados y contextos (`Customer`, precios) van antes de invocar al dominio [IDDD cap. 10].
195
+ * `Customer` actúa como fábrica (comprueba que no está bloqueado) pero no se modifica ni se guarda: el caso de uso pide `AggregateFinder<Customer>` y `AggregateSaver<Order>`, y solo se guarda `Order`: una escritura atómica, un agregado; que esa escritura sea atómica lo garantiza el repositorio, no el caso de uso `[Fuera de los libros: convención de @archi-code/ddd]`.
196
+ * El id nuevo sale del value object (`OrderId.generate()`), no del repositorio.
197
+ * `toCommand` informa de todos los campos a la vez, también los de cada línea (`lines.0.quantity`).
198
+
199
+ Los gastos de envío se han dejado fuera de este caso de uso para mantenerlo alineado con el `Order` de `ddd-skills/ddd-agregados/GUIA.md`; si el negocio los incorpora al pedido, el caso de uso carga lo que `ShippingCostCalculator` necesita, lo invoca y pasa el `Money` resultante a `placeOrder`.
200
+
201
+ ## Registrar la reserva de stock
202
+
203
+ Una reacción a un evento es un listener `<Acción>On<Evento>`, decorado con `@ListensTo` y con un nombre de consumidor estable, que solo traduce el evento a la entrada de un caso de uso. Ese caso de uso registra un hecho que ya ocurrió: si el pedido no existe, retorna en vez de lanzar (el canal lo reentregaría sin fin), y `registerStockReservation` es idempotente, porque la entrega es al menos una vez.
204
+
205
+ ```ts
206
+ import { EventBase } from '@archi-code/ddd/domain';
207
+ import type { AggregateFinder, AggregateSaver, EventMetadata } from '@archi-code/ddd/domain';
208
+ import { EventListener, ListensTo, UseCase, ValueObjectBuilder } from '@archi-code/ddd/application';
209
+ import type { Clock, EventBus } from '@archi-code/ddd/application';
210
+
211
+ export class StockReserved extends EventBase {
212
+ static readonly eventName = 'StockReserved';
213
+
214
+ constructor(
215
+ readonly reservationId: string,
216
+ readonly orderId: string,
217
+ metadata?: EventMetadata,
218
+ ) {
219
+ super(metadata);
220
+ }
221
+ }
222
+
223
+ export interface RegisterReservationInput {
224
+ orderId: string;
225
+ reservationId: string;
226
+ }
227
+
228
+ interface RegisterReservationCommand {
229
+ orderId: OrderId;
230
+ reservationId: ReservationId;
231
+ }
232
+
233
+ export class RegisterReservation extends UseCase<
234
+ RegisterReservationInput,
235
+ RegisterReservationCommand,
236
+ void
237
+ > {
238
+ constructor(
239
+ private readonly orders: AggregateFinder<Order> & AggregateSaver<Order>,
240
+ private readonly events: EventBus<OrderEvent>,
241
+ private readonly clock: Clock,
242
+ ) {
243
+ super();
244
+ }
245
+
246
+ protected toCommand(input: RegisterReservationInput): RegisterReservationCommand {
247
+ return ValueObjectBuilder.build({
248
+ orderId: () => new OrderId(input.orderId),
249
+ reservationId: () => new ReservationId(input.reservationId),
250
+ });
251
+ }
252
+
253
+ protected async handle(command: RegisterReservationCommand): Promise<void> {
254
+ const order = await this.orders.findById(command.orderId);
255
+ if (order === null) {
256
+ return;
257
+ }
258
+ order.registerStockReservation(command.reservationId, this.clock);
259
+ await this.orders.save(order);
260
+ await this.events.publish(order.pullDomainEvents());
261
+ }
262
+ }
263
+
264
+ @ListensTo(StockReserved, { consumer: 'register-reservation-on-stock-reserved' })
265
+ export class RegisterReservationOnStockReserved extends EventListener<StockReserved> {
266
+ constructor(private readonly registerReservation: RegisterReservation) {
267
+ super();
268
+ }
269
+
270
+ async handle(event: StockReserved): Promise<void> {
271
+ await this.registerReservation.execute({
272
+ orderId: event.orderId,
273
+ reservationId: event.reservationId,
274
+ });
275
+ }
276
+ }
277
+ ```
278
+
279
+ ## Test del flujo
280
+
281
+ El test comprueba lo que el listener provoca, no que se le llame, y entrega el mismo evento dos veces. `aPendingOrderOf`, `laura` y `orderId` son fábricas y datos de `test/support/` del proyecto, y `InMemoryOrderRepository` es el repositorio en memoria que el proyecto escribe allí (`ddd-skills/ddd-repositorios-persistencia/references/tests-repositorios.md`), como en `ddd-skills/ddd-servicios-capa-aplicacion/references/capa-aplicacion-ts.md`:
282
+
283
+ ```ts
284
+ import { describe, expect, it } from 'vitest';
285
+ import {
286
+ DomainEventBus,
287
+ EventRegistry,
288
+ FixedClock,
289
+ InMemoryEventChannel,
290
+ } from '@archi-code/ddd/infrastructure';
291
+ import { InMemoryOrderRepository } from '../support/in-memory-order-repository';
292
+
293
+ const clock = new FixedClock('2026-03-01T09:30:00Z');
294
+ const reservationId = '0f1e2d3c-4b5a-4968-8776-655443322110';
295
+
296
+ describe('Registering the stock reservation of an order', () => {
297
+ it('the order keeps the reservation the warehouse made, also when it is delivered twice', async () => {
298
+ const orders = new InMemoryOrderRepository();
299
+ const registry = new EventRegistry<StockReserved>().register(
300
+ StockReserved,
301
+ (json) => new StockReserved(json.reservationId as string, json.orderId as string, json),
302
+ );
303
+ const channel = new InMemoryEventChannel();
304
+ const bus = new DomainEventBus<StockReserved>(channel, registry);
305
+ const sales = new DomainEventBus<OrderEvent>(channel, new EventRegistry<OrderEvent>());
306
+ await bus.subscribe(
307
+ new RegisterReservationOnStockReserved(
308
+ new RegisterReservation(orders, sales, clock),
309
+ ),
310
+ );
311
+ await orders.save(aPendingOrderOf(laura));
312
+ const reserved = new StockReserved(reservationId, orderId, { clock });
313
+
314
+ await bus.publish([reserved]);
315
+ await bus.publish([reserved]);
316
+
317
+ const order = await orders.findById(new OrderId(orderId));
318
+ expect(order?.stockReservation?.value).toBe(reservationId);
319
+ });
320
+ });
321
+ ```