@archi-code/ddd 0.0.0-stage → 3.0.0-alpha.1

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 (312) hide show
  1. package/LICENSE +46 -0
  2. package/README.md +2693 -2
  3. package/SKILL.md +104 -0
  4. package/ddd-skills/archi-code-ddd/imports.md +84 -0
  5. package/ddd-skills/archi-code-ddd/layers.md +299 -0
  6. package/ddd-skills/archi-code-ddd/patterns.md +676 -0
  7. package/ddd-skills/archi-code-ddd/what-not-to-do.md +189 -0
  8. package/ddd-skills/archi-code-ddd/where-each-class-goes.md +63 -0
  9. package/ddd-skills/ddd-agregados/GUIA.md +114 -0
  10. package/ddd-skills/ddd-agregados/references/consistencia-eventual.md +159 -0
  11. package/ddd-skills/ddd-agregados/references/ejemplo-ecommerce.md +52 -0
  12. package/ddd-skills/ddd-agregados/references/fabricas.md +196 -0
  13. package/ddd-skills/ddd-agregados/references/implementacion-ts.md +711 -0
  14. package/ddd-skills/ddd-arquitectura/GUIA.md +80 -0
  15. package/ddd-skills/ddd-arquitectura/assets/dependency-cruiser.cjs +92 -0
  16. package/ddd-skills/ddd-arquitectura/references/estilos-adicionales.md +51 -0
  17. package/ddd-skills/ddd-arquitectura/references/estructura-hexagonal-ts.md +352 -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 +316 -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 +404 -0
  26. package/ddd-skills/ddd-context-mapping/references/estilos-integracion.md +155 -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 +471 -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 +131 -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 +526 -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 +296 -0
  38. package/ddd-skills/ddd-repositorios-persistencia/references/tests-repositorios.md +177 -0
  39. package/ddd-skills/ddd-repositorios-persistencia/references/transacciones-ts.md +294 -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 +324 -0
  43. package/ddd-skills/ddd-servicios-capa-aplicacion/references/servicios-dominio-ts.md +191 -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 +220 -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 +377 -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/index.d.ts +7 -0
  58. package/dist/cjs/application/index.js +9 -0
  59. package/dist/cjs/application/ports/clock.d.ts +3 -0
  60. package/dist/cjs/application/ports/clock.js +2 -0
  61. package/dist/cjs/application/ports/logger.d.ts +8 -0
  62. package/dist/cjs/application/ports/logger.js +2 -0
  63. package/dist/cjs/application/use-case/use-case.d.ts +6 -0
  64. package/dist/cjs/application/use-case/use-case.js +24 -0
  65. package/dist/cjs/domain/aggregate/aggregate-root.d.ts +14 -0
  66. package/dist/cjs/domain/aggregate/aggregate-root.js +33 -0
  67. package/dist/cjs/domain/entity/entity.d.ts +9 -0
  68. package/dist/cjs/domain/entity/entity.js +19 -0
  69. package/dist/cjs/domain/event/event-base.d.ts +29 -0
  70. package/dist/cjs/domain/event/event-base.js +51 -0
  71. package/dist/cjs/domain/index.d.ts +10 -0
  72. package/dist/cjs/domain/index.js +24 -0
  73. package/dist/cjs/domain/primitive/primitive-types.d.ts +22 -0
  74. package/dist/cjs/domain/primitive/primitive-types.js +2 -0
  75. package/dist/cjs/domain/repository/aggregate-finder.d.ts +4 -0
  76. package/dist/cjs/domain/repository/aggregate-finder.js +2 -0
  77. package/dist/cjs/domain/repository/aggregate-remover.d.ts +4 -0
  78. package/dist/cjs/domain/repository/aggregate-remover.js +2 -0
  79. package/dist/cjs/domain/repository/aggregate-saver.d.ts +4 -0
  80. package/dist/cjs/domain/repository/aggregate-saver.js +2 -0
  81. package/dist/cjs/domain/specification/specification.d.ts +9 -0
  82. package/dist/cjs/domain/specification/specification.js +44 -0
  83. package/dist/cjs/domain/type/abstract-type.d.ts +20 -0
  84. package/dist/cjs/domain/type/abstract-type.js +69 -0
  85. package/dist/cjs/domain/type/array-type.d.ts +16 -0
  86. package/dist/cjs/domain/type/array-type.js +65 -0
  87. package/dist/cjs/domain/type/boolean-type.d.ts +4 -0
  88. package/dist/cjs/domain/type/boolean-type.js +16 -0
  89. package/dist/cjs/domain/type/composite/composite-field.d.ts +19 -0
  90. package/dist/cjs/domain/type/composite/composite-field.js +63 -0
  91. package/dist/cjs/domain/type/composite/composite.d.ts +26 -0
  92. package/dist/cjs/domain/type/composite/composite.js +55 -0
  93. package/dist/cjs/domain/type/date-type.d.ts +18 -0
  94. package/dist/cjs/domain/type/date-type.js +35 -0
  95. package/dist/cjs/domain/type/enum-type.d.ts +7 -0
  96. package/dist/cjs/domain/type/enum-type.js +30 -0
  97. package/dist/cjs/domain/type/equality/value-equality.d.ts +7 -0
  98. package/dist/cjs/domain/type/equality/value-equality.js +41 -0
  99. package/dist/cjs/domain/type/id-type.d.ts +7 -0
  100. package/dist/cjs/domain/type/id-type.js +17 -0
  101. package/dist/cjs/domain/type/immutable/immutable-date.d.ts +4 -0
  102. package/dist/cjs/domain/type/immutable/immutable-date.js +37 -0
  103. package/dist/cjs/domain/type/immutable/immutable.d.ts +4 -0
  104. package/dist/cjs/domain/type/immutable/immutable.js +19 -0
  105. package/dist/cjs/domain/type/index.d.ts +17 -0
  106. package/dist/cjs/domain/type/index.js +33 -0
  107. package/dist/cjs/domain/type/json-type.d.ts +5 -0
  108. package/dist/cjs/domain/type/json-type.js +17 -0
  109. package/dist/cjs/domain/type/number-type.d.ts +4 -0
  110. package/dist/cjs/domain/type/number-type.js +16 -0
  111. package/dist/cjs/domain/type/required/required-mark.d.ts +9 -0
  112. package/dist/cjs/domain/type/required/required-mark.js +13 -0
  113. package/dist/cjs/domain/type/required/required.d.ts +7 -0
  114. package/dist/cjs/domain/type/required/required.js +21 -0
  115. package/dist/cjs/domain/type/string-type.d.ts +4 -0
  116. package/dist/cjs/domain/type/string-type.js +16 -0
  117. package/dist/cjs/domain/type/uuid-type.d.ts +7 -0
  118. package/dist/cjs/domain/type/uuid-type.js +27 -0
  119. package/dist/cjs/domain/type/value-objects/value-objects.d.ts +16 -0
  120. package/dist/cjs/domain/type/value-objects/value-objects.js +70 -0
  121. package/dist/cjs/exceptions/abstract.exception.d.ts +23 -0
  122. package/dist/cjs/exceptions/abstract.exception.js +66 -0
  123. package/dist/cjs/exceptions/application/aggregate-not-found.exception.d.ts +5 -0
  124. package/dist/cjs/exceptions/application/aggregate-not-found.exception.js +17 -0
  125. package/dist/cjs/exceptions/application/application.exception.d.ts +6 -0
  126. package/dist/cjs/exceptions/application/application.exception.js +17 -0
  127. package/dist/cjs/exceptions/application/concurrency.exception.d.ts +5 -0
  128. package/dist/cjs/exceptions/application/concurrency.exception.js +17 -0
  129. package/dist/cjs/exceptions/application/not-allowed.exception.d.ts +5 -0
  130. package/dist/cjs/exceptions/application/not-allowed.exception.js +17 -0
  131. package/dist/cjs/exceptions/brand/exception-brand.d.ts +6 -0
  132. package/dist/cjs/exceptions/brand/exception-brand.js +29 -0
  133. package/dist/cjs/exceptions/domain/domain.exception.d.ts +6 -0
  134. package/dist/cjs/exceptions/domain/domain.exception.js +17 -0
  135. package/dist/cjs/exceptions/domain/required-value.exception.d.ts +4 -0
  136. package/dist/cjs/exceptions/domain/required-value.exception.js +21 -0
  137. package/dist/cjs/exceptions/domain/type-primitive.exception.d.ts +4 -0
  138. package/dist/cjs/exceptions/domain/type-primitive.exception.js +21 -0
  139. package/dist/cjs/exceptions/domain/validation.exception.d.ts +14 -0
  140. package/dist/cjs/exceptions/domain/validation.exception.js +23 -0
  141. package/dist/cjs/exceptions/exception-code.d.ts +15 -0
  142. package/dist/cjs/exceptions/exception-code.js +19 -0
  143. package/dist/cjs/exceptions/index.d.ts +14 -0
  144. package/dist/cjs/exceptions/index.js +30 -0
  145. package/dist/cjs/exceptions/infrastructure/event-delivery.exception.d.ts +5 -0
  146. package/dist/cjs/exceptions/infrastructure/event-delivery.exception.js +20 -0
  147. package/dist/cjs/exceptions/infrastructure/infrastructure.exception.d.ts +6 -0
  148. package/dist/cjs/exceptions/infrastructure/infrastructure.exception.js +17 -0
  149. package/dist/cjs/exceptions/infrastructure/internal-error.exception.d.ts +5 -0
  150. package/dist/cjs/exceptions/infrastructure/internal-error.exception.js +17 -0
  151. package/dist/cjs/exceptions/infrastructure/unknown-event.exception.d.ts +4 -0
  152. package/dist/cjs/exceptions/infrastructure/unknown-event.exception.js +19 -0
  153. package/dist/cjs/exceptions/received/received-value.d.ts +4 -0
  154. package/dist/cjs/exceptions/received/received-value.js +13 -0
  155. package/dist/cjs/infrastructure/clock/fixed-clock.d.ts +8 -0
  156. package/dist/cjs/infrastructure/clock/fixed-clock.js +19 -0
  157. package/dist/cjs/infrastructure/clock/system-clock.d.ts +4 -0
  158. package/dist/cjs/infrastructure/clock/system-clock.js +9 -0
  159. package/dist/cjs/infrastructure/event/domain-event-bus.d.ts +16 -0
  160. package/dist/cjs/infrastructure/event/domain-event-bus.js +60 -0
  161. package/dist/cjs/infrastructure/event/event-channel.d.ts +9 -0
  162. package/dist/cjs/infrastructure/event/event-channel.js +2 -0
  163. package/dist/cjs/infrastructure/event/event-registry.d.ts +9 -0
  164. package/dist/cjs/infrastructure/event/event-registry.js +43 -0
  165. package/dist/cjs/infrastructure/event/in-memory/emitter.d.ts +11 -0
  166. package/dist/cjs/infrastructure/event/in-memory/emitter.js +54 -0
  167. package/dist/cjs/infrastructure/event/in-memory/in-memory-event-channel.d.ts +16 -0
  168. package/dist/cjs/infrastructure/event/in-memory/in-memory-event-channel.js +51 -0
  169. package/dist/cjs/infrastructure/http/http-error-mapper.d.ts +23 -0
  170. package/dist/cjs/infrastructure/http/http-error-mapper.js +63 -0
  171. package/dist/cjs/infrastructure/index.d.ts +16 -0
  172. package/dist/cjs/infrastructure/index.js +21 -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/esm/application/event/event-bus.d.ts +4 -0
  182. package/dist/esm/application/event/event-bus.js +1 -0
  183. package/dist/esm/application/event/event-listener.d.ts +4 -0
  184. package/dist/esm/application/event/event-listener.js +2 -0
  185. package/dist/esm/application/event/listens-to.d.ts +15 -0
  186. package/dist/esm/application/event/listens-to.js +25 -0
  187. package/dist/esm/application/index.d.ts +7 -0
  188. package/dist/esm/application/index.js +3 -0
  189. package/dist/esm/application/ports/clock.d.ts +3 -0
  190. package/dist/esm/application/ports/clock.js +1 -0
  191. package/dist/esm/application/ports/logger.d.ts +8 -0
  192. package/dist/esm/application/ports/logger.js +1 -0
  193. package/dist/esm/application/use-case/use-case.d.ts +6 -0
  194. package/dist/esm/application/use-case/use-case.js +20 -0
  195. package/dist/esm/domain/aggregate/aggregate-root.d.ts +14 -0
  196. package/dist/esm/domain/aggregate/aggregate-root.js +29 -0
  197. package/dist/esm/domain/entity/entity.d.ts +9 -0
  198. package/dist/esm/domain/entity/entity.js +15 -0
  199. package/dist/esm/domain/event/event-base.d.ts +29 -0
  200. package/dist/esm/domain/event/event-base.js +47 -0
  201. package/dist/esm/domain/index.d.ts +10 -0
  202. package/dist/esm/domain/index.js +6 -0
  203. package/dist/esm/domain/primitive/primitive-types.d.ts +22 -0
  204. package/dist/esm/domain/primitive/primitive-types.js +1 -0
  205. package/dist/esm/domain/repository/aggregate-finder.d.ts +4 -0
  206. package/dist/esm/domain/repository/aggregate-finder.js +1 -0
  207. package/dist/esm/domain/repository/aggregate-remover.d.ts +4 -0
  208. package/dist/esm/domain/repository/aggregate-remover.js +1 -0
  209. package/dist/esm/domain/repository/aggregate-saver.d.ts +4 -0
  210. package/dist/esm/domain/repository/aggregate-saver.js +1 -0
  211. package/dist/esm/domain/specification/specification.d.ts +9 -0
  212. package/dist/esm/domain/specification/specification.js +40 -0
  213. package/dist/esm/domain/type/abstract-type.d.ts +20 -0
  214. package/dist/esm/domain/type/abstract-type.js +65 -0
  215. package/dist/esm/domain/type/array-type.d.ts +16 -0
  216. package/dist/esm/domain/type/array-type.js +61 -0
  217. package/dist/esm/domain/type/boolean-type.d.ts +4 -0
  218. package/dist/esm/domain/type/boolean-type.js +12 -0
  219. package/dist/esm/domain/type/composite/composite-field.d.ts +19 -0
  220. package/dist/esm/domain/type/composite/composite-field.js +59 -0
  221. package/dist/esm/domain/type/composite/composite.d.ts +26 -0
  222. package/dist/esm/domain/type/composite/composite.js +52 -0
  223. package/dist/esm/domain/type/date-type.d.ts +18 -0
  224. package/dist/esm/domain/type/date-type.js +29 -0
  225. package/dist/esm/domain/type/enum-type.d.ts +7 -0
  226. package/dist/esm/domain/type/enum-type.js +26 -0
  227. package/dist/esm/domain/type/equality/value-equality.d.ts +7 -0
  228. package/dist/esm/domain/type/equality/value-equality.js +37 -0
  229. package/dist/esm/domain/type/id-type.d.ts +7 -0
  230. package/dist/esm/domain/type/id-type.js +13 -0
  231. package/dist/esm/domain/type/immutable/immutable-date.d.ts +4 -0
  232. package/dist/esm/domain/type/immutable/immutable-date.js +33 -0
  233. package/dist/esm/domain/type/immutable/immutable.d.ts +4 -0
  234. package/dist/esm/domain/type/immutable/immutable.js +15 -0
  235. package/dist/esm/domain/type/index.d.ts +17 -0
  236. package/dist/esm/domain/type/index.js +14 -0
  237. package/dist/esm/domain/type/json-type.d.ts +5 -0
  238. package/dist/esm/domain/type/json-type.js +13 -0
  239. package/dist/esm/domain/type/number-type.d.ts +4 -0
  240. package/dist/esm/domain/type/number-type.js +12 -0
  241. package/dist/esm/domain/type/required/required-mark.d.ts +9 -0
  242. package/dist/esm/domain/type/required/required-mark.js +9 -0
  243. package/dist/esm/domain/type/required/required.d.ts +7 -0
  244. package/dist/esm/domain/type/required/required.js +18 -0
  245. package/dist/esm/domain/type/string-type.d.ts +4 -0
  246. package/dist/esm/domain/type/string-type.js +12 -0
  247. package/dist/esm/domain/type/uuid-type.d.ts +7 -0
  248. package/dist/esm/domain/type/uuid-type.js +23 -0
  249. package/dist/esm/domain/type/value-objects/value-objects.d.ts +16 -0
  250. package/dist/esm/domain/type/value-objects/value-objects.js +66 -0
  251. package/dist/esm/exceptions/abstract.exception.d.ts +23 -0
  252. package/dist/esm/exceptions/abstract.exception.js +62 -0
  253. package/dist/esm/exceptions/application/aggregate-not-found.exception.d.ts +5 -0
  254. package/dist/esm/exceptions/application/aggregate-not-found.exception.js +13 -0
  255. package/dist/esm/exceptions/application/application.exception.d.ts +6 -0
  256. package/dist/esm/exceptions/application/application.exception.js +13 -0
  257. package/dist/esm/exceptions/application/concurrency.exception.d.ts +5 -0
  258. package/dist/esm/exceptions/application/concurrency.exception.js +13 -0
  259. package/dist/esm/exceptions/application/not-allowed.exception.d.ts +5 -0
  260. package/dist/esm/exceptions/application/not-allowed.exception.js +13 -0
  261. package/dist/esm/exceptions/brand/exception-brand.d.ts +6 -0
  262. package/dist/esm/exceptions/brand/exception-brand.js +25 -0
  263. package/dist/esm/exceptions/domain/domain.exception.d.ts +6 -0
  264. package/dist/esm/exceptions/domain/domain.exception.js +13 -0
  265. package/dist/esm/exceptions/domain/required-value.exception.d.ts +4 -0
  266. package/dist/esm/exceptions/domain/required-value.exception.js +17 -0
  267. package/dist/esm/exceptions/domain/type-primitive.exception.d.ts +4 -0
  268. package/dist/esm/exceptions/domain/type-primitive.exception.js +17 -0
  269. package/dist/esm/exceptions/domain/validation.exception.d.ts +14 -0
  270. package/dist/esm/exceptions/domain/validation.exception.js +19 -0
  271. package/dist/esm/exceptions/exception-code.d.ts +15 -0
  272. package/dist/esm/exceptions/exception-code.js +16 -0
  273. package/dist/esm/exceptions/index.d.ts +14 -0
  274. package/dist/esm/exceptions/index.js +14 -0
  275. package/dist/esm/exceptions/infrastructure/event-delivery.exception.d.ts +5 -0
  276. package/dist/esm/exceptions/infrastructure/event-delivery.exception.js +16 -0
  277. package/dist/esm/exceptions/infrastructure/infrastructure.exception.d.ts +6 -0
  278. package/dist/esm/exceptions/infrastructure/infrastructure.exception.js +13 -0
  279. package/dist/esm/exceptions/infrastructure/internal-error.exception.d.ts +5 -0
  280. package/dist/esm/exceptions/infrastructure/internal-error.exception.js +13 -0
  281. package/dist/esm/exceptions/infrastructure/unknown-event.exception.d.ts +4 -0
  282. package/dist/esm/exceptions/infrastructure/unknown-event.exception.js +15 -0
  283. package/dist/esm/exceptions/received/received-value.d.ts +4 -0
  284. package/dist/esm/exceptions/received/received-value.js +9 -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 +47 -0
  299. package/dist/esm/infrastructure/http/http-error-mapper.d.ts +23 -0
  300. package/dist/esm/infrastructure/http/http-error-mapper.js +59 -0
  301. package/dist/esm/infrastructure/index.d.ts +16 -0
  302. package/dist/esm/infrastructure/index.js +9 -0
  303. package/dist/esm/infrastructure/logging/console-logger.d.ts +17 -0
  304. package/dist/esm/infrastructure/logging/console-logger.js +33 -0
  305. package/dist/esm/infrastructure/logging/in-memory-logger.d.ts +15 -0
  306. package/dist/esm/infrastructure/logging/in-memory-logger.js +23 -0
  307. package/dist/esm/infrastructure/logging/log-serializer.d.ts +7 -0
  308. package/dist/esm/infrastructure/logging/log-serializer.js +55 -0
  309. package/dist/esm/infrastructure/logging/silent-logger.d.ts +7 -0
  310. package/dist/esm/infrastructure/logging/silent-logger.js +14 -0
  311. package/dist/esm/package.json +3 -0
  312. package/package.json +106 -4
@@ -0,0 +1,711 @@
1
+ # Implementar agregados en TypeScript
2
+
3
+ Contenido: 1. Raíz base · 2. Construcción paso a paso · 3. Agregado completo · 4. Dependencias de otros agregados · 5. Concurrencia optimista · 6. Variante funcional · 7. Tests
4
+
5
+ ## 1. Raíz base
6
+
7
+ La raíz no se escribe a mano: se extiende `AggregateRoot<Id, Event>` de `@archi-code/ddd/domain`, que ya trae lo que IDDD cap. 10 describe para la raíz (identidad, igualdad por identidad, versión para la concurrencia optimista y lista de eventos registrados) `[Fuera de los libros: convención de @archi-code/ddd]`.
8
+
9
+ ```ts
10
+ import { AggregateRoot } from '@archi-code/ddd/domain';
11
+
12
+ export class Order extends AggregateRoot<OrderId, OrderEvent> {
13
+ private constructor(
14
+ id: OrderId,
15
+ readonly customerId: CustomerId,
16
+ ) {
17
+ super(id);
18
+ }
19
+ }
20
+ ```
21
+
22
+ La clase completa está en la sección 3. Lo que da la base:
23
+
24
+ * `readonly id` y `equals(other)` (misma clase y mismo id) de `Entity<Id>`; un id sin valor lanza `RequiredValueException`.
25
+ * `protected record(event)`: guarda el evento congelado (los hechos no cambian [IDDD cap. 8]).
26
+ * `pullDomainEvents()`: devuelve los eventos en orden (tipados como `Event[]`) y vacía la lista; lo llama el caso de uso tras guardar.
27
+ * `version`: `0` si es nuevo; no es un campo del agregado ni aparece en `PrimitiveTypes`.
28
+ * `restoreVersion(v)` y `markPersisted()`: solo para repositorios (al cargar y tras escribir).
29
+
30
+ Sin `markPersisted`, el objeto en memoria conservaría la versión antigua tras guardar y un segundo `save` del mismo objeto (por ejemplo, dos comandos seguidos en el mismo proceso) daría un falso conflicto de concurrencia `[Fuera de los libros]`. Los repositorios que sigan el contrato lo llaman siempre, también el `InMemoryOrderRepository` que el proyecto escribe en `test/support/` para los tests.
31
+
32
+ Por qué registrar en vez de publicar: IDDD publica desde el agregado con un publicador estático [IDDD cap. 8]. Registrar y dejar que la aplicación publique no requiere un singleton global en el dominio y, con una bandeja de salida (outbox), evita entregar eventos de cambios que no llegaron a guardarse. *DDD in PHP* discute ambas variantes [PHP: Domain Events]. Con el paquete, el caso de uso llama a `await this.orders.save(order)` y después a `events.publish(order.pullDomainEvents())`; la aplicación no abre transacciones, porque la transacción es del repositorio. Si `publish` falla tras `save`, el evento puede perderse; cuando eso no es admisible, el repositorio guarda el agregado y sus eventos pendientes en la misma escritura atómica (outbox) y un proceso aparte los entrega por un `EventChannel`, y el caso de uso no cambia `[Fuera de los libros: convención de @archi-code/ddd]`.
33
+
34
+ ## 2. Construcción paso a paso
35
+
36
+ Orden que sigue Destilado para construir la raíz [Destilado cap. 5]:
37
+
38
+ 1. Clase de la raíz (`extends AggregateRoot<OrderId, OrderEvent>`).
39
+ 2. Identidad global única como value object (`class OrderId extends IdType<'OrderId'> {}`; en multi-tenant, también el `TenantId`).
40
+ 3. Atributos intrínsecos necesarios para encontrarlo (nombre, descripción).
41
+ 4. Getters solo si hacen falta; **sin setters públicos**. Con el paquete, cada campo que se guarda tiene un getter público con su nombre sin `_` (o es `readonly` público), porque `PrimitiveTypes` los lee para tipar `toPrimitives()`/`fromPrimitives()` (nada lo comprueba en tiempo de ejecución: lo cubre el test de ida y vuelta de cada agregado); los valores derivados son métodos (`total()`) `[Fuera de los libros: convención de @archi-code/ddd]`.
42
+ 5. Comportamiento complejo con nombres del lenguaje (`planBacklogItem`, `scheduleRelease`).
43
+
44
+ ## 3. Agregado completo: Order
45
+
46
+ Value objects del pedido (su diseño detallado está en `ddd-value-objects-entidades`):
47
+
48
+ ```ts
49
+ import {
50
+ Composite,
51
+ DateType,
52
+ EnumType,
53
+ IdType,
54
+ NumberType,
55
+ Required,
56
+ StringType,
57
+ } from '@archi-code/ddd/domain';
58
+ import { DomainException } from '@archi-code/ddd/exceptions';
59
+ import { Validations } from '@archi-code/validation';
60
+
61
+ export class OrderId extends IdType<'OrderId'> {}
62
+ export class CustomerId extends IdType<'CustomerId'> {}
63
+ export class ReservationId extends IdType<'ReservationId'> {}
64
+ export class PaymentId extends IdType<'PaymentId'> {}
65
+ export class Sku extends Required(StringType) {}
66
+ export class Street extends Required(StringType) {}
67
+ export class City extends Required(StringType) {}
68
+ export class CancellationReason extends Required(StringType) {}
69
+ export class ExpiresAt extends Required(DateType) {}
70
+
71
+ @Validations([{ validator: 'isInt', properties: { min: 1 } }])
72
+ export class Quantity extends Required(NumberType) {}
73
+
74
+ @Validations([{ validator: 'isInt' }])
75
+ export class Cents extends Required(NumberType) {}
76
+
77
+ @Validations([{ validator: 'isISO4217' }])
78
+ export class Currency extends Required(StringType) {
79
+ protected filter(value: unknown): string {
80
+ return super.filter(value).trim().toUpperCase();
81
+ }
82
+ }
83
+
84
+ @Validations([{ validator: 'isISO31661Alpha2' }])
85
+ export class Country extends Required(StringType) {
86
+ protected filter(value: unknown): string {
87
+ return super.filter(value).trim().toUpperCase();
88
+ }
89
+ }
90
+
91
+ export class CurrencyMismatch extends DomainException {
92
+ constructor(expected: string, received: string) {
93
+ super(`Cannot add ${received} to ${expected}`, ['PAY001']);
94
+ }
95
+ }
96
+
97
+ export class Money extends Composite({ amount: Cents, currency: Currency }) {
98
+ add(other: Money): Money {
99
+ if (!this.currency.equals(other.currency)) {
100
+ throw new CurrencyMismatch(this.currency.value, other.currency.value);
101
+ }
102
+ return new Money({
103
+ amount: new Cents(this.amount.value + other.amount.value),
104
+ currency: this.currency,
105
+ });
106
+ }
107
+
108
+ times(quantity: Quantity): Money {
109
+ return new Money({
110
+ amount: new Cents(this.amount.value * quantity.value),
111
+ currency: this.currency,
112
+ });
113
+ }
114
+
115
+ covers(other: Money): boolean {
116
+ return this.currency.equals(other.currency) && this.amount.value >= other.amount.value;
117
+ }
118
+ }
119
+
120
+ export class OrderLine extends Composite({ sku: Sku, quantity: Quantity, unitPrice: Money }) {
121
+ subtotal(): Money {
122
+ return this.unitPrice.times(this.quantity);
123
+ }
124
+ }
125
+
126
+ @Validations([
127
+ {
128
+ custom: 'hasLines',
129
+ fn: (lines) => (lines as OrderLines).items.length > 0,
130
+ message: 'An order needs at least one line',
131
+ },
132
+ {
133
+ custom: 'uniqueSku',
134
+ fn: (lines) => {
135
+ const skus = (lines as OrderLines).items.map((line) => line.sku.value);
136
+ return new Set(skus).size === skus.length;
137
+ },
138
+ message: 'Each SKU appears in one line only',
139
+ },
140
+ ])
141
+ export class OrderLines extends Composite({ items: [OrderLine] }) {
142
+ withLine(line: OrderLine): OrderLines {
143
+ return new OrderLines({ items: [...this.items, line] });
144
+ }
145
+
146
+ total(): Money {
147
+ const [first, ...rest] = this.items;
148
+ return rest.reduce((sum, line) => sum.add(line.subtotal()), first.subtotal());
149
+ }
150
+ }
151
+
152
+ export class ShippingAddress extends Composite({ street: Street, city: City, country: Country }) {}
153
+
154
+ export class PaymentAuthorization extends Composite({
155
+ paymentId: PaymentId,
156
+ amount: Money,
157
+ expiresAt: ExpiresAt,
158
+ }) {
159
+ covers(total: Money): boolean {
160
+ return this.amount.covers(total);
161
+ }
162
+ }
163
+
164
+ export enum Status {
165
+ Pending = 'pending',
166
+ Confirmed = 'confirmed',
167
+ Cancelled = 'cancelled',
168
+ }
169
+
170
+ export class StatusType extends EnumType<Status> {
171
+ protected getEnum(): typeof Status {
172
+ return Status;
173
+ }
174
+ }
175
+
176
+ export class OrderStatus extends Required(StatusType) {
177
+ isPending(): boolean {
178
+ return this.value === Status.Pending;
179
+ }
180
+
181
+ isConfirmed(): boolean {
182
+ return this.value === Status.Confirmed;
183
+ }
184
+
185
+ canBeCancelled(): boolean {
186
+ return this.value !== Status.Cancelled;
187
+ }
188
+ }
189
+ ```
190
+
191
+ Eventos (hechos en pasado, con primitivos tomados de los value objects) y errores de negocio:
192
+
193
+ ```ts
194
+ import { EventBase } from '@archi-code/ddd/domain';
195
+ import type { EventMetadata } from '@archi-code/ddd/domain';
196
+ import { DomainException } from '@archi-code/ddd/exceptions';
197
+
198
+ export interface MoneyData {
199
+ readonly amount: number;
200
+ readonly currency: string;
201
+ }
202
+
203
+ export interface OrderLineData {
204
+ readonly sku: string;
205
+ readonly quantity: number;
206
+ readonly unitPrice: MoneyData;
207
+ }
208
+
209
+ export class OrderPlaced extends EventBase {
210
+ static readonly eventName = 'OrderPlaced';
211
+
212
+ constructor(
213
+ orderId: string,
214
+ readonly customerId: string,
215
+ readonly lines: readonly OrderLineData[],
216
+ readonly total: MoneyData,
217
+ metadata?: EventMetadata,
218
+ ) {
219
+ super(orderId, metadata);
220
+ }
221
+ }
222
+
223
+ export class OrderLineAdded extends EventBase {
224
+ static readonly eventName = 'OrderLineAdded';
225
+
226
+ constructor(
227
+ orderId: string,
228
+ readonly sku: string,
229
+ readonly quantity: number,
230
+ readonly unitPrice: MoneyData,
231
+ readonly total: MoneyData,
232
+ metadata?: EventMetadata,
233
+ ) {
234
+ super(orderId, metadata);
235
+ }
236
+ }
237
+
238
+ export class OrderConfirmed extends EventBase {
239
+ static readonly eventName = 'OrderConfirmed';
240
+
241
+ constructor(
242
+ orderId: string,
243
+ readonly total: MoneyData,
244
+ metadata?: EventMetadata,
245
+ ) {
246
+ super(orderId, metadata);
247
+ }
248
+ }
249
+
250
+ export class OrderCancelled extends EventBase {
251
+ static readonly eventName = 'OrderCancelled';
252
+
253
+ constructor(
254
+ orderId: string,
255
+ readonly reason: string,
256
+ readonly reservationId: string | null,
257
+ readonly paymentId: string | null,
258
+ metadata?: EventMetadata,
259
+ ) {
260
+ super(orderId, metadata);
261
+ }
262
+ }
263
+
264
+ export class PaymentAuthorizationInsufficient extends EventBase {
265
+ static readonly eventName = 'PaymentAuthorizationInsufficient';
266
+
267
+ constructor(
268
+ orderId: string,
269
+ readonly paymentId: string,
270
+ readonly authorized: MoneyData,
271
+ readonly required: MoneyData,
272
+ metadata?: EventMetadata,
273
+ ) {
274
+ super(orderId, metadata);
275
+ }
276
+ }
277
+
278
+ export type OrderEvent =
279
+ | OrderPlaced
280
+ | OrderLineAdded
281
+ | OrderConfirmed
282
+ | OrderCancelled
283
+ | PaymentAuthorizationInsufficient;
284
+
285
+ export class OrderNoLongerEditable extends DomainException {
286
+ constructor(orderId: string) {
287
+ super(`Order ${orderId} no longer accepts line changes`, ['ORD001']);
288
+ }
289
+ }
290
+
291
+ export class OrderCannotBeCancelled extends DomainException {
292
+ constructor(orderId: string, status: string) {
293
+ super(`Order ${orderId} cannot be cancelled while ${status}`, ['ORD002']);
294
+ }
295
+ }
296
+ ```
297
+
298
+ La raíz:
299
+
300
+ ```ts
301
+ import { AggregateRoot } from '@archi-code/ddd/domain';
302
+ import type { PrimitiveTypes } from '@archi-code/ddd/domain';
303
+
304
+ export class Order extends AggregateRoot<OrderId, OrderEvent> {
305
+ private constructor(
306
+ id: OrderId,
307
+ readonly customerId: CustomerId,
308
+ private _lines: OrderLines,
309
+ readonly shippingAddress: ShippingAddress,
310
+ private _status: OrderStatus,
311
+ private _stockReservation: ReservationId | null,
312
+ private _paymentAuthorization: PaymentAuthorization | null,
313
+ ) {
314
+ super(id);
315
+ }
316
+
317
+ static place(
318
+ id: OrderId,
319
+ customerId: CustomerId,
320
+ lines: OrderLines,
321
+ address: ShippingAddress,
322
+ clock: { now(): Date },
323
+ ): Order {
324
+ const order = new Order(
325
+ id,
326
+ customerId,
327
+ lines,
328
+ address,
329
+ new OrderStatus(Status.Pending),
330
+ null,
331
+ null,
332
+ );
333
+ order.record(
334
+ new OrderPlaced(
335
+ id.value,
336
+ customerId.value,
337
+ lines.items.map((line) => ({
338
+ sku: line.sku.value,
339
+ quantity: line.quantity.value,
340
+ unitPrice: Order.moneyData(line.unitPrice),
341
+ })),
342
+ Order.moneyData(order.total()),
343
+ { clock },
344
+ ),
345
+ );
346
+ return order;
347
+ }
348
+
349
+ static fromPrimitives(primitives: PrimitiveTypes<Order>): Order {
350
+ return new Order(
351
+ new OrderId(primitives.id),
352
+ new CustomerId(primitives.customerId),
353
+ OrderLines.fromPrimitives(primitives.lines),
354
+ ShippingAddress.fromPrimitives(primitives.shippingAddress),
355
+ new OrderStatus(primitives.status),
356
+ primitives.stockReservation === null ? null : new ReservationId(primitives.stockReservation),
357
+ primitives.paymentAuthorization === null
358
+ ? null
359
+ : PaymentAuthorization.fromPrimitives(primitives.paymentAuthorization),
360
+ );
361
+ }
362
+
363
+ get lines(): OrderLines {
364
+ return this._lines;
365
+ }
366
+
367
+ get status(): OrderStatus {
368
+ return this._status;
369
+ }
370
+
371
+ get stockReservation(): ReservationId | null {
372
+ return this._stockReservation;
373
+ }
374
+
375
+ get paymentAuthorization(): PaymentAuthorization | null {
376
+ return this._paymentAuthorization;
377
+ }
378
+
379
+ registerStockReservation(reservation: ReservationId, clock: { now(): Date }): void {
380
+ if (!this._status.isPending()) {
381
+ return;
382
+ }
383
+ this._stockReservation = reservation;
384
+ this.tryToConfirm(clock);
385
+ }
386
+
387
+ registerPaymentAuthorization(authorization: PaymentAuthorization, clock: { now(): Date }): void {
388
+ if (!this._status.isPending()) {
389
+ return;
390
+ }
391
+ if (!authorization.covers(this.total())) {
392
+ this.record(
393
+ new PaymentAuthorizationInsufficient(
394
+ this.id.value,
395
+ authorization.paymentId.value,
396
+ Order.moneyData(authorization.amount),
397
+ Order.moneyData(this.total()),
398
+ { clock },
399
+ ),
400
+ );
401
+ return;
402
+ }
403
+ this._paymentAuthorization = authorization;
404
+ this.tryToConfirm(clock);
405
+ }
406
+
407
+ cancel(reason: CancellationReason, clock: { now(): Date }): void {
408
+ if (!this._status.canBeCancelled()) {
409
+ throw new OrderCannotBeCancelled(this.id.value, this._status.value);
410
+ }
411
+ this._status = new OrderStatus(Status.Cancelled);
412
+ this.record(
413
+ new OrderCancelled(
414
+ this.id.value,
415
+ reason.value,
416
+ this._stockReservation?.value ?? null,
417
+ this._paymentAuthorization?.paymentId.value ?? null,
418
+ { clock },
419
+ ),
420
+ );
421
+ }
422
+
423
+ addLine(line: OrderLine, clock: { now(): Date }): void {
424
+ if (!this._status.isPending()) {
425
+ throw new OrderNoLongerEditable(this.id.value);
426
+ }
427
+ this._lines = this._lines.withLine(line);
428
+ this.record(
429
+ new OrderLineAdded(
430
+ this.id.value,
431
+ line.sku.value,
432
+ line.quantity.value,
433
+ Order.moneyData(line.unitPrice),
434
+ Order.moneyData(this.total()),
435
+ { clock },
436
+ ),
437
+ );
438
+ }
439
+
440
+ total(): Money {
441
+ return this._lines.total();
442
+ }
443
+
444
+ isConfirmed(): boolean {
445
+ return this._status.isConfirmed();
446
+ }
447
+
448
+ toPrimitives(): PrimitiveTypes<Order> {
449
+ return {
450
+ id: this.id.value,
451
+ customerId: this.customerId.value,
452
+ lines: this._lines.toPrimitives(),
453
+ shippingAddress: this.shippingAddress.toPrimitives(),
454
+ status: this._status.value,
455
+ stockReservation: this._stockReservation?.value ?? null,
456
+ paymentAuthorization: this._paymentAuthorization?.toPrimitives() ?? null,
457
+ };
458
+ }
459
+
460
+ private tryToConfirm(clock: { now(): Date }): void {
461
+ if (this._stockReservation !== null && this._paymentAuthorization !== null) {
462
+ this._status = new OrderStatus(Status.Confirmed);
463
+ this.record(new OrderConfirmed(this.id.value, Order.moneyData(this.total()), { clock }));
464
+ }
465
+ }
466
+
467
+ private static moneyData(money: Money): MoneyData {
468
+ return { amount: money.amount.value, currency: money.currency.value };
469
+ }
470
+ }
471
+ ```
472
+
473
+ Cómo encaja con las reglas:
474
+
475
+ * `customerId` es una referencia por identidad (regla 3); `stockReservation` es la identidad de otro agregado de otro contexto, y `paymentAuthorization` un value object con lo mínimo que el pedido necesita de Pagos.
476
+ * Las líneas son un value object (`OrderLines`, parte reemplazable entera); `addLine` sustituye la colección por la que devuelve `withLine`, y la regla de SKU único la comprueba el propio value object.
477
+ * Constructor privado, una factoría de negocio (`place`, que registra `OrderPlaced`) y `fromPrimitives`, que reconstruye sin reglas de creación ni eventos; `toPrimitives()` devuelve solo datos planos. La conversión es solo del propio agregado (instantánea): el repositorio, cuyo constructor recibe solo las dependencias de su tecnología, llama directamente a `order.toPrimitives()` al escribir y a `Order.fromPrimitives(row).restoreVersion(row.version)` al leer, sin mapper ni comprobación de forma en medio, y no lee los campos. Quien añade un campo lo añade a `toPrimitives`/`fromPrimitives`, y el spec del agregado comprueba la ida y vuelta `[Fuera de los libros: convención de @archi-code/ddd]`.
478
+ * Cada método recibe el reloj (`clock: { now(): Date }`) y lo pasa a los eventos (`{ clock }`): el dominio no llama a `new Date()` y los tests son deterministas.
479
+
480
+ ### Comandos frente a hechos
481
+
482
+ `registerStockReservation` y `registerPaymentAuthorization` no son peticiones que el agregado pueda denegar: los invoca una política cuando **ya ha ocurrido** algo en otro contexto. Un comando se puede rechazar; un evento es un hecho y no se puede negar [Destilado cap. 6]. Si estos métodos lanzaran una excepción de negocio, el mensaje fallaría, la mensajería lo reenviaría y se reintentaría sin fin [IDDD cap. 10 describe el reintento hasta un límite]. Por eso:
483
+
484
+ * Los métodos que reaccionan a hechos son **idempotentes** y **no lanzan errores de negocio**.
485
+ * Si el hecho no encaja con las reglas, el agregado registra un evento con nombre del negocio (`PaymentAuthorizationInsufficient`) y otra política decide la compensación (anular la autorización, avisar al cliente). Así el problema queda visible en el modelo en vez de perdido en una cola de errores.
486
+ * Los errores de negocio con excepción (`DomainException` con su código) quedan para comandos de usuario (`cancel`, `addLine`), donde alguien puede recibir la respuesta (`HttpErrorMapper` los traduce a 422 salvo que se le indique otro estado para su código).
487
+
488
+ Qué protege este agregado (sus invariantes, regla 1):
489
+ * El total es siempre la suma de sus líneas (las líneas son un value object con su regla).
490
+ * Un pedido confirmado tiene reserva y autorización que cubre el total.
491
+ * Las líneas solo cambian en estado pendiente.
492
+
493
+ Qué **no** protege (y por eso no contiene): el stock del almacén ni el estado de la pasarela. Son otros agregados en otros contextos; llegan por identidad o como value object mínimo.
494
+
495
+ ## 4. Dependencias de otros agregados
496
+
497
+ No inyectes repositorios ni servicios en el agregado. Busca antes lo necesario y pásalo al método [IDDD cap. 10]. El "modelo de dominio desconectado" (usar un repositorio desde dentro del agregado) es una forma de carga perezosa y es la opción menos recomendada [IDDD cap. 10].
498
+
499
+ El caso de uso construye los valores en `toCommand` y se los pasa al agregado en `handle`:
500
+
501
+ ```ts
502
+ import { ValueObjects } from '@archi-code/ddd/domain';
503
+ import type { AggregateFinder, AggregateSaver } from '@archi-code/ddd/domain';
504
+ import { UseCase } from '@archi-code/ddd/application';
505
+ import type { Clock, EventBus } from '@archi-code/ddd/application';
506
+
507
+ interface PaymentAuthorizationInput {
508
+ orderId: string;
509
+ paymentId: string;
510
+ amount: number;
511
+ currency: string;
512
+ expiresAt: string;
513
+ }
514
+
515
+ interface PaymentAuthorizationCommand {
516
+ orderId: OrderId;
517
+ authorization: PaymentAuthorization;
518
+ }
519
+
520
+ export class RegisterPaymentAuthorization extends UseCase<
521
+ PaymentAuthorizationInput,
522
+ PaymentAuthorizationCommand,
523
+ void
524
+ > {
525
+ constructor(
526
+ private readonly orders: AggregateFinder<Order> & AggregateSaver<Order>,
527
+ private readonly events: EventBus<OrderEvent>,
528
+ private readonly clock: Clock,
529
+ ) {
530
+ super();
531
+ }
532
+
533
+ protected toCommand(input: PaymentAuthorizationInput): PaymentAuthorizationCommand {
534
+ return ValueObjects.build({
535
+ orderId: () => new OrderId(input.orderId),
536
+ authorization: () =>
537
+ PaymentAuthorization.fromPrimitives({
538
+ paymentId: input.paymentId,
539
+ amount: { amount: input.amount, currency: input.currency },
540
+ expiresAt: input.expiresAt,
541
+ }),
542
+ });
543
+ }
544
+
545
+ protected async handle(command: PaymentAuthorizationCommand): Promise<void> {
546
+ const order = await this.orders.findById(command.orderId);
547
+ if (order === null) {
548
+ return;
549
+ }
550
+ order.registerPaymentAuthorization(command.authorization, this.clock);
551
+ await this.orders.save(order);
552
+ await this.events.publish(order.pullDomainEvents());
553
+ }
554
+ }
555
+ ```
556
+
557
+ La fecha llega como texto y se pasa tal cual (`DateType` acepta `Date | string`) para que `ExpiresAt` la convierta y rechace una fecha-hora sin zona; `new Date(input.expiresAt)` la aceptaría en la hora local del servidor y un texto inválido llegaría como `Date(Invalid)` en el mensaje de error.
558
+
559
+ El agregado recibe valores, no repositorios. Como registra un hecho (lo ejecuta un listener), el caso de uso vuelve sin error si el pedido no existe en vez de lanzar `AggregateNotFoundException`, que haría reintentar al canal sin fin.
560
+
561
+ ## 5. Concurrencia optimista
562
+
563
+ * Versión en la raíz, incrementada con cada comando que cambie estado en cualquier parte del agregado [IDDD cap. 10]. Con el paquete es la `version` de `AggregateRoot`: el agregado no la declara ni la mapea, y solo la tocan los repositorios (`restoreVersion` al cargar, `markPersisted` tras escribir) `[Fuera de los libros: convención de @archi-code/ddd]`.
564
+ * Si las partes internas son entidades y el ORM no detecta su cambio como cambio de la raíz, es una señal más para reducir el agregado a raíz más value objects [IDDD cap. 10].
565
+ * En almacenes de documentos o clave-valor el agregado se guarda entero y la versión se comprueba en una sola operación [IDDD cap. 10].
566
+
567
+ En SQL `[Fuera de los libros]`:
568
+
569
+ ```sql
570
+ UPDATE orders
571
+ SET status = $2, lines = $3, version = version + 1
572
+ WHERE id = $1 AND version = $4;
573
+ ```
574
+
575
+ Si la sentencia no afecta a ninguna fila, el repositorio lanza `new ConcurrencyException('Order', order.id.value, order.version)` (de `@archi-code/ddd/exceptions`, HTTP 409 con `HttpErrorMapper`; el nombre del agregado va como texto literal porque los minificadores renombran las clases); si escribe, llama a `order.markPersisted()`. En los tests lo cumple el `InMemoryOrderRepository` que el proyecto escribe en `test/support/`; el repositorio real y su suite de contrato están en `ddd-repositorios-persistencia`.
576
+
577
+ Reintentar automáticamente solo es seguro si el comando es idempotente o se vuelve a evaluar contra el estado recargado: con el paquete, un bucle con límite en `handle` en el que cada intento vuelve a cargar el agregado y vuelve a guardarlo (unos pocos intentos y después se relanza); un caso de uso que ejecuta un listener no reintenta por su cuenta, lo hace la reentrega del canal `[Fuera de los libros: convención de @archi-code/ddd]`. Ejemplo en `ddd-skills/ddd-repositorios-persistencia/references/transacciones-ts.md`.
578
+
579
+ ## 6. Variante funcional
580
+
581
+ `[Fuera de los libros como código; la idea está en Destilado cap. 5]`
582
+
583
+ El paquete no la da: `AggregateRoot` y `PrimitiveTypes` necesitan clases `[Fuera de los libros: convención de @archi-code/ddd]`. Si se elige, se escribe en el proyecto con los mismos value objects y eventos, y su persistencia queda a cargo del proyecto:
584
+
585
+ ```ts
586
+ type OrderState = Readonly<{
587
+ id: OrderId;
588
+ status: OrderStatus;
589
+ lines: OrderLines;
590
+ reservation: ReservationId | null;
591
+ authorization: PaymentAuthorization | null;
592
+ }>;
593
+
594
+ export function registerStockReservation(
595
+ state: OrderState,
596
+ reservation: ReservationId,
597
+ clock: { now(): Date },
598
+ ): [OrderState, OrderEvent[]] {
599
+ if (!state.status.isPending()) {
600
+ return [state, []];
601
+ }
602
+ const next = { ...state, reservation };
603
+ if (next.authorization === null) {
604
+ return [next, []];
605
+ }
606
+ const total = state.lines.total();
607
+ return [
608
+ { ...next, status: new OrderStatus(Status.Confirmed) },
609
+ [
610
+ new OrderConfirmed(
611
+ state.id.value,
612
+ { amount: total.amount.value, currency: total.currency.value },
613
+ { clock },
614
+ ),
615
+ ],
616
+ ];
617
+ }
618
+ ```
619
+
620
+ Mismas reglas: un estado, un límite de consistencia, eventos como salida.
621
+
622
+ ## 7. Tests
623
+
624
+ Los agregados deben ser unidades fáciles de probar; uno complejo es difícil de verificar [Destilado cap. 5]. Los tests unitarios del agregado van **después** de los tests de aceptación de los escenarios y comprueban que hace bien lo que debe [Destilado cap. 5].
625
+
626
+ Fábricas de datos en `test/support/` del proyecto, con un reloj fijo:
627
+
628
+ ```ts
629
+ import { FixedClock } from '@archi-code/ddd/infrastructure';
630
+
631
+ export const clock = new FixedClock('2026-03-01T09:30:00Z');
632
+
633
+ export function aPendingOrderOf(cents: number): Order {
634
+ return Order.place(
635
+ OrderId.generate(),
636
+ CustomerId.generate(),
637
+ OrderLines.fromPrimitives({
638
+ items: [{ sku: 'MUG-350', quantity: 1, unitPrice: { amount: cents, currency: 'EUR' } }],
639
+ }),
640
+ ShippingAddress.fromPrimitives({ street: 'Calle Mayor 1', city: 'Huelva', country: 'ES' }),
641
+ clock,
642
+ );
643
+ }
644
+
645
+ export function anAuthorizationOf(cents: number): PaymentAuthorization {
646
+ return PaymentAuthorization.fromPrimitives({
647
+ paymentId: '0b8e4f2a-6c1d-4e7f-9a3b-5d2c8e1f4a6b',
648
+ amount: { amount: cents, currency: 'EUR' },
649
+ expiresAt: new Date('2026-03-08T09:30:00Z'),
650
+ });
651
+ }
652
+
653
+ export function aReservation(): ReservationId {
654
+ return new ReservationId('7c3d9e1f-2a4b-4c6d-8e0f-1a2b3c4d5e6f');
655
+ }
656
+
657
+ export function aConfirmedOrder(): Order {
658
+ const order = aPendingOrderOf(1700);
659
+ order.registerStockReservation(aReservation(), clock);
660
+ order.registerPaymentAuthorization(anAuthorizationOf(1700), clock);
661
+ order.pullDomainEvents();
662
+ return order;
663
+ }
664
+ ```
665
+
666
+ ```ts
667
+ import { describe, expect, it } from 'vitest';
668
+
669
+ describe('Order', () => {
670
+ it('is confirmed when the reservation and the authorization arrive, in any order', () => {
671
+ const order = aPendingOrderOf(1700);
672
+ order.registerPaymentAuthorization(anAuthorizationOf(1700), clock);
673
+ expect(order.isConfirmed()).toBe(false);
674
+ order.registerStockReservation(aReservation(), clock);
675
+ expect(order.isConfirmed()).toBe(true);
676
+ expect(order.pullDomainEvents().map((event) => event.eventName())).toContain('OrderConfirmed');
677
+ });
678
+
679
+ it('records an insufficient authorization without throwing and without confirming', () => {
680
+ const order = aPendingOrderOf(1700);
681
+ order.registerStockReservation(aReservation(), clock);
682
+ order.registerPaymentAuthorization(anAuthorizationOf(1000), clock);
683
+ expect(order.isConfirmed()).toBe(false);
684
+ expect(order.pullDomainEvents().map((event) => event.eventName())).toContain(
685
+ 'PaymentAuthorizationInsufficient',
686
+ );
687
+ });
688
+
689
+ it('ignores a repeated reservation (duplicated message)', () => {
690
+ const order = aConfirmedOrder();
691
+ order.registerStockReservation(aReservation(), clock);
692
+ expect(order.pullDomainEvents()).toHaveLength(0);
693
+ });
694
+
695
+ it('accepts no new lines once confirmed', () => {
696
+ const line = OrderLine.fromPrimitives({
697
+ sku: 'PLATE-27',
698
+ quantity: 3,
699
+ unitPrice: { amount: 900, currency: 'EUR' },
700
+ });
701
+ expect(() => aConfirmedOrder().addLine(line, clock)).toThrow(OrderNoLongerEditable);
702
+ });
703
+
704
+ it('keeps every field when stored and loaded', () => {
705
+ const order = aConfirmedOrder();
706
+ expect(Order.fromPrimitives(order.toPrimitives()).toPrimitives()).toEqual(order.toPrimitives());
707
+ });
708
+ });
709
+ ```
710
+
711
+ El último test es el de ida y vuelta que exige el paquete para cada agregado `[Fuera de los libros: convención de @archi-code/ddd]`.