ddd-ts-kit 0.13.0 → 0.15.0

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 (457) hide show
  1. package/README.md +34 -3
  2. package/dist/check/catalogue/architecture-rules.d.ts.map +1 -1
  3. package/dist/check/catalogue/architecture-rules.js +2 -0
  4. package/dist/check/catalogue/architecture-rules.js.map +1 -1
  5. package/dist/check/errors/kit-error-codes.d.ts.map +1 -1
  6. package/dist/check/errors/kit-error-codes.js +1 -0
  7. package/dist/check/errors/kit-error-codes.js.map +1 -1
  8. package/dist/check/explain/authz-docs.d.ts.map +1 -1
  9. package/dist/check/explain/authz-docs.js +12 -0
  10. package/dist/check/explain/authz-docs.js.map +1 -1
  11. package/dist/check/explain/errors-docs.js +1 -1
  12. package/dist/check/explain/errors-docs.js.map +1 -1
  13. package/dist/check/explain/tenancy-docs.d.ts.map +1 -1
  14. package/dist/check/explain/tenancy-docs.js +3 -1
  15. package/dist/check/explain/tenancy-docs.js.map +1 -1
  16. package/dist/check/fixtures/authz-fixtures.d.ts.map +1 -1
  17. package/dist/check/fixtures/authz-fixtures.js +38 -1
  18. package/dist/check/fixtures/authz-fixtures.js.map +1 -1
  19. package/dist/check/fixtures/tenancy-fixtures.d.ts.map +1 -1
  20. package/dist/check/fixtures/tenancy-fixtures.js +28 -0
  21. package/dist/check/fixtures/tenancy-fixtures.js.map +1 -1
  22. package/dist/check/rules/authz-fields-by-schema.d.ts +3 -0
  23. package/dist/check/rules/authz-fields-by-schema.d.ts.map +1 -0
  24. package/dist/check/rules/authz-fields-by-schema.js +33 -0
  25. package/dist/check/rules/authz-fields-by-schema.js.map +1 -0
  26. package/dist/check/tenancy/knex-unscoped.d.ts +6 -1
  27. package/dist/check/tenancy/knex-unscoped.d.ts.map +1 -1
  28. package/dist/check/tenancy/knex-unscoped.js +19 -6
  29. package/dist/check/tenancy/knex-unscoped.js.map +1 -1
  30. package/dist/check/tenancy/raw-reaches-rows.d.ts +8 -0
  31. package/dist/check/tenancy/raw-reaches-rows.d.ts.map +1 -0
  32. package/dist/check/tenancy/raw-reaches-rows.js +91 -0
  33. package/dist/check/tenancy/raw-reaches-rows.js.map +1 -0
  34. package/dist/check/tenancy/raw-sql-text.d.ts +9 -0
  35. package/dist/check/tenancy/raw-sql-text.d.ts.map +1 -0
  36. package/dist/check/tenancy/raw-sql-text.js +29 -0
  37. package/dist/check/tenancy/raw-sql-text.js.map +1 -0
  38. package/dist/commands/generate.js +2 -0
  39. package/dist/commands/generate.js.map +1 -1
  40. package/dist/commands/init.d.ts.map +1 -1
  41. package/dist/commands/init.js +13 -1
  42. package/dist/commands/init.js.map +1 -1
  43. package/dist/config/index.d.ts +1 -1
  44. package/dist/config/index.d.ts.map +1 -1
  45. package/dist/config/index.js +1 -1
  46. package/dist/config/index.js.map +1 -1
  47. package/dist/config/parse-stack.d.ts.map +1 -1
  48. package/dist/config/parse-stack.js +3 -1
  49. package/dist/config/parse-stack.js.map +1 -1
  50. package/dist/config/stack.d.ts +14 -0
  51. package/dist/config/stack.d.ts.map +1 -1
  52. package/dist/config/stack.js +2 -0
  53. package/dist/config/stack.js.map +1 -1
  54. package/dist/declared/injection/laid-out-calls.d.ts +7 -0
  55. package/dist/declared/injection/laid-out-calls.d.ts.map +1 -0
  56. package/dist/declared/injection/laid-out-calls.js +14 -0
  57. package/dist/declared/injection/laid-out-calls.js.map +1 -0
  58. package/dist/declared/render-barrel.d.ts.map +1 -1
  59. package/dist/declared/render-barrel.js +2 -1
  60. package/dist/declared/render-barrel.js.map +1 -1
  61. package/dist/declared/render-dependencies-file.d.ts.map +1 -1
  62. package/dist/declared/render-dependencies-file.js +2 -1
  63. package/dist/declared/render-dependencies-file.js.map +1 -1
  64. package/dist/describe/context-description.d.ts +6 -0
  65. package/dist/describe/context-description.d.ts.map +1 -1
  66. package/dist/describe/format-description.js +9 -1
  67. package/dist/describe/format-description.js.map +1 -1
  68. package/dist/describe/presentation-of-route.d.ts +4 -0
  69. package/dist/describe/presentation-of-route.d.ts.map +1 -0
  70. package/dist/describe/presentation-of-route.js +15 -0
  71. package/dist/describe/presentation-of-route.js.map +1 -0
  72. package/dist/describe/read-controllers.d.ts.map +1 -1
  73. package/dist/describe/read-controllers.js +4 -1
  74. package/dist/describe/read-controllers.js.map +1 -1
  75. package/dist/describe/read-routes.d.ts.map +1 -1
  76. package/dist/describe/read-routes.js +2 -0
  77. package/dist/describe/read-routes.js.map +1 -1
  78. package/dist/generate/kind.d.ts +2 -0
  79. package/dist/generate/kind.d.ts.map +1 -1
  80. package/dist/generate/kinds/context-kind.d.ts.map +1 -1
  81. package/dist/generate/kinds/context-kind.js +13 -6
  82. package/dist/generate/kinds/context-kind.js.map +1 -1
  83. package/dist/generate/kinds/form-kind.d.ts +3 -0
  84. package/dist/generate/kinds/form-kind.d.ts.map +1 -0
  85. package/dist/generate/kinds/form-kind.js +38 -0
  86. package/dist/generate/kinds/form-kind.js.map +1 -0
  87. package/dist/generate/kinds/next-adr-number.d.ts +4 -0
  88. package/dist/generate/kinds/next-adr-number.d.ts.map +1 -0
  89. package/dist/generate/kinds/next-adr-number.js +7 -0
  90. package/dist/generate/kinds/next-adr-number.js.map +1 -0
  91. package/dist/generate/kinds/presentation/controller-of.d.ts +10 -0
  92. package/dist/generate/kinds/presentation/controller-of.d.ts.map +1 -0
  93. package/dist/generate/kinds/presentation/controller-of.js +13 -0
  94. package/dist/generate/kinds/presentation/controller-of.js.map +1 -0
  95. package/dist/generate/kinds/presentation/controller-source.d.ts +13 -0
  96. package/dist/generate/kinds/presentation/controller-source.d.ts.map +1 -0
  97. package/dist/generate/kinds/presentation/controller-source.js +44 -0
  98. package/dist/generate/kinds/presentation/controller-source.js.map +1 -0
  99. package/dist/generate/kinds/representation-kind.d.ts +3 -0
  100. package/dist/generate/kinds/representation-kind.d.ts.map +1 -0
  101. package/dist/generate/kinds/representation-kind.js +54 -0
  102. package/dist/generate/kinds/representation-kind.js.map +1 -0
  103. package/dist/generate/kinds/write-adr.js +3 -6
  104. package/dist/generate/kinds/write-adr.js.map +1 -1
  105. package/dist/generate/kinds.d.ts.map +1 -1
  106. package/dist/generate/kinds.js +4 -0
  107. package/dist/generate/kinds.js.map +1 -1
  108. package/dist/help/usage.d.ts +1 -1
  109. package/dist/help/usage.d.ts.map +1 -1
  110. package/dist/help/usage.js +3 -2
  111. package/dist/help/usage.js.map +1 -1
  112. package/dist/init/files/platform-files.js +3 -1
  113. package/dist/init/files/platform-files.js.map +1 -1
  114. package/dist/init/manifest/versions.js +24 -24
  115. package/dist/init/run-init.d.ts +2 -0
  116. package/dist/init/run-init.d.ts.map +1 -1
  117. package/dist/init/run-init.js +15 -1
  118. package/dist/init/run-init.js.map +1 -1
  119. package/dist/init/stack/resolve-stack.d.ts.map +1 -1
  120. package/dist/init/stack/resolve-stack.js +15 -1
  121. package/dist/init/stack/resolve-stack.js.map +1 -1
  122. package/dist/init/stack/stack-options.d.ts +2 -0
  123. package/dist/init/stack/stack-options.d.ts.map +1 -1
  124. package/dist/presets/apply-preset.d.ts +15 -0
  125. package/dist/presets/apply-preset.d.ts.map +1 -0
  126. package/dist/presets/apply-preset.js +94 -0
  127. package/dist/presets/apply-preset.js.map +1 -0
  128. package/dist/presets/grant-to-signed-in.d.ts +9 -0
  129. package/dist/presets/grant-to-signed-in.d.ts.map +1 -0
  130. package/dist/presets/grant-to-signed-in.js +61 -0
  131. package/dist/presets/grant-to-signed-in.js.map +1 -0
  132. package/dist/presets/identity-preset.d.ts +9 -0
  133. package/dist/presets/identity-preset.d.ts.map +1 -0
  134. package/dist/presets/identity-preset.js +44 -0
  135. package/dist/presets/identity-preset.js.map +1 -0
  136. package/dist/presets/identity-refusal.d.ts +5 -0
  137. package/dist/presets/identity-refusal.d.ts.map +1 -0
  138. package/dist/presets/identity-refusal.js +29 -0
  139. package/dist/presets/identity-refusal.js.map +1 -0
  140. package/dist/presets/identity-terms.d.ts +4 -0
  141. package/dist/presets/identity-terms.d.ts.map +1 -0
  142. package/dist/presets/identity-terms.js +57 -0
  143. package/dist/presets/identity-terms.js.map +1 -0
  144. package/dist/presets/no-flags.d.ts +4 -0
  145. package/dist/presets/no-flags.d.ts.map +1 -0
  146. package/dist/presets/no-flags.js +18 -0
  147. package/dist/presets/no-flags.js.map +1 -0
  148. package/dist/presets/organisations-as-tenants.d.ts +9 -0
  149. package/dist/presets/organisations-as-tenants.d.ts.map +1 -0
  150. package/dist/presets/organisations-as-tenants.js +43 -0
  151. package/dist/presets/organisations-as-tenants.js.map +1 -0
  152. package/dist/presets/preset-decision.d.ts +8 -0
  153. package/dist/presets/preset-decision.d.ts.map +1 -0
  154. package/dist/presets/preset-decision.js +19 -0
  155. package/dist/presets/preset-decision.js.map +1 -0
  156. package/dist/presets/preset-file.d.ts +13 -0
  157. package/dist/presets/preset-file.d.ts.map +1 -0
  158. package/dist/presets/preset-file.js +2 -0
  159. package/dist/presets/preset-file.js.map +1 -0
  160. package/dist/presets/preset-files.d.ts +9 -0
  161. package/dist/presets/preset-files.d.ts.map +1 -0
  162. package/dist/presets/preset-files.js +34 -0
  163. package/dist/presets/preset-files.js.map +1 -0
  164. package/dist/presets/preset-named.d.ts +3 -0
  165. package/dist/presets/preset-named.d.ts.map +1 -0
  166. package/dist/presets/preset-named.js +12 -0
  167. package/dist/presets/preset-named.js.map +1 -0
  168. package/dist/presets/preset-refusal.d.ts +6 -0
  169. package/dist/presets/preset-refusal.d.ts.map +1 -0
  170. package/dist/presets/preset-refusal.js +2 -0
  171. package/dist/presets/preset-refusal.js.map +1 -0
  172. package/dist/presets/preset-step.d.ts +8 -0
  173. package/dist/presets/preset-step.d.ts.map +1 -0
  174. package/dist/presets/preset-step.js +2 -0
  175. package/dist/presets/preset-step.js.map +1 -0
  176. package/dist/presets/preset.d.ts +36 -0
  177. package/dist/presets/preset.d.ts.map +1 -0
  178. package/dist/presets/preset.js +2 -0
  179. package/dist/presets/preset.js.map +1 -0
  180. package/dist/presets/with-authorizer-import.d.ts +6 -0
  181. package/dist/presets/with-authorizer-import.d.ts.map +1 -0
  182. package/dist/presets/with-authorizer-import.js +27 -0
  183. package/dist/presets/with-authorizer-import.js.map +1 -0
  184. package/dist/stack/stack-vars.js +19 -2
  185. package/dist/stack/stack-vars.js.map +1 -1
  186. package/dist/version.d.ts +1 -1
  187. package/dist/version.js +1 -1
  188. package/dist/workspace/workspace.d.ts +2 -0
  189. package/dist/workspace/workspace.d.ts.map +1 -1
  190. package/dist/workspace/workspace.js +4 -0
  191. package/dist/workspace/workspace.js.map +1 -1
  192. package/package.json +2 -2
  193. package/templates/docs/openapi.json.tpl +15 -0
  194. package/templates/init/CLAUDE.md.tpl +2 -2
  195. package/templates/init/platform/auth/http-boundary.ts.tpl +20 -3
  196. package/templates/init/platform/compose-application.manual.ts.tpl +18 -0
  197. package/templates/init/platform/compose-application.tsyringe.ts.tpl +23 -0
  198. package/templates/init/platform/http/presentation-of.ts.tpl +27 -0
  199. package/templates/init/platform/http/start-http-server.express.ts.tpl +1 -1
  200. package/templates/init/platform/http/start-http-server.fastify.ts.tpl +1 -1
  201. package/templates/init/platform/http/start-http-server.hono.ts.tpl +1 -1
  202. package/templates/init/platform/http/start-http-server.nest.ts.tpl +2 -2
  203. package/templates/init/platform/http/with-documentation.ts.tpl +13 -3
  204. package/templates/init/platform/http/with-forms.ts.tpl +7 -0
  205. package/templates/init/rules/application.md.tpl +1 -1
  206. package/templates/init/rules/presentation.md.tpl +1 -1
  207. package/templates/init/scripts/openapi.ts.tpl +27 -6
  208. package/templates/pipeline/biome.json.tpl +1 -0
  209. package/templates/pipeline/package-scripts.json +1 -1
  210. package/templates/presets/identity/context/application/authorization/__context__.permissions.ts.tpl +23 -0
  211. package/templates/presets/identity/context/application/authorization/change-member-roles.policy.ts.tpl +17 -0
  212. package/templates/presets/identity/context/application/authorization/grant-delegations.policy.ts.tpl +17 -0
  213. package/templates/presets/identity/context/application/authorization/invite-members.policy.ts.tpl +17 -0
  214. package/templates/presets/identity/context/application/authorization/read-members.policy.ts.tpl +17 -0
  215. package/templates/presets/identity/context/application/authorization/remove-members.policy.ts.tpl +17 -0
  216. package/templates/presets/identity/context/application/authorization/revoke-delegations.policy.ts.tpl +17 -0
  217. package/templates/presets/identity/context/application/authorization/revoke-invitations.policy.ts.tpl +17 -0
  218. package/templates/presets/identity/context/application/commands/accept-invitation/accept-invitation.command.ts.tpl +14 -0
  219. package/templates/presets/identity/context/application/commands/accept-invitation/accept-invitation.handler.ts.tpl +56 -0
  220. package/templates/presets/identity/context/application/commands/change-member-role/change-member-role.command.ts.tpl +17 -0
  221. package/templates/presets/identity/context/application/commands/change-member-role/change-member-role.handler.ts.tpl +82 -0
  222. package/templates/presets/identity/context/application/commands/deactivate-account/deactivate-account.command.ts.tpl +7 -0
  223. package/templates/presets/identity/context/application/commands/deactivate-account/deactivate-account.handler.ts.tpl +69 -0
  224. package/templates/presets/identity/context/application/commands/decline-invitation/decline-invitation.command.ts.tpl +14 -0
  225. package/templates/presets/identity/context/application/commands/decline-invitation/decline-invitation.handler.ts.tpl +36 -0
  226. package/templates/presets/identity/context/application/commands/grant-delegation/grant-delegation.command.ts.tpl +21 -0
  227. package/templates/presets/identity/context/application/commands/grant-delegation/grant-delegation.handler.ts.tpl +43 -0
  228. package/templates/presets/identity/context/application/commands/invite-member/invite-member.command.ts.tpl +16 -0
  229. package/templates/presets/identity/context/application/commands/invite-member/invite-member.handler.ts.tpl +53 -0
  230. package/templates/presets/identity/context/application/commands/open-organisation/open-organisation.command.ts.tpl +14 -0
  231. package/templates/presets/identity/context/application/commands/open-organisation/open-organisation.handler.ts.tpl +50 -0
  232. package/templates/presets/identity/context/application/commands/provision-account/provision-account.command.ts.tpl +14 -0
  233. package/templates/presets/identity/context/application/commands/provision-account/provision-account.handler.ts.tpl +41 -0
  234. package/templates/presets/identity/context/application/commands/remove-member/remove-member.command.ts.tpl +15 -0
  235. package/templates/presets/identity/context/application/commands/remove-member/remove-member.handler.ts.tpl +79 -0
  236. package/templates/presets/identity/context/application/commands/revoke-delegation/revoke-delegation.command.ts.tpl +15 -0
  237. package/templates/presets/identity/context/application/commands/revoke-delegation/revoke-delegation.handler.ts.tpl +39 -0
  238. package/templates/presets/identity/context/application/commands/revoke-invitation/revoke-invitation.command.ts.tpl +15 -0
  239. package/templates/presets/identity/context/application/commands/revoke-invitation/revoke-invitation.handler.ts.tpl +42 -0
  240. package/templates/presets/identity/context/application/integration/handlers/account-deactivated.integration-handler.ts.tpl +19 -0
  241. package/templates/presets/identity/context/application/integration/handlers/member-joined.integration-handler.ts.tpl +33 -0
  242. package/templates/presets/identity/context/application/integration/handlers/member-removed.integration-handler.ts.tpl +30 -0
  243. package/templates/presets/identity/context/application/integration/handlers/organisation-opened.integration-handler.ts.tpl +19 -0
  244. package/templates/presets/identity/context/application/integration/publishers/account-deactivated.publisher.ts.tpl +24 -0
  245. package/templates/presets/identity/context/application/integration/publishers/delegation-granted.publisher.ts.tpl +36 -0
  246. package/templates/presets/identity/context/application/integration/publishers/delegation-revoked.publisher.ts.tpl +33 -0
  247. package/templates/presets/identity/context/application/integration/publishers/member-joined.publisher.ts.tpl +33 -0
  248. package/templates/presets/identity/context/application/integration/publishers/member-removed.publisher.ts.tpl +33 -0
  249. package/templates/presets/identity/context/application/integration/publishers/member-role-changed.publisher.ts.tpl +35 -0
  250. package/templates/presets/identity/context/application/integration/publishers/organisation-opened.publisher.ts.tpl +32 -0
  251. package/templates/presets/identity/context/application/ports/identity-provider-admin.port.ts.tpl +17 -0
  252. package/templates/presets/identity/context/application/ports/identity-provider-connection.port.ts.tpl +14 -0
  253. package/templates/presets/identity/context/application/ports/invitation-tokens.port.ts.tpl +7 -0
  254. package/templates/presets/identity/context/application/ports/search-members-reader.port.ts.tpl +10 -0
  255. package/templates/presets/identity/context/application/queries/search-members/search-members.handler.ts.tpl +33 -0
  256. package/templates/presets/identity/context/application/queries/search-members/search-members.query.ts.tpl +19 -0
  257. package/templates/presets/identity/context/application/queries/search-members/search-members.result.ts.tpl +17 -0
  258. package/templates/presets/identity/context/domain/aggregates/account.aggregate.ts.tpl +92 -0
  259. package/templates/presets/identity/context/domain/aggregates/delegation.aggregate.ts.tpl +117 -0
  260. package/templates/presets/identity/context/domain/aggregates/invitation.aggregate.ts.tpl +128 -0
  261. package/templates/presets/identity/context/domain/aggregates/member.aggregate.ts.tpl +149 -0
  262. package/templates/presets/identity/context/domain/aggregates/organisation.aggregate.ts.tpl +52 -0
  263. package/templates/presets/identity/context/domain/errors/account.error.ts.tpl +40 -0
  264. package/templates/presets/identity/context/domain/errors/delegation.error.ts.tpl +45 -0
  265. package/templates/presets/identity/context/domain/errors/invitation.error.ts.tpl +30 -0
  266. package/templates/presets/identity/context/domain/errors/member.error.ts.tpl +50 -0
  267. package/templates/presets/identity/context/domain/errors/organisation.error.ts.tpl +32 -0
  268. package/templates/presets/identity/context/domain/events/account-deactivated.event.ts.tpl +13 -0
  269. package/templates/presets/identity/context/domain/events/account-provisioned.event.ts.tpl +17 -0
  270. package/templates/presets/identity/context/domain/events/delegation-granted.event.ts.tpl +22 -0
  271. package/templates/presets/identity/context/domain/events/delegation-revoked.event.ts.tpl +18 -0
  272. package/templates/presets/identity/context/domain/events/invitation-accepted.event.ts.tpl +17 -0
  273. package/templates/presets/identity/context/domain/events/invitation-declined.event.ts.tpl +16 -0
  274. package/templates/presets/identity/context/domain/events/invitation-issued.event.ts.tpl +22 -0
  275. package/templates/presets/identity/context/domain/events/invitation-revoked.event.ts.tpl +16 -0
  276. package/templates/presets/identity/context/domain/events/member-joined.event.ts.tpl +19 -0
  277. package/templates/presets/identity/context/domain/events/member-removed.event.ts.tpl +19 -0
  278. package/templates/presets/identity/context/domain/events/member-role-changed.event.ts.tpl +22 -0
  279. package/templates/presets/identity/context/domain/events/organisation-opened.event.ts.tpl +17 -0
  280. package/templates/presets/identity/context/domain/repositories/account.repository.ts.tpl +5 -0
  281. package/templates/presets/identity/context/domain/repositories/delegation.repository.ts.tpl +10 -0
  282. package/templates/presets/identity/context/domain/repositories/invitation.repository.ts.tpl +7 -0
  283. package/templates/presets/identity/context/domain/repositories/member.repository.ts.tpl +13 -0
  284. package/templates/presets/identity/context/domain/repositories/organisation.repository.ts.tpl +5 -0
  285. package/templates/presets/identity/context/domain/services/role-rights.service.ts.tpl +59 -0
  286. package/templates/presets/identity/context/domain/value-objects/delegation-period.vo.ts.tpl +42 -0
  287. package/templates/presets/identity/context/domain/value-objects/display-name.vo.ts.tpl +29 -0
  288. package/templates/presets/identity/context/domain/value-objects/email-address.vo.ts.tpl +31 -0
  289. package/templates/presets/identity/context/domain/value-objects/organisation-name.vo.ts.tpl +29 -0
  290. package/templates/presets/identity/context/domain/value-objects/organisation-rights.vo.ts.tpl +43 -0
  291. package/templates/presets/identity/context/domain/value-objects/organisation-slug.vo.ts.tpl +30 -0
  292. package/templates/presets/identity/context/domain/vocabularies/account-status.vocabulary.ts.tpl +7 -0
  293. package/templates/presets/identity/context/domain/vocabularies/invitation-status.vocabulary.ts.tpl +9 -0
  294. package/templates/presets/identity/context/domain/vocabularies/member-right.vocabulary.ts.tpl +9 -0
  295. package/templates/presets/identity/context/domain/vocabularies/member-role.vocabulary.ts.tpl +8 -0
  296. package/templates/presets/identity/context/infrastructure/external/crypto-invitation-tokens.adapter.ts.tpl +20 -0
  297. package/templates/presets/identity/context/infrastructure/external/environment-identity-provider-connection.adapter.ts.tpl +37 -0
  298. package/templates/presets/identity/context/infrastructure/external/keycloak-identity-provider-admin.adapter.ts.tpl +149 -0
  299. package/templates/presets/identity/context/infrastructure/persistence/__context__.schema.drizzle.ts.tpl +102 -0
  300. package/templates/presets/identity/context/infrastructure/persistence/account-row.schema.knex.ts.tpl +15 -0
  301. package/templates/presets/identity/context/infrastructure/persistence/account.mapper.drizzle.ts.tpl +38 -0
  302. package/templates/presets/identity/context/infrastructure/persistence/account.mapper.knex.ts.tpl +33 -0
  303. package/templates/presets/identity/context/infrastructure/persistence/create-__context__-tables.schema.knex.ts.tpl +107 -0
  304. package/templates/presets/identity/context/infrastructure/persistence/delegation-row.schema.knex.ts.tpl +17 -0
  305. package/templates/presets/identity/context/infrastructure/persistence/delegation.mapper.drizzle.ts.tpl +50 -0
  306. package/templates/presets/identity/context/infrastructure/persistence/delegation.mapper.knex.ts.tpl +45 -0
  307. package/templates/presets/identity/context/infrastructure/persistence/drizzle-account.repository.drizzle.ts.tpl +59 -0
  308. package/templates/presets/identity/context/infrastructure/persistence/drizzle-delegation.repository.drizzle.ts.tpl +77 -0
  309. package/templates/presets/identity/context/infrastructure/persistence/drizzle-invitation.repository.drizzle.ts.tpl +65 -0
  310. package/templates/presets/identity/context/infrastructure/persistence/drizzle-member.repository.drizzle.ts.tpl +84 -0
  311. package/templates/presets/identity/context/infrastructure/persistence/drizzle-organisation.repository.drizzle.ts.tpl +59 -0
  312. package/templates/presets/identity/context/infrastructure/persistence/drizzle-search-members-reader.repository.drizzle.ts.tpl +53 -0
  313. package/templates/presets/identity/context/infrastructure/persistence/invitation-row.schema.knex.ts.tpl +20 -0
  314. package/templates/presets/identity/context/infrastructure/persistence/invitation.mapper.drizzle.ts.tpl +45 -0
  315. package/templates/presets/identity/context/infrastructure/persistence/invitation.mapper.knex.ts.tpl +40 -0
  316. package/templates/presets/identity/context/infrastructure/persistence/knex-account.repository.knex.ts.tpl +61 -0
  317. package/templates/presets/identity/context/infrastructure/persistence/knex-delegation.repository.knex.ts.tpl +80 -0
  318. package/templates/presets/identity/context/infrastructure/persistence/knex-invitation.repository.knex.ts.tpl +67 -0
  319. package/templates/presets/identity/context/infrastructure/persistence/knex-member.repository.knex.ts.tpl +82 -0
  320. package/templates/presets/identity/context/infrastructure/persistence/knex-organisation.repository.knex.ts.tpl +61 -0
  321. package/templates/presets/identity/context/infrastructure/persistence/knex-search-members-reader.repository.knex.ts.tpl +77 -0
  322. package/templates/presets/identity/context/infrastructure/persistence/member-row.schema.knex.ts.tpl +15 -0
  323. package/templates/presets/identity/context/infrastructure/persistence/member.mapper.drizzle.ts.tpl +36 -0
  324. package/templates/presets/identity/context/infrastructure/persistence/member.mapper.knex.ts.tpl +31 -0
  325. package/templates/presets/identity/context/infrastructure/persistence/organisation-row.schema.knex.ts.tpl +12 -0
  326. package/templates/presets/identity/context/infrastructure/persistence/organisation.mapper.drizzle.ts.tpl +28 -0
  327. package/templates/presets/identity/context/infrastructure/persistence/organisation.mapper.knex.ts.tpl +26 -0
  328. package/templates/presets/identity/context/presentation/http/accounts/deactivate-account/deactivate-account.accepted.class-validator.per-route.ts.tpl +6 -0
  329. package/templates/presets/identity/context/presentation/http/accounts/deactivate-account/deactivate-account.controller.ts.tpl +71 -0
  330. package/templates/presets/identity/context/presentation/http/accounts/provision-account/provision-account.accepted.class-validator.per-route.ts.tpl +6 -0
  331. package/templates/presets/identity/context/presentation/http/accounts/provision-account/provision-account.body.class-validator.ts.tpl +9 -0
  332. package/templates/presets/identity/context/presentation/http/accounts/provision-account/provision-account.controller.ts.tpl +85 -0
  333. package/templates/presets/identity/context/presentation/http/accounts.routes.ts.tpl +5 -0
  334. package/templates/presets/identity/context/presentation/http/invitations/accept-invitation/accept-invitation.accepted.class-validator.per-route.ts.tpl +6 -0
  335. package/templates/presets/identity/context/presentation/http/invitations/accept-invitation/accept-invitation.body.class-validator.per-route.ts.tpl +8 -0
  336. package/templates/presets/identity/context/presentation/http/invitations/accept-invitation/accept-invitation.controller.ts.tpl +100 -0
  337. package/templates/presets/identity/context/presentation/http/invitations/decline-invitation/decline-invitation.accepted.class-validator.per-route.ts.tpl +6 -0
  338. package/templates/presets/identity/context/presentation/http/invitations/decline-invitation/decline-invitation.body.class-validator.per-route.ts.tpl +8 -0
  339. package/templates/presets/identity/context/presentation/http/invitations/decline-invitation/decline-invitation.controller.ts.tpl +98 -0
  340. package/templates/presets/identity/context/presentation/http/invitations.routes.ts.tpl +5 -0
  341. package/templates/presets/identity/context/presentation/http/organisations/change-member-role/change-member-role.accepted.class-validator.per-route.ts.tpl +6 -0
  342. package/templates/presets/identity/context/presentation/http/organisations/change-member-role/change-member-role.body.class-validator.ts.tpl +7 -0
  343. package/templates/presets/identity/context/presentation/http/organisations/change-member-role/change-member-role.controller.ts.tpl +83 -0
  344. package/templates/presets/identity/context/presentation/http/organisations/change-member-role/change-member-role.params.class-validator.ts.tpl +11 -0
  345. package/templates/presets/identity/context/presentation/http/organisations/grant-delegation/grant-delegation.accepted.class-validator.per-route.ts.tpl +6 -0
  346. package/templates/presets/identity/context/presentation/http/organisations/grant-delegation/grant-delegation.body.class-validator.ts.tpl +18 -0
  347. package/templates/presets/identity/context/presentation/http/organisations/grant-delegation/grant-delegation.controller.ts.tpl +103 -0
  348. package/templates/presets/identity/context/presentation/http/organisations/grant-delegation/grant-delegation.params.class-validator.ts.tpl +7 -0
  349. package/templates/presets/identity/context/presentation/http/organisations/invite-member/invite-member.accepted.class-validator.ts.tpl +10 -0
  350. package/templates/presets/identity/context/presentation/http/organisations/invite-member/invite-member.body.class-validator.ts.tpl +10 -0
  351. package/templates/presets/identity/context/presentation/http/organisations/invite-member/invite-member.controller.ts.tpl +65 -0
  352. package/templates/presets/identity/context/presentation/http/organisations/invite-member/invite-member.params.class-validator.ts.tpl +7 -0
  353. package/templates/presets/identity/context/presentation/http/organisations/open-organisation/open-organisation.accepted.class-validator.per-route.ts.tpl +6 -0
  354. package/templates/presets/identity/context/presentation/http/organisations/open-organisation/open-organisation.body.class-validator.ts.tpl +12 -0
  355. package/templates/presets/identity/context/presentation/http/organisations/open-organisation/open-organisation.controller.ts.tpl +79 -0
  356. package/templates/presets/identity/context/presentation/http/organisations/remove-member/remove-member.accepted.class-validator.per-route.ts.tpl +6 -0
  357. package/templates/presets/identity/context/presentation/http/organisations/remove-member/remove-member.controller.ts.tpl +73 -0
  358. package/templates/presets/identity/context/presentation/http/organisations/remove-member/remove-member.params.class-validator.ts.tpl +11 -0
  359. package/templates/presets/identity/context/presentation/http/organisations/revoke-delegation/revoke-delegation.accepted.class-validator.per-route.ts.tpl +6 -0
  360. package/templates/presets/identity/context/presentation/http/organisations/revoke-delegation/revoke-delegation.controller.ts.tpl +73 -0
  361. package/templates/presets/identity/context/presentation/http/organisations/revoke-delegation/revoke-delegation.params.class-validator.ts.tpl +11 -0
  362. package/templates/presets/identity/context/presentation/http/organisations/revoke-invitation/revoke-invitation.accepted.class-validator.per-route.ts.tpl +6 -0
  363. package/templates/presets/identity/context/presentation/http/organisations/revoke-invitation/revoke-invitation.controller.ts.tpl +73 -0
  364. package/templates/presets/identity/context/presentation/http/organisations/revoke-invitation/revoke-invitation.params.class-validator.ts.tpl +11 -0
  365. package/templates/presets/identity/context/presentation/http/organisations/search-members/search-members.controller.ts.tpl +86 -0
  366. package/templates/presets/identity/context/presentation/http/organisations/search-members/search-members.line.class-validator.ts.tpl +22 -0
  367. package/templates/presets/identity/context/presentation/http/organisations/search-members/search-members.params.class-validator.ts.tpl +7 -0
  368. package/templates/presets/identity/context/presentation/http/organisations.routes.ts.tpl +5 -0
  369. package/templates/presets/identity/context/presentation/http/shared/invitation-token.schema.class-validator.shared.ts.tpl +9 -0
  370. package/templates/presets/identity/context/presentation/http/shared/invitation-token.schema.zod.shared.ts.tpl +9 -0
  371. package/templates/presets/identity/context/published-language/account-deactivated.v1.integration-event.ts.tpl +33 -0
  372. package/templates/presets/identity/context/published-language/account-deactivated.v1.payload.class-validator.ts.tpl +7 -0
  373. package/templates/presets/identity/context/published-language/delegation-granted.v1.integration-event.ts.tpl +39 -0
  374. package/templates/presets/identity/context/published-language/delegation-granted.v1.payload.class-validator.ts.tpl +28 -0
  375. package/templates/presets/identity/context/published-language/delegation-revoked.v1.integration-event.ts.tpl +36 -0
  376. package/templates/presets/identity/context/published-language/delegation-revoked.v1.payload.class-validator.ts.tpl +16 -0
  377. package/templates/presets/identity/context/published-language/member-joined.v1.integration-event.ts.tpl +36 -0
  378. package/templates/presets/identity/context/published-language/member-joined.v1.payload.class-validator.ts.tpl +16 -0
  379. package/templates/presets/identity/context/published-language/member-removed.v1.integration-event.ts.tpl +36 -0
  380. package/templates/presets/identity/context/published-language/member-removed.v1.payload.class-validator.ts.tpl +18 -0
  381. package/templates/presets/identity/context/published-language/member-role-changed.v1.integration-event.ts.tpl +38 -0
  382. package/templates/presets/identity/context/published-language/member-role-changed.v1.payload.class-validator.ts.tpl +24 -0
  383. package/templates/presets/identity/context/published-language/organisation-opened.v1.integration-event.ts.tpl +35 -0
  384. package/templates/presets/identity/context/published-language/organisation-opened.v1.payload.class-validator.ts.tpl +13 -0
  385. package/templates/presets/identity/context/test/builders/account.builder.ts.tpl +59 -0
  386. package/templates/presets/identity/context/test/builders/delegation.builder.ts.tpl +67 -0
  387. package/templates/presets/identity/context/test/builders/invitation.builder.ts.tpl +60 -0
  388. package/templates/presets/identity/context/test/builders/member.builder.ts.tpl +59 -0
  389. package/templates/presets/identity/context/test/builders/organisation.builder.ts.tpl +44 -0
  390. package/templates/presets/identity/context/test/fakes/account.repository.fake.ts.tpl +10 -0
  391. package/templates/presets/identity/context/test/fakes/delegation.repository.fake.ts.tpl +26 -0
  392. package/templates/presets/identity/context/test/fakes/identity-provider-admin.fake.ts.tpl +41 -0
  393. package/templates/presets/identity/context/test/fakes/invitation-tokens.fake.ts.tpl +18 -0
  394. package/templates/presets/identity/context/test/fakes/invitation.repository.fake.ts.tpl +14 -0
  395. package/templates/presets/identity/context/test/fakes/member.repository.fake.ts.tpl +29 -0
  396. package/templates/presets/identity/context/test/fakes/organisation.repository.fake.ts.tpl +10 -0
  397. package/templates/presets/identity/context/test/fakes/search-members-reader.fake.ts.tpl +25 -0
  398. package/templates/presets/identity/context/test/integration/account.repository.contract.spec.ts.tpl +48 -0
  399. package/templates/presets/identity/context/test/integration/delegation.repository.contract.spec.ts.tpl +84 -0
  400. package/templates/presets/identity/context/test/integration/invitation.repository.contract.spec.ts.tpl +76 -0
  401. package/templates/presets/identity/context/test/integration/keycloak-identity-provider-admin.adapter.spec.ts.tpl +123 -0
  402. package/templates/presets/identity/context/test/integration/member.repository.contract.spec.ts.tpl +92 -0
  403. package/templates/presets/identity/context/test/integration/organisation.repository.contract.spec.ts.tpl +46 -0
  404. package/templates/presets/identity/context/test/integration/search-members-reader.spec.ts.tpl +86 -0
  405. package/templates/presets/identity/context/test/unit/accept-invitation.controller.spec.ts.tpl +57 -0
  406. package/templates/presets/identity/context/test/unit/accept-invitation.handler.spec.ts.tpl +69 -0
  407. package/templates/presets/identity/context/test/unit/account-deactivated.integration-handler.spec.ts.tpl +24 -0
  408. package/templates/presets/identity/context/test/unit/account-deactivated.publisher.spec.ts.tpl +25 -0
  409. package/templates/presets/identity/context/test/unit/account.aggregate.spec.ts.tpl +72 -0
  410. package/templates/presets/identity/context/test/unit/change-member-role.controller.spec.ts.tpl +57 -0
  411. package/templates/presets/identity/context/test/unit/change-member-role.handler.spec.ts.tpl +135 -0
  412. package/templates/presets/identity/context/test/unit/crypto-invitation-tokens.adapter.spec.ts.tpl +15 -0
  413. package/templates/presets/identity/context/test/unit/deactivate-account.controller.spec.ts.tpl +44 -0
  414. package/templates/presets/identity/context/test/unit/deactivate-account.handler.spec.ts.tpl +106 -0
  415. package/templates/presets/identity/context/test/unit/decline-invitation.controller.spec.ts.tpl +57 -0
  416. package/templates/presets/identity/context/test/unit/decline-invitation.handler.spec.ts.tpl +40 -0
  417. package/templates/presets/identity/context/test/unit/delegation-granted.publisher.spec.ts.tpl +36 -0
  418. package/templates/presets/identity/context/test/unit/delegation-period.vo.spec.ts.tpl +32 -0
  419. package/templates/presets/identity/context/test/unit/delegation-revoked.publisher.spec.ts.tpl +35 -0
  420. package/templates/presets/identity/context/test/unit/delegation.aggregate.spec.ts.tpl +111 -0
  421. package/templates/presets/identity/context/test/unit/display-name.vo.spec.ts.tpl +13 -0
  422. package/templates/presets/identity/context/test/unit/email-address.vo.spec.ts.tpl +24 -0
  423. package/templates/presets/identity/context/test/unit/environment-identity-provider-connection.adapter.spec.ts.tpl +41 -0
  424. package/templates/presets/identity/context/test/unit/grant-delegation.controller.spec.ts.tpl +57 -0
  425. package/templates/presets/identity/context/test/unit/grant-delegation.handler.spec.ts.tpl +66 -0
  426. package/templates/presets/identity/context/test/unit/identity-application.ts.tpl +79 -0
  427. package/templates/presets/identity/context/test/unit/invitation.aggregate.spec.ts.tpl +118 -0
  428. package/templates/presets/identity/context/test/unit/invite-member.controller.spec.ts.tpl +57 -0
  429. package/templates/presets/identity/context/test/unit/invite-member.handler.spec.ts.tpl +110 -0
  430. package/templates/presets/identity/context/test/unit/member-joined.integration-handler.spec.ts.tpl +38 -0
  431. package/templates/presets/identity/context/test/unit/member-joined.publisher.spec.ts.tpl +36 -0
  432. package/templates/presets/identity/context/test/unit/member-removed.integration-handler.spec.ts.tpl +45 -0
  433. package/templates/presets/identity/context/test/unit/member-removed.publisher.spec.ts.tpl +35 -0
  434. package/templates/presets/identity/context/test/unit/member-role-changed.publisher.spec.ts.tpl +36 -0
  435. package/templates/presets/identity/context/test/unit/member.aggregate.spec.ts.tpl +142 -0
  436. package/templates/presets/identity/context/test/unit/open-organisation.controller.spec.ts.tpl +57 -0
  437. package/templates/presets/identity/context/test/unit/open-organisation.handler.spec.ts.tpl +52 -0
  438. package/templates/presets/identity/context/test/unit/organisation-name.vo.spec.ts.tpl +13 -0
  439. package/templates/presets/identity/context/test/unit/organisation-opened.integration-handler.spec.ts.tpl +24 -0
  440. package/templates/presets/identity/context/test/unit/organisation-opened.publisher.spec.ts.tpl +35 -0
  441. package/templates/presets/identity/context/test/unit/organisation-rights.vo.spec.ts.tpl +23 -0
  442. package/templates/presets/identity/context/test/unit/organisation-slug.vo.spec.ts.tpl +17 -0
  443. package/templates/presets/identity/context/test/unit/organisation.aggregate.spec.ts.tpl +33 -0
  444. package/templates/presets/identity/context/test/unit/provision-account.controller.spec.ts.tpl +73 -0
  445. package/templates/presets/identity/context/test/unit/provision-account.handler.spec.ts.tpl +65 -0
  446. package/templates/presets/identity/context/test/unit/remove-member.controller.spec.ts.tpl +44 -0
  447. package/templates/presets/identity/context/test/unit/remove-member.handler.spec.ts.tpl +125 -0
  448. package/templates/presets/identity/context/test/unit/revoke-delegation.controller.spec.ts.tpl +44 -0
  449. package/templates/presets/identity/context/test/unit/revoke-delegation.handler.spec.ts.tpl +51 -0
  450. package/templates/presets/identity/context/test/unit/revoke-invitation.controller.spec.ts.tpl +44 -0
  451. package/templates/presets/identity/context/test/unit/revoke-invitation.handler.spec.ts.tpl +44 -0
  452. package/templates/presets/identity/context/test/unit/role-rights.service.spec.ts.tpl +54 -0
  453. package/templates/presets/identity/context/test/unit/search-members.controller.spec.ts.tpl +43 -0
  454. package/templates/presets/identity/context/test/unit/search-members.handler.spec.ts.tpl +38 -0
  455. package/templates/presets/identity/decision.md.tpl +37 -0
  456. package/templates/presets/identity/design.md.tpl +71 -0
  457. package/templates/presets/signed-in-grants.ts.tpl +12 -0
@@ -0,0 +1,17 @@
1
+ import type { Page } from '@ddd-ts-kit/kernel';
2
+ import type { MemberRole } from '{{alias.context}}/domain/vocabularies/member-role.vocabulary{{alias.ext}}';
3
+
4
+ /** A current member of an organisation, with the profile of their account. */
5
+ export interface SearchMembersItem {
6
+ readonly memberId: string;
7
+ readonly accountId: string;
8
+ readonly displayName: string;
9
+ readonly email: string;
10
+ readonly role: MemberRole;
11
+ readonly joinedAt: Date;
12
+ }
13
+
14
+ /** By the instant they joined, earliest first unless the client asks otherwise. */
15
+ export type SearchMembersSort = 'joinedAt';
16
+
17
+ export type SearchMembersResult = Page<SearchMembersItem>;
@@ -0,0 +1,92 @@
1
+ import { AggregateRoot } from '@ddd-ts-kit/kernel';
2
+ import { AccountError } from '{{alias.context}}/domain/errors/account.error{{alias.ext}}';
3
+ import { AccountDeactivated } from '{{alias.context}}/domain/events/account-deactivated.event{{alias.ext}}';
4
+ import { AccountProvisioned } from '{{alias.context}}/domain/events/account-provisioned.event{{alias.ext}}';
5
+ import type { DisplayName } from '{{alias.context}}/domain/value-objects/display-name.vo{{alias.ext}}';
6
+ import type { EmailAddress } from '{{alias.context}}/domain/value-objects/email-address.vo{{alias.ext}}';
7
+ import { AccountStatus } from '{{alias.context}}/domain/vocabularies/account-status.vocabulary{{alias.ext}}';
8
+
9
+ /** What the identity provider says of a person, as the application keeps it: refreshed at each sign-in. */
10
+ export interface AccountProfile {
11
+ readonly email: EmailAddress;
12
+ readonly displayName: DisplayName;
13
+ }
14
+
15
+ export interface AccountState extends AccountProfile {
16
+ readonly status: AccountStatus;
17
+ readonly provisionedAt: Date;
18
+ readonly deactivatedAt: Date | undefined;
19
+ }
20
+
21
+ /**
22
+ * One person as the application knows them, under the subject the identity provider gives them:
23
+ * the provider authenticates, the application keeps the profile and authorizes. No password is
24
+ * stored here, nor anything the provider owns.
25
+ */
26
+ export class Account extends AggregateRoot<string> {
27
+ private constructor(
28
+ id: string,
29
+ private state: AccountState,
30
+ ) {
31
+ super(id);
32
+ }
33
+
34
+ /** At the first sign-in of a person: the account takes the subject of the provider as its identity. */
35
+ static provision(subject: string, profile: AccountProfile, now: Date): Account {
36
+ const status = AccountStatus.Active;
37
+ const provisioned = new Account(subject, { ...profile, status, provisionedAt: now, deactivatedAt: undefined });
38
+
39
+ provisioned.record(
40
+ new AccountProvisioned(subject, now, { email: profile.email.value, displayName: profile.displayName.value }),
41
+ );
42
+
43
+ return provisioned;
44
+ }
45
+
46
+ static rehydrate(id: string, state: AccountState): Account {
47
+ return new Account(id, state);
48
+ }
49
+
50
+ /** At a later sign-in: the profile the provider holds now replaces the one kept. */
51
+ refreshProfile(profile: AccountProfile): void {
52
+ this.assertActive();
53
+ this.state = { ...this.state, ...profile };
54
+ }
55
+
56
+ /** The person signs in no more; their memberships and their history stay. */
57
+ deactivate(now: Date): void {
58
+ if (!this.isActive) {
59
+ throw AccountError.alreadyDeactivated(this.id);
60
+ }
61
+
62
+ this.state = { ...this.state, status: AccountStatus.Deactivated, deactivatedAt: now };
63
+ this.record(new AccountDeactivated(this.id, now));
64
+ }
65
+
66
+ /** Refuses every intention of a deactivated account. */
67
+ assertActive(): void {
68
+ if (!this.isActive) {
69
+ throw AccountError.deactivated(this.id);
70
+ }
71
+ }
72
+
73
+ get isActive(): boolean {
74
+ return this.state.status === AccountStatus.Active;
75
+ }
76
+
77
+ get email(): EmailAddress {
78
+ return this.state.email;
79
+ }
80
+
81
+ get displayName(): DisplayName {
82
+ return this.state.displayName;
83
+ }
84
+
85
+ get provisionedAt(): Date {
86
+ return this.state.provisionedAt;
87
+ }
88
+
89
+ toState(): AccountState {
90
+ return { ...this.state };
91
+ }
92
+ }
@@ -0,0 +1,117 @@
1
+ import { AggregateRoot } from '@ddd-ts-kit/kernel';
2
+ import { DelegationError } from '{{alias.context}}/domain/errors/delegation.error{{alias.ext}}';
3
+ import { DelegationGranted } from '{{alias.context}}/domain/events/delegation-granted.event{{alias.ext}}';
4
+ import { DelegationRevoked } from '{{alias.context}}/domain/events/delegation-revoked.event{{alias.ext}}';
5
+ import type { DelegationPeriod } from '{{alias.context}}/domain/value-objects/delegation-period.vo{{alias.ext}}';
6
+ import type { OrganisationRights } from '{{alias.context}}/domain/value-objects/organisation-rights.vo{{alias.ext}}';
7
+ import type { MemberRight } from '{{alias.context}}/domain/vocabularies/member-right.vocabulary{{alias.ext}}';
8
+
9
+ export interface DelegationState {
10
+ readonly organisationId: string;
11
+ readonly delegatorId: string;
12
+ readonly delegateId: string;
13
+ readonly rights: readonly MemberRight[];
14
+ readonly period: DelegationPeriod;
15
+ readonly endedAt: Date | undefined;
16
+ }
17
+
18
+ /** What a delegation lends, and what its delegator may do by their role: a delegation lends nothing beyond it. */
19
+ export interface DelegationTerms extends Omit<DelegationState, 'endedAt'> {
20
+ readonly held: OrganisationRights;
21
+ }
22
+
23
+ /**
24
+ * A member lends some of the rights of their role to another member of the organisation, for a
25
+ * bounded period: the delegate acts in their own name, with those rights, until the end of the
26
+ * period or until the delegation is revoked. Every grant and every end is a fact, and every use
27
+ * case is recorded in the audit trail of the project.
28
+ */
29
+ export class Delegation extends AggregateRoot<string> {
30
+ private constructor(
31
+ id: string,
32
+ private state: DelegationState,
33
+ ) {
34
+ super(id);
35
+ }
36
+
37
+ static grant(id: string, { held, ...terms }: DelegationTerms, now: Date): Delegation {
38
+ assertLendable(terms, held);
39
+
40
+ const granted = new Delegation(id, { ...terms, endedAt: undefined });
41
+ const { organisationId, delegatorId, delegateId, rights, period } = terms;
42
+ const facts = { organisationId, delegatorId, delegateId, rights, startsAt: period.startsAt, endsAt: period.endsAt };
43
+
44
+ granted.record(new DelegationGranted(id, now, facts));
45
+
46
+ return granted;
47
+ }
48
+
49
+ static rehydrate(id: string, state: DelegationState): Delegation {
50
+ return new Delegation(id, state);
51
+ }
52
+
53
+ /** Withdrawn by hand while it holds or before it starts: an ended delegation cannot be. */
54
+ revoke(now: Date): void {
55
+ if (this.state.endedAt !== undefined || now.getTime() >= this.state.period.endsAt.getTime()) {
56
+ throw DelegationError.notActive(this.id);
57
+ }
58
+
59
+ this.end(now);
60
+ }
61
+
62
+ /** Ends because the delegator or the delegate no longer stands as it did when it was granted; already ended, nothing changes. */
63
+ end(now: Date): void {
64
+ if (this.state.endedAt !== undefined) {
65
+ return;
66
+ }
67
+
68
+ const { organisationId, delegatorId, delegateId } = this.state;
69
+
70
+ this.state = { ...this.state, endedAt: now };
71
+ this.record(new DelegationRevoked(this.id, now, { organisationId, delegatorId, delegateId }));
72
+ }
73
+
74
+ isActiveAt(instant: Date): boolean {
75
+ return this.state.endedAt === undefined && this.state.period.includes(instant);
76
+ }
77
+
78
+ get organisationId(): string {
79
+ return this.state.organisationId;
80
+ }
81
+
82
+ get delegatorId(): string {
83
+ return this.state.delegatorId;
84
+ }
85
+
86
+ get delegateId(): string {
87
+ return this.state.delegateId;
88
+ }
89
+
90
+ get rights(): readonly MemberRight[] {
91
+ return this.state.rights;
92
+ }
93
+
94
+ get period(): DelegationPeriod {
95
+ return this.state.period;
96
+ }
97
+
98
+ toState(): DelegationState {
99
+ return { ...this.state };
100
+ }
101
+ }
102
+
103
+ function assertLendable(terms: Omit<DelegationState, 'endedAt'>, held: OrganisationRights): void {
104
+ if (terms.delegateId === terms.delegatorId) {
105
+ throw DelegationError.toOneself(terms.delegatorId);
106
+ }
107
+
108
+ if (terms.rights.length === 0) {
109
+ throw DelegationError.withoutRights();
110
+ }
111
+
112
+ const beyond = terms.rights.find((right) => !held.includes(right));
113
+
114
+ if (beyond !== undefined) {
115
+ throw DelegationError.beyondRights(beyond);
116
+ }
117
+ }
@@ -0,0 +1,128 @@
1
+ import { AggregateRoot } from '@ddd-ts-kit/kernel';
2
+ import { InvitationError } from '{{alias.context}}/domain/errors/invitation.error{{alias.ext}}';
3
+ import { InvitationAccepted } from '{{alias.context}}/domain/events/invitation-accepted.event{{alias.ext}}';
4
+ import { InvitationDeclined } from '{{alias.context}}/domain/events/invitation-declined.event{{alias.ext}}';
5
+ import { InvitationIssued } from '{{alias.context}}/domain/events/invitation-issued.event{{alias.ext}}';
6
+ import { InvitationRevoked } from '{{alias.context}}/domain/events/invitation-revoked.event{{alias.ext}}';
7
+ import type { EmailAddress } from '{{alias.context}}/domain/value-objects/email-address.vo{{alias.ext}}';
8
+ import { InvitationStatus } from '{{alias.context}}/domain/vocabularies/invitation-status.vocabulary{{alias.ext}}';
9
+ import type { MemberRole } from '{{alias.context}}/domain/vocabularies/member-role.vocabulary{{alias.ext}}';
10
+
11
+ /** How long an invitation can be answered: a week, then a new one is sent. */
12
+ const VALIDITY_MS = 7 * 86_400_000;
13
+
14
+ export interface InvitationState {
15
+ readonly organisationId: string;
16
+ readonly email: EmailAddress;
17
+ readonly role: MemberRole;
18
+ /** The digest of the token sent to the person: the token itself is never stored. */
19
+ readonly tokenHash: string;
20
+ readonly invitedBy: string;
21
+ readonly issuedAt: Date;
22
+ readonly expiresAt: Date;
23
+ readonly status: InvitationStatus;
24
+ readonly answeredAt: Date | undefined;
25
+ }
26
+
27
+ /** What an invitation is issued with. */
28
+ export type InvitationTerms = Pick<InvitationState, 'organisationId' | 'email' | 'role' | 'tokenHash' | 'invitedBy'> & {
29
+ readonly delegationId?: string;
30
+ };
31
+
32
+ /** The person who answers an invitation: an account, with the email address its provider knows. */
33
+ export interface Invitee {
34
+ readonly id: string;
35
+ readonly email: EmailAddress;
36
+ }
37
+
38
+ /** An organisation asks a person, by email address, to join it in a role; the answer is theirs alone. */
39
+ export class Invitation extends AggregateRoot<string> {
40
+ private constructor(
41
+ id: string,
42
+ private state: InvitationState,
43
+ ) {
44
+ super(id);
45
+ }
46
+
47
+ /** `delegationId` names the delegation that lent the right to invite, when the role of the inviter does not grant it. */
48
+ static issue(id: string, terms: InvitationTerms, now: Date): Invitation {
49
+ const expiresAt = new Date(now.getTime() + VALIDITY_MS);
50
+ const pending = { status: InvitationStatus.Pending, issuedAt: now, expiresAt, answeredAt: undefined };
51
+ const { organisationId, email, role, tokenHash, invitedBy, delegationId } = terms;
52
+ const issued = new Invitation(id, { organisationId, email, role, tokenHash, invitedBy, ...pending });
53
+ const under = delegationId === undefined ? {} : { delegationId };
54
+
55
+ issued.record(new InvitationIssued(id, now, { organisationId, email: email.value, role, expiresAt, ...under }));
56
+
57
+ return issued;
58
+ }
59
+
60
+ static rehydrate(id: string, state: InvitationState): Invitation {
61
+ return new Invitation(id, state);
62
+ }
63
+
64
+ /** By the person it was addressed to, before it expires: they become a member (see `AcceptInvitationHandler`). */
65
+ accept(invitee: Invitee, now: Date): void {
66
+ this.assertAnswerableBy(invitee, now);
67
+ this.answer(InvitationStatus.Accepted, now);
68
+ this.record(new InvitationAccepted(this.id, now, { organisationId: this.organisationId, accountId: invitee.id }));
69
+ }
70
+
71
+ decline(invitee: Invitee, now: Date): void {
72
+ this.assertAnswerableBy(invitee, now);
73
+ this.answer(InvitationStatus.Declined, now);
74
+ this.record(new InvitationDeclined(this.id, now, { organisationId: this.organisationId }));
75
+ }
76
+
77
+ /** Taken back by the organisation while it waits for an answer, expired or not. */
78
+ revoke(now: Date): void {
79
+ if (this.state.status !== InvitationStatus.Pending) {
80
+ throw InvitationError.notPending(this.id, this.state.status);
81
+ }
82
+
83
+ this.answer(InvitationStatus.Revoked, now);
84
+ this.record(new InvitationRevoked(this.id, now, { organisationId: this.organisationId }));
85
+ }
86
+
87
+ get organisationId(): string {
88
+ return this.state.organisationId;
89
+ }
90
+
91
+ get email(): EmailAddress {
92
+ return this.state.email;
93
+ }
94
+
95
+ get role(): MemberRole {
96
+ return this.state.role;
97
+ }
98
+
99
+ get status(): InvitationStatus {
100
+ return this.state.status;
101
+ }
102
+
103
+ get expiresAt(): Date {
104
+ return this.state.expiresAt;
105
+ }
106
+
107
+ toState(): InvitationState {
108
+ return { ...this.state };
109
+ }
110
+
111
+ private assertAnswerableBy(invitee: Invitee, now: Date): void {
112
+ if (this.state.status !== InvitationStatus.Pending) {
113
+ throw InvitationError.notPending(this.id, this.state.status);
114
+ }
115
+
116
+ if (now.getTime() >= this.state.expiresAt.getTime()) {
117
+ throw InvitationError.expired(this.id, this.state.expiresAt);
118
+ }
119
+
120
+ if (!invitee.email.equals(this.state.email)) {
121
+ throw InvitationError.addressedToAnother(this.id);
122
+ }
123
+ }
124
+
125
+ private answer(status: InvitationStatus, now: Date): void {
126
+ this.state = { ...this.state, status, answeredAt: now };
127
+ }
128
+ }
@@ -0,0 +1,149 @@
1
+ import { AggregateRoot } from '@ddd-ts-kit/kernel';
2
+ import { MemberError } from '{{alias.context}}/domain/errors/member.error{{alias.ext}}';
3
+ import { MemberJoined } from '{{alias.context}}/domain/events/member-joined.event{{alias.ext}}';
4
+ import { MemberRemoved } from '{{alias.context}}/domain/events/member-removed.event{{alias.ext}}';
5
+ import { MemberRoleChanged } from '{{alias.context}}/domain/events/member-role-changed.event{{alias.ext}}';
6
+ import { MemberRole } from '{{alias.context}}/domain/vocabularies/member-role.vocabulary{{alias.ext}}';
7
+
8
+ export interface MemberState {
9
+ readonly organisationId: string;
10
+ readonly accountId: string;
11
+ readonly role: MemberRole;
12
+ readonly joinedAt: Date;
13
+ readonly removedAt: Date | undefined;
14
+ }
15
+
16
+ /**
17
+ * Who asks for a change of membership: their account and their own role (never the rights a
18
+ * delegation lends them, so that nobody appoints above themselves), the delegation that lends
19
+ * them the right when their role does not grant it, and how many owners the organisation has.
20
+ */
21
+ export interface MembershipChange {
22
+ readonly by: MemberRole;
23
+ readonly byAccount: string;
24
+ readonly delegationId: string | undefined;
25
+ readonly owners: number;
26
+ }
27
+
28
+ /** The roles from the least to the most: a member appoints, changes and removes no one above their own role. */
29
+ const FROM_LEAST: readonly MemberRole[] = [MemberRole.Member, MemberRole.Admin, MemberRole.Owner];
30
+
31
+ /** The membership of one account in one organisation, in a role. */
32
+ export class Member extends AggregateRoot<string> {
33
+ private constructor(
34
+ id: string,
35
+ private state: MemberState,
36
+ ) {
37
+ super(id);
38
+ }
39
+
40
+ static join(id: string, terms: Pick<MemberState, 'organisationId' | 'accountId' | 'role'>, now: Date): Member {
41
+ const joined = new Member(id, { ...terms, joinedAt: now, removedAt: undefined });
42
+
43
+ joined.record(new MemberJoined(id, now, terms));
44
+
45
+ return joined;
46
+ }
47
+
48
+ static rehydrate(id: string, state: MemberState): Member {
49
+ return new Member(id, state);
50
+ }
51
+
52
+ /** Whoever invites or appoints in `role` holds that role or a higher one: only an owner makes an owner. */
53
+ static assertAppointable(organisationId: string, role: MemberRole, by: MemberRole): void {
54
+ if (!outranks(role, by)) {
55
+ return;
56
+ }
57
+
58
+ throw role === MemberRole.Owner ? MemberError.ownersOnly(organisationId) : MemberError.aboveOwnRole(organisationId);
59
+ }
60
+
61
+ /** Never one's own role, never above one's own role, and the last owner stays one. */
62
+ changeRole(role: MemberRole, change: MembershipChange, now: Date): void {
63
+ this.assertActive();
64
+
65
+ if (change.byAccount === this.state.accountId) {
66
+ throw MemberError.ownRole(this.state.organisationId);
67
+ }
68
+
69
+ if (role === this.state.role) {
70
+ return;
71
+ }
72
+
73
+ this.assertWithinRole(change.by, role);
74
+ this.assertOwnersKept(change, role);
75
+
76
+ const { organisationId, accountId, role: from } = this.state;
77
+
78
+ this.state = { ...this.state, role };
79
+ this.record(new MemberRoleChanged(this.id, now, { organisationId, accountId, from, to: role, ...under(change) }));
80
+ }
81
+
82
+ /** A member leaves, or is removed by someone of their role or above; the last owner stays. */
83
+ remove(change: MembershipChange, now: Date): void {
84
+ this.assertActive();
85
+
86
+ if (change.byAccount !== this.state.accountId) {
87
+ this.assertWithinRole(change.by, this.state.role);
88
+ }
89
+
90
+ this.assertOwnersKept(change, undefined);
91
+
92
+ const { organisationId, accountId } = this.state;
93
+
94
+ this.state = { ...this.state, removedAt: now };
95
+ this.record(new MemberRemoved(this.id, now, { organisationId, accountId, ...under(change) }));
96
+ }
97
+
98
+ get isActive(): boolean {
99
+ return this.state.removedAt === undefined;
100
+ }
101
+
102
+ get organisationId(): string {
103
+ return this.state.organisationId;
104
+ }
105
+
106
+ get accountId(): string {
107
+ return this.state.accountId;
108
+ }
109
+
110
+ get role(): MemberRole {
111
+ return this.state.role;
112
+ }
113
+
114
+ get joinedAt(): Date {
115
+ return this.state.joinedAt;
116
+ }
117
+
118
+ toState(): MemberState {
119
+ return { ...this.state };
120
+ }
121
+
122
+ private assertActive(): void {
123
+ if (!this.isActive) {
124
+ throw MemberError.notFound(this.state.organisationId, this.state.accountId);
125
+ }
126
+ }
127
+
128
+ /** The role the member holds, and the one they are given, are both within the role of who decides. */
129
+ private assertWithinRole(by: MemberRole, next: MemberRole): void {
130
+ Member.assertAppointable(this.state.organisationId, this.state.role, by);
131
+ Member.assertAppointable(this.state.organisationId, next, by);
132
+ }
133
+
134
+ /** `next` is the role after the change, none for a removal. */
135
+ private assertOwnersKept(change: MembershipChange, next: MemberRole | undefined): void {
136
+ if (this.state.role === MemberRole.Owner && next !== MemberRole.Owner && change.owners <= 1) {
137
+ throw MemberError.lastOwner(this.state.organisationId);
138
+ }
139
+ }
140
+ }
141
+
142
+ function outranks(role: MemberRole, other: MemberRole): boolean {
143
+ return FROM_LEAST.indexOf(role) > FROM_LEAST.indexOf(other);
144
+ }
145
+
146
+ /** The delegation a change was decided under, named in its fact so that the trail says whose rights were used. */
147
+ function under(change: MembershipChange): { readonly delegationId?: string } {
148
+ return change.delegationId === undefined ? {} : { delegationId: change.delegationId };
149
+ }
@@ -0,0 +1,52 @@
1
+ import { AggregateRoot } from '@ddd-ts-kit/kernel';
2
+ import { OrganisationOpened } from '{{alias.context}}/domain/events/organisation-opened.event{{alias.ext}}';
3
+ import type { OrganisationName } from '{{alias.context}}/domain/value-objects/organisation-name.vo{{alias.ext}}';
4
+ import type { OrganisationSlug } from '{{alias.context}}/domain/value-objects/organisation-slug.vo{{alias.ext}}';
5
+
6
+ export interface OrganisationState {
7
+ readonly name: OrganisationName;
8
+ readonly openedBy: string;
9
+ readonly openedAt: Date;
10
+ }
11
+
12
+ /**
13
+ * A group of accounts that work together: a company, a clinic, a school. Its slug is its identity,
14
+ * and the tenant of its data when the project keeps the data of each organisation apart.
15
+ */
16
+ export class Organisation extends AggregateRoot<string> {
17
+ private constructor(
18
+ id: string,
19
+ private readonly state: OrganisationState,
20
+ ) {
21
+ super(id);
22
+ }
23
+
24
+ /** Opened by an account, which becomes its first owner (see `OpenOrganisationHandler`). */
25
+ static open(slug: OrganisationSlug, terms: Pick<OrganisationState, 'name' | 'openedBy'>, now: Date): Organisation {
26
+ const opened = new Organisation(slug.value, { ...terms, openedAt: now });
27
+
28
+ opened.record(new OrganisationOpened(slug.value, now, { name: terms.name.value, openedBy: terms.openedBy }));
29
+
30
+ return opened;
31
+ }
32
+
33
+ static rehydrate(id: string, state: OrganisationState): Organisation {
34
+ return new Organisation(id, state);
35
+ }
36
+
37
+ get name(): OrganisationName {
38
+ return this.state.name;
39
+ }
40
+
41
+ get openedBy(): string {
42
+ return this.state.openedBy;
43
+ }
44
+
45
+ get openedAt(): Date {
46
+ return this.state.openedAt;
47
+ }
48
+
49
+ toState(): OrganisationState {
50
+ return { ...this.state };
51
+ }
52
+ }
@@ -0,0 +1,40 @@
1
+ import { ConceptError } from '@ddd-ts-kit/kernel';
2
+
3
+ /** What the accounts refuse: an account is the application's view of one person the identity provider authenticates. */
4
+ export class AccountError extends ConceptError {
5
+ static notFound(accountId: string) {
6
+ return AccountError.refusal(
7
+ 'ACCOUNT_NOT_FOUND',
8
+ `No account ${accountId}: the person signs in once before acting.`,
9
+ { accountId },
10
+ ).exposing('accountId');
11
+ }
12
+
13
+ static deactivated(accountId: string) {
14
+ return AccountError.refusal('ACCOUNT_DEACTIVATED', `The account ${accountId} is deactivated.`, { accountId });
15
+ }
16
+
17
+ static alreadyDeactivated(accountId: string) {
18
+ return AccountError.refusal('ACCOUNT_ALREADY_DEACTIVATED', `The account ${accountId} is already deactivated.`, {
19
+ accountId,
20
+ });
21
+ }
22
+
23
+ static emailNotVerified(accountId: string) {
24
+ return AccountError.refusal(
25
+ 'EMAIL_NOT_VERIFIED',
26
+ 'The identity provider has not verified the email address of this person: they verify it, then sign in again.',
27
+ { accountId },
28
+ );
29
+ }
30
+
31
+ static invalidEmailAddress(raw: string) {
32
+ return AccountError.refusal('INVALID_EMAIL_ADDRESS', 'An email address is a local part, an @ and a domain.', {
33
+ raw,
34
+ });
35
+ }
36
+
37
+ static invalidDisplayName(raw: string) {
38
+ return AccountError.refusal('INVALID_DISPLAY_NAME', 'A display name holds between 1 and 100 characters.', { raw });
39
+ }
40
+ }
@@ -0,0 +1,45 @@
1
+ import { ConceptError } from '@ddd-ts-kit/kernel';
2
+
3
+ /** What the delegations refuse: a member lends some of their rights to another, for a bounded time. */
4
+ export class DelegationError extends ConceptError {
5
+ static notFound(delegationId: string) {
6
+ return DelegationError.refusal('DELEGATION_NOT_FOUND', `No delegation ${delegationId}.`, {
7
+ delegationId,
8
+ }).exposing('delegationId');
9
+ }
10
+
11
+ static notActive(delegationId: string) {
12
+ return DelegationError.refusal('DELEGATION_NOT_ACTIVE', `The delegation ${delegationId} already ended.`, {
13
+ delegationId,
14
+ });
15
+ }
16
+
17
+ static toOneself(accountId: string) {
18
+ return DelegationError.refusal('DELEGATION_TO_ONESELF', 'A member delegates to another member.', { accountId });
19
+ }
20
+
21
+ static withoutRights() {
22
+ return DelegationError.refusal('DELEGATION_WITHOUT_RIGHTS', 'A delegation lends at least one right.');
23
+ }
24
+
25
+ static beyondRights(right: string) {
26
+ return DelegationError.refusal('DELEGATION_BEYOND_RIGHTS', `A member lends only the rights they hold: ${right}.`, {
27
+ right,
28
+ }).exposing('right');
29
+ }
30
+
31
+ static periodInverted(startsAt: Date, endsAt: Date) {
32
+ return DelegationError.refusal('DELEGATION_PERIOD_INVERTED', 'A delegation ends after it starts.', {
33
+ startsAt,
34
+ endsAt,
35
+ }).exposing('startsAt', 'endsAt');
36
+ }
37
+
38
+ static tooLong(longestDays: number) {
39
+ return DelegationError.refusal(
40
+ 'DELEGATION_TOO_LONG',
41
+ `A delegation lasts ${longestDays} days at most: a longer one is a role.`,
42
+ { longestDays },
43
+ ).exposing('longestDays');
44
+ }
45
+ }
@@ -0,0 +1,30 @@
1
+ import { ConceptError } from '@ddd-ts-kit/kernel';
2
+
3
+ /** What the invitations refuse. The token of an invitation is never a fact of a refusal. */
4
+ export class InvitationError extends ConceptError {
5
+ static notFound() {
6
+ return InvitationError.refusal('INVITATION_NOT_FOUND', 'No invitation answers this token or this identifier.');
7
+ }
8
+
9
+ static expired(invitationId: string, expiresAt: Date) {
10
+ return InvitationError.refusal('INVITATION_EXPIRED', `The invitation ${invitationId} expired.`, {
11
+ invitationId,
12
+ expiresAt,
13
+ }).exposing('expiresAt');
14
+ }
15
+
16
+ static notPending(invitationId: string, status: string) {
17
+ return InvitationError.refusal('INVITATION_NOT_PENDING', `The invitation ${invitationId} is already ${status}.`, {
18
+ invitationId,
19
+ status,
20
+ }).exposing('status');
21
+ }
22
+
23
+ static addressedToAnother(invitationId: string) {
24
+ return InvitationError.refusal(
25
+ 'INVITATION_ADDRESSED_TO_ANOTHER',
26
+ `The invitation ${invitationId} is addressed to another email address than the account's.`,
27
+ { invitationId },
28
+ );
29
+ }
30
+ }