@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,121 @@
1
+ # Repositorios y persistencia
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.** Repositorios DDD y persistencia de agregados en TypeScript/Node. Cubre un repositorio por agregado, repositorios orientados a colección frente a orientados a persistencia (save), interfaz en el dominio e implementación en infraestructura, mappers entre filas o documentos y el modelo, concurrencia optimista, la transacción como responsabilidad del repositorio, consultas y "use case optimal queries", diferencias con un DAO, implementaciones en memoria y tests de contrato. Consúltala siempre que haya que guardar o cargar agregados, integrar Prisma, TypeORM, Drizzle, MikroORM, pg o MongoDB con un modelo de dominio, diseñar métodos find/save, gestionar transacciones, o cuando el ORM esté dictando la forma de las clases del dominio, aunque no se mencione "repository".
6
+
7
+ ## Con @archi-code/ddd
8
+
9
+ [Fuera de los libros: convención de @archi-code/ddd]
10
+
11
+ * No hay un `Repository` completo: los contratos son tres roles en `@archi-code/ddd/domain` (`AggregateFinder.findById`, `AggregateSaver.save`, `AggregateRemover.remove`) y un caso de uso pide solo los que usa, combinados con `&`.
12
+ * Los repositorios solo obtienen y persisten: sin `nextId()` ni consultas de negocio como `pendingOlderThan`, que pasan a ser consultas de lectura en `application/`.
13
+ * Cada repositorio es único para su agregado (`MongoOrderRepository implements AggregateFinder<Order>, AggregateSaver<Order>, AggregateRemover<Order>`) y sabe qué agregado reconstruye. Su constructor recibe solo las dependencias de su tecnología (una `collection` de MongoDB, un `pool` de Postgres), nunca la clase del agregado ni un mapper; los agregados solo entran y salen por sus métodos (`save(order)`, `remove(order)`, `findById(id)` → `Order | null`).
14
+ * La conversión es del agregado, su único dueño: `toPrimitives()` y `static fromPrimitives()` (instantánea o Memento), sin un mapper, un servicio de dominio ni una comprobación de forma en medio. El repositorio no lee campos del agregado: guarda `order.toPrimitives()` con `order.version + 1` al lado y carga con `Order.fromPrimitives(row).restoreVersion(row.version)`. Nada comprueba en ejecución que la conversión esté completa: lo garantiza el test del agregado con el viaje de ida y vuelta (`Order.fromPrimitives(order.toPrimitives()).toPrimitives()` igual a `order.toPrimitives()`), y quien añade un campo lo añade a los dos métodos.
15
+ * La versión de concurrencia optimista vive en la base `AggregateRoot` (`version`, `restoreVersion()`, `markPersisted()`): el agregado no la declara ni la mapea, y solo los repositorios la tocan. Un conflicto es `ConcurrencyException('Order', id, version, { cause })` (HTTP 409), con el nombre del agregado como texto literal y no como nombre de clase, que los minificadores cambian.
16
+ * `remove(aggregate)` recibe el agregado cargado y comprueba su versión como `save`: borrar un agregado que otro cambió después de cargarlo es `ConcurrencyException`.
17
+ * El repositorio no publica eventos: lo hace el caso de uso con `events.publish(order.pullDomainEvents())` después de `save`.
18
+ * La transacción es del repositorio: no hay puerto de transacciones en `@archi-code/ddd/application` y la aplicación nunca abre, une ni cierra una. Cada `save`/`remove` es una escritura atómica, y si el agregado ocupa varias tablas el repositorio abre y confirma la transacción de la base de datos dentro de ese método, con su propia conexión o sesión.
19
+ * El paquete no da un repositorio en memoria: cada proyecto escribe el suyo en `test/support/`, uno por agregado (`InMemoryOrderRepository`), con el mismo contrato que el real (copias guardadas, comprobación de versión, `markPersisted()`), y lo usa en los tests con un `DomainEventBus` sobre `InMemoryEventChannel`. Solo tiene `findAll()` si los tests del proyecto lo necesitan; un repositorio real no lo tiene.
20
+
21
+ Un repositorio da la ilusión de una colección en memoria con todos los agregados de un tipo, con una interfaz conocida para añadir, quitar y seleccionar. **Solo los agregados tienen repositorio**, normalmente uno por tipo de agregado [IDDD cap. 12, citando a Evans].
22
+
23
+
24
+ ## Reglas
25
+
26
+ 1. **Un repositorio por agregado**, nunca por entidad interna ni por tabla [IDDD cap. 12].
27
+ 2. **La interfaz vive en el dominio** con lenguaje del dominio; la implementación, en infraestructura [IDDD cap. 4 y cap. 12; PHP: Repositories]. Con el paquete, los contratos ya están en `@archi-code/ddd/domain` y tu adaptador (`MongoOrderRepository`) vive en `infrastructure/persistence/`.
28
+ 3. **Devuelve agregados completos** (o value objects para consultas concretas), nunca filas, DTOs del ORM ni partes que la raíz no permita alcanzar [IDDD cap. 12].
29
+ 4. **No es un DAO.** Un DAO se expresa en términos de tablas y CRUD; un repositorio, en términos de colección de agregados. Operaciones CRUD de grano fino sobre partes del agregado rompen su encapsulación [IDDD cap. 12; PHP: Repositories].
30
+ 5. **El dominio no gestiona transacciones** [IDDD cap. 12; Destilado cap. 2]. Los libros las controlan desde la capa de aplicación; con el paquete las controla el repositorio, dentro de `save`/`remove`, y la aplicación tampoco las ve `[Fuera de los libros: convención de @archi-code/ddd]`.
31
+ 6. **Una escritura atómica, un agregado.** La "transacción por agregado" de los libros es aquí una escritura atómica por agregado, garantizada por el repositorio. Que funcione en tests no significa que funcione en producción con concurrencia [IDDD cap. 12].
32
+ 7. **Nada de lógica de negocio en procedimientos almacenados** salvo requisito no funcional que lo exija; saca la lógica del modelo y de su lenguaje [IDDD cap. 12].
33
+
34
+ ## Paso 1. Elegir el estilo
35
+
36
+ | Estilo | Interfaz | Cuándo [IDDD cap. 12; PHP: Repositories] |
37
+ |---|---|---|
38
+ | Orientado a colección | `add`, `remove` y búsqueda por id; los cambios se detectan solos | El mecanismo tiene unidad de trabajo que rastrea cambios |
39
+ | Orientado a persistencia | `save`, `remove` y búsqueda por id; hay que guardar explícitamente | Sin rastreo de cambios (almacenes clave-valor, documentos) o cuando es realista cambiar de mecanismo |
40
+
41
+ **Con el paquete el estilo es orientado a persistencia** `[Fuera de los libros: convención de @archi-code/ddd]`: los roles son `findById`, `save` y `remove`, y cada cambio se guarda con un `save` explícito. Los clientes más usados (driver de MongoDB, pg, Prisma, Drizzle) no rastrean cambios de objetos de dominio, y un `save` explícito es más legible. Una unidad de trabajo de ORM (MikroORM) ataría el dominio al ORM.
42
+
43
+ ## Paso 2. Elegir los roles
44
+
45
+ El proyecto no declara un `OrderRepository`: usa los contratos del paquete, que reciben el agregado como parámetro de tipo.
46
+
47
+ | Rol (`@archi-code/ddd/domain`) | Método | Contrato |
48
+ |---|---|---|
49
+ | `AggregateFinder<Order>` | `findById(id: OrderId): Promise<Order \| null>` | Devuelve el agregado completo con su versión almacenada, o `null` |
50
+ | `AggregateSaver<Order>` | `save(order: Order): Promise<void>` | Alta (versión 0) y cambios; comprueba la versión y llama a `markPersisted()` |
51
+ | `AggregateRemover<Order>` | `remove(order: Order): Promise<void>` | Borra el agregado cargado solo si nadie lo cambió después |
52
+
53
+ Un caso de uso pide exactamente los roles que usa. Constructor de `AddOrderLine` (la clase completa, con `toCommand` y `handle`, está en `ddd-skills/ddd-repositorios-persistencia/references/transacciones-ts.md`):
54
+
55
+ ```ts
56
+ import { UseCase } from '@archi-code/ddd/application';
57
+ import type { Clock, EventBus } from '@archi-code/ddd/application';
58
+ import type { AggregateFinder, AggregateSaver } from '@archi-code/ddd/domain';
59
+
60
+ export class AddOrderLine extends UseCase<AddOrderLineInput, AddOrderLineCommand, void> {
61
+ constructor(
62
+ private readonly orders: AggregateFinder<Order> & AggregateSaver<Order>,
63
+ private readonly events: EventBus<OrderEvent>,
64
+ private readonly clock: Clock,
65
+ ) {
66
+ super();
67
+ }
68
+ }
69
+ ```
70
+
71
+ * El repositorio solo obtiene y persiste: sin lógica de negocio, sin consultas de negocio (`pendingOlderThan`) y sin generar identidades (`nextId()`) `[Fuera de los libros: convención de @archi-code/ddd]`. La identidad nueva la genera su value object (`OrderId.generate()`, ver `ddd-skills/ddd-value-objects-entidades/GUIA.md`); las selecciones son consultas de lectura (Paso 5).
72
+ * Nada de `findAll()` ni `findBy(field, value)` genéricos.
73
+ * Consultas para pantallas que cruzan agregados no van aquí: van a consultas de lectura (ver Paso 5).
74
+
75
+ ## Paso 3. Implementar
76
+
77
+ Guía completa con MongoDB, PostgreSQL y Prisma en `ddd-skills/ddd-repositorios-persistencia/references/implementacion-ts.md`. Puntos clave:
78
+
79
+ * El agregado se convierte en datos planos y vuelve a construirse con sus value objects [IDDD cap. 6]: la conversión es del propio agregado y el repositorio llama a `order.toPrimitives()` y a `Order.fromPrimitives(row).restoreVersion(version)` `[Fuera de los libros: convención de @archi-code/ddd]`. Que no falte ningún campo lo prueba el test de ida y vuelta del agregado, no una comprobación en ejecución.
80
+ * La **versión** se comprueba al guardar y al borrar (concurrencia optimista) [IDDD cap. 10]: se guarda junto a los primitivos, nunca dentro, y tras escribir el repositorio llama a `order.markPersisted()`.
81
+ * Los **eventos** los publica el caso de uso con `EventBus.publish` después de `save`. Si un evento no puede perderse, el repositorio escribe el agregado y sus eventos pendientes en la misma escritura atómica (outbox) y un proceso aparte los entrega después por un `EventChannel` (`ddd-skills/ddd-repositorios-persistencia/references/transacciones-ts.md`, `ddd-skills/ddd-eventos-dominio/GUIA.md`).
82
+ * El **esquema** sirve al modelo, no al revés [IDDD cap. 6]. Un agregado pequeño encaja a menudo en un documento, o en una fila con una columna `jsonb` para sus value objects.
83
+
84
+ ## Paso 4. Transacciones
85
+
86
+ La transacción es del repositorio, como todo lo demás de la persistencia `[Fuera de los libros: convención de @archi-code/ddd]`. La aplicación no abre, une ni cierra transacciones: el caso de uso carga, llama al método de negocio, `await this.orders.save(order)` y después `await this.events.publish(order.pullDomainEvents())`. El adaptador garantiza que cada `save`/`remove` es atómico (un documento en MongoDB; varias tablas SQL en una transacción que abre y confirma o revierte dentro del propio `save`, con su conexión), comprueba la versión (`ConcurrencyException`), gestiona la conexión, la sesión y el pool, y envuelve los errores del driver en `InternalErrorException` con `{ cause }`. Un conflicto se reintenta en `handle` volviendo a cargar y a guardar, con un límite y solo para comandos seguros de repetir. En tests basta el `InMemoryOrderRepository` que el proyecto escribe en `test/support/`. Patrón y ejemplos en `ddd-skills/ddd-repositorios-persistencia/references/transacciones-ts.md`.
87
+
88
+ ## Paso 5. Consultas
89
+
90
+ * Para pantallas que cruzan varios agregados, IDDD admite consultas optimizadas por caso de uso que devuelven un value object a medida. Si necesitas muchas, sospecha: o los límites de agregado están mal ("el repositorio enmascara un mal diseño de agregados") o te conviene CQRS [IDDD cap. 12].
91
+ * En la práctica, separa: los roles `AggregateFinder`/`AggregateSaver` para escribir y una consulta de lectura (`OrderSummaries`, `StaleOrders`) declarada como interfaz en la capa de aplicación e implementada en infraestructura directamente sobre la base de datos, con filtros de `@archi-code/criteria` `[Fuera de los libros]`. Nunca carga agregados con `AggregateFinder`. Ejemplo en `ddd-skills/ddd-repositorios-persistencia/references/implementacion-ts.md`.
92
+ * *DDD in PHP* propone consultar el repositorio con especificaciones (`query(specification)`) [PHP: Repositories; PHP: Factories]. Aquí no: el repositorio no selecciona por criterios de negocio `[Fuera de los libros: convención de @archi-code/ddd]`. Una selección con nombre del negocio es una consulta de lectura que devuelve ids o DTOs; una `Specification` se aplica sobre datos ya cargados (`ddd-skills/ddd-value-objects-entidades/references/specification-y-diseno-flexible.md`).
93
+
94
+ ## Paso 6. Probar
95
+
96
+ Dos tipos de prueba [IDDD cap. 12]:
97
+ * **Del repositorio real**, contra la base de datos de verdad (por ejemplo con Testcontainers o `mongodb-memory-server`) `[herramienta fuera de los libros]`.
98
+ * **Del código que usa repositorios**, con una implementación **en memoria** cuando la real es lenta o aún no existe [IDDD cap. 12; PHP: Repositories]. Con el paquete se escribe en `test/support/` del proyecto, una por agregado (`new InMemoryOrderRepository()`), como cualquier otra implementación `[Fuera de los libros: convención de @archi-code/ddd]`.
99
+
100
+ Un mismo conjunto de tests de contrato, en `test/support/` del proyecto, se ejecuta contra ambas implementaciones para que la versión en memoria no mienta `[Fuera de los libros]`. Ver `ddd-skills/ddd-repositorios-persistencia/references/tests-repositorios.md`.
101
+
102
+ ## Señales de alarma
103
+
104
+ * `UserRepository`, `OrderLineRepository`, `AddressRepository`: repositorios de partes internas.
105
+ * Una interfaz `OrderRepository` todo en uno en el proyecto, o un caso de uso que recibe los tres roles cuando solo lee o solo guarda.
106
+ * El repositorio devuelve tipos de Prisma, documentos de MongoDB o filas.
107
+ * Decoradores `@Entity` del ORM en clases del dominio.
108
+ * `updateStatus(id, status)` en el repositorio: CRUD de grano fino que salta al agregado.
109
+ * El caso de uso recibe una dependencia `transactions`, `unitOfWork` o parecida, o abre `BEGIN`/`COMMIT` o `startTransaction()`: la transacción es del repositorio.
110
+ * Un repositorio que reparte un agregado en varias tablas sin agrupar sus sentencias en una transacción dentro de `save`.
111
+ * Docenas de métodos `findByXAndYOrderByZ` para pantallas.
112
+ * El repositorio lee campos del agregado o construye filas a mano en vez de llamar a `order.toPrimitives()` y `Order.fromPrimitives(row)`.
113
+ * El constructor del repositorio recibe la clase del agregado o un mapper: recibe solo las dependencias de su tecnología.
114
+ * Un campo nuevo del agregado que no está en `toPrimitives()`/`fromPrimitives()`, sobre todo uno privado que `PrimitiveTypes<Order>` no ve, sin un test de ida y vuelta que lo detecte.
115
+ * `save` con `upsert` sin condición de versión, `fromPrimitives(row)` sin la versión almacenada o `markPersisted()` olvidado tras escribir.
116
+ * Un repositorio en memoria que no comprueba versiones ni copia objetos (los tests pasan, producción falla): escríbelo como `InMemoryOrderRepository` y ejecuta contra él la suite de contrato.
117
+
118
+ ## Siguiente guía
119
+
120
+ * Casos de uso que usan los repositorios → `ddd-skills/ddd-servicios-capa-aplicacion/GUIA.md`.
121
+ * Persistir como eventos en vez de estado → `ddd-skills/ddd-event-sourcing-cqrs/GUIA.md`.
@@ -0,0 +1,296 @@
1
+ # Implementar repositorios en TypeScript
2
+
3
+ Todos los ejemplos usan el agregado `Order` de `ddd-skills/ddd-agregados/references/implementacion-ts.md`, con sus value objects (`OrderId`, `CustomerId`, `OrderLines`, `OrderLine`, `Sku`, `Quantity`, `Money`, `ShippingAddress`, `OrderStatus`, `PaymentAuthorization`) y sus eventos: `Order.place(id, customerId, lines, address, clock)` lo crea pendiente con sus líneas, `addLine(line, clock)` añade una, y `toPrimitives()` y `static fromPrimitives()` son su conversión, la que llama el repositorio (ver `ddd-skills/ddd-agregados/GUIA.md`). El repositorio es código del proyecto en `infrastructure/persistence/` que implementa los roles del paquete.
4
+
5
+ ## Qué se guarda
6
+
7
+ `[Fuera de los libros: esquema propio siguiendo IDDD cap. 6, valores embebidos en el registro del padre]`
8
+
9
+ `PrimitiveTypes<Order>` es la forma persistida: cada value object se convierte en su valor, cada composite en sus primitivos, un enum en su opción y las fechas siguen siendo `Date`. Para `Order`:
10
+
11
+ ```ts
12
+ import type { PrimitiveTypes } from '@archi-code/ddd/domain';
13
+ import { Order, Status } from '../../domain/order/order';
14
+
15
+ const primitives: PrimitiveTypes<Order> = {
16
+ id: '9b2f6c1e-4d3a-4f8b-a1c2-3e4d5f6a7b8c',
17
+ customerId: '3f1d2c4b-5a6e-4f70-8b9c-0d1e2f3a4b5c',
18
+ lines: { items: [{ sku: 'TORCH-01', quantity: 2, unitPrice: { amount: 4990, currency: 'EUR' } }] },
19
+ shippingAddress: { street: 'Calle Mayor 1', city: 'Huelva', country: 'ES' },
20
+ status: Status.Pending,
21
+ stockReservation: null,
22
+ paymentAuthorization: {
23
+ paymentId: '0b8e4f2a-6c1d-4e7f-9a3b-5d2c8e1f4a6b',
24
+ amount: { amount: 9980, currency: 'EUR' },
25
+ expiresAt: new Date('2026-03-08T09:30:00Z'),
26
+ },
27
+ };
28
+ ```
29
+
30
+ Un documento (o una fila) por agregado: lectura rápida, sin joins, y la versión protege todo el agregado [IDDD cap. 10]. La versión se guarda **junto a** los primitivos, nunca dentro: el agregado no la declara y `PrimitiveTypes` la deja fuera `[Fuera de los libros: convención de @archi-code/ddd]`.
31
+
32
+ Convención de versiones del paquete: un agregado nuevo tiene `version` 0; al insertarlo se guarda con 1 y `markPersisted()` lo lleva a 1. Cada actualización exige que la versión almacenada coincida con `order.version`, guarda `order.version + 1` y llama a `markPersisted()`. Al cargar, el repositorio llama a `Order.fromPrimitives(primitives).restoreVersion(storedVersion)`. El repositorio llama a `markPersisted()` solo cuando la escritura se completó (en SQL, tras el `COMMIT`); si `save` lanza, el objeto conserva su versión y el caso de uso lo descarta y vuelve a cargarlo.
33
+
34
+ ## Conversión
35
+
36
+ No se escribe un mapper: la conversión es del agregado, su único dueño (`toPrimitives()` y `static fromPrimitives()`, su instantánea o Memento), no de un servicio de dominio ni de un mapper hecho a mano, y no hay comprobación de forma en medio `[Fuera de los libros: convención de @archi-code/ddd]`. El repositorio llama al agregado directamente:
37
+
38
+ * Al escribir: guarda `order.toPrimitives()` con `order.version + 1` al lado y, tras escribir, llama a `order.markPersisted()`.
39
+ * Al leer: `Order.fromPrimitives(row).restoreVersion(row.version)`.
40
+ * En un conflicto: `throw new ConcurrencyException('Order', key, order.version)`, con el nombre del agregado como texto literal, no como nombre de clase: los minificadores cambian los nombres de las clases.
41
+
42
+ Nada comprueba en ejecución que la conversión esté completa. Lo garantizan el agregado y sus tests: el spec de cada agregado prueba el viaje de ida y vuelta (`Order.fromPrimitives(order.toPrimitives()).toPrimitives()` igual a `order.toPrimitives()`), y quien añade un campo lo añade a `toPrimitives()` y a `fromPrimitives()`, también un campo privado que TypeScript no ve en `PrimitiveTypes<Order>`.
43
+
44
+ Cada repositorio es único para su agregado y su constructor recibe solo las dependencias de su tecnología (la `collection` de MongoDB, el `pool` de Postgres, el `PrismaClient`), nunca la clase del agregado ni un mapper: los agregados solo entran y salen por `save(order)`, `remove(order)` y `findById(id)` `[Fuera de los libros: convención de @archi-code/ddd]`. `toPrimitives()`/`fromPrimitives()` son la única puerta de infraestructura al estado interno `[Fuera de los libros: convención de @archi-code/ddd]`; el repositorio nunca lee getters del agregado ni construye filas a mano. Los constructores de los value objects validan al cargar: un dato corrupto falla en `fromPrimitives` y no dentro del modelo.
45
+
46
+ ## Repositorio con MongoDB
47
+
48
+ Un documento por agregado es el encaje natural de un almacén orientado a agregados [IDDD cap. 12]. Cada `save` y cada `remove` es una sola operación sobre un documento, atómica por sí misma, así que no necesita sesión ni transacción: la transacción es del repositorio y aquí no hace falta abrir ninguna (`ddd-skills/ddd-repositorios-persistencia/references/transacciones-ts.md`) `[Fuera de los libros: convención de @archi-code/ddd]`. El repositorio implementa los tres roles y envuelve los errores de escritura del driver con su causa:
49
+
50
+ ```ts
51
+ import { MongoServerError } from 'mongodb';
52
+ import type { Collection } from 'mongodb';
53
+ import type {
54
+ AggregateFinder,
55
+ AggregateRemover,
56
+ AggregateSaver,
57
+ PrimitiveTypes,
58
+ } from '@archi-code/ddd/domain';
59
+ import { ConcurrencyException, InternalErrorException } from '@archi-code/ddd/exceptions';
60
+ import { Order, OrderId } from '../../domain/order/order';
61
+
62
+ export type OrderDocument = PrimitiveTypes<Order> & { _id: string; version: number };
63
+
64
+ const DUPLICATE_KEY = 11000;
65
+
66
+ export class MongoOrderRepository
67
+ implements AggregateFinder<Order>, AggregateSaver<Order>, AggregateRemover<Order>
68
+ {
69
+ constructor(private readonly collection: Collection<OrderDocument>) {}
70
+
71
+ async findById(id: OrderId): Promise<Order | null> {
72
+ const document = await this.collection.findOne({ _id: id.value });
73
+ if (document === null) {
74
+ return null;
75
+ }
76
+ const { _id, version, ...primitives } = document;
77
+ return Order.fromPrimitives(primitives).restoreVersion(version);
78
+ }
79
+
80
+ async save(order: Order): Promise<void> {
81
+ const document: OrderDocument = {
82
+ ...order.toPrimitives(),
83
+ _id: order.id.value,
84
+ version: order.version + 1,
85
+ };
86
+ const written = await this.write(order, async () => {
87
+ if (order.version === 0) {
88
+ await this.collection.insertOne(document);
89
+ return true;
90
+ }
91
+ const { matchedCount } = await this.collection.replaceOne(
92
+ { _id: document._id, version: order.version },
93
+ document,
94
+ );
95
+ return matchedCount === 1;
96
+ });
97
+ if (!written) {
98
+ throw new ConcurrencyException('Order', order.id.value, order.version);
99
+ }
100
+ order.markPersisted();
101
+ }
102
+
103
+ async remove(order: Order): Promise<void> {
104
+ const filter = { _id: order.id.value };
105
+ const removed = await this.write(order, async () => {
106
+ if (order.version === 0) {
107
+ return (await this.collection.countDocuments(filter, { limit: 1 })) === 0;
108
+ }
109
+ const { deletedCount } = await this.collection.deleteOne({ ...filter, version: order.version });
110
+ return deletedCount === 1;
111
+ });
112
+ if (!removed) {
113
+ throw new ConcurrencyException('Order', order.id.value, order.version);
114
+ }
115
+ }
116
+
117
+ private async write(order: Order, operation: () => Promise<boolean>): Promise<boolean> {
118
+ try {
119
+ return await operation();
120
+ } catch (error) {
121
+ if (error instanceof MongoServerError && error.code === DUPLICATE_KEY) {
122
+ throw new ConcurrencyException('Order', order.id.value, order.version, {
123
+ cause: error,
124
+ });
125
+ }
126
+ throw new InternalErrorException(`writing order ${order.id.value}`, { cause: error });
127
+ }
128
+ }
129
+ }
130
+ ```
131
+
132
+ * Alta (`version === 0`): `insertOne`; un `_id` ya ocupado (error 11000) es `ConcurrencyException` con el error del driver como `cause`.
133
+ * Cambio: `replaceOne` con la condición `{ _id, version: order.version }`; si no casa ningún documento, otro guardó antes.
134
+ * Un conflicto es `ConcurrencyException`: el caso de uso puede reintentar volviendo a cargar y a guardar, y `HttpErrorMapper` responde 409, no 500.
135
+ * Borrado: `deleteOne` con la misma condición. Un agregado que nunca se guardó (versión 0) no tiene nada que borrar, salvo que su id ya esté ocupado: entonces es un conflicto, igual que en el `InMemoryOrderRepository` de los tests (`ddd-skills/ddd-repositorios-persistencia/references/tests-repositorios.md`).
136
+ * Cualquier otro error del driver al escribir es `InternalErrorException` con el error como `cause`.
137
+ * Cuidado con la serialización: IDDD advierte de que en estos almacenes hay que cuidar el formato serializado, que no es gratis [IDDD cap. 12]. Los primitivos son datos planos y `Date`, que el driver guarda tal cual.
138
+
139
+ ## Con PostgreSQL
140
+
141
+ `[Fuera de los libros: esquema propio siguiendo IDDD cap. 6]`
142
+
143
+ ```sql
144
+ CREATE TABLE orders (
145
+ id UUID PRIMARY KEY,
146
+ version INT NOT NULL,
147
+ customer_id UUID NOT NULL,
148
+ status TEXT NOT NULL,
149
+ data JSONB NOT NULL
150
+ );
151
+ ```
152
+
153
+ `data` guarda `PrimitiveTypes<Order>` entero; las columnas sueltas (`customer_id`, `status`) se duplican solo para indexar las consultas de lectura. Todo el agregado cabe en una fila, así que cada `save` es una sola sentencia, atómica por sí misma, sobre el pool. `PgPool` (la interfaz estructural de los métodos de `pg` que usa) y la variante con las líneas en otra tabla, donde el repositorio abre y confirma una transacción dentro de `save`, están en `ddd-skills/ddd-repositorios-persistencia/references/transacciones-ts.md`. Este adaptador solo implementa los roles que soporta:
154
+
155
+ ```ts
156
+ import type { AggregateFinder, AggregateSaver, PrimitiveTypes } from '@archi-code/ddd/domain';
157
+ import { ConcurrencyException, InternalErrorException } from '@archi-code/ddd/exceptions';
158
+ import { Order, OrderId } from '../../domain/order/order';
159
+ import type { PgPool } from './pg-pool';
160
+
161
+ interface OrderRow {
162
+ version: number;
163
+ data: PrimitiveTypes<Order>;
164
+ }
165
+
166
+ export class PostgresOrderRepository implements AggregateFinder<Order>, AggregateSaver<Order> {
167
+ constructor(private readonly pool: PgPool) {}
168
+
169
+ async findById(id: OrderId): Promise<Order | null> {
170
+ const { rows } = await this.pool.query<OrderRow>(
171
+ 'SELECT version, data FROM orders WHERE id = $1',
172
+ [id.value],
173
+ );
174
+ return rows[0] ? Order.fromPrimitives(rows[0].data).restoreVersion(rows[0].version) : null;
175
+ }
176
+
177
+ async save(order: Order): Promise<void> {
178
+ const primitives = order.toPrimitives();
179
+ const write =
180
+ order.version === 0
181
+ ? this.pool.query(
182
+ 'INSERT INTO orders (id, version, customer_id, status, data) VALUES ($1, 1, $2, $3, $4) ON CONFLICT (id) DO NOTHING',
183
+ [primitives.id, primitives.customerId, primitives.status, primitives],
184
+ )
185
+ : this.pool.query(
186
+ 'UPDATE orders SET version = version + 1, status = $3, data = $4 WHERE id = $1 AND version = $2',
187
+ [primitives.id, order.version, primitives.status, primitives],
188
+ );
189
+ const { rowCount } = await write.catch((error: unknown) => {
190
+ throw new InternalErrorException(`writing order ${order.id.value}`, { cause: error });
191
+ });
192
+ if (rowCount === 0) {
193
+ throw new ConcurrencyException('Order', order.id.value, order.version);
194
+ }
195
+ order.markPersisted();
196
+ }
197
+ }
198
+ ```
199
+
200
+ Al leer `data` de `jsonb`, `paymentAuthorization.expiresAt` llega como texto ISO con zona, que `ExpiresAt` (un `DateType`) convierte.
201
+
202
+ ## Con Prisma
203
+
204
+ `[Fuera de los libros]`
205
+ * El esquema de Prisma y su cliente viven en infraestructura. El repositorio implementa `AggregateFinder<Order>` y `AggregateSaver<Order>`, recibe el `PrismaClient` y devuelve agregados con `Order.fromPrimitives(row.data).restoreVersion(row.version)`, nunca modelos de Prisma.
206
+ * La concurrencia optimista con Prisma: `updateMany({ where: { id, version: order.version }, data: { ..., version: { increment: 1 } } })`; si `count === 0`, `ConcurrencyException('Order', order.id.value, order.version)`; si no, `order.markPersisted()`.
207
+ * Si el agregado ocupa varios modelos de Prisma, el repositorio agrupa sus escrituras con `prisma.$transaction(async (tx) => ...)` dentro de su propio `save`; la aplicación nunca la abre `[Fuera de los libros: convención de @archi-code/ddd]`.
208
+
209
+ ## Consultas de lectura
210
+
211
+ Las selecciones no pasan por el repositorio: son interfaces de la capa de aplicación que devuelven DTOs o ids, implementadas en infraestructura directamente sobre la base de datos [IDDD cap. 12, "use case optimal query"] `[Fuera de los libros: convención de @archi-code/ddd]`. Los filtros se escriben con `@archi-code/criteria` sobre la forma persistida (`PrimitiveTypes<Order>`) y `@archi-code/criteria-adapters/mongodb` los ejecuta:
212
+
213
+ En `application/order-summaries/`:
214
+
215
+ ```ts
216
+ import type { CustomerId } from '../../domain/order/order';
217
+
218
+ export interface OrderSummary {
219
+ readonly id: string;
220
+ readonly status: string;
221
+ readonly lines: number;
222
+ }
223
+
224
+ export interface OrderSummaries {
225
+ ofCustomer(
226
+ customerId: CustomerId,
227
+ page: { limit: number; offset: number },
228
+ ): Promise<OrderSummary[]>;
229
+ }
230
+
231
+ export interface StaleOrders {
232
+ ids(expiredBefore: Date, limit: number): Promise<string[]>;
233
+ }
234
+ ```
235
+
236
+ En `infrastructure/persistence/`:
237
+
238
+ ```ts
239
+ import { Criteria } from '@archi-code/criteria';
240
+ import { searchMongo } from '@archi-code/criteria-adapters/mongodb';
241
+ import type { Collection } from 'mongodb';
242
+ import type { PrimitiveTypes } from '@archi-code/ddd/domain';
243
+ import { Status } from '../../domain/order/order';
244
+ import type { CustomerId, Order } from '../../domain/order/order';
245
+ import type { OrderSummaries, OrderSummary, StaleOrders } from '../../application/order-summaries/order-summaries';
246
+ import type { OrderDocument } from './mongo-order-repository';
247
+
248
+ export class MongoOrderSummaries implements OrderSummaries {
249
+ constructor(private readonly collection: Collection<OrderDocument>) {}
250
+
251
+ async ofCustomer(
252
+ customerId: CustomerId,
253
+ page: { limit: number; offset: number },
254
+ ): Promise<OrderSummary[]> {
255
+ const { items } = await searchMongo<PrimitiveTypes<Order>, OrderDocument>(
256
+ this.collection,
257
+ Criteria.create<PrimitiveTypes<Order>>({
258
+ where: { customerId: customerId.value },
259
+ orderBy: { id: 'asc' },
260
+ limit: page.limit,
261
+ offset: page.offset,
262
+ }),
263
+ );
264
+ return items.map((order) => ({
265
+ id: order.id,
266
+ status: order.status,
267
+ lines: order.lines.items.length,
268
+ }));
269
+ }
270
+ }
271
+
272
+ export class MongoStaleOrders implements StaleOrders {
273
+ constructor(private readonly collection: Collection<OrderDocument>) {}
274
+
275
+ async ids(expiredBefore: Date, limit: number): Promise<string[]> {
276
+ const { items } = await searchMongo<PrimitiveTypes<Order>, OrderDocument>(
277
+ this.collection,
278
+ Criteria.create<PrimitiveTypes<Order>>({
279
+ where: {
280
+ status: Status.Pending,
281
+ 'paymentAuthorization.expiresAt': { lt: expiredBefore },
282
+ },
283
+ orderBy: { 'paymentAuthorization.expiresAt': 'asc' },
284
+ limit,
285
+ }),
286
+ );
287
+ return items.map((order) => order.id);
288
+ }
289
+ }
290
+ ```
291
+
292
+ `T` es la forma persistida, no el agregado: la consulta nunca construye un `Order`. Un proceso por tiempo (pedidos pendientes cuya autorización de pago ha caducado) pide los ids a `StaleOrders` y ejecuta un caso de uso por agregado.
293
+
294
+ ## Jerarquías de tipos
295
+
296
+ IDDD trata repositorios compartidos por subtipos y sus complicaciones [IDDD cap. 12]. Antes de diseñar una jerarquía de agregados, revisa si un tipo estándar (value object de estado o tipo, como `OrderStatus`) basta. Cada repositorio reconstruye una clase concreta con su `fromPrimitives`.
@@ -0,0 +1,177 @@
1
+ # Probar repositorios
2
+
3
+ ## Repositorio en memoria
4
+
5
+ Un mapa con la identidad como clave y el agregado como valor [IDDD cap. 12]. Dos detalles para que no mienta `[Fuera de los libros]`: guardar copias (para que modificar un objeto sin `save` no "persista") y comprobar versiones.
6
+
7
+ El paquete no lo da: cada proyecto escribe el suyo en `test/support/`, uno por agregado, como cualquier otra implementación de una tecnología `[Fuera de los libros: convención de @archi-code/ddd]`. Implementa los roles de `@archi-code/ddd/domain` para su agregado (`AggregateFinder<Order>`, `AggregateSaver<Order>`, `AggregateRemover<Order>`), su constructor no recibe nada (no hay tecnología de la que depender) y llama a `order.toPrimitives()` y `Order.fromPrimitives(row).restoreVersion(version)`, igual que un adaptador real.
8
+
9
+ En `test/support/in-memory-order-repository.ts`:
10
+
11
+ ```ts
12
+ import type {
13
+ AggregateFinder,
14
+ AggregateRemover,
15
+ AggregateSaver,
16
+ PrimitiveTypes,
17
+ } from '@archi-code/ddd/domain';
18
+ import { ConcurrencyException } from '@archi-code/ddd/exceptions';
19
+ import { Order } from '../../src/contexts/sales/domain/order/order';
20
+ import type { OrderId } from '../../src/contexts/sales/domain/order/order';
21
+
22
+ export class InMemoryOrderRepository
23
+ implements AggregateFinder<Order>, AggregateSaver<Order>, AggregateRemover<Order>
24
+ {
25
+ private readonly rows = new Map<string, { version: number; primitives: PrimitiveTypes<Order> }>();
26
+
27
+ async findById(id: OrderId): Promise<Order | null> {
28
+ const row = this.rows.get(id.value);
29
+ return row === undefined
30
+ ? null
31
+ : Order.fromPrimitives(structuredClone(row.primitives)).restoreVersion(row.version);
32
+ }
33
+
34
+ async save(order: Order): Promise<void> {
35
+ const key = this.currentKey(order);
36
+ this.rows.set(key, { version: order.version + 1, primitives: structuredClone(order.toPrimitives()) });
37
+ order.markPersisted();
38
+ }
39
+
40
+ async remove(order: Order): Promise<void> {
41
+ this.rows.delete(this.currentKey(order));
42
+ }
43
+
44
+ private currentKey(order: Order): string {
45
+ const key = order.id.value;
46
+ if ((this.rows.get(key)?.version ?? 0) !== order.version) {
47
+ throw new ConcurrencyException('Order', key, order.version);
48
+ }
49
+ return key;
50
+ }
51
+ }
52
+ ```
53
+
54
+ * Guarda `structuredClone` de `order.toPrimitives()` con su versión al lado, y cada `findById` reconstruye un objeto nuevo con `Order.fromPrimitives(copy).restoreVersion(version)`: cambiar un agregado sin `save` no persiste nada.
55
+ * Sigue el mismo contrato de concurrencia que un repositorio real: `save` y `remove` lanzan `ConcurrencyException` si la versión almacenada no coincide con `order.version` (también al dar de alta un id ocupado), y tras guardar llama a `markPersisted()`.
56
+ * Las claves son `id.value`; `IdType` y `UuidType` guardan el valor en minúsculas, así que la búsqueda no distingue mayúsculas.
57
+ * Cada `save` y cada `remove` es atómico, como el de un repositorio real: no hay transacción que abrir ni estado que restaurar en los tests (la transacción es del repositorio, `ddd-skills/ddd-repositorios-persistencia/references/transacciones-ts.md`).
58
+ * No tiene `findAll()` salvo que los tests del proyecto lo necesiten; si se añade, es solo comodidad de los tests y un repositorio real no lo tiene `[Fuera de los libros: convención de @archi-code/ddd]`. Nunca se usa en producción.
59
+ * Otro agregado tiene el suyo, escrito igual (`InMemoryProductRepository` con `Product.fromPrimitives` y `product.toPrimitives()`).
60
+
61
+ El repositorio en memoria no comprueba que la conversión esté completa: que `fromPrimitives` deshaga `toPrimitives` sin perder ningún campo lo prueba el spec del agregado (`Order.fromPrimitives(order.toPrimitives()).toPrimitives()` igual a `order.toPrimitives()`) `[Fuera de los libros: convención de @archi-code/ddd]`. La suite de contrato de abajo se ejecuta contra este repositorio y contra el adaptador real, así que el doble no puede apartarse del contrato. El repositorio no guarda los eventos publicados: los publica el caso de uso, y los tests los leen de `channel.sent` del `InMemoryEventChannel` `[Fuera de los libros: convención de @archi-code/ddd]`.
62
+
63
+ ## Tests de contrato compartidos
64
+
65
+ Un único conjunto de tests, en `test/support/order-repository-contract.ts` del proyecto, recibe una fábrica de repositorios y se ejecuta contra la referencia en memoria y contra el adaptador real `[Fuera de los libros]`:
66
+
67
+ ```ts
68
+ import { expect, it } from 'vitest';
69
+ import type { AggregateFinder, AggregateRemover, AggregateSaver } from '@archi-code/ddd/domain';
70
+ import { ConcurrencyException } from '@archi-code/ddd/exceptions';
71
+ import { FixedClock } from '@archi-code/ddd/infrastructure';
72
+ import {
73
+ CustomerId,
74
+ Order,
75
+ OrderId,
76
+ OrderLine,
77
+ OrderLines,
78
+ ShippingAddress,
79
+ } from '../../src/contexts/sales/domain/order/order';
80
+
81
+ const orderId = '9b2f6c1e-4d3a-4f8b-a1c2-3e4d5f6a7b8c';
82
+ const customerId = '3f1d2c4b-5a6e-4f70-8b9c-0d1e2f3a4b5c';
83
+
84
+ export function orderRepositoryContract(
85
+ make: () => Promise<AggregateFinder<Order> & AggregateSaver<Order> & AggregateRemover<Order>>,
86
+ ): void {
87
+ const clock = new FixedClock('2026-03-01T09:30:00Z');
88
+ const line = (sku: string) =>
89
+ OrderLine.fromPrimitives({ sku, quantity: 1, unitPrice: { amount: 4990, currency: 'EUR' } });
90
+ const placeOrder = () =>
91
+ Order.place(
92
+ new OrderId(orderId),
93
+ new CustomerId(customerId),
94
+ new OrderLines({ items: [line('TORCH-01')] }),
95
+ ShippingAddress.fromPrimitives({ street: 'Calle Mayor 1', city: 'Huelva', country: 'ES' }),
96
+ clock,
97
+ );
98
+
99
+ it('loads what it saved', async () => {
100
+ const orders = await make();
101
+ const order = placeOrder();
102
+ order.addLine(line('HELMET-02'), clock);
103
+ await orders.save(order);
104
+ expect((await orders.findById(order.id))?.toPrimitives()).toEqual(order.toPrimitives());
105
+ });
106
+
107
+ it('saves the same object twice', async () => {
108
+ const orders = await make();
109
+ const order = placeOrder();
110
+ await orders.save(order);
111
+ order.addLine(line('HELMET-02'), clock);
112
+ await expect(orders.save(order)).resolves.toBeUndefined();
113
+ });
114
+
115
+ it('refuses a stale save and a new order with a taken id', async () => {
116
+ const orders = await make();
117
+ await orders.save(placeOrder());
118
+ const first = await orders.findById(new OrderId(orderId));
119
+ const second = await orders.findById(new OrderId(orderId));
120
+ first!.addLine(line('HELMET-02'), clock);
121
+ await orders.save(first!);
122
+ second!.addLine(line('GLOVES-03'), clock);
123
+ await expect(orders.save(second!)).rejects.toThrow(ConcurrencyException);
124
+ await expect(orders.save(placeOrder())).rejects.toThrow(ConcurrencyException);
125
+ });
126
+
127
+ it('refuses to remove an order someone changed after it was loaded', async () => {
128
+ const orders = await make();
129
+ await orders.save(placeOrder());
130
+ const first = await orders.findById(new OrderId(orderId));
131
+ const second = await orders.findById(new OrderId(orderId));
132
+ await orders.save(first!);
133
+ await expect(orders.remove(second!)).rejects.toThrow(ConcurrencyException);
134
+ });
135
+
136
+ it('forgets a removed order', async () => {
137
+ const orders = await make();
138
+ const order = placeOrder();
139
+ await orders.save(order);
140
+ await orders.remove(order);
141
+ expect(await orders.findById(order.id)).toBeNull();
142
+ });
143
+ }
144
+ ```
145
+
146
+ El spec del adaptador lo ejecuta contra las dos implementaciones. El de MongoDB necesita un servidor real (Testcontainers, `mongodb-memory-server` o el de CI) `[herramienta fuera de los libros]`; cada fábrica usa una colección nueva para que los tests no compartan datos. En `test/contexts/sales/infrastructure/persistence/mongo-order-repository.spec.ts`, espejo de `src/`:
147
+
148
+ ```ts
149
+ import { randomUUID } from 'node:crypto';
150
+ import { MongoClient } from 'mongodb';
151
+ import { afterAll, beforeAll, describe } from 'vitest';
152
+ import { MongoOrderRepository } from '../../../../../src/contexts/sales/infrastructure/persistence/mongo-order-repository';
153
+ import type { OrderDocument } from '../../../../../src/contexts/sales/infrastructure/persistence/mongo-order-repository';
154
+ import { InMemoryOrderRepository } from '../../../../support/in-memory-order-repository';
155
+ import { orderRepositoryContract } from '../../../../support/order-repository-contract';
156
+
157
+ let client: MongoClient;
158
+
159
+ beforeAll(async () => {
160
+ client = await MongoClient.connect(process.env.MONGO_TEST_URL!);
161
+ });
162
+
163
+ afterAll(() => client.close());
164
+
165
+ const mongoOrders = async () =>
166
+ new MongoOrderRepository(
167
+ client.db('sales-test').collection<OrderDocument>(`orders-${randomUUID()}`),
168
+ );
169
+
170
+ describe('InMemoryOrderRepository', () =>
171
+ orderRepositoryContract(async () => new InMemoryOrderRepository()));
172
+ describe('MongoOrderRepository', () => orderRepositoryContract(mongoOrders));
173
+ ```
174
+
175
+ Los tests del repositorio real usan la implementación de producción; si no, no sabes si funciona [IDDD cap. 12].
176
+
177
+ Cada agregado tiene además un test de ida y vuelta de sus primitivos (`Order.fromPrimitives(order.toPrimitives()).toPrimitives()` igual a `order.toPrimitives()`), que no necesita repositorio (ver `ddd-skills/ddd-agregados/GUIA.md`).