@archi-code/ddd 3.0.0-alpha.1 → 3.0.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (282) hide show
  1. package/README.md +478 -376
  2. package/SKILL.md +10 -8
  3. package/ddd-skills/archi-code-ddd/imports.md +28 -18
  4. package/ddd-skills/archi-code-ddd/layers.md +68 -53
  5. package/ddd-skills/archi-code-ddd/patterns.md +50 -43
  6. package/ddd-skills/archi-code-ddd/what-not-to-do.md +25 -18
  7. package/ddd-skills/archi-code-ddd/where-each-class-goes.md +11 -10
  8. package/ddd-skills/ddd-agregados/GUIA.md +2 -2
  9. package/ddd-skills/ddd-agregados/references/consistencia-eventual.md +5 -6
  10. package/ddd-skills/ddd-agregados/references/fabricas.md +5 -8
  11. package/ddd-skills/ddd-agregados/references/implementacion-ts.md +22 -27
  12. package/ddd-skills/ddd-arquitectura/GUIA.md +4 -4
  13. package/ddd-skills/ddd-arquitectura/assets/dependency-cruiser.cjs +13 -6
  14. package/ddd-skills/ddd-arquitectura/references/estilos-adicionales.md +2 -2
  15. package/ddd-skills/ddd-arquitectura/references/estructura-hexagonal-ts.md +70 -16
  16. package/ddd-skills/ddd-auditoria-refactorizacion/GUIA.md +1 -1
  17. package/ddd-skills/ddd-auditoria-refactorizacion/references/ejemplo-auditoria.md +9 -11
  18. package/ddd-skills/ddd-auditoria-refactorizacion/references/lista-comprobacion.md +4 -4
  19. package/ddd-skills/ddd-auditoria-refactorizacion/references/plan-migracion.md +2 -2
  20. package/ddd-skills/ddd-context-mapping/GUIA.md +5 -5
  21. package/ddd-skills/ddd-context-mapping/references/anticorrupcion-ts.md +33 -18
  22. package/ddd-skills/ddd-context-mapping/references/estilos-integracion.md +4 -7
  23. package/ddd-skills/ddd-event-sourcing-cqrs/GUIA.md +2 -2
  24. package/ddd-skills/ddd-event-sourcing-cqrs/references/event-sourcing-ts.md +32 -27
  25. package/ddd-skills/ddd-event-sourcing-cqrs/references/proyecciones-cqrs-ts.md +7 -7
  26. package/ddd-skills/ddd-eventos-dominio/GUIA.md +3 -3
  27. package/ddd-skills/ddd-eventos-dominio/references/diseno-eventos.md +15 -16
  28. package/ddd-skills/ddd-eventos-dominio/references/ejemplo-ecommerce.md +11 -11
  29. package/ddd-skills/ddd-eventos-dominio/references/publicacion-ts.md +34 -31
  30. package/ddd-skills/ddd-repositorios-persistencia/GUIA.md +7 -7
  31. package/ddd-skills/ddd-repositorios-persistencia/references/implementacion-ts.md +50 -32
  32. package/ddd-skills/ddd-repositorios-persistencia/references/tests-repositorios.md +31 -17
  33. package/ddd-skills/ddd-repositorios-persistencia/references/transacciones-ts.md +21 -14
  34. package/ddd-skills/ddd-servicios-capa-aplicacion/GUIA.md +2 -2
  35. package/ddd-skills/ddd-servicios-capa-aplicacion/references/capa-aplicacion-ts.md +19 -19
  36. package/ddd-skills/ddd-servicios-capa-aplicacion/references/ejemplo-ecommerce.md +12 -15
  37. package/ddd-skills/ddd-servicios-capa-aplicacion/references/servicios-dominio-ts.md +5 -6
  38. package/ddd-skills/ddd-value-objects-entidades/GUIA.md +3 -3
  39. package/ddd-skills/ddd-value-objects-entidades/references/entidades-ts.md +7 -9
  40. package/ddd-skills/ddd-value-objects-entidades/references/persistencia-valores.md +5 -5
  41. package/ddd-skills/ddd-value-objects-entidades/references/validacion.md +9 -9
  42. package/ddd-skills/ddd-value-objects-entidades/references/value-objects-ts.md +2 -3
  43. package/dist/cjs/application/event/listens-to.js +4 -4
  44. package/dist/cjs/{exceptions/application → application/exceptions}/aggregate-not-found.exception.d.ts +1 -1
  45. package/dist/cjs/{exceptions/application → application/exceptions}/aggregate-not-found.exception.js +4 -4
  46. package/dist/cjs/application/exceptions/application-exception-code.d.ts +7 -0
  47. package/dist/cjs/application/exceptions/application-exception-code.js +11 -0
  48. package/dist/cjs/application/exceptions/application.exception.d.ts +6 -0
  49. package/dist/cjs/application/exceptions/application.exception.js +16 -0
  50. package/dist/cjs/{exceptions/application → application/exceptions}/concurrency.exception.d.ts +1 -1
  51. package/dist/cjs/{exceptions/application → application/exceptions}/concurrency.exception.js +4 -4
  52. package/dist/cjs/{exceptions/application → application/exceptions}/not-allowed.exception.d.ts +1 -1
  53. package/dist/cjs/{exceptions/application → application/exceptions}/not-allowed.exception.js +4 -4
  54. package/dist/cjs/application/exceptions/unexpected-error.exception.d.ts +5 -0
  55. package/dist/cjs/application/exceptions/unexpected-error.exception.js +17 -0
  56. package/dist/cjs/application/index.d.ts +7 -0
  57. package/dist/cjs/application/index.js +15 -1
  58. package/dist/cjs/application/use-case/use-case.js +3 -2
  59. package/dist/{esm/domain/type/value-objects/value-objects.d.ts → cjs/application/value-object-builder/value-object-builder.d.ts} +3 -3
  60. package/dist/cjs/application/value-object-builder/value-object-builder.js +51 -0
  61. package/dist/cjs/domain/aggregate/aggregate-root.d.ts +0 -5
  62. package/dist/cjs/domain/aggregate/aggregate-root.js +4 -20
  63. package/dist/cjs/domain/entity/entity.js +2 -2
  64. package/dist/cjs/domain/event/event-base.d.ts +3 -9
  65. package/dist/cjs/domain/event/event-base.js +8 -38
  66. package/dist/cjs/domain/exceptions/domain-exception-code.d.ts +7 -0
  67. package/dist/cjs/domain/exceptions/domain-exception-code.js +11 -0
  68. package/dist/cjs/domain/exceptions/domain.exception.d.ts +6 -0
  69. package/dist/cjs/domain/exceptions/domain.exception.js +16 -0
  70. package/dist/cjs/domain/exceptions/invalid-event-definition.exception.d.ts +4 -0
  71. package/dist/cjs/domain/exceptions/invalid-event-definition.exception.js +19 -0
  72. package/dist/cjs/{exceptions/domain → domain/exceptions}/required-value.exception.js +5 -6
  73. package/dist/cjs/{exceptions/domain → domain/exceptions}/type-primitive.exception.js +5 -6
  74. package/dist/cjs/{exceptions/domain → domain/exceptions}/validation.exception.js +5 -5
  75. package/dist/cjs/domain/index.d.ts +8 -1
  76. package/dist/cjs/domain/index.js +14 -2
  77. package/dist/cjs/domain/primitive/primitive-types.d.ts +1 -3
  78. package/dist/cjs/domain/type/abstract-type.d.ts +6 -8
  79. package/dist/cjs/domain/type/abstract-type.js +15 -23
  80. package/dist/cjs/domain/type/array-type.d.ts +2 -1
  81. package/dist/cjs/domain/type/array-type.js +17 -18
  82. package/dist/cjs/domain/type/boolean-type.js +2 -2
  83. package/dist/cjs/domain/type/composite/composite-brand.d.ts +4 -0
  84. package/dist/cjs/domain/type/composite/composite-brand.js +15 -0
  85. package/dist/cjs/domain/type/composite/composite-field.d.ts +7 -8
  86. package/dist/cjs/domain/type/composite/composite-field.js +70 -47
  87. package/dist/cjs/domain/type/composite/composite-issues.d.ts +6 -0
  88. package/dist/cjs/domain/type/composite/composite-issues.js +35 -0
  89. package/dist/cjs/domain/type/composite/composite.js +15 -19
  90. package/dist/cjs/domain/type/date-type.d.ts +3 -2
  91. package/dist/cjs/domain/type/date-type.js +42 -7
  92. package/dist/cjs/domain/type/enum-type.js +2 -2
  93. package/dist/cjs/domain/type/id-type.d.ts +1 -1
  94. package/dist/cjs/domain/type/id-type.js +4 -4
  95. package/dist/cjs/domain/type/index.d.ts +2 -4
  96. package/dist/cjs/domain/type/index.js +3 -7
  97. package/dist/cjs/domain/type/json-type.d.ts +4 -0
  98. package/dist/cjs/domain/type/json-type.js +33 -4
  99. package/dist/cjs/domain/type/number-type.js +2 -2
  100. package/dist/cjs/domain/type/required/required-mark.d.ts +0 -1
  101. package/dist/cjs/domain/type/string-type.js +2 -2
  102. package/dist/cjs/domain/type/uuid-type.js +2 -2
  103. package/dist/cjs/infrastructure/event/domain-event-bus.js +9 -9
  104. package/dist/cjs/infrastructure/event/event-registry.js +9 -9
  105. package/dist/cjs/infrastructure/event/in-memory/emitter.js +2 -2
  106. package/dist/cjs/infrastructure/event/in-memory/in-memory-event-channel.d.ts +1 -1
  107. package/dist/cjs/infrastructure/event/in-memory/in-memory-event-channel.js +6 -7
  108. package/dist/cjs/{exceptions/infrastructure → infrastructure/exceptions}/event-delivery.exception.js +4 -4
  109. package/dist/cjs/infrastructure/exceptions/infrastructure-exception-code.d.ts +6 -0
  110. package/dist/cjs/infrastructure/exceptions/infrastructure-exception-code.js +10 -0
  111. package/dist/cjs/infrastructure/exceptions/infrastructure.exception.d.ts +6 -0
  112. package/dist/cjs/infrastructure/exceptions/infrastructure.exception.js +16 -0
  113. package/dist/cjs/{exceptions/infrastructure → infrastructure/exceptions}/internal-error.exception.d.ts +1 -1
  114. package/dist/cjs/{exceptions/infrastructure → infrastructure/exceptions}/internal-error.exception.js +4 -4
  115. package/dist/cjs/infrastructure/exceptions/kind/exception-classifier.d.ts +4 -0
  116. package/dist/cjs/infrastructure/exceptions/kind/exception-classifier.js +34 -0
  117. package/dist/cjs/infrastructure/exceptions/kind/exception-kind.d.ts +9 -0
  118. package/dist/cjs/infrastructure/exceptions/kind/exception-kind.js +13 -0
  119. package/dist/cjs/{exceptions/infrastructure → infrastructure/exceptions}/unknown-event.exception.js +4 -4
  120. package/dist/cjs/infrastructure/index.d.ts +7 -2
  121. package/dist/cjs/infrastructure/index.js +15 -3
  122. package/dist/cjs/infrastructure/logging/console-logger.js +2 -2
  123. package/dist/{esm → cjs/shared}/exceptions/abstract.exception.d.ts +6 -4
  124. package/dist/cjs/{exceptions → shared/exceptions}/abstract.exception.js +9 -16
  125. package/dist/cjs/shared/index.d.ts +8 -0
  126. package/dist/cjs/shared/index.js +13 -0
  127. package/dist/cjs/shared/issue-collection-error.d.ts +9 -0
  128. package/dist/cjs/shared/issue-collection-error.js +11 -0
  129. package/dist/cjs/shared/issue-collector.d.ts +8 -0
  130. package/dist/cjs/shared/issue-collector.js +41 -0
  131. package/dist/esm/application/event/listens-to.js +1 -1
  132. package/dist/esm/{exceptions/application → application/exceptions}/aggregate-not-found.exception.d.ts +1 -1
  133. package/dist/esm/application/exceptions/aggregate-not-found.exception.js +13 -0
  134. package/dist/esm/application/exceptions/application-exception-code.d.ts +7 -0
  135. package/dist/esm/application/exceptions/application-exception-code.js +8 -0
  136. package/dist/esm/application/exceptions/application.exception.d.ts +6 -0
  137. package/dist/esm/application/exceptions/application.exception.js +12 -0
  138. package/dist/esm/{exceptions/application → application/exceptions}/concurrency.exception.d.ts +1 -1
  139. package/dist/esm/{exceptions/application → application/exceptions}/concurrency.exception.js +4 -4
  140. package/dist/esm/{exceptions/application → application/exceptions}/not-allowed.exception.d.ts +1 -1
  141. package/dist/esm/application/exceptions/not-allowed.exception.js +13 -0
  142. package/dist/esm/application/exceptions/unexpected-error.exception.d.ts +5 -0
  143. package/dist/esm/application/exceptions/unexpected-error.exception.js +13 -0
  144. package/dist/esm/application/index.d.ts +7 -0
  145. package/dist/esm/application/index.js +7 -0
  146. package/dist/esm/application/use-case/use-case.js +3 -2
  147. package/dist/{cjs/domain/type/value-objects/value-objects.d.ts → esm/application/value-object-builder/value-object-builder.d.ts} +3 -3
  148. package/dist/esm/application/value-object-builder/value-object-builder.js +47 -0
  149. package/dist/esm/domain/aggregate/aggregate-root.d.ts +0 -5
  150. package/dist/esm/domain/aggregate/aggregate-root.js +4 -20
  151. package/dist/esm/domain/entity/entity.js +1 -1
  152. package/dist/esm/domain/event/event-base.d.ts +3 -9
  153. package/dist/esm/domain/event/event-base.js +8 -38
  154. package/dist/esm/domain/exceptions/domain-exception-code.d.ts +7 -0
  155. package/dist/esm/domain/exceptions/domain-exception-code.js +8 -0
  156. package/dist/esm/domain/exceptions/domain.exception.d.ts +6 -0
  157. package/dist/esm/domain/exceptions/domain.exception.js +12 -0
  158. package/dist/esm/domain/exceptions/invalid-event-definition.exception.d.ts +4 -0
  159. package/dist/esm/domain/exceptions/invalid-event-definition.exception.js +15 -0
  160. package/dist/esm/{exceptions/domain → domain/exceptions}/required-value.exception.js +4 -5
  161. package/dist/esm/{exceptions/domain → domain/exceptions}/type-primitive.exception.js +4 -5
  162. package/dist/esm/{exceptions/domain → domain/exceptions}/validation.exception.js +5 -5
  163. package/dist/esm/domain/index.d.ts +7 -0
  164. package/dist/esm/domain/index.js +6 -0
  165. package/dist/esm/domain/primitive/primitive-types.d.ts +1 -3
  166. package/dist/esm/domain/type/abstract-type.d.ts +6 -8
  167. package/dist/esm/domain/type/abstract-type.js +14 -22
  168. package/dist/esm/domain/type/array-type.d.ts +2 -1
  169. package/dist/esm/domain/type/array-type.js +16 -17
  170. package/dist/esm/domain/type/boolean-type.js +1 -1
  171. package/dist/esm/domain/type/composite/composite-brand.d.ts +4 -0
  172. package/dist/esm/domain/type/composite/composite-brand.js +11 -0
  173. package/dist/esm/domain/type/composite/composite-field.d.ts +7 -8
  174. package/dist/esm/domain/type/composite/composite-field.js +69 -46
  175. package/dist/esm/domain/type/composite/composite-issues.d.ts +6 -0
  176. package/dist/esm/domain/type/composite/composite-issues.js +31 -0
  177. package/dist/esm/domain/type/composite/composite.js +13 -17
  178. package/dist/esm/domain/type/date-type.d.ts +3 -2
  179. package/dist/esm/domain/type/date-type.js +39 -4
  180. package/dist/esm/domain/type/enum-type.js +1 -1
  181. package/dist/esm/domain/type/id-type.d.ts +1 -1
  182. package/dist/esm/domain/type/id-type.js +2 -2
  183. package/dist/esm/domain/type/index.d.ts +2 -4
  184. package/dist/esm/domain/type/index.js +1 -3
  185. package/dist/esm/domain/type/json-type.d.ts +4 -0
  186. package/dist/esm/domain/type/json-type.js +32 -3
  187. package/dist/esm/domain/type/number-type.js +1 -1
  188. package/dist/esm/domain/type/required/required-mark.d.ts +0 -1
  189. package/dist/esm/domain/type/string-type.js +1 -1
  190. package/dist/esm/domain/type/uuid-type.js +1 -1
  191. package/dist/esm/infrastructure/event/domain-event-bus.js +5 -5
  192. package/dist/esm/infrastructure/event/event-registry.js +4 -4
  193. package/dist/esm/infrastructure/event/in-memory/emitter.js +1 -1
  194. package/dist/esm/infrastructure/event/in-memory/in-memory-event-channel.d.ts +1 -1
  195. package/dist/esm/infrastructure/event/in-memory/in-memory-event-channel.js +5 -6
  196. package/dist/esm/{exceptions/infrastructure → infrastructure/exceptions}/event-delivery.exception.js +4 -4
  197. package/dist/esm/infrastructure/exceptions/infrastructure-exception-code.d.ts +6 -0
  198. package/dist/esm/infrastructure/exceptions/infrastructure-exception-code.js +7 -0
  199. package/dist/esm/infrastructure/exceptions/infrastructure.exception.d.ts +6 -0
  200. package/dist/esm/infrastructure/exceptions/infrastructure.exception.js +12 -0
  201. package/dist/esm/{exceptions/infrastructure → infrastructure/exceptions}/internal-error.exception.d.ts +1 -1
  202. package/dist/esm/infrastructure/exceptions/internal-error.exception.js +13 -0
  203. package/dist/esm/infrastructure/exceptions/kind/exception-classifier.d.ts +4 -0
  204. package/dist/esm/infrastructure/exceptions/kind/exception-classifier.js +30 -0
  205. package/dist/esm/infrastructure/exceptions/kind/exception-kind.d.ts +9 -0
  206. package/dist/esm/infrastructure/exceptions/kind/exception-kind.js +10 -0
  207. package/dist/esm/{exceptions/infrastructure → infrastructure/exceptions}/unknown-event.exception.js +4 -4
  208. package/dist/esm/infrastructure/index.d.ts +7 -2
  209. package/dist/esm/infrastructure/index.js +7 -1
  210. package/dist/esm/infrastructure/logging/console-logger.js +1 -1
  211. package/dist/{cjs → esm/shared}/exceptions/abstract.exception.d.ts +6 -4
  212. package/dist/esm/{exceptions → shared/exceptions}/abstract.exception.js +9 -16
  213. package/dist/esm/shared/index.d.ts +8 -0
  214. package/dist/esm/shared/index.js +5 -0
  215. package/dist/esm/shared/issue-collection-error.d.ts +9 -0
  216. package/dist/esm/shared/issue-collection-error.js +7 -0
  217. package/dist/esm/shared/issue-collector.d.ts +8 -0
  218. package/dist/esm/shared/issue-collector.js +37 -0
  219. package/package.json +7 -18
  220. package/dist/cjs/domain/type/equality/value-equality.d.ts +0 -7
  221. package/dist/cjs/domain/type/equality/value-equality.js +0 -41
  222. package/dist/cjs/domain/type/immutable/immutable-date.d.ts +0 -4
  223. package/dist/cjs/domain/type/immutable/immutable-date.js +0 -37
  224. package/dist/cjs/domain/type/immutable/immutable.d.ts +0 -4
  225. package/dist/cjs/domain/type/immutable/immutable.js +0 -19
  226. package/dist/cjs/domain/type/value-objects/value-objects.js +0 -70
  227. package/dist/cjs/exceptions/application/application.exception.d.ts +0 -6
  228. package/dist/cjs/exceptions/application/application.exception.js +0 -17
  229. package/dist/cjs/exceptions/domain/domain.exception.d.ts +0 -6
  230. package/dist/cjs/exceptions/domain/domain.exception.js +0 -17
  231. package/dist/cjs/exceptions/exception-code.d.ts +0 -15
  232. package/dist/cjs/exceptions/exception-code.js +0 -19
  233. package/dist/cjs/exceptions/index.d.ts +0 -14
  234. package/dist/cjs/exceptions/index.js +0 -30
  235. package/dist/cjs/exceptions/infrastructure/infrastructure.exception.d.ts +0 -6
  236. package/dist/cjs/exceptions/infrastructure/infrastructure.exception.js +0 -17
  237. package/dist/cjs/infrastructure/http/http-error-mapper.d.ts +0 -23
  238. package/dist/cjs/infrastructure/http/http-error-mapper.js +0 -63
  239. package/dist/esm/domain/type/equality/value-equality.d.ts +0 -7
  240. package/dist/esm/domain/type/equality/value-equality.js +0 -37
  241. package/dist/esm/domain/type/immutable/immutable-date.d.ts +0 -4
  242. package/dist/esm/domain/type/immutable/immutable-date.js +0 -33
  243. package/dist/esm/domain/type/immutable/immutable.d.ts +0 -4
  244. package/dist/esm/domain/type/immutable/immutable.js +0 -15
  245. package/dist/esm/domain/type/value-objects/value-objects.js +0 -66
  246. package/dist/esm/exceptions/application/aggregate-not-found.exception.js +0 -13
  247. package/dist/esm/exceptions/application/application.exception.d.ts +0 -6
  248. package/dist/esm/exceptions/application/application.exception.js +0 -13
  249. package/dist/esm/exceptions/application/not-allowed.exception.js +0 -13
  250. package/dist/esm/exceptions/domain/domain.exception.d.ts +0 -6
  251. package/dist/esm/exceptions/domain/domain.exception.js +0 -13
  252. package/dist/esm/exceptions/exception-code.d.ts +0 -15
  253. package/dist/esm/exceptions/exception-code.js +0 -16
  254. package/dist/esm/exceptions/index.d.ts +0 -14
  255. package/dist/esm/exceptions/index.js +0 -14
  256. package/dist/esm/exceptions/infrastructure/infrastructure.exception.d.ts +0 -6
  257. package/dist/esm/exceptions/infrastructure/infrastructure.exception.js +0 -13
  258. package/dist/esm/exceptions/infrastructure/internal-error.exception.js +0 -13
  259. package/dist/esm/infrastructure/http/http-error-mapper.d.ts +0 -23
  260. package/dist/esm/infrastructure/http/http-error-mapper.js +0 -59
  261. /package/dist/cjs/{exceptions/domain → domain/exceptions}/required-value.exception.d.ts +0 -0
  262. /package/dist/cjs/{exceptions/domain → domain/exceptions}/type-primitive.exception.d.ts +0 -0
  263. /package/dist/cjs/{exceptions/domain → domain/exceptions}/validation.exception.d.ts +0 -0
  264. /package/dist/cjs/domain/type/required/{required.d.ts → required-type.d.ts} +0 -0
  265. /package/dist/cjs/domain/type/required/{required.js → required-type.js} +0 -0
  266. /package/dist/cjs/{exceptions/infrastructure → infrastructure/exceptions}/event-delivery.exception.d.ts +0 -0
  267. /package/dist/cjs/{exceptions/infrastructure → infrastructure/exceptions}/unknown-event.exception.d.ts +0 -0
  268. /package/dist/cjs/{exceptions → shared/exceptions}/brand/exception-brand.d.ts +0 -0
  269. /package/dist/cjs/{exceptions → shared/exceptions}/brand/exception-brand.js +0 -0
  270. /package/dist/cjs/{exceptions → shared/exceptions}/received/received-value.d.ts +0 -0
  271. /package/dist/cjs/{exceptions → shared/exceptions}/received/received-value.js +0 -0
  272. /package/dist/esm/{exceptions/domain → domain/exceptions}/required-value.exception.d.ts +0 -0
  273. /package/dist/esm/{exceptions/domain → domain/exceptions}/type-primitive.exception.d.ts +0 -0
  274. /package/dist/esm/{exceptions/domain → domain/exceptions}/validation.exception.d.ts +0 -0
  275. /package/dist/esm/domain/type/required/{required.d.ts → required-type.d.ts} +0 -0
  276. /package/dist/esm/domain/type/required/{required.js → required-type.js} +0 -0
  277. /package/dist/esm/{exceptions/infrastructure → infrastructure/exceptions}/event-delivery.exception.d.ts +0 -0
  278. /package/dist/esm/{exceptions/infrastructure → infrastructure/exceptions}/unknown-event.exception.d.ts +0 -0
  279. /package/dist/esm/{exceptions → shared/exceptions}/brand/exception-brand.d.ts +0 -0
  280. /package/dist/esm/{exceptions → shared/exceptions}/brand/exception-brand.js +0 -0
  281. /package/dist/esm/{exceptions → shared/exceptions}/received/received-value.d.ts +0 -0
  282. /package/dist/esm/{exceptions → shared/exceptions}/received/received-value.js +0 -0
package/SKILL.md CHANGED
@@ -1,19 +1,21 @@
1
1
  ---
2
2
  name: archi-code-ddd
3
- description: The single entry point for Domain-Driven Design with @archi-code/ddd. It routes every DDD question to the guide suite shipped in the package (integration between contexts, architecture, value objects and entities, aggregates, domain events, repositories, application services, event sourcing and CQRS, audits) and gives the rules, patterns and common mistakes for TypeScript code with @archi-code/ddd - where each class goes and what it may import, value objects, Composite, Specification, Entity, AggregateRoot, EventBase, repository roles, exceptions, UseCase, the event contract (EventListener, @ListensTo, DomainEventBus, event channels), ports, persistence owned by the repository and HttpErrorMapper. Use it whenever someone applies DDD, designs or reviews a domain model, integrates contexts, writes or changes value objects, aggregates, events, use cases, listeners, repositories or adapters, decides which layer a class belongs to, or whenever the code imports @archi-code/ddd.
3
+ description: The single entry point for Domain-Driven Design with @archi-code/ddd. It routes every DDD question to the guide suite shipped in the package (integration between contexts, architecture, value objects and entities, aggregates, domain events, repositories, application services, event sourcing and CQRS, audits) and gives the rules, patterns and common mistakes for TypeScript code with @archi-code/ddd - where each class goes and what it may import, value objects, Composite, Specification, Entity, AggregateRoot, EventBase, repository roles, exceptions, UseCase, the event contract (EventListener, @ListensTo, DomainEventBus, event channels), ports, persistence owned by the repository and ExceptionClassifier. Use it whenever someone applies DDD, designs or reviews a domain model, integrates contexts, writes or changes value objects, aggregates, events, use cases, listeners, repositories or adapters, decides which layer a class belongs to, or whenever the code imports @archi-code/ddd.
4
4
  ---
5
5
 
6
6
  # archi-code-ddd
7
7
 
8
8
  `@archi-code/ddd` is archi-code's Domain-Driven Design package for TypeScript, one entry point per
9
- layer: `@archi-code/ddd/exceptions` (the exceptions every layer throws), `@archi-code/ddd/domain`
10
- (value objects, `Composite`, `Immutable`, `Specification`, `Entity`, `AggregateRoot`, `EventBase`
11
- and the repository contracts `AggregateFinder`, `AggregateSaver`, `AggregateRemover`),
12
- `@archi-code/ddd/application` (`UseCase`, the event contract to listen, `EventListener` and
13
- `@ListensTo`, and to publish, `EventBus`, and the ports `Clock`, `Logger`) and
14
- `@archi-code/ddd/infrastructure` (aggregate mapper for your repositories, the
9
+ layer, each with the exceptions of its layer: `@archi-code/ddd/domain` (value objects, `Composite`,
10
+ `Specification`, `Entity`, `AggregateRoot`, `EventBase`, the repository contracts
11
+ `AggregateFinder`, `AggregateSaver`, `AggregateRemover`, and `DomainException`,
12
+ `ValidationException`…), `@archi-code/ddd/application` (`UseCase`, the event contract to listen,
13
+ `EventListener` and `@ListensTo`, and to publish, `EventBus`, the ports `Clock`, `Logger`, and
14
+ `AggregateNotFoundException`, `NotAllowedException`…) and `@archi-code/ddd/infrastructure` (the
15
15
  event wiring `DomainEventBus`, `EventRegistry` and the `EventChannel` port, in-memory event
16
- channel, HTTP error mapper, clocks, loggers).
16
+ channel, clocks, loggers, `InternalErrorException`…). `@archi-code/ddd/infrastructure` also has
17
+ `ExceptionClassifier`, which gives every entry point (HTTP, gRPC, queue, CLI) the `ExceptionKind`
18
+ of an error.
17
19
 
18
20
  ## Full API
19
21
 
@@ -11,34 +11,24 @@ import {
11
11
  Composite,
12
12
  CreatedAt,
13
13
  DateType,
14
+ DomainException,
15
+ DomainExceptionCode,
14
16
  Entity,
15
17
  EnumType,
16
18
  EventBase,
17
19
  IdType,
20
+ InvalidEventDefinitionException,
18
21
  JsonType,
19
22
  NumberType,
20
23
  Required,
24
+ RequiredValueException,
21
25
  Specification,
22
26
  StringType,
27
+ TypePrimitiveException,
23
28
  UpdatedAt,
24
29
  UuidType,
25
- ValueObjects,
26
- } from '@archi-code/ddd/domain';
27
- import {
28
- AggregateNotFoundException,
29
- ApplicationException,
30
- ConcurrencyException,
31
- DomainException,
32
- ExceptionCode,
33
- InfrastructureException,
34
- InternalErrorException,
35
- NotAllowedException,
36
- RequiredValueException,
37
- TypePrimitiveException,
38
- UnknownEventException,
39
30
  ValidationException,
40
- } from '@archi-code/ddd/exceptions';
41
- import type { ValidationIssue } from '@archi-code/ddd/exceptions';
31
+ } from '@archi-code/ddd/domain';
42
32
  import type {
43
33
  AggregateFinder,
44
34
  AggregateRemover,
@@ -49,8 +39,20 @@ import type {
49
39
  PrimitiveTypes,
50
40
  SatisfiedBy,
51
41
  SerializedEvent,
42
+ ValidationIssue,
52
43
  } from '@archi-code/ddd/domain';
53
- import { EventListener, ListensTo, UseCase } from '@archi-code/ddd/application';
44
+ import {
45
+ AggregateNotFoundException,
46
+ ApplicationException,
47
+ ApplicationExceptionCode,
48
+ ConcurrencyException,
49
+ EventListener,
50
+ ListensTo,
51
+ NotAllowedException,
52
+ UnexpectedErrorException,
53
+ UseCase,
54
+ ValueObjectBuilder,
55
+ } from '@archi-code/ddd/application';
54
56
  import type {
55
57
  Clock,
56
58
  EventBus,
@@ -63,12 +65,17 @@ import {
63
65
  ConsoleLogger,
64
66
  DomainEventBus,
65
67
  EventRegistry,
68
+ ExceptionClassifier,
69
+ ExceptionKind,
66
70
  FixedClock,
67
- HttpErrorMapper,
71
+ InfrastructureException,
72
+ InfrastructureExceptionCode,
68
73
  InMemoryEventChannel,
69
74
  InMemoryLogger,
75
+ InternalErrorException,
70
76
  SilentLogger,
71
77
  SystemClock,
78
+ UnknownEventException,
72
79
  } from '@archi-code/ddd/infrastructure';
73
80
  import type {
74
81
  ChannelHandler,
@@ -79,6 +86,9 @@ import type {
79
86
  import { Validations } from '@archi-code/validation';
80
87
  ```
81
88
 
89
+ Each layer exports its own exceptions and its own code enum; there is no `/exceptions` subpath
90
+ and the common base class of the exceptions is internal.
91
+
82
92
  ```js
83
93
  const { Required, StringType } = require('@archi-code/ddd/domain');
84
94
  ```
@@ -6,8 +6,9 @@ Part of the `archi-code-ddd` skill: read it only when that skill routes here.
6
6
  de @archi-code/ddd]`: the books build every value object with a value and model an optional one
7
7
  as a field that may be `null` (`reservation: ReservationId | null`) [IDDD ch. 5 and 6]; both
8
8
  forms work here. `Required(XType)` makes them
9
- required: the constructor demands `T` and, if it receives an empty value (`null`, `undefined` or a
10
- blank string), throws `RequiredValueException` with the class name. `Required(...)` also declares
9
+ required: the constructor demands `T` and, if it receives an empty value (`null` or `undefined`),
10
+ throws `RequiredValueException` with the class name. A blank text is a value: `StringType` keeps
11
+ it and the other types refuse it with `TypePrimitiveException`. `Required(...)` also declares
11
12
  the `isRequired` validation. An id is always required (see the aggregate root below).
12
13
  - A domain VO is always a named subclass (`class Email extends ...`): the name appears in the error
13
14
  messages, and `equals` compares the class too (`BillingCountry('ES')` is not a
@@ -18,15 +19,18 @@ Part of the `archi-code-ddd` skill: read it only when that skill routes here.
18
19
  (`errors: [{ field: null, code: 'isEmail', message: '<Class>: <message>' }]`; `code` is the
19
20
  validator or custom rule name, or the code of the exception). There is no `assertValid()`;
20
21
  `validate()` only re-runs the rules and is always ok on a built VO.
21
- - **Report every field, never only the first.** Build the value objects of an input with
22
- `ValueObjects.build({ field: () => new X(input.field), … })`: it runs every factory and throws one
22
+ - **Report every field, never only the first.** In `toCommand`, build the value objects of an
23
+ input with `ValueObjectBuilder.build({ field: () => new X(input.field), … })` from
24
+ `@archi-code/ddd/application` (the domain never uses it): it runs every factory and throws one
23
25
  `ValidationException` whose `errors` carry the `field` and `code` of each problem (required
24
26
  `DOM004`, conversion `DOM003`, a rule name, or the code of any `DomainException`, such as your
25
- `ORD003`); `ValueObjects.buildList(list, (item) => …)` names list items by position
27
+ `ORD003`); `ValueObjectBuilder.buildList(list, (item) => …)` names list items by position
26
28
  (`lines.1.quantity`). It returns the command typed field by field. Factories are synchronous (one
27
29
  that returns a Promise does not compile and is refused at run time with
28
- `InternalErrorException`), and an error that is not a `DomainException` is a bug that wins over the
30
+ `UnexpectedErrorException`), and an error that is not a `DomainException` is a bug that wins over the
29
31
  validation errors already collected.
32
+ - An `ArrayType` receives items that are already valid: it does not collect item errors and lets
33
+ the error of the first invalid item through as the item throws it.
30
34
  - **Which layer validates what:** the input adapter checks the shape and format of the request (it
31
35
  parses, a field is a string, an empty field or a length for a good user experience [IDDD ch. 4]),
32
36
  never a business rule, and that never replaces the value object; value objects check each value (built in
@@ -39,9 +43,9 @@ Part of the `archi-code-ddd` skill: read it only when that skill routes here.
39
43
  - Values are **immutable**: `_value` is `readonly`; to change a value, build a new VO.
40
44
  `ArrayType.withItem/withoutItem` return a new instance; `UpdatedAt` is replaced with
41
45
  `UpdatedAt.now()`.
42
- - `toString()` is a method, `toJSON()` returns the plain value, `equals(other)` compares class and
43
- value (dates by instant, arrays and objects by content). Template literals and `JSON.stringify`
44
- work on VOs.
46
+ - `toString()` is a method, `equals(other)` compares class and
47
+ value (dates by instant, arrays and objects by content). Template literals work on VOs;
48
+ `JSON.stringify` does not unwrap them: serialize `value` or the aggregate's `toPrimitives()`.
45
49
  - Dates: `DateType` accepts `Date`, ISO dates (`'2024-01-31'`, UTC midnight) and ISO date-times
46
50
  **with** a zone (`Z` or offset); a date-time without a zone throws `TypePrimitiveException`.
47
51
  `UuidType`/`IdType` store lowercase. New ids: `OrderId.generate()` (an `OrderId`). Every id class
@@ -107,56 +111,60 @@ Part of the `archi-code-ddd` skill: read it only when that skill routes here.
107
111
  from its primitive and records nothing.
108
112
  4. `toPrimitives(): PrimitiveTypes<Order>` returns only plain data (`vo.value`, child primitives),
109
113
  never a value object.
110
- 5. Repositories only call `order.toPrimitives()` and `Order.fromPrimitives(row)` (plus
111
- `.restoreVersion(version)`); they never read the aggregate's fields. There is no mapper.
114
+ 5. Repositories only call `order.toPrimitives()` and `Order.fromPrimitives(row)`; they never
115
+ read the aggregate's fields. There is no mapper.
112
116
  6. One round-trip test per aggregate: `Order.fromPrimitives(order.toPrimitives()).toPrimitives()`
113
117
  equals `order.toPrimitives()`.
114
118
  The compiler rejects public raw state (`PrimitiveTypes` gives `never`); nothing checks private
115
119
  state at run time: a private field left out of `toPrimitives()` compiles and is lost when
116
120
  saving, so rule 2 and the round-trip test of rule 6 are the aggregate's responsibility. An enum value object stores its option (`PrimitiveType` is the enum type),
117
121
  so `new OrderStatus(primitives.status)` needs no cast.
118
- - Aggregates extend `AggregateRoot<Id, Event>` and call `super(id)`. **Every aggregate has a
119
- `version`** for optimistic concurrency: `0` when new, the stored one when loaded, `+1` after each
120
- save. The aggregate never declares, stores nor maps it (it is not a field nor in
121
- `PrimitiveTypes`); only repositories touch it.
122
- - **Every repository checks the version** (contract): load with
123
- `Order.fromPrimitives(row).restoreVersion(storedVersion)`; save a new aggregate (`version === 0`) as version 1
124
- and treat an existing id as a conflict; save an existing one only where the stored version still
125
- equals `aggregate.version`, storing `aggregate.version + 1` next to (never inside) the
126
- primitives; when nothing was written throw
127
- `ConcurrencyException('Order', id, aggregate.version)` (HTTP 409); after a write
128
- call `aggregate.markPersisted()`. Keep the driver error: `ConcurrencyException(…, { cause })`.
122
+ - Aggregates extend `AggregateRoot<Id, Event>` and call `super(id)`. The aggregate carries **no
123
+ version**: it never declares, stores nor maps one; optimistic concurrency is the repository's.
124
+ - **Every repository checks the version** (contract): it remembers the version of each aggregate
125
+ object it loaded or saved in a private `WeakMap<Order, number>` (`versions`); load with
126
+ `Order.fromPrimitives(row)` and `versions.set(order, row.version)`; on save take
127
+ `versions.get(order) ?? 0`, save a new aggregate (`0`) as version 1 and treat an existing id as a
128
+ conflict; save an existing one only where the stored version still equals the remembered one,
129
+ storing one more next to (never inside) the primitives; when nothing was written throw
130
+ `ConcurrencyException('Order', id, version)` (`ExceptionKind.Conflict`); after a write
131
+ remember `version + 1`. Keep the driver error: `ConcurrencyException(…, { cause })`.
129
132
  Removing is a change too: `remove(aggregate)` receives the loaded aggregate and deletes only
130
- where the stored version still equals `aggregate.version`, or throws `ConcurrencyException`
133
+ where the stored version still equals the remembered one, or throws `ConcurrencyException`
131
134
  (someone changed or removed it after it was loaded) [IDDD ch. 10].
132
135
  After a conflict, the use case may repeat its own work when the command is safe against the new
133
136
  state [IDDD ch. 10; IDDD appendix A]: a loop in `handle` where each attempt loads the aggregate
134
137
  again and saves it again (a few attempts, then rethrow); otherwise the conflict reaches the user
135
138
  (409). A use case run by a listener does not retry by itself: the channel's redelivery tries
136
139
  again.
137
- - `record(event)` is `protected` and deep-freezes the event (facts never change [IDDD ch. 8]); `pullDomainEvents()` returns the events (typed `Event[]`) in order
138
- and empties the list. Pull them after persisting, to publish. `DomainEventBus.publish` does not freeze
139
- them again: it only sends the `toJSON()` of each event to the channel.
140
+ - `record(event)` is `protected` and keeps the event as it is (facts never change [IDDD ch. 8]: the
141
+ event declares its fields `readonly`); `pullDomainEvents()` returns the events (typed `Event[]`) in order
142
+ and empties the list. Pull them after persisting, to publish. `DomainEventBus.publish` only sends a
143
+ plain copy of each event (`{ ...event }`) to the channel.
140
144
  - Events extend `EventBase`, declare `static readonly eventName` with their class name, a fact in
141
145
  the past tense (`'OrderPlaced'`) [IDDD ch. 8] (a published contract is named
142
146
  `<context>.<module>.<Event>`, `sales.order.OrderPlaced` [IDDD ch. 13]) and
143
- call `super(aggregateId, metadata)`; `event.eventName()` returns it. The payload is primitives
144
- taken from value objects (`customer.value`, money as `{ amount, currency }`), only what
145
- consumers need: never value objects, `toPrimitives()` nor personal data nobody needs. Listeners and registries refer to
147
+ call `super(metadata)`; the base copies the name into the event's `eventName` field. An event
148
+ is plain data with no methods. The id of the aggregate is a field the event declares like any
149
+ other (`readonly orderId: string`). Rule (not checked at run time): each event declares as
150
+ `readonly` constructor fields only the primitive data it needs (text, finite numbers, booleans,
151
+ `null`, lists and plain objects of them), taken from value objects (`id.value`,
152
+ `customer.value`, money as `{ amount, currency }`, dates as ISO text): never value objects,
153
+ `Date`s, `toPrimitives()` nor personal data nobody needs. Listeners and registries refer to
146
154
  the event by its class (`@ListensTo(OrderPlaced, { consumer: 'reserve-stock-on-order-placed' })`, `register(OrderPlaced, …)`). The
147
- base sets `eventId` (UUID v4) and `occurredOn` (immutable `Date`, from `metadata.clock` when
155
+ base sets `eventId` (UUID v4) and `occurredOn` (ISO text in UTC, from `metadata.clock` when
148
156
  given, otherwise the system time); pass `{ eventId, occurredOn }` only when rebuilding an event
149
157
  a channel delivered. Inside an aggregate method, always pass the clock it received:
150
- `new OrderPlaced(id.value, customer.value, { clock })`. Serialize with `JSON.stringify` /
151
- `toJSON()` (`eventName`, `eventVersion`, `eventId`, `aggregateId`, ISO `occurredOn` and the
152
- payload).
158
+ `new OrderPlaced(id.value, customer.value, { clock })`. The event is its own JSON:
159
+ `{ ...event }` / `JSON.stringify(event)` give `eventName`, `eventId`, `occurredOn` and the
160
+ fields, in that order.
153
161
  - `PrimitiveTypes<T>` only sees **public** non-method members (it strips the leading `_`): expose
154
162
  VOs with public getters or public `readonly` properties. For an aggregate it includes `id`;
155
163
  child entities become their primitives; raw `string`/`number`/`boolean`/`Date`
156
164
  members become `never`.
157
165
  - Where to throw each exception:
158
166
  - Domain (aggregates, VOs, business rules): `DomainException` or a custom subclass with its own
159
- code; `ValidationException` (thrown by value objects and `ValueObjects.build`).
167
+ code; `ValidationException` (thrown by value objects and `ValueObjectBuilder.build`).
160
168
  - Use cases: `AggregateNotFoundException` when a repository returns `null` (except a use case
161
169
  run by a listener to register a fact: it returns, so the listener is not retried forever),
162
170
  `NotAllowedException` when the requester may not run the action (authorization belongs to the
@@ -165,13 +173,19 @@ Part of the `archi-code-ddd` skill: read it only when that skill routes here.
165
173
  `{ cause: error }` when wrapping another error.
166
174
  - `TypePrimitiveException` and `RequiredValueException` are thrown by the VOs; throw them yourself
167
175
  only inside the `filter` of a custom type.
168
- - Map errors to HTTP with `HttpErrorMapper` (overrides by code for your own codes): 400 for a
169
- conversion or required error thrown alone (a value object built with `new` outside
170
- `ValueObjects.build`; inside it they become a `ValidationException`, 422, with `DOM003`/`DOM004`
171
- in `errors`), 403 `NotAllowedException`, 404 `AggregateNotFoundException`, 409
172
- `ConcurrencyException`, 422 `ValidationException` and any other `DomainException` (a broken
173
- business rule), 400 any other `ApplicationException`, 500 otherwise; never hand-write status tables that leak infrastructure
174
- messages. For logs or messages use `error.code` and the exported `ExceptionCode` enum, or
176
+ - The package speaks no protocol: each entry point (HTTP, gRPC, queue consumer, CLI) translates
177
+ errors itself from `ExceptionClassifier.classify(error)` (`/infrastructure`), which returns the
178
+ `ExceptionKind`: `Malformed` for a conversion or required error thrown alone (a value object
179
+ built with `new` outside `ValueObjectBuilder.build`; inside it they become a `ValidationException` with
180
+ `DOM003`/`DOM004` in `errors`), `NotAllowed` for `NotAllowedException`, `NotFound` for
181
+ `AggregateNotFoundException`, `Conflict` for `ConcurrencyException`, `Invalid` for
182
+ `ValidationException` and any other `DomainException` (a broken business rule), `Rejected` for
183
+ any other `ApplicationException`, `Internal` for `UnexpectedErrorException`,
184
+ `InvalidEventDefinitionException` and anything else. The entry point maps the kind to its
185
+ protocol (HTTP: 400, 403, 404, 409, 422, 400, 500), handles your own codes with `error.code`,
186
+ returns the `errors` of a `ValidationException`, never sends the message of an `Internal`
187
+ failure and logs it once; never classify exceptions with hand-written `instanceof` chains. For logs or messages use `error.code` and the code enum of each layer
188
+ (`DomainExceptionCode`, `ApplicationExceptionCode`, `InfrastructureExceptionCode`), or
175
189
  `instanceof`: the built-in exceptions are recognized across the CJS and ESM builds.
176
190
  - A use case does **one thing**: one business action, one class, one `handle`. It changes one
177
191
  aggregate and saves it once [Destilado ch. 5; IDDD ch. 10] (the exceptions IDDD ch. 10 accepts, such
@@ -193,13 +207,13 @@ Part of the `archi-code-ddd` skill: read it only when that skill routes here.
193
207
  (`AggregateSaver<Order>`, `EventBus<OrderEvent>`, `Clock`) and are called
194
208
  with `execute(input)`.
195
209
  They implement two steps: `protected toCommand(input)` builds the value objects with
196
- `ValueObjects.build` and returns the command (a small interface of value objects, or one value
210
+ `ValueObjectBuilder.build` and returns the command (a small interface of value objects, or one value
197
211
  object built with `new`); `protected handle(command)` works only with value objects. `UseCase` has no helpers:
198
212
  write every step in `handle` (load with `findById` and throw `AggregateNotFoundException` when it
199
213
  is `null` — or return, in a use case a listener runs —, change the aggregate, `save`, then
200
214
  `publish(aggregate.pullDomainEvents())`); it never opens a transaction. Let errors
201
215
  propagate: `execute` keeps package exceptions and wraps any other error (also from `toCommand`)
202
- in `InternalErrorException` with `cause`.
216
+ in `UnexpectedErrorException` with `cause`.
203
217
  - A use case never imports nor calls another use case, and **returns an id, a DTO or nothing**,
204
218
  never an aggregate. Screens that read several aggregates use read queries, not repositories.
205
219
  - **Authorization belongs to the use case**: the entry point takes the requester id from the token
@@ -221,9 +235,9 @@ Part of the `archi-code-ddd` skill: read it only when that skill routes here.
221
235
  a pool), never an aggregate class nor a mapper; aggregates only go in and out through its
222
236
  methods. The aggregate converts itself (`toPrimitives()` / `static fromPrimitives()`), never a
223
237
  domain service nor a mapper; the repository calls those methods directly
224
- (`Order.fromPrimitives(row).restoreVersion(row.version)` on load, `order.toPrimitives()` with
225
- `order.version + 1` on save, `ConcurrencyException('Order', …)` on a conflict) and maps the
226
- primitives to its storage.
238
+ (`Order.fromPrimitives(row)` on load, `order.toPrimitives()` on save, with the version it keeps
239
+ in its own `WeakMap`, `ConcurrencyException('Order', …)` on a conflict) and maps the primitives
240
+ to its storage.
227
241
  The package ships no repository: the in-memory one is written in the project's `test/support/`.
228
242
  - **Another aggregate is referenced by its id** (`customerId: CustomerId`), never by the object.
229
243
  - **A command can be refused, a fact cannot**: a method that registers something that already
@@ -243,9 +257,10 @@ Part of the `archi-code-ddd` skill: read it only when that skill routes here.
243
257
  and wraps driver errors. When events must not be lost between `save` and `publish` [Destilado
244
258
  ch. 6; IDDD ch. 8], the repository stores them in the same atomic write (an outbox) and a relay
245
259
  delivers them through an `EventChannel`: use cases do not change.
246
- - **Events have a schema version** (`static readonly eventVersion = 2` when their data changes
247
- incompatibly; 1 by default, written in `toJSON()`); an `EventRegistry` factory reads older
248
- stored versions from `json.eventVersion`.
260
+ - **Events carry no schema version.** Adding an optional field old readers can ignore is
261
+ compatible; a change they cannot follow is a new event with its own `eventName`
262
+ (`CustomerRenamedV2`), or the `EventRegistry` factory reads both shapes from the stored JSON
263
+ (`typeof json.name === 'string' ? … : …`).
249
264
  - **The application holds only the event contract, in `application/event`**: to publish, the
250
265
  port `EventBus` (`publish`, the only thing use cases see); to listen, `EventListener` and
251
266
  `@ListensTo` (`ListensTo.subscriptionOf(listener)` → `{ eventName, consumer }`). What moves the
@@ -257,8 +272,8 @@ Part of the `archi-code-ddd` skill: read it only when that skill routes here.
257
272
  `EventChannel` (`InMemoryEventChannel` in the package; RabbitMQ, a table or Kafka in your
258
273
  infrastructure); swapping it changes one line of the wiring.
259
274
  - **Consumers are idempotent**: delivery is at least once, so a listener ignores an `eventId` it
260
- already processed. Listeners always receive the event rebuilt from its JSON (frozen, equal by
261
- `toJSON()`), also in memory, so nothing changes when the channel does.
275
+ already processed. Listeners always receive the event rebuilt from its JSON (equal field
276
+ by field), also in memory, so nothing changes when the channel does.
262
277
  - **Other contexts never receive domain classes**: translate the serialized event to a published,
263
278
  versioned contract (`sales.order.OrderPlaced`, only what consumers need, `messageId` = `eventId`)
264
279
  in the adapter that talks to the broker [IDDD ch. 13]; expose a new contract version next to the
@@ -274,7 +289,7 @@ Part of the `archi-code-ddd` skill: read it only when that skill routes here.
274
289
  provider: `ConsoleLogger({ level })` (one JSON line per entry), an adapter over pino or winston,
275
290
  `InMemoryLogger` in tests (`entries`), `SilentLogger` by default. Pass it to
276
291
  `new DomainEventBus(channel, registry, logger)` (listener failures, subscriptions, what was
277
- published) and `new HttpErrorMapper({ statusByCode, logger })` (5xx as `error`, 4xx at `debug`):
292
+ published) and to the entry points, which log an `Internal` failure as `error`:
278
293
  each error is logged once, where it is handled, never logged and rethrown in every layer.
279
294
  - Tests use the project's `InMemoryOrderRepository` (in `test/support/`) and
280
295
  `new DomainEventBus(new InMemoryEventChannel(), registry)` (`channel.sent` lists
@@ -294,6 +309,6 @@ Part of the `archi-code-ddd` skill: read it only when that skill routes here.
294
309
  of the primitives with their version and follows the same concurrency contract.
295
310
  - `DomainEventBus` rebuilds events with `EventRegistry`: register a factory per
296
311
  event class that passes the JSON as metadata
297
- (`register(OrderPlaced, (json) => new OrderPlaced(json.aggregateId, …, json))`); `fromJSON`
312
+ (`register(OrderPlaced, (json) => new OrderPlaced(json.orderId as string, …, json))`); `fromJSON`
298
313
  throws `InternalErrorException` when the rebuilt event lost its `eventId` or `occurredOn`, which
299
314
  listeners need to skip a redelivery.
@@ -29,7 +29,7 @@ new Email('x');
29
29
  new PackSize(-3);
30
30
  // ValidationException: Validation failed, PackSize: Value does not satisfy isInt, PackSize: Must be even
31
31
 
32
- const command = ValueObjects.build({
32
+ const command = ValueObjectBuilder.build({
33
33
  email: () => new Email(input.email),
34
34
  packSize: () => new PackSize(input.packSize),
35
35
  });
@@ -55,11 +55,11 @@ class OrderPlaced extends EventBase {
55
55
  static readonly eventName = 'OrderPlaced';
56
56
 
57
57
  constructor(
58
- orderId: string,
58
+ readonly orderId: string,
59
59
  readonly customer: string,
60
60
  metadata?: EventMetadata,
61
61
  ) {
62
- super(orderId, metadata);
62
+ super(metadata);
63
63
  }
64
64
  }
65
65
 
@@ -67,12 +67,12 @@ class OrderLineAdded extends EventBase {
67
67
  static readonly eventName = 'OrderLineAdded';
68
68
 
69
69
  constructor(
70
- orderId: string,
70
+ readonly orderId: string,
71
71
  readonly sku: string,
72
72
  readonly quantity: number,
73
73
  metadata?: EventMetadata,
74
74
  ) {
75
- super(orderId, metadata);
75
+ super(metadata);
76
76
  }
77
77
  }
78
78
 
@@ -180,7 +180,7 @@ class AddOrderLine extends UseCase<AddOrderLineInput, AddOrderLineCommand, void>
180
180
  }
181
181
 
182
182
  protected toCommand(input: AddOrderLineInput): AddOrderLineCommand {
183
- return ValueObjects.build({
183
+ return ValueObjectBuilder.build({
184
184
  orderId: () => new OrderId(input.orderId),
185
185
  sku: () => new Sku(input.sku),
186
186
  quantity: () => new Quantity(input.quantity),
@@ -216,7 +216,7 @@ class PrepareShipment extends UseCase<
216
216
  }
217
217
 
218
218
  protected toCommand(input: { orderId: string; sku: string }): { orderId: OrderId; sku: Sku } {
219
- return ValueObjects.build({
219
+ return ValueObjectBuilder.build({
220
220
  orderId: () => new OrderId(input.orderId),
221
221
  sku: () => new Sku(input.sku),
222
222
  });
@@ -234,7 +234,7 @@ class PrepareShipmentOnOrderLineAdded extends EventListener<OrderLineAdded> {
234
234
  }
235
235
 
236
236
  async handle(event: OrderLineAdded): Promise<void> {
237
- await this.prepareShipment.execute({ orderId: event.aggregateId, sku: event.sku });
237
+ await this.prepareShipment.execute({ orderId: event.orderId, sku: event.sku });
238
238
  }
239
239
  }
240
240
  ```
@@ -246,6 +246,8 @@ which receives the loaded aggregate) and wraps driver errors with their cause:
246
246
  class MongoOrderRepository
247
247
  implements AggregateFinder<Order>, AggregateSaver<Order>, AggregateRemover<Order>
248
248
  {
249
+ private readonly versions = new WeakMap<Order, number>();
250
+
249
251
  constructor(private readonly collection: Collection<OrderDocument>) {}
250
252
 
251
253
  async findById(id: OrderId): Promise<Order | null> {
@@ -254,34 +256,34 @@ class MongoOrderRepository
254
256
  return null;
255
257
  }
256
258
  const { _id, version, ...primitives } = document;
257
- return Order.fromPrimitives(primitives).restoreVersion(version);
259
+ const order = Order.fromPrimitives(primitives);
260
+ this.versions.set(order, version);
261
+ return order;
258
262
  }
259
263
 
260
264
  async save(order: Order): Promise<void> {
261
- const data = { ...order.toPrimitives(), version: order.version + 1 };
265
+ const version = this.versions.get(order) ?? 0;
266
+ const data = { ...order.toPrimitives(), version: version + 1 };
262
267
  let written: boolean;
263
268
  try {
264
269
  written =
265
- order.version === 0
270
+ version === 0
266
271
  ? await this.insertUnlessTaken(data)
267
- : (await this.collection.replaceOne({ _id: data.id, version: order.version }, data))
268
- .matchedCount === 1;
272
+ : (await this.collection.replaceOne({ _id: data.id, version }, data)).matchedCount === 1;
269
273
  } catch (error) {
270
274
  throw new InternalErrorException('saving order', { cause: error });
271
275
  }
272
276
  if (!written) {
273
- throw new ConcurrencyException('Order', order.id.value, order.version);
277
+ throw new ConcurrencyException('Order', order.id.value, version);
274
278
  }
275
- order.markPersisted();
279
+ this.versions.set(order, version + 1);
276
280
  }
277
281
 
278
282
  async remove(order: Order): Promise<void> {
279
- const { deletedCount } = await this.collection.deleteOne({
280
- _id: order.id.value,
281
- version: order.version,
282
- });
283
- if (deletedCount === 0 && order.version > 0) {
284
- throw new ConcurrencyException('Order', order.id.value, order.version);
283
+ const version = this.versions.get(order) ?? 0;
284
+ const { deletedCount } = await this.collection.deleteOne({ _id: order.id.value, version });
285
+ if (deletedCount === 0 && version > 0) {
286
+ throw new ConcurrencyException('Order', order.id.value, version);
285
287
  }
286
288
  }
287
289
  }
@@ -298,48 +300,54 @@ import type {
298
300
  AggregateSaver,
299
301
  PrimitiveTypes,
300
302
  } from '@archi-code/ddd/domain';
301
- import { ConcurrencyException } from '@archi-code/ddd/exceptions';
303
+ import { ConcurrencyException } from '@archi-code/ddd/application';
302
304
 
303
305
  export class InMemoryOrderRepository
304
306
  implements AggregateFinder<Order>, AggregateSaver<Order>, AggregateRemover<Order>
305
307
  {
306
308
  private readonly rows = new Map<string, { version: number; primitives: PrimitiveTypes<Order> }>();
309
+ private readonly versions = new WeakMap<Order, number>();
307
310
 
308
311
  async findById(id: OrderId): Promise<Order | null> {
309
312
  const row = this.rows.get(id.value);
310
- return row === undefined
311
- ? null
312
- : Order.fromPrimitives(structuredClone(row.primitives)).restoreVersion(row.version);
313
+ if (row === undefined) {
314
+ return null;
315
+ }
316
+ const order = Order.fromPrimitives(structuredClone(row.primitives));
317
+ this.versions.set(order, row.version);
318
+ return order;
313
319
  }
314
320
 
315
321
  async save(order: Order): Promise<void> {
316
- const key = this.currentKey(order);
322
+ const version = this.checkedVersion(order);
317
323
  const primitives = structuredClone(order.toPrimitives());
318
- this.rows.set(key, { version: order.version + 1, primitives });
319
- order.markPersisted();
324
+ this.rows.set(order.id.value, { version: version + 1, primitives });
325
+ this.versions.set(order, version + 1);
320
326
  }
321
327
 
322
328
  async remove(order: Order): Promise<void> {
323
- this.rows.delete(this.currentKey(order));
329
+ this.checkedVersion(order);
330
+ this.rows.delete(order.id.value);
324
331
  }
325
332
 
326
- private currentKey(order: Order): string {
333
+ private checkedVersion(order: Order): number {
327
334
  const key = order.id.value;
328
- if ((this.rows.get(key)?.version ?? 0) !== order.version) {
329
- throw new ConcurrencyException('Order', key, order.version);
335
+ const version = this.versions.get(order) ?? 0;
336
+ if ((this.rows.get(key)?.version ?? 0) !== version) {
337
+ throw new ConcurrencyException('Order', key, version);
330
338
  }
331
- return key;
339
+ return version;
332
340
  }
333
341
  }
334
342
  ```
335
343
 
336
- Wiring and tests with the in-memory adapters (repository, event channel) and the HTTP mapper:
344
+ Wiring and tests with the in-memory adapters (repository, event channel), and the kind of an error:
337
345
 
338
346
  ```ts
339
347
  const orders = new InMemoryOrderRepository();
340
348
  const registry = new EventRegistry<OrderEvent>().register(
341
349
  OrderLineAdded,
342
- (json) => new OrderLineAdded(json.aggregateId, json.sku as string, json.quantity as number, json),
350
+ (json) => new OrderLineAdded(json.orderId as string, json.sku as string, json.quantity as number, json),
343
351
  );
344
352
  const channel = new InMemoryEventChannel(); // swap for your RabbitMQ or database channel
345
353
  const bus = new DomainEventBus<OrderEvent>(channel, registry);
@@ -354,8 +362,7 @@ await new AddOrderLine(orders, bus, clock).execute({
354
362
  });
355
363
  channel.sent.map((event) => event.eventName); // ['OrderLineAdded']: PrepareShipment already ran
356
364
 
357
- const errors = new HttpErrorMapper({ statusByCode: { ORD001: 409 } }); // a domain rule defaults to 422
358
- const { status, body } = errors.map(caughtError);
365
+ ExceptionClassifier.classify(new OrderShippedException(orderId)); // ExceptionKind.Invalid ('invalid')
359
366
  ```
360
367
 
361
368
  A test of the event flow asserts on what the listener caused and on what the channel carried,
@@ -385,7 +392,7 @@ describe('Adding a line to an order', () => {
385
392
 
386
393
  expect(prepared).toEqual([`${orderId}:TORCH-01`]);
387
394
  expect(channel.sent).toMatchObject([
388
- { eventName: 'OrderLineAdded', aggregateId: orderId, sku: 'TORCH-01', quantity: 2 },
395
+ { eventName: 'OrderLineAdded', orderId, sku: 'TORCH-01', quantity: 2 },
389
396
  ]);
390
397
  });
391
398
  });
@@ -478,9 +485,9 @@ integration test waits for the effect (polling until `prepared` has the line) in
478
485
  right after `execute`.
479
486
 
480
487
  Tests with Vitest: describe real situations, use a plain fixture factory in the project's
481
- `test/support/` and the native matchers (there is no archi-code test package). Dates held by VOs and
482
- events are immutable `Date` subclasses: compare them with `toEqual` or `.toISOString()`, not
483
- `toStrictEqual` against a plain `Date`.
488
+ `test/support/` and the native matchers (there is no archi-code test package). Dates held by VOs are
489
+ immutable `Date` subclasses: compare them with `toEqual` or `.toISOString()`, not
490
+ `toStrictEqual` against a plain `Date`; an event's `occurredOn` is already ISO text.
484
491
 
485
492
  ```ts
486
493
  import { describe, expect, it } from 'vitest';
@@ -496,8 +503,8 @@ describe('Order', () => {
496
503
  id: orderId,
497
504
  lines: [{ sku: 'TORCH-01', quantity: 2 }],
498
505
  });
499
- expect(order.pullDomainEvents().map((event) => event.toJSON())).toMatchObject([
500
- { eventName: 'OrderPlaced', aggregateId: orderId, customer: 'Ana' },
506
+ expect(order.pullDomainEvents().map((event) => ({ ...event }))).toMatchObject([
507
+ { eventName: 'OrderPlaced', orderId, customer: 'Ana' },
501
508
  { eventName: 'OrderLineAdded', sku: 'TORCH-01', quantity: 2 },
502
509
  ]);
503
510
  });