@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,404 @@
1
+ # Implementar una capa anticorrupción en TypeScript
2
+
3
+ ## Estructura
4
+
5
+ IDDD propone tres piezas [IDDD cap. 3 y cap. 13]:
6
+
7
+ 1. **Interfaz de la capa en el dominio:** un servicio de dominio con nombre del lenguaje local (en IDDD, `MemberService` que proporciona `ProductOwner` y `TeamMember`).
8
+ 2. **Adaptador:** cliente del sistema remoto; habla su protocolo (en IDDD, `IdentityAccessNotificationAdapter`).
9
+ 3. **Traductor:** convierte el Published Language remoto en objetos del modelo local (en IDDD, `MemberTranslator`).
10
+
11
+ El dominio solo ve la interfaz. Adaptador y traductor viven en infraestructura. Así el modelo usa datos de otro contexto para expresar sus propios conceptos sin contaminarse [IDDD cap. 3].
12
+
13
+ `@archi-code/ddd` no trae ninguna de las tres piezas: se escriben en el proyecto con sus value objects, sus excepciones y sus puertos `[Fuera de los libros: convención de @archi-code/ddd]`.
14
+
15
+ ```
16
+ src/contexts/sales/
17
+ domain/
18
+ catalog/ProductPricing.ts ← puerto con lenguaje de Ventas (SellableProduct, ProductPricing)
19
+ order/...
20
+ infrastructure/
21
+ catalog/
22
+ CatalogHttpProductPricing.ts ← adaptador: habla HTTP con Catálogo
23
+ CatalogTranslator.ts ← traductor: JSON de Catálogo → value objects de Ventas
24
+ CatalogProductV2.ts ← forma del Published Language de Catálogo que Ventas lee
25
+ ```
26
+
27
+ ## Ejemplo: Ventas consulta precios a Catálogo
28
+
29
+ Catálogo expone un OHS con fichas completas (fotos, descripciones, variantes, tarifas por canal). Ventas solo necesita saber si una referencia se puede vender y a qué precio en el canal web.
30
+
31
+ ### 1. Puerto en el dominio de Ventas
32
+
33
+ ```ts
34
+ // src/contexts/sales/domain/catalog/ProductPricing.ts
35
+ import { Composite, Required, StringType } from '@archi-code/ddd/domain';
36
+ import { Money } from '../shared/Money';
37
+ import { Sku } from '../order/OrderLine';
38
+
39
+ export class ProductName extends Required(StringType) {}
40
+
41
+ export class SellableProduct extends Composite({ sku: Sku, name: ProductName, unitPrice: Money }) {}
42
+
43
+ export interface ProductPricing {
44
+ priceForSale(sku: Sku): Promise<SellableProduct | null>;
45
+ }
46
+ ```
47
+
48
+ Fíjate en el nombre: `ProductPricing` y `SellableProduct`, no `CatalogClient` ni `CatalogProductDto`. El dominio pregunta con su lenguaje y recibe value objects ya válidos (`SellableProduct` es un `Composite` de `Sku`, `ProductName` y `Money`); `null` significa que la referencia no se puede vender ahora en el canal web.
49
+
50
+ ### 2. Forma del Published Language
51
+
52
+ ```ts
53
+ // src/contexts/sales/infrastructure/catalog/CatalogProductV2.ts
54
+ export interface CatalogProductV2 {
55
+ sku: string;
56
+ displayName: string;
57
+ lifecycle: 'DRAFT' | 'ACTIVE' | 'DISCONTINUED';
58
+ channelPrices: { channel: string; amountMinor: number; currency: string }[];
59
+ }
60
+ ```
61
+
62
+ Solo lo que Ventas lee; el resto de campos de Catálogo se ignora. El adaptador comprueba solo la forma; los valores los validan los value objects al construirse (`Sku`, `Money`). Si prefieres validar con JSON Schema, cualquier validador sirve en este punto `[Fuera de los libros: elección de librería]`.
63
+
64
+ ### 3. Traductor
65
+
66
+ ```ts
67
+ // src/contexts/sales/infrastructure/catalog/CatalogTranslator.ts
68
+ import { SellableProduct } from '../../domain/catalog/ProductPricing';
69
+ import type { CatalogProductV2 } from './CatalogProductV2';
70
+
71
+ export class CatalogTranslator {
72
+ static isProductV2(body: unknown): body is CatalogProductV2 {
73
+ const candidate = body as Partial<CatalogProductV2> | null;
74
+ return (
75
+ typeof candidate === 'object' &&
76
+ candidate !== null &&
77
+ typeof candidate.sku === 'string' &&
78
+ typeof candidate.displayName === 'string' &&
79
+ typeof candidate.lifecycle === 'string' &&
80
+ Array.isArray(candidate.channelPrices)
81
+ );
82
+ }
83
+
84
+ toSellableProduct(remote: CatalogProductV2): SellableProduct | null {
85
+ if (remote.lifecycle !== 'ACTIVE') {
86
+ return null;
87
+ }
88
+ const web = remote.channelPrices.find((price) => price.channel === 'WEB');
89
+ if (web === undefined) {
90
+ return null;
91
+ }
92
+ return SellableProduct.fromPrimitives({
93
+ sku: remote.sku,
94
+ name: remote.displayName,
95
+ unitPrice: { amount: web.amountMinor, currency: web.currency },
96
+ });
97
+ }
98
+ }
99
+ ```
100
+
101
+ La regla "solo se vende lo activo con precio web" es una decisión de traducción. Si se convierte en regla del negocio de Ventas con matices propios, muévela al dominio. `SellableProduct.fromPrimitives` construye cada value object y, si Catálogo envía un valor que el modelo de Ventas no acepta, lanza una `ValidationException` con todos los campos afectados.
102
+
103
+ ### 4. Adaptador
104
+
105
+ ```ts
106
+ // src/contexts/sales/infrastructure/catalog/CatalogHttpProductPricing.ts
107
+ import { InfrastructureException } from '@archi-code/ddd/exceptions';
108
+ import type { ProductPricing, SellableProduct } from '../../domain/catalog/ProductPricing';
109
+ import type { Sku } from '../../domain/order/OrderLine';
110
+ import { CatalogTranslator } from './CatalogTranslator';
111
+
112
+ export class CatalogUnavailable extends InfrastructureException {
113
+ constructor(sku: string, options: { cause?: unknown } = {}) {
114
+ super(`Catalog did not answer for ${sku}`, ['CAT001'], options);
115
+ }
116
+ }
117
+
118
+ export class CatalogContractViolation extends InfrastructureException {
119
+ constructor(sku: string, options: { cause?: unknown } = {}) {
120
+ super(`Catalog answered ${sku} outside its published contract`, ['CAT002'], options);
121
+ }
122
+ }
123
+
124
+ export interface CatalogHttp {
125
+ get(
126
+ path: string,
127
+ options: { headers: Record<string, string>; signal: AbortSignal },
128
+ ): Promise<{ status: number; ok: boolean; json(): Promise<unknown> }>;
129
+ }
130
+
131
+ export class CatalogHttpProductPricing implements ProductPricing {
132
+ constructor(
133
+ private readonly http: CatalogHttp,
134
+ private readonly translator = new CatalogTranslator(),
135
+ ) {}
136
+
137
+ async priceForSale(sku: Sku): Promise<SellableProduct | null> {
138
+ let response: Awaited<ReturnType<CatalogHttp['get']>>;
139
+ try {
140
+ response = await this.http.get(`/products/${encodeURIComponent(sku.value)}`, {
141
+ headers: { Accept: 'application/vnd.catalog.product.v2+json' },
142
+ signal: AbortSignal.timeout(800),
143
+ });
144
+ } catch (error) {
145
+ throw new CatalogUnavailable(sku.value, { cause: error });
146
+ }
147
+ if (response.status === 404) {
148
+ return null;
149
+ }
150
+ if (!response.ok) {
151
+ throw new CatalogUnavailable(sku.value);
152
+ }
153
+ const body = await response.json();
154
+ if (!CatalogTranslator.isProductV2(body)) {
155
+ throw new CatalogContractViolation(sku.value);
156
+ }
157
+ try {
158
+ return this.translator.toSellableProduct(body);
159
+ } catch (error) {
160
+ throw new CatalogContractViolation(sku.value, { cause: error });
161
+ }
162
+ }
163
+ }
164
+ ```
165
+
166
+ Decisiones visibles:
167
+ * Timeout explícito: la red falla y hay latencia [IDDD cap. 13].
168
+ * Validación del contrato al entrar: si Catálogo rompe el esquema, el error se detecta en el borde y no dentro del modelo. Un valor inválido de Catálogo no llega al usuario como `ValidationException` de Ventas: el adaptador la envuelve en `CatalogContractViolation` con la original como `cause`.
169
+ * Errores con nombre y código propios (`InfrastructureException` con `CAT001`, `CAT002`). El punto de entrada los traduce con `HttpErrorMapper`, que responde 500 por defecto a cualquier error de infraestructura; para avisar de que Catálogo no está disponible se configura por código: `new HttpErrorMapper({ statusByCode: { CAT001: 503 } })` (con 5xx el cuerpo lleva un mensaje genérico, nunca el del error).
170
+
171
+ ### 5. Uso desde la capa de aplicación
172
+
173
+ ```ts
174
+ // src/contexts/sales/application/place-order/PlaceOrder.ts
175
+ import { ValueObjects } from '@archi-code/ddd/domain';
176
+ import type { AggregateSaver } from '@archi-code/ddd/domain';
177
+ import { UseCase } from '@archi-code/ddd/application';
178
+ import type { Clock, EventBus } from '@archi-code/ddd/application';
179
+ import type { ProductPricing } from '../../domain/catalog/ProductPricing';
180
+ import { CustomerId } from '../../domain/customer/CustomerId';
181
+ import { Order, OrderEvent, OrderId } from '../../domain/order/Order';
182
+ import { OrderLine, OrderLines, Quantity, Sku } from '../../domain/order/OrderLine';
183
+ import { ShippingAddress } from '../../domain/order/ShippingAddress';
184
+ import { ProductNotSellable } from '../../domain/order/ProductNotSellable';
185
+
186
+ interface PlaceOrderInput {
187
+ customerId: string;
188
+ items: { sku: string; quantity: number }[];
189
+ shippingAddress: { street: string; city: string; country: string };
190
+ }
191
+
192
+ interface PlaceOrderCommand {
193
+ orderId: OrderId;
194
+ customerId: CustomerId;
195
+ items: { sku: Sku; quantity: Quantity }[];
196
+ shippingAddress: ShippingAddress;
197
+ }
198
+
199
+ export class PlaceOrder extends UseCase<PlaceOrderInput, PlaceOrderCommand, string> {
200
+ constructor(
201
+ private readonly orders: AggregateSaver<Order>,
202
+ private readonly pricing: ProductPricing,
203
+ private readonly events: EventBus<OrderEvent>,
204
+ private readonly clock: Clock,
205
+ ) {
206
+ super();
207
+ }
208
+
209
+ protected toCommand(input: PlaceOrderInput): PlaceOrderCommand {
210
+ return ValueObjects.build({
211
+ orderId: () => OrderId.generate(),
212
+ customerId: () => new CustomerId(input.customerId),
213
+ items: () =>
214
+ ValueObjects.buildList(input.items, (item) =>
215
+ ValueObjects.build({
216
+ sku: () => new Sku(item.sku),
217
+ quantity: () => new Quantity(item.quantity),
218
+ }),
219
+ ),
220
+ shippingAddress: () => ShippingAddress.fromPrimitives(input.shippingAddress),
221
+ });
222
+ }
223
+
224
+ protected async handle(command: PlaceOrderCommand): Promise<string> {
225
+ const lines: OrderLine[] = [];
226
+ for (const item of command.items) {
227
+ const product = await this.pricing.priceForSale(item.sku);
228
+ if (product === null) {
229
+ throw new ProductNotSellable(item.sku.value);
230
+ }
231
+ lines.push(
232
+ new OrderLine({ sku: product.sku, quantity: item.quantity, unitPrice: product.unitPrice }),
233
+ );
234
+ }
235
+ const order = Order.place(
236
+ command.orderId,
237
+ command.customerId,
238
+ new OrderLines({ items: lines }),
239
+ command.shippingAddress,
240
+ this.clock,
241
+ );
242
+ await this.orders.save(order);
243
+ await this.events.publish(order.pullDomainEvents());
244
+ return order.id.value;
245
+ }
246
+ }
247
+ ```
248
+
249
+ El precio se copia en la línea del pedido: Ventas guarda el estado remoto **mínimo** que necesita y deja de depender de Catálogo para ese pedido [IDDD cap. 3]. La consulta a Catálogo va antes de crear y guardar el pedido; como la transacción solo existe dentro del `save` del repositorio, nunca queda una abierta mientras se espera a la red `[Fuera de los libros: convención de @archi-code/ddd]`. `ProductNotSellable` es una `DomainException` de Ventas con código propio (`ORD003`), que `HttpErrorMapper` responde con 422.
250
+
251
+ ## ACL con un webhook entrante
252
+
253
+ Mismo patrón con un producto externo que llama a este contexto: el punto de entrada es el adaptador, la forma del mensaje es el contrato y la traducción produce la entrada de un caso de uso en el lenguaje local. Vive en `interfaces/` (como cualquier controlador) y solo conoce casos de uso y `HttpErrorMapper` [IDDD cap. 4]. El router ya ha comprobado la firma del emisor y la forma del cuerpo antes de llamarlo `[Fuera de los libros]`.
254
+
255
+ ```ts
256
+ // src/contexts/payments/interfaces/http/PaymentGatewayWebhook.ts
257
+ import { HttpErrorMapper } from '@archi-code/ddd/infrastructure';
258
+ import type { HttpErrorBody } from '@archi-code/ddd/infrastructure';
259
+ import type { RecordPaymentAuthorization } from '../../application/record-payment-authorization/RecordPaymentAuthorization';
260
+ import type { RecordPaymentRejection } from '../../application/record-payment-rejection/RecordPaymentRejection';
261
+
262
+ export interface GatewayEvent {
263
+ type: string;
264
+ created: number;
265
+ amount: number;
266
+ currency: string;
267
+ failure_code?: string;
268
+ metadata: { paymentId: string };
269
+ }
270
+
271
+ export class PaymentGatewayWebhook {
272
+ constructor(
273
+ private readonly recordAuthorization: RecordPaymentAuthorization,
274
+ private readonly recordRejection: RecordPaymentRejection,
275
+ private readonly errors: HttpErrorMapper,
276
+ ) {}
277
+
278
+ async receive(event: GatewayEvent): Promise<{ status: number; body?: HttpErrorBody }> {
279
+ try {
280
+ switch (event.type) {
281
+ case 'charge.authorized':
282
+ await this.recordAuthorization.execute({
283
+ paymentId: event.metadata.paymentId,
284
+ amount: { amount: event.amount, currency: event.currency },
285
+ authorizedAt: new Date(event.created * 1000).toISOString(),
286
+ });
287
+ break;
288
+ case 'charge.failed':
289
+ await this.recordRejection.execute({
290
+ paymentId: event.metadata.paymentId,
291
+ reason: event.failure_code ?? 'unknown',
292
+ });
293
+ break;
294
+ }
295
+ return { status: 204 };
296
+ } catch (error) {
297
+ return this.errors.map(error);
298
+ }
299
+ }
300
+ }
301
+ ```
302
+
303
+ `charge.authorized` es lenguaje de la pasarela; `RecordPaymentAuthorization` es lenguaje de Pagos. Cada tipo de evento que interesa va a un caso de uso distinto, que construye sus value objects en `toCommand` (`PaymentId`, `Money`) y registra un hecho: no lo rechaza y es idempotente, porque la pasarela reintenta. Los tipos que este contexto no necesita se ignoran con 204.
304
+
305
+ ## ACL con eventos de otro contexto
306
+
307
+ Si el upstream publica eventos (con el mismo paquete o no), el downstream los lee con su propia clase: un `EventBase` con el nombre publicado y solo los campos que usa, nunca la clase del otro contexto importada de su código `[Fuera de los libros: convención de @archi-code/ddd]`. La factoría de `EventRegistry` es el traductor del mensaje, y el listener convierte el evento en la entrada de un caso de uso:
308
+
309
+ ```ts
310
+ // src/contexts/sales/application/register-stock-reservation/StockReserved.ts
311
+ import { EventBase } from '@archi-code/ddd/domain';
312
+ import type { EventMetadata } from '@archi-code/ddd/domain';
313
+
314
+ export class StockReserved extends EventBase {
315
+ static readonly eventName = 'warehouse.stock.StockReserved';
316
+
317
+ constructor(
318
+ reservationId: string,
319
+ readonly orderId: string,
320
+ metadata?: EventMetadata,
321
+ ) {
322
+ super(reservationId, metadata);
323
+ }
324
+ }
325
+ ```
326
+
327
+ ```ts
328
+ // src/contexts/sales/application/register-stock-reservation/RegisterStockReservationOnStockReserved.ts
329
+ import { EventListener, ListensTo } from '@archi-code/ddd/application';
330
+ import { RegisterStockReservation } from './RegisterStockReservation';
331
+ import { StockReserved } from './StockReserved';
332
+
333
+ @ListensTo(StockReserved, { consumer: 'sales.register-stock-reservation-on-stock-reserved' })
334
+ export class RegisterStockReservationOnStockReserved extends EventListener<StockReserved> {
335
+ constructor(private readonly registerStockReservation: RegisterStockReservation) {
336
+ super();
337
+ }
338
+
339
+ async handle(event: StockReserved): Promise<void> {
340
+ await this.registerStockReservation.execute({
341
+ orderId: event.orderId,
342
+ reservationId: event.aggregateId,
343
+ });
344
+ }
345
+ }
346
+ ```
347
+
348
+ ```ts
349
+ // src/contexts/sales/composition.ts (extracto)
350
+ import { DomainEventBus, EventRegistry } from '@archi-code/ddd/infrastructure';
351
+
352
+ const warehouseEvents = new EventRegistry<StockReserved>().register(
353
+ StockReserved,
354
+ (json) => new StockReserved(json.aggregateId, json.orderId as string, json),
355
+ );
356
+ const warehouseBus = new DomainEventBus<StockReserved>(
357
+ new RabbitMqEventChannel(amqpChannel, 'warehouse.published'),
358
+ warehouseEvents,
359
+ );
360
+ await warehouseBus.subscribe(
361
+ new RegisterStockReservationOnStockReserved(
362
+ new RegisterStockReservation(orders, salesBus, clock),
363
+ ),
364
+ );
365
+ ```
366
+
367
+ `RabbitMqEventChannel` es el canal de broker del proyecto (el de `ddd-skills/archi-code-ddd/patterns.md`), aquí suscrito al exchange donde Almacén publica su Published Language. La factoría pasa `json` como metadatos para conservar el `eventId` con el que se deduplica. Leer `json.orderId as string` no valida nada: el caso de uso construye `OrderId` y `ReservationId` en `toCommand`, y si el mensaje no cumple el contrato lanza una `ValidationException` con cada campo, el listener falla y el canal lo aparta (en RabbitMQ, `nack` a la cola de mensajes muertos). El caso de uso `RegisterStockReservation` y su idempotencia están en `ddd-skills/ddd-context-mapping/references/estilos-integracion.md`.
368
+
369
+ ## Tests de la capa
370
+
371
+ `[Fuera de los libros]`
372
+ * **Traductor:** tests unitarios con respuestas reales anonimizadas del sistema remoto.
373
+ * **Adaptador:** tests de contrato contra el esquema publicado o un entorno de pruebas del upstream.
374
+ * **Dominio y casos de uso:** usan una implementación en memoria del puerto; nunca se prueban contra el sistema remoto:
375
+
376
+ ```ts
377
+ import type { ProductPricing } from '../../src/contexts/sales/domain/catalog/ProductPricing';
378
+ import { SellableProduct } from '../../src/contexts/sales/domain/catalog/ProductPricing';
379
+ import type { Sku } from '../../src/contexts/sales/domain/order/OrderLine';
380
+
381
+ export class InMemoryProductPricing implements ProductPricing {
382
+ private readonly products = new Map<string, SellableProduct>();
383
+
384
+ add(product: SellableProduct): this {
385
+ this.products.set(product.sku.value, product);
386
+ return this;
387
+ }
388
+
389
+ async priceForSale(sku: Sku): Promise<SellableProduct | null> {
390
+ return this.products.get(sku.value) ?? null;
391
+ }
392
+ }
393
+ ```
394
+
395
+ Con ella, `PlaceOrder` se prueba con `new InMemoryOrderRepository()` (el repositorio en memoria que el proyecto escribe en `test/support/`, como en `ddd-skills/ddd-repositorios-persistencia/references/tests-repositorios.md`), un `DomainEventBus` sobre `InMemoryEventChannel` y `new FixedClock('2026-03-01T09:30:00Z')`, comprobando `channel.sent` y lo guardado.
396
+
397
+ ## Errores frecuentes
398
+
399
+ * El puerto devuelve el DTO remoto "por comodidad" en vez de value objects. La capa ya no protege nada.
400
+ * El traductor vive en el dominio porque "es lógica". Si conoce el formato remoto, es infraestructura.
401
+ * Una única ACL genérica para todos los sistemas externos. Cada relación tiene su traducción.
402
+ * Copiar todos los campos remotos "por si acaso" en vez del estado mínimo.
403
+ * Dejar que una `ValidationException` provocada por datos del sistema remoto salga como si fuera un error del usuario (422): el adaptador la envuelve en su propia `InfrastructureException`.
404
+ * Importar la clase de evento del otro contexto para el `@ListensTo`: el downstream declara la suya, con el nombre publicado.
@@ -0,0 +1,155 @@
1
+ # Estilos de integración
2
+
3
+ ## Comparativa
4
+
5
+ | Estilo | Acoplamiento temporal | Robustez | Cuándo |
6
+ |---|---|---|---|
7
+ | RPC (SOAP, gRPC, HTTP con procedimientos) | Alto: si el otro cae, fallas | Baja. Parece una llamada local y no lo es [Destilado cap. 4] | Consultas rápidas a servicios muy disponibles; siempre con ACL |
8
+ | REST | Alto en peticiones directas | Media. Se apoya en la fiabilidad probada de la web [Destilado cap. 4] | OHS para varios consumidores; consultas de detalle (query-back) |
9
+ | Mensajería con eventos | Bajo | La más robusta: elimina gran parte del acoplamiento temporal y obliga a diseñar contando con latencia [Destilado cap. 4] | Notificar hechos, coordinar procesos entre contextos |
10
+ | Base de datos o ficheros compartidos | Estructural | Pésima. Evitar [Destilado cap. 4; IDDD cap. 13] | Solo heredados sin alternativa, con ACL |
11
+
12
+ ## Principios de la computación distribuida
13
+
14
+ IDDD los formula como principios, no como falacias, para subrayar que hay que diseñar contando con ellos [IDDD cap. 13]: la red no es fiable; siempre hay latencia; el ancho de banda no es infinito; la red no es segura; la topología cambia; el conocimiento y las políticas están repartidos entre varios administradores; el transporte tiene coste; la red es heterogénea.
15
+
16
+ ## REST
17
+
18
+ * El contexto que ofrece REST debe proporcionar un OHS con Published Language; los recursos son el idioma publicado [Destilado cap. 4].
19
+ * Recursos **sintéticos**: con la forma que necesita el caso de uso del cliente, no la del agregado. Reflejar los agregados fuerza a todos los clientes a ser Conformist ante cualquier cambio del modelo [Destilado cap. 4].
20
+ * Para reducir la dependencia temporal aunque solo haya REST: llama al sistema remoto desde un temporizador o al recibir un mensaje propio, con reintentos y espera creciente, en lugar de hacerlo dentro de la petición del usuario [IDDD cap. 13].
21
+
22
+ ### REST asíncrono (notification log)
23
+
24
+ Publicar los eventos de dominio como un feed de recursos (al estilo Atom) que los clientes sondean periódicamente. Si el servicio no está disponible, el cliente reintenta más tarde [Destilado cap. 4]. IDDD lo implementa con un log de notificaciones actual y logs archivados navegables; cada cliente es responsable de no consumir duplicados [IDDD cap. 3 y cap. 13].
25
+
26
+ ## Mensajería
27
+
28
+ * El contexto suscrito reacciona a eventos de dominio publicados por otros, y eso suele crear o modificar un agregado propio [Destilado cap. 4].
29
+ * Si el cliente necesita forzar una acción, envía un **mensaje de comando** y recibe el resultado como un evento publicado [Destilado cap. 4].
30
+ * **Entrega al menos una vez** en el mecanismo y **receptor idempotente** en el consumidor: deduplicar o reaplicar con el mismo resultado [Destilado cap. 4].
31
+ * Las peticiones no deben bloquearse esperando respuesta; siempre habrá algo de latencia y hay que planificarla [Destilado cap. 4].
32
+
33
+ ### Publicar de forma fiable
34
+
35
+ `[Fuera de los libros como nombre; la idea de guardar eventos en la misma transacción está en IDDD cap. 8]`. Patrón **outbox transaccional**: en la misma transacción que guarda el agregado, se guardan sus eventos en una tabla; un proceso aparte los lee y los publica en el bus, marcándolos como enviados. Evita perder eventos si el bus falla tras confirmar la transacción, o publicar eventos de cambios que luego se revierten. Se desarrolla en `ddd-skills/ddd-eventos-dominio/GUIA.md`. Con `@archi-code/ddd` la aplicación no abre transacciones: el caso de uso sigue llamando a `EventBus.publish` después de `save`, y el outbox lo escribe el adaptador del repositorio, que guarda el agregado y sus eventos pendientes en la misma escritura atómica de `save` (el mismo documento o la misma transacción de base de datos); un reenviador los entrega después por un `EventChannel` `[Fuera de los libros: convención de @archi-code/ddd]`.
36
+
37
+ ### Receptor idempotente en TypeScript
38
+
39
+ El evento de Almacén llega por un `EventChannel` a un listener (`RegisterStockReservationOnStockReserved`, en `ddd-skills/ddd-context-mapping/references/anticorrupcion-ts.md`), que ejecuta un solo caso de uso. Ese caso de uso registra un hecho: no lo rechaza, vuelve sin error si el pedido no existe (un error haría que el canal lo reentregara sin fin) y el método del agregado es idempotente:
40
+
41
+ ```ts
42
+ // src/contexts/sales/application/register-stock-reservation/RegisterStockReservation.ts
43
+ import { ValueObjects } from '@archi-code/ddd/domain';
44
+ import type { AggregateFinder, AggregateSaver } from '@archi-code/ddd/domain';
45
+ import { UseCase } from '@archi-code/ddd/application';
46
+ import type { Clock, EventBus } from '@archi-code/ddd/application';
47
+ import { Order, OrderEvent, OrderId, ReservationId } from '../../domain/order/Order';
48
+
49
+ interface RegisterStockReservationInput {
50
+ orderId: string;
51
+ reservationId: string;
52
+ }
53
+
54
+ interface RegisterStockReservationCommand {
55
+ orderId: OrderId;
56
+ reservationId: ReservationId;
57
+ }
58
+
59
+ export class RegisterStockReservation extends UseCase<
60
+ RegisterStockReservationInput,
61
+ RegisterStockReservationCommand,
62
+ void
63
+ > {
64
+ constructor(
65
+ private readonly orders: AggregateFinder<Order> & AggregateSaver<Order>,
66
+ private readonly events: EventBus<OrderEvent>,
67
+ private readonly clock: Clock,
68
+ ) {
69
+ super();
70
+ }
71
+
72
+ protected toCommand(input: RegisterStockReservationInput): RegisterStockReservationCommand {
73
+ return ValueObjects.build({
74
+ orderId: () => new OrderId(input.orderId),
75
+ reservationId: () => new ReservationId(input.reservationId),
76
+ });
77
+ }
78
+
79
+ protected async handle(command: RegisterStockReservationCommand): Promise<void> {
80
+ const order = await this.orders.findById(command.orderId);
81
+ if (order === null) {
82
+ return;
83
+ }
84
+ order.registerStockReservation(command.reservationId, this.clock);
85
+ await this.orders.save(order);
86
+ await this.events.publish(order.pullDomainEvents());
87
+ }
88
+ }
89
+ ```
90
+
91
+ El método de `Order` (el de `ddd-skills/ddd-agregados/references/implementacion-ts.md`), que guarda la reserva en `private _stockReservation: ReservationId | null` (`null` porque al principio no hay reserva) y confirma el pedido cuando también tiene la autorización de pago:
92
+
93
+ ```ts
94
+ registerStockReservation(reservation: ReservationId, clock: { now(): Date }): void {
95
+ if (!this._status.isPending()) {
96
+ return;
97
+ }
98
+ this._stockReservation = reservation;
99
+ this.tryToConfirm(clock);
100
+ }
101
+ ```
102
+
103
+ Aplicar dos veces "reserva registrada" no cambia nada la segunda vez ni graba un segundo evento. Cuando la operación no es idempotente por naturaleza, el `eventId` del mensaje se guarda como procesado en la misma escritura atómica que el cambio, y si ya estaba no se hace nada `[Fuera de los libros]`; con el paquete esa escritura la hace el adaptador que persiste el cambio, no el caso de uso, que solo le pasa el `eventId` (ver "Consumidor idempotente" en `ddd-skills/ddd-eventos-dominio/references/publicacion-ts.md`) `[Fuera de los libros: convención de @archi-code/ddd]`. Si dos mensajes del mismo pedido llegan a la vez, `save` lanza `ConcurrencyException` en uno de ellos y el listener falla; lo que pase después lo decide el canal (reintento o cola de mensajes muertos), y en el reintento el caso de uso vuelve a cargar el pedido y la operación idempotente no cambia nada.
104
+
105
+ ## Contratos y versionado
106
+
107
+ * Dos opciones para intercambiar estructuras: distribuir clases e interfaces a todos los sistemas (tipado fuerte, pero recompilaciones y riesgo de usar objetos ajenos como propios) o un contrato basado en estándar, un media type propio que es un Published Language [IDDD cap. 13].
108
+ * Con contrato, el consumidor solo lee datos; no tiene acceso a comportamiento del otro modelo, y IDDD lo ve como protección, no como carencia. Los cálculos los hace el productor y los envía como atributos del evento [IDDD cap. 13].
109
+ * Los eventos y notificaciones llevan versión. Si se diseñan con cuidado, muchos consumidores pueden leer cualquier versión como si fuera la 1 y no necesitan cambiar [IDDD cap. 13].
110
+ * Los value objects estables (identificadores) se pueden incluir en el evento; quedan congelados en la estructura [IDDD cap. 13]. Con el paquete, el payload lleva solo primitivos tomados de los value objects (`orderId: string`), también los identificadores `[Fuera de los libros: convención de @archi-code/ddd]`.
111
+
112
+ La notificación de IDDD envuelve el evento en `{ notificationId, typeName, version, occurredOn, event }` [IDDD cap. 13]. Con el paquete, el mensaje publicado es un `SerializedEvent` plano, con el nombre y la versión del Published Language `[Fuera de los libros: convención de @archi-code/ddd]`:
113
+
114
+ ```json
115
+ {
116
+ "eventName": "sales.order.OrderPlaced",
117
+ "eventVersion": 1,
118
+ "eventId": "6f1c2a3b-8d4e-4f5a-9b6c-7d8e9f0a1b2c",
119
+ "aggregateId": "9b2f6c1e-4d3a-4f8b-a1c2-3e4d5f6a7b8c",
120
+ "occurredOn": "2026-10-18T10:15:00.000Z",
121
+ "orderId": "9b2f6c1e-4d3a-4f8b-a1c2-3e4d5f6a7b8c",
122
+ "customerId": "1c5e7a90-2b3d-4e6f-8a1b-2c3d4e5f6a7b",
123
+ "lines": [
124
+ { "sku": "TORCH-01", "quantity": 2, "unitPrice": { "amount": 850, "currency": "EUR" } }
125
+ ],
126
+ "total": { "amount": 1700, "currency": "EUR" }
127
+ }
128
+ ```
129
+
130
+ `eventName` hace de `typeName`, `eventVersion` de `version` y `eventId` es el identificador con el que el consumidor deduplica. No hay `notificationId` secuencial: si publicas un feed REST de notificaciones, la secuencia la asigna tu canal duradero (la fila del outbox).
131
+
132
+ El evento interno no sale tal cual. La traducción al Published Language vive en infraestructura, en el adaptador que habla con el broker: un `EventChannel` del proyecto (`ContractPublishingChannel`) que pasa cada `SerializedEvent` por `SalesContracts` (qué eventos salen y con qué contrato, uno por versión publicada, con `messageId` = `eventId`) y entrega todos al canal interno. El código está en `ddd-skills/ddd-eventos-dominio/references/publicacion-ts.md` ("Lenguaje publicado en el adaptador del broker"); los casos de uso siguen viendo solo `EventBus`, y un evento sin contrato es interno y no sale del contexto. Para no perder mensajes, la publicación al broker la hace el reenviador del outbox después del commit (ver "Publicar de forma fiable").
133
+
134
+ El consumidor no valida el mensaje con un esquema aparte: lo lee con su propia clase `extends EventBase` (solo los campos que usa) y el caso de uso que ejecuta su listener construye cada valor como value object en `toCommand`, que lanza una `ValidationException` con todos los campos que no cumplen el contrato. El ejemplo completo está en `ddd-skills/ddd-context-mapping/references/anticorrupcion-ts.md`.
135
+
136
+ ## Evento enriquecido o consulta al origen
137
+
138
+ | | Evento enriquecido | Evento ligero + query-back |
139
+ |---|---|---|
140
+ | Autonomía del consumidor | Alta | Depende de que el origen responda |
141
+ | Previsión de necesidades | Difícil acertar con todo | No hace falta |
142
+ | Seguridad | Riesgo de exponer datos de más | El origen controla el acceso en cada consulta |
143
+
144
+ Hay casos que piden una mezcla [Destilado cap. 4]. En el ejemplo del libro, `PolicyIssued` lleva el identificador de la póliza; el consumidor lo guarda como `issuedPolicyId` para trazabilidad y consulta al origen con `GET` si necesita más [Destilado cap. 4].
145
+
146
+ Pauta `[Fuera de los libros]`: incluye en el evento lo que el consumidor necesita para decidir y que no cambiará (identificadores, importes cerrados, fechas del hecho); deja fuera datos personales que el consumidor no necesite (minimización de datos) y datos que cambian a menudo.
147
+
148
+ ## Procesos de larga duración
149
+
150
+ Cuando un proceso implica a varios contextos, se coordina con eventos y un objeto que guarda el estado del proceso, con un rastreador de tiempos para detectar respuestas que no llegan (estados como no completado, completado normalmente o agotado por tiempo) [IDDD cap. 13]. En la práctica actual se llama a esto process manager o saga `[Fuera de los libros como nombres]`. Reglas:
151
+
152
+ * El estado del proceso es un concepto del dominio con nombre del lenguaje (`ConfirmaciónDePedido`), no un `SagaState` genérico.
153
+ * Cada paso tiene un resultado esperado y un plazo.
154
+ * Las acciones compensatorias (liberar reserva, anular autorización) son comandos de negocio, no "rollbacks".
155
+ * Cuando un recurso remoto aún no existe, modela esa situación como estado. En IDDD, una discusión remota tiene estados de disponibilidad: complemento no contratado, no solicitada, solicitada y lista [IDDD cap. 3].
@@ -0,0 +1,92 @@
1
+ # Event Sourcing y CQRS
2
+
3
+ > Parte del skill `archi-code-ddd`: léela solo cuando ese skill te dirija aquí. Los ejemplos están escritos con la API de `@archi-code/ddd` (las clases de su `src`) y se copian tal cual; las guías son la fuente de verdad: el paquete solo se aparta de ellas donde lo marca `[Fuera de los libros: convención de @archi-code/ddd]`, y cualquier otra discrepancia es un defecto del paquete.
4
+ >
5
+ > **Cuándo consultar esta guía.** Event Sourcing (agregados persistidos como secuencia de eventos, A+ES) y CQRS (modelo de comandos separado de modelos de lectura) con DDD en TypeScript. Empieza por decidir si compensan, y cubre stream de eventos por agregado, reconstitución, control de concurrencia con versión esperada, event store, snapshots, proyecciones y read models reconstruibles, consistencia eventual en la interfaz, enriquecimiento y versionado de eventos, y cómo cumplir el RGPD con eventos inmutables. Consúltala siempre que alguien mencione event sourcing, CQRS, read models, proyecciones, auditoría completa de cambios, "volver a un estado anterior", event store, o pantallas lentas que cruzan muchos agregados, aunque la pregunta sea solo "¿debería usar...?".
6
+
7
+ ## Con @archi-code/ddd
8
+
9
+ [Fuera de los libros: convención de @archi-code/ddd]
10
+
11
+ * El paquete no trae event store, repositorios con Event Sourcing, snapshots ni proyecciones con checkpoint: se construyen en la infraestructura del proyecto sobre sus clases, y solo si la tabla de decisión de esta guía lo justifica.
12
+ * La `version` de `AggregateRoot` crece 1 por guardado, no por evento: un agregado con Event Sourcing lleva su propia versión de stream (`streamVersion()`), se reconstruye con `fromHistory` a partir de los eventos que reconstruye `EventRegistry` y no usa `toPrimitives()`/`fromPrimitives()`.
13
+ * El contrato del stream (`load`/`append`) lo declara el dominio y no es `AggregateFinder`/`AggregateSaver`; su adaptador es responsable de la persistencia: anexa los eventos de un `append` en una sola escritura atómica con la versión esperada y lanza `ConcurrencyException` si otro escribió antes. La aplicación no abre transacciones; el reintento ante `ConcurrencyException` va en el `handle` del caso de uso, cada intento vuelve a cargar y vuelve a anexar.
14
+ * Una proyección es un `EventListener` con `@ListensTo(Event, { consumer })` que llama a un puerto de proyección (no a un caso de uso), idempotente por `eventId`; un `EventChannel` no guarda historial, así que reconstruirla exige un log de eventos propio (con Event Sourcing, el propio event store).
15
+ * Una consulta de lectura es una interfaz en `application/` implementada en infraestructura directamente sobre la base de datos.
16
+
17
+ Dos patrones distintos que a menudo van juntos. **CQRS** separa el modelo que ejecuta comandos (agregados) de los modelos que responden consultas [IDDD cap. 4]. **Event Sourcing** guarda el estado de un agregado como la secuencia de eventos que le han ocurrido y lo reconstruye aplicándolos [Destilado cap. 6; IDDD apéndice A]. Event Sourcing casi siempre exige alguna forma de CQRS, porque los streams de eventos son difíciles de consultar [IDDD apéndice A; Destilado cap. 6]. CQRS no exige Event Sourcing.
18
+
19
+
20
+ ## Paso 1. Decidir (la mayoría de las veces, no)
21
+
22
+ Ningún estilo es para todo: se usa donde mitiga un riesgo concreto [IDDD cap. 4].
23
+
24
+ ### CQRS
25
+
26
+ | A favor | En contra |
27
+ |---|---|
28
+ | Vistas que cruzan muchos agregados y no encajan en repositorios [IDDD cap. 4] | Interfaz sencilla que sigue a un agregado: CQRS añade complejidad accidental [IDDD cap. 4] |
29
+ | Volumen de lectura muy superior al de escritura `[Fuera de los libros]` | Usuarios que necesitan ver al instante lo que acaban de escribir, sin margen de diseño |
30
+ | Muchos `findBy` en repositorios: señal de IDDD [IDDD cap. 12] | |
31
+
32
+ Antes de CQRS completo, prueba el paso intermedio: consultas SQL de solo lectura sobre las mismas tablas (`ddd-skills/ddd-arquitectura/GUIA.md`).
33
+
34
+ ### Event Sourcing
35
+
36
+ Beneficios [IDDD apéndice A; Destilado cap. 6]:
37
+ * No se pierde el motivo de ningún cambio: auditoría completa, análisis, depuración mirando atrás.
38
+ * Escritura solo por anexión, muy rápida.
39
+ * Libertad estructural: la forma interna del agregado puede cambiar sin migrar datos; el stream se puede reproducir en otra infraestructura para depurar un error.
40
+ * Tiende a producir agregados más pequeños.
41
+
42
+ Inconvenientes que el propio apéndice reconoce [IDDD apéndice A]:
43
+ * Definir buenos eventos exige conocer el dominio a fondo; solo compensa en modelos complejos que dan ventaja competitiva.
44
+ * Obliga a CQRS: más carga cognitiva y curva de aprendizaje.
45
+
46
+ Recomendación de estas fuentes `[Fuera de los libros]`: Event Sourcing solo en agregados del Core Domain donde el historial es un requisito de negocio (o legal) o donde las preguntas "¿cómo llegó a este estado?" son frecuentes. No en todo el sistema. Si solo hace falta auditoría, el outbox de `ddd-skills/ddd-eventos-dominio/GUIA.md` ya deja un registro de eventos sin cambiar la persistencia. Con el paquete, un `EventChannel` entrega los eventos pero no los conserva: para auditar, tu canal duradero (o un listener) guarda cada `SerializedEvent` en una tabla propia que no se borre `[Fuera de los libros: convención de @archi-code/ddd]`.
47
+
48
+ ## Paso 2. Implementar Event Sourcing
49
+
50
+ Guía completa en `ddd-skills/ddd-event-sourcing-cqrs/references/event-sourcing-ts.md`. Esquema:
51
+
52
+ 1. El agregado (`extends AggregateRoot<PaymentId, PaymentEvent>`) separa **decidir** (métodos de comando que comprueban reglas y emiten eventos con `record`) de **aplicar** (un `apply` privado que cambia estado a partir de un evento). IDDD propone incluso dos clases, una de estado y otra de comportamiento, que solo colaboran a través de `apply` [IDDD apéndice A].
53
+ 2. Cargar: leer el stream del agregado, reconstruir cada evento con `EventRegistry.fromJSON` y aplicarlos en orden (`Payment.fromHistory`).
54
+ 3. Ejecutar el comando: genera eventos nuevos, que `pullDomainEvents()` devuelve como cambios pendientes.
55
+ 4. Guardar: anexar los cambios con la **versión esperada** del stream (`streamVersion()`); si otro escribió antes, conflicto (`ConcurrencyException`) [IDDD apéndice A].
56
+ 5. Ante conflicto: reintentar recargando y reejecutando, o propagar al cliente [IDDD apéndice A]. Con el paquete, el bucle va en el `handle` del caso de uso, con un límite de intentos, y cada intento vuelve a cargar el stream y vuelve a anexar; un caso de uso que lanza un listener no reintenta por sí mismo, lo hace la reentrega del canal.
57
+ 6. Publicar: un proceso en segundo plano lee lo anexado y lo publica [IDDD apéndice A]. Con el paquete, el caso de uso publica con `EventBus` después de `append`; para no perder eventos si `publish` falla, el propio event store hace de bandeja (el `append` atómico ya los guardó) y un reenviador los entrega por un `EventChannel`.
58
+
59
+ Rendimiento: caché de streams en memoria (los eventos son inmutables) y **snapshots** para streams largos [IDDD apéndice A; Destilado cap. 6].
60
+
61
+ ## Paso 3. Proyecciones y read models
62
+
63
+ Suscriptores que reciben eventos y mantienen modelos de lectura persistentes (con el paquete, `EventListener` suscritos al `DomainEventBus` e idempotentes por `eventId`); parecidos a un agregado, pero para consultar [IDDD apéndice A]. Se pueden **reconstruir** reproduciendo todos los eventos, lo que permite cambiar una vista sin tocar el agregado [IDDD apéndice A]. También sirven para dar a un agregado datos de otro de forma desacoplada, a través de un servicio de dominio que consulta la proyección [IDDD apéndice A].
64
+
65
+ Guía en `ddd-skills/ddd-event-sourcing-cqrs/references/proyecciones-cqrs-ts.md`.
66
+
67
+ ## Paso 4. La interfaz con un modelo de lectura eventual
68
+
69
+ Diseña para el peor caso: la vista siguiente puede no reflejar todavía el comando [IDDD cap. 4]. Opciones de IDDD: mostrar temporalmente lo enviado; mostrar la antigüedad de los datos; actualizaciones push; o avisar de que la operación se está procesando [IDDD cap. 4].
70
+
71
+ ## Paso 5. Eventos con doble función
72
+
73
+ En Event Sourcing los eventos sirven para persistir el agregado **y** para comunicar al resto de la empresa. Eso crea tensión: lo que necesita el agregado no es lo que necesitan los consumidores. IDDD lo trata como enriquecimiento de eventos [IDDD apéndice A]. Pauta `[Fuera de los libros]`: eventos internos del stream con lo mínimo para reconstruir, y eventos publicados traducidos al Published Language (igual que en `ddd-skills/ddd-eventos-dominio/GUIA.md`).
74
+
75
+ ## Datos personales e inmutabilidad
76
+
77
+ `[Fuera de los libros]` Un stream inmutable choca con el derecho de supresión del RGPD. Opciones: no guardar datos personales en los eventos (solo identificadores y los datos en un almacén borrable); o cifrar los datos personales con una clave por persona y destruir la clave al suprimir ("crypto-shredding"). Decide esto antes de escribir el primer evento.
78
+
79
+ ## Señales de alarma
80
+
81
+ * Event Sourcing en todo el sistema "por si acaso".
82
+ * Eventos tipo `OrderUpdated` con el estado completo en el stream.
83
+ * Proyecciones que no se pueden reconstruir (dependen de estado externo no reproducible).
84
+ * Proyecciones no idempotentes (sin comprobar el `eventId` ya aplicado).
85
+ * Un agregado con Event Sourcing que usa la `version` de `AggregateRoot` como versión de stream o se guarda como instantánea con `toPrimitives()`.
86
+ * Agregados que consultan read models para proteger invariantes (las invariantes viven en el agregado).
87
+ * Datos personales en eventos sin estrategia de supresión.
88
+ * CQRS con dos bases de datos para un CRUD de cinco pantallas.
89
+
90
+ ## Siguiente guía
91
+
92
+ * Revisar un sistema existente → `ddd-skills/ddd-auditoria-refactorizacion/GUIA.md`.